use rustls::SupportedProtocolVersion;
use rustls::crypto::CryptoProvider;
#[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use std::sync::Arc;
#[derive(Clone)]
pub struct TlsPolicy {
provider: Arc<CryptoProvider>,
versions: Vec<&'static SupportedProtocolVersion>,
}
impl std::fmt::Debug for TlsPolicy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TlsPolicy")
.field("tls13", &self.versions.contains(&&rustls::version::TLS13))
.field("tls12", &self.versions.contains(&&rustls::version::TLS12))
.finish_non_exhaustive()
}
}
impl TlsPolicy {
#[must_use]
pub fn hardened() -> Self {
Self::with_provider(hardened_provider())
}
#[must_use]
pub fn with_provider(provider: Arc<CryptoProvider>) -> Self {
Self {
provider,
versions: vec![&rustls::version::TLS13, &rustls::version::TLS12],
}
}
#[must_use]
pub fn tls13_only(mut self) -> Self {
self.versions = vec![&rustls::version::TLS13];
self
}
#[must_use]
pub fn provider(&self) -> Arc<CryptoProvider> {
self.provider.clone()
}
#[must_use]
pub fn versions(&self) -> &[&'static SupportedProtocolVersion] {
&self.versions
}
pub fn install_as_process_default(&self) {
let _ = (*self.provider).clone().install_default();
}
#[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
pub(crate) fn server_config_from_pem(
&self,
cert: &[u8],
key: &[u8],
) -> Result<rustls::ServerConfig, std::io::Error> {
use rustls_pemfile::{certs, private_key};
let mut cert_reader = std::io::BufReader::new(cert);
let cert_chain: Vec<CertificateDer<'static>> = certs(&mut cert_reader)
.filter_map(std::result::Result::ok)
.collect();
let mut key_reader = std::io::BufReader::new(key);
let key_der: PrivateKeyDer<'static> = private_key(&mut key_reader)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Failed to read private key: {e}"),
)
})?
.ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::NotFound, "No private key found in PEM")
})?;
let mut server_config = rustls::ServerConfig::builder_with_provider(self.provider())
.with_protocol_versions(&self.versions)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("TLS version configuration failed: {e}"),
)
})?
.with_no_client_auth()
.with_single_cert(cert_chain, key_der)
.map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("Invalid certificate or key: {e}"),
)
})?;
server_config.alpn_protocols = crate::server::alpn_protocols(false);
Ok(server_config)
}
}
impl Default for TlsPolicy {
fn default() -> Self {
Self::hardened()
}
}
fn hardened_provider() -> Arc<CryptoProvider> {
static DEFAULT_PROVIDER: std::sync::OnceLock<Arc<CryptoProvider>> = std::sync::OnceLock::new();
DEFAULT_PROVIDER
.get_or_init(|| {
let kx_groups = vec![
rustls::crypto::aws_lc_rs::kx_group::X25519MLKEM768,
rustls::crypto::aws_lc_rs::kx_group::SECP256R1MLKEM768,
rustls::crypto::aws_lc_rs::kx_group::MLKEM1024,
rustls::crypto::aws_lc_rs::kx_group::MLKEM768,
rustls::crypto::aws_lc_rs::kx_group::SECP384R1,
rustls::crypto::aws_lc_rs::kx_group::X25519,
rustls::crypto::aws_lc_rs::kx_group::SECP256R1,
];
let cipher_suites = vec![
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384,
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_128_GCM_SHA256,
rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
];
Arc::new(CryptoProvider {
cipher_suites,
kx_groups,
..rustls::crypto::aws_lc_rs::default_provider()
})
})
.clone()
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::TlsPolicy;
#[test]
fn hardened_offers_both_tls_versions_by_default() {
let policy = TlsPolicy::hardened();
assert_eq!(policy.versions().len(), 2);
}
#[test]
fn tls13_only_restricts_to_a_single_version() {
let policy = TlsPolicy::hardened().tls13_only();
assert_eq!(policy.versions(), &[&rustls::version::TLS13]);
}
#[test]
fn default_matches_hardened() {
let default_versions = TlsPolicy::default().versions().len();
let hardened_versions = TlsPolicy::hardened().versions().len();
assert_eq!(default_versions, hardened_versions);
}
#[test]
fn debug_format_reports_negotiated_versions() {
let both = format!("{:?}", TlsPolicy::hardened());
assert!(both.contains("tls13: true"));
assert!(both.contains("tls12: true"));
let tls13_only = format!("{:?}", TlsPolicy::hardened().tls13_only());
assert!(tls13_only.contains("tls13: true"));
assert!(tls13_only.contains("tls12: false"));
}
#[test]
fn install_as_process_default_is_idempotent() {
TlsPolicy::hardened().install_as_process_default();
TlsPolicy::hardened()
.tls13_only()
.install_as_process_default();
}
#[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
#[test]
fn server_config_from_pem_builds_a_working_config_from_a_self_signed_cert() {
let cert = crate::tls::generate_self_signed_cert(vec!["localhost".to_string()])
.expect("generate self-signed cert");
let config = TlsPolicy::hardened()
.server_config_from_pem(cert.cert_pem.as_bytes(), cert.key_pem.as_bytes())
.expect("build server config from valid PEM");
assert_eq!(config.alpn_protocols, crate::server::alpn_protocols(false));
}
#[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
#[test]
fn server_config_from_pem_rejects_garbage_input() {
let err = TlsPolicy::hardened()
.server_config_from_pem(b"not a certificate", b"not a key")
.expect_err("garbage PEM must not build a config");
assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
}
#[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
#[test]
fn server_config_from_pem_rejects_a_key_that_does_not_match_the_cert() {
let cert_a = crate::tls::generate_self_signed_cert(vec!["a.example".to_string()])
.expect("generate cert a");
let cert_b = crate::tls::generate_self_signed_cert(vec!["b.example".to_string()])
.expect("generate cert b");
let err = TlsPolicy::hardened()
.server_config_from_pem(cert_a.cert_pem.as_bytes(), cert_b.key_pem.as_bytes())
.expect_err("mismatched cert/key pair must not build a config");
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
}