Skip to content

Commit 53f7fce

Browse files
committed
feat: TLS 1.3 session resumption
1 parent 5b6311d commit 53f7fce

File tree

6 files changed

+55
-11
lines changed

6 files changed

+55
-11
lines changed

src/imap/client.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ impl Client {
242242
let buffered_tcp_stream = client.into_inner();
243243
let tcp_stream = buffered_tcp_stream.into_inner();
244244

245-
let tls_stream = wrap_tls(strict_tls, host, "", tcp_stream)
245+
let tls_stream = wrap_tls(strict_tls, host, addr.port(), "", tcp_stream)
246246
.await
247247
.context("STARTTLS upgrade failed")?;
248248

@@ -262,7 +262,7 @@ impl Client {
262262
let proxy_stream = proxy_config
263263
.connect(context, domain, port, strict_tls)
264264
.await?;
265-
let tls_stream = wrap_tls(strict_tls, domain, alpn(port), proxy_stream).await?;
265+
let tls_stream = wrap_tls(strict_tls, domain, port, alpn(port), proxy_stream).await?;
266266
let buffered_stream = BufWriter::new(tls_stream);
267267
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
268268
let mut client = Client::new(session_stream);
@@ -315,7 +315,7 @@ impl Client {
315315
let buffered_proxy_stream = client.into_inner();
316316
let proxy_stream = buffered_proxy_stream.into_inner();
317317

318-
let tls_stream = wrap_tls(strict_tls, hostname, "", proxy_stream)
318+
let tls_stream = wrap_tls(strict_tls, hostname, port, "", proxy_stream)
319319
.await
320320
.context("STARTTLS upgrade failed")?;
321321
let buffered_stream = BufWriter::new(tls_stream);

src/net.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ pub(crate) async fn connect_tls_inner(
130130
alpn: &str,
131131
) -> Result<impl SessionStream> {
132132
let tcp_stream = connect_tcp_inner(addr).await?;
133-
let tls_stream = wrap_tls(strict_tls, host, alpn, tcp_stream).await?;
133+
let tls_stream = wrap_tls(strict_tls, host, addr.port(), alpn, tcp_stream).await?;
134134
Ok(tls_stream)
135135
}
136136

src/net/http.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ where
7272
let proxy_stream = proxy_config
7373
.connect(context, host, port, load_cache)
7474
.await?;
75-
let tls_stream = wrap_rustls(host, "", proxy_stream).await?;
75+
let tls_stream = wrap_rustls(host, port, "", proxy_stream).await?;
7676
Box::new(tls_stream)
7777
} else {
7878
let tcp_stream = crate::net::connect_tcp(context, host, port, load_cache).await?;
79-
let tls_stream = wrap_rustls(host, "", tcp_stream).await?;
79+
let tls_stream = wrap_rustls(host, port, "", tcp_stream).await?;
8080
Box::new(tls_stream)
8181
}
8282
}

src/net/proxy.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,8 @@ impl ProxyConfig {
425425
load_cache,
426426
)
427427
.await?;
428-
let tls_stream = wrap_rustls(&https_config.host, "", tcp_stream).await?;
428+
let tls_stream =
429+
wrap_rustls(&https_config.host, https_config.port, "", tcp_stream).await?;
429430
let auth = if let Some((username, password)) = &https_config.user_password {
430431
Some((username.as_str(), password.as_str()))
431432
} else {

src/net/tls.rs

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
//! TLS support.
2+
use std::collections::HashMap;
23
use std::sync::Arc;
34

45
use anyhow::Result;
6+
use once_cell::sync::Lazy;
7+
use parking_lot::Mutex;
58

69
use crate::net::session::SessionStream;
710

11+
use tokio_rustls::rustls::client::ClientSessionStore;
12+
813
pub async fn wrap_tls(
914
strict_tls: bool,
1015
hostname: &str,
16+
port: u16,
1117
alpn: &str,
1218
stream: impl SessionStream + 'static,
1319
) -> Result<impl SessionStream> {
1420
if strict_tls {
15-
let tls_stream = wrap_rustls(hostname, alpn, stream).await?;
21+
let tls_stream = wrap_rustls(hostname, port, alpn, stream).await?;
1622
let boxed_stream: Box<dyn SessionStream> = Box::new(tls_stream);
1723
Ok(boxed_stream)
1824
} else {
@@ -35,8 +41,20 @@ pub async fn wrap_tls(
3541
}
3642
}
3743

44+
type SessionMap = HashMap<(u16, String), Arc<dyn ClientSessionStore>>;
45+
46+
/// Map to store TLS session tickets.
47+
///
48+
/// Tickets are separated by port and ALPN
49+
/// to avoid trying to use Postfix ticket for Dovecot and vice versa.
50+
/// Doing so would not be a security issue,
51+
/// but wastes the ticket and the opportunity to resume TLS session unnecessarily.
52+
/// Rustls takes care of separating tickets that belong to different domain names.
53+
static RESUMPTION_STORE: Lazy<Mutex<SessionMap>> = Lazy::new(Default::default);
54+
3855
pub async fn wrap_rustls(
3956
hostname: &str,
57+
port: u16,
4058
alpn: &str,
4159
stream: impl SessionStream,
4260
) -> Result<impl SessionStream> {
@@ -52,6 +70,31 @@ pub async fn wrap_rustls(
5270
vec![alpn.as_bytes().to_vec()]
5371
};
5472

73+
// Enable TLS 1.3 session resumption
74+
// as defined in <https://www.rfc-editor.org/rfc/rfc8446#section-2.2>.
75+
//
76+
// Obsolete TLS 1.2 mechanisms defined in RFC 5246
77+
// and RFC 5077 have worse security
78+
// and are not worth increasing
79+
// attack surface: <https://words.filippo.io/we-need-to-talk-about-session-tickets/>.
80+
let resumption_store = Arc::clone(
81+
RESUMPTION_STORE
82+
.lock()
83+
.entry((port, alpn.to_string()))
84+
.or_insert_with(|| {
85+
// This is the default as of Rustls version 0.23.16,
86+
// but we want to create multiple caches
87+
// to separate them by port and ALPN.
88+
Arc::new(tokio_rustls::rustls::client::ClientSessionMemoryCache::new(
89+
256,
90+
))
91+
}),
92+
);
93+
94+
let resumption = tokio_rustls::rustls::client::Resumption::store(resumption_store)
95+
.tls12_resumption(tokio_rustls::rustls::client::Tls12Resumption::Disabled);
96+
config.resumption = resumption;
97+
5598
let tls = tokio_rustls::TlsConnector::from(Arc::new(config));
5699
let name = rustls_pki_types::ServerName::try_from(hostname)?.to_owned();
57100
let tls_stream = tls.connect(name, stream).await?;

src/smtp/connect.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ async fn connect_secure_proxy(
225225
let proxy_stream = proxy_config
226226
.connect(context, hostname, port, strict_tls)
227227
.await?;
228-
let tls_stream = wrap_tls(strict_tls, hostname, alpn(port), proxy_stream).await?;
228+
let tls_stream = wrap_tls(strict_tls, hostname, port, alpn(port), proxy_stream).await?;
229229
let mut buffered_stream = BufStream::new(tls_stream);
230230
skip_smtp_greeting(&mut buffered_stream).await?;
231231
let session_stream: Box<dyn SessionBufStream> = Box::new(buffered_stream);
@@ -248,7 +248,7 @@ async fn connect_starttls_proxy(
248248
skip_smtp_greeting(&mut buffered_stream).await?;
249249
let transport = new_smtp_transport(buffered_stream).await?;
250250
let tcp_stream = transport.starttls().await?.into_inner();
251-
let tls_stream = wrap_tls(strict_tls, hostname, "", tcp_stream)
251+
let tls_stream = wrap_tls(strict_tls, hostname, port, "", tcp_stream)
252252
.await
253253
.context("STARTTLS upgrade failed")?;
254254
let buffered_stream = BufStream::new(tls_stream);
@@ -293,7 +293,7 @@ async fn connect_starttls(
293293
skip_smtp_greeting(&mut buffered_stream).await?;
294294
let transport = new_smtp_transport(buffered_stream).await?;
295295
let tcp_stream = transport.starttls().await?.into_inner();
296-
let tls_stream = wrap_tls(strict_tls, host, "", tcp_stream)
296+
let tls_stream = wrap_tls(strict_tls, host, addr.port(), "", tcp_stream)
297297
.await
298298
.context("STARTTLS upgrade failed")?;
299299

0 commit comments

Comments
 (0)