Skip to content

Commit

Permalink
feat: TLS 1.3 session resumption
Browse files Browse the repository at this point in the history
  • Loading branch information
link2xt committed Nov 10, 2024
1 parent 7657955 commit c0067f8
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 11 deletions.
6 changes: 3 additions & 3 deletions src/imap/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ impl Client {
let buffered_tcp_stream = client.into_inner();
let tcp_stream = buffered_tcp_stream.into_inner();

let tls_stream = wrap_tls(strict_tls, host, "", tcp_stream)
let tls_stream = wrap_tls(strict_tls, host, addr.port(), "", tcp_stream)
.await
.context("STARTTLS upgrade failed")?;

Expand All @@ -262,7 +262,7 @@ impl Client {
let proxy_stream = proxy_config
.connect(context, domain, port, strict_tls)
.await?;
let tls_stream = wrap_tls(strict_tls, domain, alpn(port), proxy_stream).await?;
let tls_stream = wrap_tls(strict_tls, domain, port, alpn(port), proxy_stream).await?;
let buffered_stream = BufWriter::new(tls_stream);
let session_stream: Box<dyn SessionStream> = Box::new(buffered_stream);
let mut client = Client::new(session_stream);
Expand Down Expand Up @@ -315,7 +315,7 @@ impl Client {
let buffered_proxy_stream = client.into_inner();
let proxy_stream = buffered_proxy_stream.into_inner();

let tls_stream = wrap_tls(strict_tls, hostname, "", proxy_stream)
let tls_stream = wrap_tls(strict_tls, hostname, port, "", proxy_stream)
.await
.context("STARTTLS upgrade failed")?;
let buffered_stream = BufWriter::new(tls_stream);
Expand Down
2 changes: 1 addition & 1 deletion src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pub(crate) async fn connect_tls_inner(
alpn: &str,
) -> Result<impl SessionStream> {
let tcp_stream = connect_tcp_inner(addr).await?;
let tls_stream = wrap_tls(strict_tls, host, alpn, tcp_stream).await?;
let tls_stream = wrap_tls(strict_tls, host, addr.port(), alpn, tcp_stream).await?;
Ok(tls_stream)
}

Expand Down
4 changes: 2 additions & 2 deletions src/net/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ where
let proxy_stream = proxy_config
.connect(context, host, port, load_cache)
.await?;
let tls_stream = wrap_rustls(host, "", proxy_stream).await?;
let tls_stream = wrap_rustls(host, port, "", proxy_stream).await?;
Box::new(tls_stream)
} else {
let tcp_stream = crate::net::connect_tcp(context, host, port, load_cache).await?;
let tls_stream = wrap_rustls(host, "", tcp_stream).await?;
let tls_stream = wrap_rustls(host, port, "", tcp_stream).await?;
Box::new(tls_stream)
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/net/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ impl ProxyConfig {
load_cache,
)
.await?;
let tls_stream = wrap_rustls(&https_config.host, "", tcp_stream).await?;
let tls_stream =
wrap_rustls(&https_config.host, https_config.port, "", tcp_stream).await?;
let auth = if let Some((username, password)) = &https_config.user_password {
Some((username.as_str(), password.as_str()))
} else {
Expand Down
45 changes: 44 additions & 1 deletion src/net/tls.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
//! TLS support.
use std::collections::HashMap;
use std::sync::Arc;

use anyhow::Result;
use once_cell::sync::Lazy;
use parking_lot::Mutex;

use crate::net::session::SessionStream;

use tokio_rustls::rustls::client::ClientSessionStore;

pub async fn wrap_tls(
strict_tls: bool,
hostname: &str,
port: u16,
alpn: &str,
stream: impl SessionStream + 'static,
) -> Result<impl SessionStream> {
if strict_tls {
let tls_stream = wrap_rustls(hostname, alpn, stream).await?;
let tls_stream = wrap_rustls(hostname, port, alpn, stream).await?;
let boxed_stream: Box<dyn SessionStream> = Box::new(tls_stream);
Ok(boxed_stream)
} else {
Expand All @@ -35,8 +41,20 @@ pub async fn wrap_tls(
}
}

type SessionMap = HashMap<(u16, String), Arc<dyn ClientSessionStore>>;

/// 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.
static RESUMPTION_STORE: Lazy<Mutex<SessionMap>> = Lazy::new(Default::default);

pub async fn wrap_rustls(
hostname: &str,
port: u16,
alpn: &str,
stream: impl SessionStream,
) -> Result<impl SessionStream> {
Expand All @@ -52,6 +70,31 @@ pub async fn wrap_rustls(
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 = Arc::clone(
RESUMPTION_STORE
.lock()
.entry((port, alpn.to_string()))
.or_insert_with(|| {
// This is the default as of Rustls version 0.23.16,
// but we want to create multiple caches
// to separate them by port and ALPN.
Arc::new(tokio_rustls::rustls::client::ClientSessionMemoryCache::new(
256,
))
}),
);

let resumption = tokio_rustls::rustls::client::Resumption::store(resumption_store)
.tls12_resumption(tokio_rustls::rustls::client::Tls12Resumption::Disabled);
config.resumption = resumption;

let tls = tokio_rustls::TlsConnector::from(Arc::new(config));
let name = rustls_pki_types::ServerName::try_from(hostname)?.to_owned();
let tls_stream = tls.connect(name, stream).await?;
Expand Down
6 changes: 3 additions & 3 deletions src/smtp/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ async fn connect_secure_proxy(
let proxy_stream = proxy_config
.connect(context, hostname, port, strict_tls)
.await?;
let tls_stream = wrap_tls(strict_tls, hostname, alpn(port), proxy_stream).await?;
let tls_stream = wrap_tls(strict_tls, hostname, port, alpn(port), proxy_stream).await?;
let mut buffered_stream = BufStream::new(tls_stream);
skip_smtp_greeting(&mut buffered_stream).await?;
let session_stream: Box<dyn SessionBufStream> = Box::new(buffered_stream);
Expand All @@ -248,7 +248,7 @@ async fn connect_starttls_proxy(
skip_smtp_greeting(&mut buffered_stream).await?;
let transport = new_smtp_transport(buffered_stream).await?;
let tcp_stream = transport.starttls().await?.into_inner();
let tls_stream = wrap_tls(strict_tls, hostname, "", tcp_stream)
let tls_stream = wrap_tls(strict_tls, hostname, port, "", tcp_stream)
.await
.context("STARTTLS upgrade failed")?;
let buffered_stream = BufStream::new(tls_stream);
Expand Down Expand Up @@ -293,7 +293,7 @@ async fn connect_starttls(
skip_smtp_greeting(&mut buffered_stream).await?;
let transport = new_smtp_transport(buffered_stream).await?;
let tcp_stream = transport.starttls().await?.into_inner();
let tls_stream = wrap_tls(strict_tls, host, "", tcp_stream)
let tls_stream = wrap_tls(strict_tls, host, addr.port(), "", tcp_stream)
.await
.context("STARTTLS upgrade failed")?;

Expand Down

0 comments on commit c0067f8

Please sign in to comment.