mirror of
https://github.com/chatmail/core.git
synced 2026-04-27 18:36:30 +03:00
feat: TLS 1.3 session resumption
This commit is contained in:
@@ -76,11 +76,13 @@ 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, &context.tls_session_store).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, &context.tls_session_store).await?;
|
||||
Box::new(tls_stream)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +429,14 @@ 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,
|
||||
&context.tls_session_store,
|
||||
)
|
||||
.await?;
|
||||
let auth = if let Some((username, password)) = &https_config.user_password {
|
||||
Some((username.as_str(), password.as_str()))
|
||||
} else {
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
//! 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::client::ClientSessionStore;
|
||||
|
||||
pub async fn wrap_tls<'a>(
|
||||
strict_tls: bool,
|
||||
hostname: &str,
|
||||
port: u16,
|
||||
alpn: &str,
|
||||
stream: impl SessionStream + 'static,
|
||||
tls_session_store: &TlsSessionStore,
|
||||
) -> Result<impl SessionStream + 'a> {
|
||||
if strict_tls {
|
||||
let tls_stream = wrap_rustls(hostname, alpn, stream).await?;
|
||||
let tls_stream = wrap_rustls(hostname, port, alpn, stream, tls_session_store).await?;
|
||||
let boxed_stream: Box<dyn SessionStream> = Box::new(tls_stream);
|
||||
Ok(boxed_stream)
|
||||
} else {
|
||||
@@ -35,10 +41,58 @@ pub async fn wrap_tls<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(tokio_rustls::rustls::client::ClientSessionMemoryCache::new(
|
||||
TLS_CACHE_SIZE,
|
||||
))
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wrap_rustls<'a>(
|
||||
hostname: &str,
|
||||
port: u16,
|
||||
alpn: &str,
|
||||
stream: impl SessionStream + 'a,
|
||||
tls_session_store: &TlsSessionStore,
|
||||
) -> Result<impl SessionStream + 'a> {
|
||||
let mut root_cert_store = tokio_rustls::rustls::RootCertStore::empty();
|
||||
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
@@ -52,6 +106,18 @@ pub async fn wrap_rustls<'a>(
|
||||
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 = 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?;
|
||||
|
||||
Reference in New Issue
Block a user