Skip to main content

simoom_quic/
crypto.rs

1//! The crypto boundary between the transport and TLS (RFC 9001).
2//!
3//! The transport never touches TLS internals: it hands handshake bytes to a [`CryptoSession`]
4//! per encryption level, receives key changes from it, and protects packets with the
5//! [`PacketKey`] and [`HeaderKey`] objects it is given. [`CryptoProvider`] supplies the
6//! endpoint-wide pieces (Initial keys, the Retry integrity tag, HMAC for tokens, randomness).
7//!
8//! [`null`] is a deterministic stand-in used by the tests and the benchmark; the real
9//! implementation lives on top of `simoom_tls::quic`.
10
11use crate::error::TransportError;
12
13/// Encryption level (RFC 9001 §4). 0-RTT is not supported.
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub enum Level {
16    Initial = 0,
17    Handshake = 1,
18    OneRtt = 2,
19}
20
21impl Level {
22    pub const ALL: [Level; 3] = [Level::Initial, Level::Handshake, Level::OneRtt];
23}
24
25/// AEAD packet protection for one direction.
26pub trait PacketKey: Send {
27    /// Encrypts `payload` in place and writes the tag (`tag_len()` bytes) to `tag`.
28    fn seal(&self, pn: u64, header: &[u8], payload: &mut [u8], tag: &mut [u8]);
29    /// Decrypts `payload_and_tag` in place; returns the plaintext length.
30    fn open(&self, pn: u64, header: &[u8], payload_and_tag: &mut [u8]) -> Result<usize, ()>;
31    fn tag_len(&self) -> usize;
32    /// Packets that may be sealed before a key update is required (RFC 9001 §6.6).
33    fn confidentiality_limit(&self) -> u64;
34    /// Failed decryptions tolerated before the connection must close (RFC 9001 §6.6).
35    fn integrity_limit(&self) -> u64;
36}
37
38/// Header protection for one direction (RFC 9001 §5.4).
39pub trait HeaderKey: Send {
40    /// The five-byte mask for a 16-byte `sample`.
41    fn mask(&self, sample: &[u8]) -> [u8; 5];
42}
43
44/// Packet and header keys for one direction.
45pub struct DirectionalKeys {
46    pub packet: Box<dyn PacketKey>,
47    pub header: Box<dyn HeaderKey>,
48}
49
50/// Keys for both directions of one level.
51pub struct Keys {
52    pub local: DirectionalKeys,
53    pub remote: DirectionalKeys,
54}
55
56/// Secrets that derive the next 1-RTT packet keys on a key update (RFC 9001 §6).
57pub trait NextSecrets: Send {
58    /// Returns (local, remote) packet keys for the next key phase and advances.
59    fn next_packet_keys(&mut self) -> (Box<dyn PacketKey>, Box<dyn PacketKey>);
60}
61
62/// A change of write level reported by [`CryptoSession::write_handshake`].
63pub enum KeyChange {
64    Handshake { keys: Keys },
65    OneRtt { keys: Keys, next: Box<dyn NextSecrets> },
66}
67
68/// A TLS alert raised by the handshake; sent as `CRYPTO_ERROR` (0x100 + alert).
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub struct CryptoError {
71    pub alert: u8,
72    pub reason: &'static str,
73}
74
75impl From<CryptoError> for TransportError {
76    fn from(e: CryptoError) -> Self {
77        TransportError::new(crate::error::CRYPTO_ERROR + e.alert as u64, e.reason)
78    }
79}
80
81/// One side of a TLS 1.3 handshake carried in CRYPTO frames.
82pub trait CryptoSession: Send {
83    /// Handshake bytes received at `level`, in order.
84    fn read_handshake(&mut self, level: Level, data: &[u8]) -> Result<(), CryptoError>;
85    /// Appends handshake bytes for the current write level. When it returns a key change,
86    /// later bytes belong to the next level.
87    fn write_handshake(&mut self, out: &mut Vec<u8>) -> Option<KeyChange>;
88    /// The peer's encoded transport parameters, once received.
89    fn peer_transport_params(&self) -> Option<&[u8]>;
90    /// The negotiated ALPN protocol.
91    fn alpn_protocol(&self) -> Option<&[u8]>;
92    fn is_handshaking(&self) -> bool;
93    /// Client: the connection continues in another QUIC version (compatible version
94    /// negotiation, RFC 9368) before any Handshake keys exist; later keys use its labels.
95    fn set_version(&mut self, _version: u32) {}
96    /// Server: the SNI name the client sent.
97    fn server_name(&self) -> Option<&str> {
98        None
99    }
100    /// The negotiated TLS cipher suite (its IANA code, `0x1301`...).
101    fn cipher_suite(&self) -> Option<u16> {
102        None
103    }
104    /// The peer's verified certificate chain (DER, leaf first); empty without one.
105    fn peer_certificates(&self) -> &[Vec<u8>] {
106        &[]
107    }
108}
109
110/// Endpoint-wide crypto services.
111pub trait CryptoProvider: Send + Sync {
112    /// Initial keys derived from the client's first destination connection ID.
113    fn initial_keys(&self, version: u32, dcid: &[u8], is_server: bool) -> Keys;
114    /// The Retry integrity tag over the Retry pseudo-packet (RFC 9001 §5.8).
115    fn retry_tag(&self, version: u32, pseudo_packet: &[u8]) -> [u8; 16];
116    /// HMAC-SHA256 over the concatenation of `parts`.
117    fn hmac(&self, key: &[u8; 32], parts: &[&[u8]]) -> [u8; 32];
118    /// Cryptographically secure random bytes.
119    fn fill_random(&self, buf: &mut [u8]);
120    /// Starts a server handshake for QUIC `version` that will send `transport_params`.
121    fn server_session(&self, version: u32, transport_params: Vec<u8>) -> Result<Box<dyn CryptoSession>, TransportError>;
122    /// Starts a client handshake to `server_name` that will send `transport_params`.
123    fn client_session(
124        &self,
125        version: u32,
126        server_name: &str,
127        transport_params: Vec<u8>,
128    ) -> Result<Box<dyn CryptoSession>, TransportError>;
129}
130
131/// Constant-time equality for tokens and tags.
132pub fn ct_eq(a: &[u8], b: &[u8]) -> bool {
133    if a.len() != b.len() {
134        return false;
135    }
136    let mut d = 0u8;
137    for (x, y) in a.iter().zip(b) {
138        d |= x ^ y;
139    }
140    d == 0
141}
142
143/// A deterministic, NOT secure crypto provider for tests and benchmarks.
144///
145/// Packets are not encrypted; a 16-byte keyed checksum stands in for the AEAD tag, so
146/// corruption, wrong keys and wrong packet numbers are still detected. The handshake is a
147/// TLS-shaped exchange (ClientHello, ServerHello, EncryptedExtensions + Finished, Finished)
148/// that carries ALPN and transport parameters and changes keys at the same points as TLS 1.3.
149pub mod null {
150    use super::*;
151    use std::sync::atomic::{AtomicU64, Ordering};
152
153    #[inline]
154    fn mix(mut x: u64) -> u64 {
155        x ^= x >> 30;
156        x = x.wrapping_mul(0xbf58476d1ce4e5b9);
157        x ^= x >> 27;
158        x = x.wrapping_mul(0x94d049bb133111eb);
159        x ^ (x >> 31)
160    }
161
162    fn hash(seed: u64, parts: &[&[u8]]) -> u64 {
163        let mut h = mix(seed ^ 0x9e3779b97f4a7c15);
164        for p in parts {
165            let mut chunks = p.chunks_exact(8);
166            for c in &mut chunks {
167                let w = u64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]);
168                h = (h ^ w).wrapping_mul(0x100000001b3).rotate_left(29);
169            }
170            for &b in chunks.remainder() {
171                h = (h ^ b as u64).wrapping_mul(0x100000001b3);
172            }
173            h = mix(h ^ p.len() as u64);
174        }
175        h
176    }
177
178    struct NullPacketKey(u64, bool);
179
180    impl PacketKey for NullPacketKey {
181        fn seal(&self, pn: u64, header: &[u8], payload: &mut [u8], tag: &mut [u8]) {
182            let a = hash(self.0 ^ pn, &[header, payload]);
183            tag[..8].copy_from_slice(&a.to_le_bytes());
184            tag[8..16].copy_from_slice(&mix(a ^ self.0).to_le_bytes());
185        }
186        fn open(&self, pn: u64, header: &[u8], payload_and_tag: &mut [u8]) -> Result<usize, ()> {
187            if payload_and_tag.len() < 16 {
188                return Err(());
189            }
190            let n = payload_and_tag.len() - 16;
191            let mut tag = [0u8; 16];
192            self.seal(pn, header, &mut payload_and_tag[..n], &mut tag);
193            if self.1 || ct_eq(&tag, &payload_and_tag[n..]) {
194                Ok(n)
195            } else {
196                Err(())
197            }
198        }
199        fn tag_len(&self) -> usize {
200            16
201        }
202        fn confidentiality_limit(&self) -> u64 {
203            1 << 23
204        }
205        fn integrity_limit(&self) -> u64 {
206            1 << 36
207        }
208    }
209
210    struct NullHeaderKey(u64);
211
212    impl HeaderKey for NullHeaderKey {
213        fn mask(&self, sample: &[u8]) -> [u8; 5] {
214            let h = hash(self.0, &[sample]).to_le_bytes();
215            [h[0], h[1], h[2], h[3], h[4]]
216        }
217    }
218
219    fn dir(seed: u64, lax: bool) -> DirectionalKeys {
220        DirectionalKeys { packet: Box::new(NullPacketKey(seed, lax)), header: Box::new(NullHeaderKey(mix(seed))) }
221    }
222
223    /// Keys for a level: `c2s`/`s2c` seeds, oriented for the role.
224    fn keys(c2s: u64, s2c: u64, is_server: bool, lax: bool) -> Keys {
225        if is_server {
226            Keys { local: dir(s2c, lax), remote: dir(c2s, lax) }
227        } else {
228            Keys { local: dir(c2s, lax), remote: dir(s2c, lax) }
229        }
230    }
231
232    struct NullSecrets {
233        local: u64,
234        remote: u64,
235        lax: bool,
236    }
237
238    impl NextSecrets for NullSecrets {
239        fn next_packet_keys(&mut self) -> (Box<dyn PacketKey>, Box<dyn PacketKey>) {
240            self.local = mix(self.local ^ 0x6b75);
241            self.remote = mix(self.remote ^ 0x6b75);
242            (Box::new(NullPacketKey(self.local, self.lax)), Box::new(NullPacketKey(self.remote, self.lax)))
243        }
244    }
245
246    const CLIENT_HELLO: u8 = 1;
247    const SERVER_HELLO: u8 = 2;
248    const ENCRYPTED_EXTENSIONS: u8 = 8;
249    const CERTIFICATE: u8 = 11;
250    const FINISHED: u8 = 20;
251    /// Alert codes used by the mock.
252    pub const ALERT_UNEXPECTED: u8 = 10;
253    pub const ALERT_NO_APPLICATION_PROTOCOL: u8 = 120;
254
255    fn put_msg(out: &mut Vec<u8>, ty: u8, body: &[u8]) {
256        out.push(ty);
257        out.extend_from_slice(&(body.len() as u32).to_be_bytes()[1..]);
258        out.extend_from_slice(body);
259    }
260
261    fn put_blob(out: &mut Vec<u8>, b: &[u8]) {
262        out.extend_from_slice(&(b.len() as u16).to_be_bytes());
263        out.extend_from_slice(b);
264    }
265
266    fn get_blob<'a>(b: &mut &'a [u8]) -> Result<&'a [u8], CryptoError> {
267        let bad = CryptoError { alert: 50, reason: "mock: bad message" };
268        if b.len() < 2 {
269            return Err(bad);
270        }
271        let n = u16::from_be_bytes([b[0], b[1]]) as usize;
272        if b.len() < 2 + n {
273            return Err(bad);
274        }
275        let v = &b[2..2 + n];
276        *b = &b[2 + n..];
277        Ok(v)
278    }
279
280    #[derive(PartialEq, Clone, Copy, Debug)]
281    enum State {
282        Start,
283        SentHello,
284        GotServerHello,
285        HandshakeKeysGiven,
286        GotServerFinished,
287        Done,
288        // Server states.
289        WaitHello,
290        GotHello,
291        SentServerHello,
292        SentFinished,
293    }
294
295    /// Mock TLS session.
296    pub struct NullSession {
297        is_server: bool,
298        state: State,
299        tp: Vec<u8>,
300        peer_tp: Option<Vec<u8>>,
301        alpn_offer: Vec<Vec<u8>>,
302        alpn: Option<Vec<u8>>,
303        buf: [Vec<u8>; 3],
304        seed: u64,
305        hello_padding: usize,
306        lax: bool,
307    }
308
309    impl NullSession {
310        fn take_msg(&mut self, level: Level) -> Option<(u8, Vec<u8>)> {
311            let b = &mut self.buf[level as usize];
312            if b.len() < 4 {
313                return None;
314            }
315            let n = u32::from_be_bytes([0, b[1], b[2], b[3]]) as usize;
316            if b.len() < 4 + n {
317                return None;
318            }
319            let ty = b[0];
320            let body = b[4..4 + n].to_vec();
321            b.drain(..4 + n);
322            Some((ty, body))
323        }
324    }
325
326    impl CryptoSession for NullSession {
327        fn read_handshake(&mut self, level: Level, data: &[u8]) -> Result<(), CryptoError> {
328            let unexpected = CryptoError { alert: ALERT_UNEXPECTED, reason: "mock: unexpected message" };
329            if level == Level::OneRtt {
330                return Err(unexpected);
331            }
332            self.buf[level as usize].extend_from_slice(data);
333            if self.buf[level as usize].len() > 1 << 20 {
334                return Err(unexpected);
335            }
336            while let Some((ty, body)) = self.take_msg(level) {
337                match (self.is_server, self.state, level, ty) {
338                    (true, State::WaitHello, Level::Initial, CLIENT_HELLO) => {
339                        let mut b = &body[..];
340                        let alpns = get_blob(&mut b)?;
341                        let tp = get_blob(&mut b)?;
342                        self.peer_tp = Some(tp.to_vec());
343                        let mut a = alpns;
344                        while !a.is_empty() {
345                            let p = get_blob(&mut a)?;
346                            if self.alpn.is_none() && self.alpn_offer.iter().any(|o| o == p) {
347                                self.alpn = Some(p.to_vec());
348                            }
349                        }
350                        if self.alpn.is_none() && !self.alpn_offer.is_empty() {
351                            return Err(CryptoError {
352                                alert: ALERT_NO_APPLICATION_PROTOCOL,
353                                reason: "mock: no common ALPN",
354                            });
355                        }
356                        self.seed = hash(0x5eed, &[&body]);
357                        self.state = State::GotHello;
358                    }
359                    (false, State::SentHello, Level::Initial, SERVER_HELLO) => {
360                        let mut b = &body[..];
361                        let alpn = get_blob(&mut b)?;
362                        let seed = get_blob(&mut b)?;
363                        if seed.len() != 8 {
364                            return Err(unexpected);
365                        }
366                        self.seed = u64::from_le_bytes(seed.try_into().unwrap());
367                        if !alpn.is_empty() {
368                            self.alpn = Some(alpn.to_vec());
369                        }
370                        self.state = State::GotServerHello;
371                    }
372                    (false, State::HandshakeKeysGiven, Level::Handshake, CERTIFICATE) => {}
373                    (false, State::HandshakeKeysGiven, Level::Handshake, ENCRYPTED_EXTENSIONS) => {
374                        self.peer_tp = Some(body);
375                    }
376                    (false, State::HandshakeKeysGiven, Level::Handshake, FINISHED) if self.peer_tp.is_some() => {
377                        self.state = State::GotServerFinished;
378                    }
379                    (true, State::SentFinished, Level::Handshake, FINISHED) => {
380                        self.state = State::Done;
381                    }
382                    _ => return Err(unexpected),
383                }
384            }
385            Ok(())
386        }
387
388        fn write_handshake(&mut self, out: &mut Vec<u8>) -> Option<KeyChange> {
389            let s = self.seed;
390            let lax = self.lax;
391            let hs = || keys(mix(s ^ 1), mix(s ^ 2), self.is_server, lax);
392            let one = || keys(mix(s ^ 3), mix(s ^ 4), self.is_server, lax);
393            let secrets = || {
394                let (c, sv) = (mix(s ^ 3), mix(s ^ 4));
395                let (local, remote) = if self.is_server { (sv, c) } else { (c, sv) };
396                Box::new(NullSecrets { local, remote, lax }) as Box<dyn NextSecrets>
397            };
398            match self.state {
399                State::Start => {
400                    let mut body = Vec::new();
401                    let mut alpns = Vec::new();
402                    for a in &self.alpn_offer {
403                        put_blob(&mut alpns, a);
404                    }
405                    put_blob(&mut body, &alpns);
406                    put_blob(&mut body, &self.tp);
407                    body.resize(body.len() + self.hello_padding, 0);
408                    put_msg(out, CLIENT_HELLO, &body);
409                    self.state = State::SentHello;
410                    None
411                }
412                State::GotServerHello => {
413                    self.state = State::HandshakeKeysGiven;
414                    Some(KeyChange::Handshake { keys: hs() })
415                }
416                State::GotServerFinished => {
417                    put_msg(out, FINISHED, &[]);
418                    self.state = State::Done;
419                    Some(KeyChange::OneRtt { keys: one(), next: secrets() })
420                }
421                State::GotHello => {
422                    let mut body = Vec::new();
423                    put_blob(&mut body, self.alpn.as_deref().unwrap_or(&[]));
424                    put_blob(&mut body, &s.to_le_bytes());
425                    put_msg(out, SERVER_HELLO, &body);
426                    self.state = State::SentServerHello;
427                    Some(KeyChange::Handshake { keys: hs() })
428                }
429                State::SentServerHello => {
430                    put_msg(out, ENCRYPTED_EXTENSIONS, &self.tp);
431                    if self.hello_padding > 0 {
432                        put_msg(out, CERTIFICATE, &vec![0x55; self.hello_padding]);
433                    }
434                    put_msg(out, FINISHED, &[]);
435                    self.state = State::SentFinished;
436                    Some(KeyChange::OneRtt { keys: one(), next: secrets() })
437                }
438                _ => None,
439            }
440        }
441
442        fn peer_transport_params(&self) -> Option<&[u8]> {
443            self.peer_tp.as_deref()
444        }
445        fn alpn_protocol(&self) -> Option<&[u8]> {
446            self.alpn.as_deref()
447        }
448        fn is_handshaking(&self) -> bool {
449            self.state != State::Done
450        }
451    }
452
453    /// The null provider. `alpn` lists the protocols offered (client) or accepted (server).
454    pub struct NullProvider {
455        rng: AtomicU64,
456        pub alpn: Vec<Vec<u8>>,
457        /// Extra bytes in the ClientHello, to exercise multi-packet CRYPTO data.
458        pub hello_padding: usize,
459        /// Size of a mock Certificate message in the server's flight.
460        pub server_flight: usize,
461        /// Accept any packet tag, so mutated packets reach frame processing (fuzzing).
462        pub accept_any_tag: bool,
463    }
464
465    impl NullProvider {
466        pub fn new(seed: u64) -> Self {
467            NullProvider { rng: AtomicU64::new(seed | 1), alpn: vec![b"h3".to_vec()], hello_padding: 0, server_flight: 0, accept_any_tag: false }
468        }
469    }
470
471    impl CryptoProvider for NullProvider {
472        fn initial_keys(&self, version: u32, dcid: &[u8], is_server: bool) -> Keys {
473            let s = hash(version as u64, &[dcid]);
474            keys(mix(s ^ 0xc), mix(s ^ 0x5), is_server, self.accept_any_tag)
475        }
476        fn retry_tag(&self, version: u32, pseudo_packet: &[u8]) -> [u8; 16] {
477            let a = hash(0x7e7 ^ version as u64, &[pseudo_packet]);
478            let mut t = [0u8; 16];
479            t[..8].copy_from_slice(&a.to_le_bytes());
480            t[8..].copy_from_slice(&mix(a).to_le_bytes());
481            t
482        }
483        fn hmac(&self, key: &[u8; 32], parts: &[&[u8]]) -> [u8; 32] {
484            let k = hash(0, &[key]);
485            let mut out = [0u8; 32];
486            let mut h = hash(k, parts);
487            for c in out.chunks_mut(8) {
488                c.copy_from_slice(&h.to_le_bytes());
489                h = mix(h ^ k);
490            }
491            out
492        }
493        fn fill_random(&self, buf: &mut [u8]) {
494            for c in buf.chunks_mut(8) {
495                let v = mix(self.rng.fetch_add(0x9e3779b97f4a7c15, Ordering::Relaxed));
496                c.copy_from_slice(&v.to_le_bytes()[..c.len()]);
497            }
498        }
499        fn server_session(&self, _version: u32, tp: Vec<u8>) -> Result<Box<dyn CryptoSession>, TransportError> {
500            Ok(Box::new(NullSession {
501                is_server: true,
502                state: State::WaitHello,
503                tp,
504                peer_tp: None,
505                alpn_offer: self.alpn.clone(),
506                alpn: None,
507                buf: Default::default(),
508                seed: 0,
509                hello_padding: self.server_flight,
510                lax: self.accept_any_tag,
511            }))
512        }
513        fn client_session(&self, _version: u32, _name: &str, tp: Vec<u8>) -> Result<Box<dyn CryptoSession>, TransportError> {
514            Ok(Box::new(NullSession {
515                is_server: false,
516                state: State::Start,
517                tp,
518                peer_tp: None,
519                alpn_offer: self.alpn.clone(),
520                alpn: None,
521                buf: Default::default(),
522                seed: 0,
523                hello_padding: self.hello_padding,
524                lax: self.accept_any_tag,
525            }))
526        }
527    }
528}