mirror of
https://github.com/chatmail/core.git
synced 2026-04-21 15:36:30 +03:00
147 lines
5.1 KiB
Rust
147 lines
5.1 KiB
Rust
//! 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)
|
|
}
|