Skip to main content

confium_net_quic/
tls.rs

1//! TLS material for the QUIC transport.
2//!
3//! QUIC requires TLS 1.3. The Confium transport is intentionally
4//! transport-unauthenticated — TC sessions sign every round message at
5//! the application layer (see `TODO.roadmap/05-networking-primitives.md`,
6//! "Application-layer signatures"). To keep the transport usable without
7//! a real PKI, this module generates a fresh self-signed certificate per
8//! process and configures clients to accept any server certificate.
9//!
10//! The certificate / key live only in memory; they are never persisted.
11
12use std::sync::Arc;
13use std::sync::OnceLock;
14
15use quinn::crypto::rustls::QuicClientConfig;
16use quinn::crypto::rustls::QuicServerConfig;
17use rcgen::CertificateParams;
18use rcgen::KeyPair;
19use rustls::pki_types::CertificateDer;
20use rustls::pki_types::PrivateKeyDer;
21use rustls::pki_types::ServerName;
22use rustls::pki_types::UnixTime;
23
24/// In-memory self-signed certificate + key, generated once and reused.
25struct SelfSigned {
26    cert_der: Vec<u8>,
27    key_der: Vec<u8>,
28}
29
30static SELF_SIGNED: OnceLock<SelfSigned> = OnceLock::new();
31
32/// Return the lazily-generated process-wide self-signed certificate and
33/// private key (in DER form). First call pays the generation cost;
34/// subsequent calls reuse the cached values. Panics on failure —
35/// generation from a fixed, in-tree algorithm cannot fail in practice,
36/// and propagating an error up through `register_transport!` /
37/// `block_on` adds complexity for no benefit.
38fn self_signed() -> &'static SelfSigned {
39    SELF_SIGNED.get_or_init(|| {
40        let params = CertificateParams::new(vec!["localhost".to_string()]).expect("valid SAN list");
41        let key_pair = KeyPair::generate().expect("generate ECDSA key pair");
42        let cert = params
43            .self_signed(&key_pair)
44            .expect("self-sign certificate");
45        SelfSigned {
46            cert_der: cert.der().to_vec(),
47            key_der: key_pair.serialize_der(),
48        }
49    })
50}
51
52/// Build a Quinn server configuration using the in-memory self-signed
53/// certificate. Used by [`crate::listener::QuicListener`].
54pub(crate) fn server_config() -> std::result::Result<quinn::ServerConfig, String> {
55    let signed = self_signed();
56    let cert = CertificateDer::from(signed.cert_der.clone());
57    let key = PrivateKeyDer::try_from(signed.key_der.clone())
58        .map_err(|e| format!("decode private key: {e}"))?;
59    let rustls_cfg = rustls::server::ServerConfig::builder()
60        .with_no_client_auth()
61        .with_single_cert(vec![cert], key)
62        .map_err(|e| format!("build rustls server config: {e}"))?;
63    let quic_cfg = QuicServerConfig::try_from(rustls_cfg)
64        .map_err(|e| format!("wrap rustls server config for quinn: {e}"))?;
65    Ok(quinn::ServerConfig::with_crypto(Arc::new(quic_cfg)))
66}
67
68/// Build a Quinn client configuration that accepts any server
69/// certificate. The TC protocol authenticates peers at the application
70/// layer; transport-level verification is intentionally disabled.
71pub(crate) fn client_config() -> std::result::Result<quinn::ClientConfig, String> {
72    // Skip server certificate verification entirely. This is safe in
73    // Confium's model because every TC round message carries an
74    // application-layer signature from the sender's long-term key; a
75    // MITM without that key cannot forge protocol messages.
76    let rustls_cfg = rustls::client::ClientConfig::builder()
77        .dangerous()
78        .with_custom_certificate_verifier(Arc::new(NoVerify))
79        .with_no_client_auth();
80    let quic_cfg = QuicClientConfig::try_from(rustls_cfg)
81        .map_err(|e| format!("wrap rustls client config for quinn: {e}"))?;
82    Ok(quinn::ClientConfig::new(Arc::new(quic_cfg)))
83}
84
85/// A certificate verifier that approves everything. See
86/// [`client_config`] for the rationale.
87#[derive(Debug)]
88struct NoVerify;
89
90impl rustls::client::danger::ServerCertVerifier for NoVerify {
91    fn verify_server_cert(
92        &self,
93        _end_entity: &CertificateDer<'_>,
94        _intermediates: &[CertificateDer<'_>],
95        _server_name: &ServerName<'_>,
96        _ocsp_response: &[u8],
97        _now: UnixTime,
98    ) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
99        Ok(rustls::client::danger::ServerCertVerified::assertion())
100    }
101
102    fn verify_tls12_signature(
103        &self,
104        _message: &[u8],
105        _cert: &CertificateDer<'_>,
106        _dss: &rustls::DigitallySignedStruct,
107    ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
108        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
109    }
110
111    fn verify_tls13_signature(
112        &self,
113        _message: &[u8],
114        _cert: &CertificateDer<'_>,
115        _dss: &rustls::DigitallySignedStruct,
116    ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
117        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
118    }
119
120    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
121        vec![
122            rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
123            rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
124            rustls::SignatureScheme::ED25519,
125            rustls::SignatureScheme::RSA_PSS_SHA256,
126            rustls::SignatureScheme::RSA_PSS_SHA384,
127            rustls::SignatureScheme::RSA_PSS_SHA512,
128        ]
129    }
130}