-
-
Notifications
You must be signed in to change notification settings - Fork 122
Expand file tree
/
Copy pathtls.rs
More file actions
146 lines (132 loc) · 5.12 KB
/
tls.rs
File metadata and controls
146 lines (132 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! TLS support.
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
use anyhow::Result;
use crate::net::session::SessionStream;
use tokio_rustls::rustls;
use tokio_rustls::rustls::client::ClientSessionStore;
mod danger;
use danger::NoCertificateVerification;
pub async fn wrap_tls<'a>(
strict_tls: bool,
hostname: &str,
port: u16,
use_sni: bool,
alpn: &str,
stream: impl SessionStream + 'static,
tls_session_store: &TlsSessionStore,
) -> Result<impl SessionStream + 'a> {
if strict_tls {
let tls_stream =
wrap_rustls(hostname, port, use_sni, alpn, stream, tls_session_store).await?;
let boxed_stream: Box<dyn SessionStream> = Box::new(tls_stream);
Ok(boxed_stream)
} else {
// We use native_tls because it accepts 1024-bit RSA keys.
// Rustls does not support them even if
// certificate checks are disabled: <https://github.com/rustls/rustls/issues/234>.
let alpns = if alpn.is_empty() {
Box::from([])
} else {
Box::from([alpn])
};
let tls = async_native_tls::TlsConnector::new()
.min_protocol_version(Some(async_native_tls::Protocol::Tlsv12))
.use_sni(use_sni)
.request_alpns(&alpns)
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true);
let tls_stream = tls.connect(hostname, stream).await?;
let boxed_stream: Box<dyn SessionStream> = Box::new(tls_stream);
Ok(boxed_stream)
}
}
/// Map to store TLS session tickets.
///
/// Tickets are separated by port and ALPN
/// to avoid trying to use Postfix ticket for Dovecot and vice versa.
/// Doing so would not be a security issue,
/// but wastes the ticket and the opportunity to resume TLS session unnecessarily.
/// Rustls takes care of separating tickets that belong to different domain names.
#[derive(Debug)]
pub(crate) struct TlsSessionStore {
sessions: Mutex<HashMap<(u16, String), Arc<dyn ClientSessionStore>>>,
}
// This is the default for TLS session store
// as of Rustls version 0.23.16,
// but we want to create multiple caches
// to separate them by port and ALPN.
const TLS_CACHE_SIZE: usize = 256;
impl TlsSessionStore {
/// Creates a new TLS session store.
///
/// One such store should be created per profile
/// to keep TLS sessions independent.
pub fn new() -> Self {
Self {
sessions: Default::default(),
}
}
/// Returns session store for given port and ALPN.
///
/// Rustls additionally separates sessions by hostname.
pub fn get(&self, port: u16, alpn: &str) -> Arc<dyn ClientSessionStore> {
Arc::clone(
self.sessions
.lock()
.entry((port, alpn.to_string()))
.or_insert_with(|| {
Arc::new(rustls::client::ClientSessionMemoryCache::new(
TLS_CACHE_SIZE,
))
}),
)
}
}
pub async fn wrap_rustls<'a>(
hostname: &str,
port: u16,
use_sni: bool,
alpn: &str,
stream: impl SessionStream + 'a,
tls_session_store: &TlsSessionStore,
) -> Result<impl SessionStream + 'a> {
let mut root_cert_store = rustls::RootCertStore::empty();
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let mut config = rustls::ClientConfig::builder()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
config.alpn_protocols = if alpn.is_empty() {
vec![]
} else {
vec![alpn.as_bytes().to_vec()]
};
// Enable TLS 1.3 session resumption
// as defined in <https://www.rfc-editor.org/rfc/rfc8446#section-2.2>.
//
// Obsolete TLS 1.2 mechanisms defined in RFC 5246
// and RFC 5077 have worse security
// and are not worth increasing
// attack surface: <https://words.filippo.io/we-need-to-talk-about-session-tickets/>.
let resumption_store = tls_session_store.get(port, alpn);
let resumption = rustls::client::Resumption::store(resumption_store)
.tls12_resumption(rustls::client::Tls12Resumption::Disabled);
config.resumption = resumption;
config.enable_sni = use_sni;
// Do not verify certificates for hostnames starting with `_`.
// They are used for servers with self-signed certificates, e.g. for local testing.
// Hostnames starting with `_` can have only self-signed TLS certificates or wildcard certificates.
// It is not possible to get valid non-wildcard TLS certificates because CA/Browser Forum requirements
// explicitly state that domains should start with a letter, digit or hyphen:
// https://github.com/cabforum/servercert/blob/24f38fd4765e019db8bb1a8c56bf63c7115ce0b0/docs/BR.md
if hostname.starts_with("_") {
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification::new()));
}
let tls = tokio_rustls::TlsConnector::from(Arc::new(config));
let name = tokio_rustls::rustls::pki_types::ServerName::try_from(hostname)?.to_owned();
let tls_stream = tls.connect(name, stream).await?;
Ok(tls_stream)
}