Skip to main content

simoom_quic/
tls.rs

1//! The production [`CryptoProvider`]: TLS 1.3 from `simoom_tls::quic` (RFC 9001), with
2//! AES-GCM or ChaCha20-Poly1305 packet protection, the Retry integrity tag, HMAC-SHA256 for
3//! tokens and the TLS crate's DRBG for randomness.
4
5use crate::crypto::{
6    CryptoError, CryptoProvider, CryptoSession, DirectionalKeys, HeaderKey, KeyChange, Keys, Level, NextSecrets, PacketKey,
7};
8use crate::error::TransportError;
9use simoom_tls::crypto::{rng, sha2::HashAlg};
10use simoom_tls::quic as tq;
11#[cfg(feature = "client")]
12use simoom_tls::ClientConfig;
13use simoom_tls::ServerConfig;
14use std::sync::Arc;
15
16struct Pk(tq::PacketKey);
17
18impl PacketKey for Pk {
19    fn seal(&self, pn: u64, header: &[u8], payload: &mut [u8], tag: &mut [u8]) {
20        let t: &mut [u8; 16] = tag.try_into().expect("16-byte tag");
21        self.0.seal_in_place(pn, header, payload, t);
22    }
23    fn open(&self, pn: u64, header: &[u8], payload_and_tag: &mut [u8]) -> Result<usize, ()> {
24        self.0.open_in_place(pn, header, payload_and_tag).map(|p| p.len()).map_err(|_| ())
25    }
26    fn tag_len(&self) -> usize {
27        self.0.tag_len()
28    }
29    fn confidentiality_limit(&self) -> u64 {
30        self.0.confidentiality_limit()
31    }
32    fn integrity_limit(&self) -> u64 {
33        self.0.integrity_limit()
34    }
35}
36
37struct Hk(tq::HeaderProtectionKey);
38
39impl HeaderKey for Hk {
40    fn mask(&self, sample: &[u8]) -> [u8; 5] {
41        self.0.mask(sample)
42    }
43}
44
45struct Ns(tq::Secrets);
46
47impl NextSecrets for Ns {
48    fn next_packet_keys(&mut self) -> (Box<dyn PacketKey>, Box<dyn PacketKey>) {
49        let (l, r) = self.0.next_packet_keys();
50        (Box::new(Pk(l)), Box::new(Pk(r)))
51    }
52}
53
54fn dir(k: tq::DirectionalKeys) -> DirectionalKeys {
55    DirectionalKeys { packet: Box::new(Pk(k.packet)), header: Box::new(Hk(k.header)) }
56}
57
58fn keys(k: tq::Keys) -> Keys {
59    Keys { local: dir(k.local), remote: dir(k.remote) }
60}
61
62fn level(l: Level) -> simoom_tls::Level {
63    match l {
64        Level::Initial => simoom_tls::Level::Initial,
65        Level::Handshake => simoom_tls::Level::Handshake,
66        Level::OneRtt => simoom_tls::Level::OneRtt,
67    }
68}
69
70struct Session(tq::QuicConnection);
71
72impl CryptoSession for Session {
73    fn read_handshake(&mut self, l: Level, data: &[u8]) -> Result<(), CryptoError> {
74        self.0.read_handshake(level(l), data).map_err(|e| CryptoError {
75            alert: e.alert().unwrap_or(simoom_tls::alert::INTERNAL_ERROR),
76            reason: "TLS handshake failed",
77        })
78    }
79    fn write_handshake(&mut self, out: &mut Vec<u8>) -> Option<KeyChange> {
80        Some(match self.0.write_handshake(out)? {
81            tq::KeyChange::Handshake { keys: k } => KeyChange::Handshake { keys: keys(k) },
82            tq::KeyChange::OneRtt { keys: k, next } => KeyChange::OneRtt { keys: keys(k), next: Box::new(Ns(next)) },
83        })
84    }
85    fn peer_transport_params(&self) -> Option<&[u8]> {
86        self.0.peer_transport_params()
87    }
88    fn alpn_protocol(&self) -> Option<&[u8]> {
89        self.0.alpn_protocol()
90    }
91    fn is_handshaking(&self) -> bool {
92        self.0.is_handshaking()
93    }
94    fn set_version(&mut self, version: u32) {
95        self.0.set_version(version);
96    }
97    fn server_name(&self) -> Option<&str> {
98        self.0.server_name()
99    }
100    fn cipher_suite(&self) -> Option<u16> {
101        self.0.negotiated_cipher_suite().map(|s| s.id())
102    }
103    fn peer_certificates(&self) -> &[Vec<u8>] {
104        self.0.peer_certificates()
105    }
106}
107
108/// Starts a client session; set only by the constructors that take a client configuration, so
109/// a server-only binary never links the TLS client handshake.
110#[cfg(feature = "client")]
111type StartClient = fn(&Arc<ClientConfig>, u32, &str, Vec<u8>) -> Result<Box<dyn CryptoSession>, TransportError>;
112
113/// TLS-backed crypto for an endpoint. A server needs `server`, a client `client`; the
114/// configurations must list ALPN protocols (`h3` for HTTP/3). Build a client provider with
115/// [`TlsProvider::client`] or [`TlsProvider::with_client`] (which link the TLS client); a
116/// provider made with [`TlsProvider::server`] refuses client sessions.
117pub struct TlsProvider {
118    pub server: Option<Arc<ServerConfig>>,
119    #[cfg(feature = "client")]
120    pub client: Option<Arc<ClientConfig>>,
121    #[cfg(feature = "client")]
122    start_client: Option<StartClient>,
123}
124
125#[cfg(feature = "client")]
126fn start_client(cfg: &Arc<ClientConfig>, version: u32, name: &str, tp: Vec<u8>) -> Result<Box<dyn CryptoSession>, TransportError> {
127    let mut c = tq::QuicConnection::new_client(cfg.clone(), name, tp)
128        .map_err(|_| TransportError::new(crate::error::INTERNAL_ERROR, "TLS client configuration rejected"))?;
129    c.set_version(version);
130    Ok(Box::new(Session(c)))
131}
132
133impl TlsProvider {
134    pub fn server(cfg: Arc<ServerConfig>) -> Self {
135        TlsProvider {
136            server: Some(cfg),
137            #[cfg(feature = "client")]
138            client: None,
139            #[cfg(feature = "client")]
140            start_client: None,
141        }
142    }
143    #[cfg(feature = "client")]
144    pub fn client(cfg: Arc<ClientConfig>) -> Self {
145        TlsProvider { server: None, client: None, start_client: None }.with_client(cfg)
146    }
147    /// Adds a client configuration (an endpoint that both accepts and opens connections).
148    #[cfg(feature = "client")]
149    pub fn with_client(mut self, cfg: Arc<ClientConfig>) -> Self {
150        self.client = Some(cfg);
151        self.start_client = Some(start_client);
152        self
153    }
154}
155
156impl CryptoProvider for TlsProvider {
157    fn initial_keys(&self, version: u32, dcid: &[u8], is_server: bool) -> Keys {
158        keys(tq::initial_keys(version, dcid, is_server))
159    }
160    fn retry_tag(&self, version: u32, pseudo: &[u8]) -> [u8; 16] {
161        let n = pseudo[0] as usize;
162        tq::retry_integrity_tag(version, &pseudo[1..1 + n], &pseudo[1 + n..])
163    }
164    fn hmac(&self, key: &[u8; 32], parts: &[&[u8]]) -> [u8; 32] {
165        let out = HashAlg::Sha256.hmac(key, parts);
166        out[..32].try_into().unwrap()
167    }
168    fn fill_random(&self, buf: &mut [u8]) {
169        rng::fill(buf);
170    }
171    fn server_session(&self, version: u32, tp: Vec<u8>) -> Result<Box<dyn CryptoSession>, TransportError> {
172        let cfg = self.server.clone().ok_or(TransportError::new(crate::error::INTERNAL_ERROR, "no TLS server configuration"))?;
173        let mut c = tq::QuicConnection::new_server(cfg, tp);
174        c.set_version(version);
175        Ok(Box::new(Session(c)))
176    }
177    fn client_session(&self, version: u32, name: &str, tp: Vec<u8>) -> Result<Box<dyn CryptoSession>, TransportError> {
178        #[cfg(feature = "client")]
179        if let (Some(cfg), Some(start)) = (&self.client, self.start_client) {
180            return start(cfg, version, name, tp);
181        }
182        let _ = (version, name, tp);
183        Err(TransportError::new(crate::error::INTERNAL_ERROR, "no TLS client configuration"))
184    }
185}