Skip to main content

simoom_tls/
quic.rs

1//! The TLS interface for QUIC (RFC 9001), with QUIC version 2 key labels (RFC 9369).
2//!
3//! [`QuicConnection`] runs the same handshake engine as the record layer, but exchanges raw
4//! handshake messages tagged with their encryption [`Level`] (carried in CRYPTO frames) and
5//! hands out packet and header-protection keys as they become available.
6//!
7//! Driving it:
8//! 1. Feed CRYPTO frame data with [`QuicConnection::read_handshake`].
9//! 2. Call [`QuicConnection::write_handshake`] until it returns `None` and writes nothing. The
10//!    bytes it appends belong to the level reported by [`QuicConnection::write_level`] *before*
11//!    the call; a returned [`KeyChange`] applies to the bytes of later calls.
12//! 3. On an error, close the connection with [`QuicConnection::tls_alert`]
13//!    (QUIC CRYPTO_ERROR, 0x100 + the TLS alert).
14//!
15//! 0-RTT is not offered or accepted. KeyUpdate messages are refused (QUIC updates keys with
16//! [`Secrets::next_packet_keys`] instead).
17
18use crate::crypto::aes::Aes;
19#[cfg(feature = "chacha20")]
20use crate::crypto::chacha::chacha20_block;
21use crate::crypto::gcm::AesGcm;
22use crate::crypto::sha2::HashAlg;
23#[cfg(feature = "client")]
24use crate::tls::client::ClientHs;
25#[cfg(feature = "client")]
26use crate::tls::config::ClientConfig;
27use crate::tls::config::ServerConfig;
28use crate::tls::engine::{Engine, Event, HsReader};
29use crate::tls::keyschedule::{expand_label, expand_label_vec};
30use crate::tls::record::{nonce, Aead};
31use crate::tls::server::ServerHs;
32use crate::tls::{alert, AeadAlg, CipherSuite, Error, Result};
33use std::sync::Arc;
34
35pub use crate::tls::Level;
36
37/// QUIC version 1 (RFC 9000).
38pub const VERSION_1: u32 = 0x0000_0001;
39/// QUIC version 2 (RFC 9369).
40pub const VERSION_2: u32 = 0x6b33_43cf;
41
42struct Labels {
43    key: &'static [u8],
44    iv: &'static [u8],
45    hp: &'static [u8],
46    ku: &'static [u8],
47}
48
49fn labels(version: u32) -> Labels {
50    if version == VERSION_2 {
51        Labels { key: b"quicv2 key", iv: b"quicv2 iv", hp: b"quicv2 hp", ku: b"quicv2 ku" }
52    } else {
53        Labels { key: b"quic key", iv: b"quic iv", hp: b"quic hp", ku: b"quic ku" }
54    }
55}
56
57/// AEAD packet protection for one direction (RFC 9001 section 5.3).
58#[derive(Clone)]
59pub struct PacketKey {
60    aead: Aead,
61    iv: [u8; 12],
62    alg: AeadAlg,
63}
64
65impl std::fmt::Debug for PacketKey {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(f, "PacketKey({:?})", self.alg)
68    }
69}
70
71impl PacketKey {
72    fn new(suite: CipherSuite, version: u32, secret: &[u8]) -> PacketKey {
73        let l = labels(version);
74        let h = suite.hash();
75        let key = expand_label_vec(h, secret, l.key, &[], suite.aead().key_len());
76        let mut iv = [0u8; 12];
77        expand_label(h, secret, l.iv, &[], &mut iv);
78        PacketKey { aead: Aead::new(suite.aead(), &key), iv, alg: suite.aead() }
79    }
80
81    /// Encrypts `payload` in place; `header` is the unprotected header (the AAD).
82    pub fn seal_in_place(&self, pn: u64, header: &[u8], payload: &mut [u8], tag_out: &mut [u8; 16]) {
83        *tag_out = self.aead.seal(&nonce(&self.iv, pn), header, payload);
84    }
85
86    /// Decrypts `payload_with_tag` in place and returns the plaintext part.
87    pub fn open_in_place<'a>(&self, pn: u64, header: &[u8], payload_with_tag: &'a mut [u8]) -> Result<&'a mut [u8]> {
88        if payload_with_tag.len() < 16 {
89            return Err(Error::proto(alert::BAD_RECORD_MAC, "packet too short"));
90        }
91        let n = payload_with_tag.len() - 16;
92        let (ct, tag) = payload_with_tag.split_at_mut(n);
93        if !self.aead.open(&nonce(&self.iv, pn), header, ct, tag) {
94            return Err(Error::proto(alert::BAD_RECORD_MAC, "packet authentication failed"));
95        }
96        Ok(ct)
97    }
98
99    pub fn tag_len(&self) -> usize {
100        16
101    }
102
103    /// Packets that may be encrypted under this key (RFC 9001 section 6.6).
104    pub fn confidentiality_limit(&self) -> u64 {
105        match self.alg {
106            #[cfg(feature = "chacha20")]
107            AeadAlg::Chacha20Poly1305 => 1 << 62,
108            _ => 1 << 23,
109        }
110    }
111
112    /// Forged packets tolerated before the connection must close (RFC 9001 section 6.6).
113    pub fn integrity_limit(&self) -> u64 {
114        match self.alg {
115            #[cfg(feature = "chacha20")]
116            AeadAlg::Chacha20Poly1305 => 1 << 36,
117            _ => 1 << 52,
118        }
119    }
120}
121
122/// Header protection (RFC 9001 section 5.4): AES-ECB or ChaCha20.
123#[derive(Clone)]
124#[allow(clippy::large_enum_variant)] // one per connection or key; boxing would add an indirection on the hot path
125pub enum HeaderProtectionKey {
126    Aes(Aes),
127    #[cfg(feature = "chacha20")]
128    ChaCha([u8; 32]),
129}
130
131impl std::fmt::Debug for HeaderProtectionKey {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        f.write_str(match self {
134            HeaderProtectionKey::Aes(_) => "HeaderProtectionKey(AES)",
135            #[cfg(feature = "chacha20")]
136            HeaderProtectionKey::ChaCha(_) => "HeaderProtectionKey(ChaCha20)",
137        })
138    }
139}
140
141impl HeaderProtectionKey {
142    fn new(suite: CipherSuite, version: u32, secret: &[u8]) -> HeaderProtectionKey {
143        let key = expand_label_vec(suite.hash(), secret, labels(version).hp, &[], suite.aead().key_len());
144        match suite.aead() {
145            #[cfg(feature = "chacha20")]
146            AeadAlg::Chacha20Poly1305 => HeaderProtectionKey::ChaCha(key.try_into().unwrap()),
147            _ => HeaderProtectionKey::Aes(Aes::new(&key)),
148        }
149    }
150
151    /// The 5-byte mask for a 16-byte `sample` of the protected payload.
152    pub fn mask(&self, sample: &[u8]) -> [u8; 5] {
153        assert!(sample.len() >= 16, "header protection sample must be 16 bytes");
154        let mut out = [0u8; 5];
155        match self {
156            HeaderProtectionKey::Aes(a) => {
157                let mut b: [u8; 16] = sample[..16].try_into().unwrap();
158                a.encrypt_block(&mut b);
159                out.copy_from_slice(&b[..5]);
160            }
161            #[cfg(feature = "chacha20")]
162            HeaderProtectionKey::ChaCha(k) => {
163                let counter = u32::from_le_bytes(sample[..4].try_into().unwrap());
164                let block = chacha20_block(k, counter, sample[4..16].try_into().unwrap());
165                out.copy_from_slice(&block[..5]);
166            }
167        }
168        out
169    }
170}
171
172/// Packet and header protection for one direction.
173#[derive(Clone, Debug)]
174pub struct DirectionalKeys {
175    pub packet: PacketKey,
176    pub header: HeaderProtectionKey,
177}
178
179impl DirectionalKeys {
180    /// Packet and header protection keys derived from a traffic secret.
181    pub fn new(suite: CipherSuite, version: u32, secret: &[u8]) -> DirectionalKeys {
182        DirectionalKeys { packet: PacketKey::new(suite, version, secret), header: HeaderProtectionKey::new(suite, version, secret) }
183    }
184}
185
186/// Keys for sending (`local`) and receiving (`remote`).
187#[derive(Clone, Debug)]
188pub struct Keys {
189    pub local: DirectionalKeys,
190    pub remote: DirectionalKeys,
191}
192
193impl Keys {
194    fn new(suite: CipherSuite, version: u32, is_client: bool, client: &[u8], server: &[u8]) -> Keys {
195        let (l, r) = if is_client { (client, server) } else { (server, client) };
196        Keys { local: DirectionalKeys::new(suite, version, l), remote: DirectionalKeys::new(suite, version, r) }
197    }
198}
199
200fn initial_salt(version: u32) -> &'static [u8; 20] {
201    if version == VERSION_2 {
202        &[0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9]
203    } else {
204        &[0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a]
205    }
206}
207
208/// The client's and server's Initial secrets for a destination connection ID.
209pub fn initial_secrets(version: u32, dcid: &[u8]) -> (Vec<u8>, Vec<u8>) {
210    let h = HashAlg::Sha256;
211    let initial = h.hkdf_extract(initial_salt(version), dcid);
212    (expand_label_vec(h, &initial, b"client in", &[], 32), expand_label_vec(h, &initial, b"server in", &[], 32))
213}
214
215/// Initial packet protection keys (AES-128-GCM, RFC 9001 section 5.2; RFC 9369 for v2).
216pub fn initial_keys(version: u32, dcid: &[u8], is_server: bool) -> Keys {
217    let (c, s) = initial_secrets(version, dcid);
218    Keys::new(CipherSuite::Aes128GcmSha256, version, !is_server, &c, &s)
219}
220
221/// The Retry Integrity Tag (RFC 9001 section 5.8, RFC 9369 section 3.3.3) over
222/// `retry_without_tag` (the Retry packet up to the tag) for the original destination CID.
223pub fn retry_integrity_tag(version: u32, original_dcid: &[u8], retry_without_tag: &[u8]) -> [u8; 16] {
224    let (key, nonce): ([u8; 16], [u8; 12]) = if version == VERSION_2 {
225        (
226            [0x8f, 0xb4, 0xb0, 0x1b, 0x56, 0xac, 0x48, 0xe2, 0x60, 0xfb, 0xcb, 0xce, 0xad, 0x7c, 0xcc, 0x92],
227            [0xd8, 0x69, 0x69, 0xbc, 0x2d, 0x7c, 0x6d, 0x99, 0x90, 0xef, 0xb0, 0x4a],
228        )
229    } else {
230        (
231            [0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e],
232            [0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb],
233        )
234    };
235    let mut aad = Vec::with_capacity(1 + original_dcid.len() + retry_without_tag.len());
236    aad.push(original_dcid.len() as u8);
237    aad.extend_from_slice(original_dcid);
238    aad.extend_from_slice(retry_without_tag);
239    AesGcm::new(&key).seal(&nonce, &aad, &mut [])
240}
241
242/// The 1-RTT secrets, for key updates.
243pub struct Secrets {
244    suite: CipherSuite,
245    version: u32,
246    is_client: bool,
247    client: Vec<u8>,
248    server: Vec<u8>,
249}
250
251impl std::fmt::Debug for Secrets {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        write!(f, "Secrets({:?})", self.suite)
254    }
255}
256
257impl Secrets {
258    /// Key-update state from the current 1-RTT traffic secrets.
259    pub fn new(suite: CipherSuite, version: u32, is_client: bool, client: Vec<u8>, server: Vec<u8>) -> Secrets {
260        Secrets { suite, version, is_client, client, server }
261    }
262
263    /// Advances both secrets with the key-update label and returns the next generation of
264    /// packet keys as (local, remote). Header protection keys never change.
265    pub fn next_packet_keys(&mut self) -> (PacketKey, PacketKey) {
266        let h = self.suite.hash();
267        let ku = labels(self.version).ku;
268        self.client = expand_label_vec(h, &self.client, ku, &[], h.output_len());
269        self.server = expand_label_vec(h, &self.server, ku, &[], h.output_len());
270        let c = PacketKey::new(self.suite, self.version, &self.client);
271        let s = PacketKey::new(self.suite, self.version, &self.server);
272        if self.is_client {
273            (c, s)
274        } else {
275            (s, c)
276        }
277    }
278}
279
280/// New keys from the handshake.
281pub enum KeyChange {
282    /// Handshake packet keys.
283    Handshake { keys: Keys },
284    /// 1-RTT packet keys and the secrets for later key updates.
285    OneRtt { keys: Keys, next: Secrets },
286}
287
288impl std::fmt::Debug for KeyChange {
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        f.write_str(match self {
291            KeyChange::Handshake { .. } => "KeyChange::Handshake",
292            KeyChange::OneRtt { .. } => "KeyChange::OneRtt",
293        })
294    }
295}
296
297/// A TLS 1.3 handshake for a QUIC connection.
298pub struct QuicConnection {
299    engine: Engine,
300    hs: HsReader,
301    write_level: Level,
302    version: u32,
303    alert: Option<u8>,
304    error: Option<Error>,
305}
306
307impl std::fmt::Debug for QuicConnection {
308    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309        f.debug_struct("QuicConnection").field("client", &self.engine.is_client()).field("handshaking", &self.is_handshaking()).finish()
310    }
311}
312
313impl QuicConnection {
314    /// A server; `transport_params` is our encoded transport_parameters extension body.
315    pub fn new_server(cfg: Arc<ServerConfig>, transport_params: Vec<u8>) -> QuicConnection {
316        QuicConnection::from_engine(Engine::Server(Box::new(ServerHs::new(cfg, Some(transport_params)))))
317    }
318
319    /// A client; the ClientHello is ready for the first `write_handshake`. The configuration
320    /// must have ALPN protocols.
321    #[cfg(feature = "client")]
322    pub fn new_client(cfg: Arc<ClientConfig>, server_name: &str, transport_params: Vec<u8>) -> Result<QuicConnection> {
323        Ok(QuicConnection::from_engine(Engine::Client(Box::new(ClientHs::new(cfg, server_name, Some(transport_params))?))))
324    }
325
326    fn from_engine(engine: Engine) -> QuicConnection {
327        QuicConnection { engine, hs: HsReader::new(), write_level: Level::Initial, version: VERSION_1, alert: None, error: None }
328    }
329
330    /// Selects the QUIC version whose key labels are used (default [`VERSION_1`]); set it
331    /// before the first key change.
332    pub fn set_version(&mut self, version: u32) {
333        self.version = version;
334    }
335
336    /// Feeds CRYPTO frame data received at `level`, in order.
337    pub fn read_handshake(&mut self, level: Level, data: &[u8]) -> Result<()> {
338        if let Some(e) = &self.error {
339            return Err(e.clone());
340        }
341        let r = self.read_inner(level, data);
342        if let Err(e) = &r {
343            self.alert = e.alert();
344            self.error = Some(e.clone());
345        }
346        r
347    }
348
349    fn read_inner(&mut self, level: Level, data: &[u8]) -> Result<()> {
350        if level != self.engine.shared().expect_level {
351            return Err(Error::unexpected("CRYPTO data at this encryption level"));
352        }
353        self.hs.push(level, data)?;
354        let max = self.engine.shared().max_message;
355        while let Some((lvl, msg)) = self.hs.next(max)? {
356            if msg[0] == crate::tls::hs::KEY_UPDATE {
357                return Err(Error::unexpected("KeyUpdate in QUIC"));
358            }
359            self.engine.handle(lvl, &msg)?;
360            if self.engine.shared().expect_level != lvl && !self.hs.is_empty() {
361                return Err(Error::unexpected("handshake data across a key change"));
362            }
363        }
364        Ok(())
365    }
366
367    /// The level of the bytes the next [`write_handshake`](Self::write_handshake) appends.
368    pub fn write_level(&self) -> Level {
369        self.write_level
370    }
371
372    /// Appends handshake bytes to send at [`write_level`](Self::write_level); returns a key
373    /// change when one is due, after which the write level has advanced.
374    pub fn write_handshake(&mut self, out: &mut Vec<u8>) -> Option<KeyChange> {
375        let is_client = self.engine.is_client();
376        let version = self.version;
377        while let Some(ev) = self.engine.shared_mut().events.pop_front() {
378            match ev {
379                Event::Msg(_, m) => out.extend_from_slice(&m),
380                Event::Ccs => {}
381                // QUIC is TLS 1.3 only: the engines never choose TLS 1.2 for it
382                #[cfg(feature = "tls12")]
383                Event::ChangeCipher12(_) => {}
384                Event::Secrets { level, client, server } => {
385                    let suite = self.engine.shared().suite.expect("suite");
386                    let keys = Keys::new(suite, version, is_client, &client, &server);
387                    self.write_level = level;
388                    return Some(match level {
389                        Level::OneRtt => KeyChange::OneRtt { keys, next: Secrets { suite, version, is_client, client, server } },
390                        _ => KeyChange::Handshake { keys },
391                    });
392                }
393            }
394        }
395        None
396    }
397
398    pub fn is_handshaking(&self) -> bool {
399        !self.engine.shared().done
400    }
401
402    /// The peer's transport_parameters extension body.
403    pub fn peer_transport_params(&self) -> Option<&[u8]> {
404        self.engine.shared().peer_tp.as_deref()
405    }
406
407    pub fn alpn_protocol(&self) -> Option<&[u8]> {
408        self.engine.shared().alpn.as_deref()
409    }
410
411    pub fn negotiated_cipher_suite(&self) -> Option<CipherSuite> {
412        self.engine.shared().suite
413    }
414
415    pub fn server_name(&self) -> Option<&str> {
416        self.engine.shared().sni.as_deref()
417    }
418
419    pub fn peer_certificates(&self) -> &[Vec<u8>] {
420        &self.engine.shared().peer_certs
421    }
422
423    pub fn is_resumed(&self) -> bool {
424        self.engine.shared().resumed
425    }
426
427    /// The QUIC CRYPTO_ERROR code (0x100 + TLS alert) after a handshake failure.
428    pub fn tls_alert(&self) -> Option<u64> {
429        self.alert.map(|a| 0x100 + a as u64)
430    }
431}