noq_proto/connection/
packet_crypto.rs

1use std::mem;
2use std::ops::{Index, IndexMut};
3
4use tracing::{debug, trace};
5
6use super::SpaceKind;
7use crate::connection::assembler::Assembler;
8use crate::crypto::{self, HeaderKey, KeyPair, Keys, PacketKey};
9use crate::packet::{Packet, PartialDecode};
10use crate::token::ResetToken;
11use rand::{CryptoRng, RngExt};
12
13use crate::{ConnectionId, Instant, Side};
14use crate::{RESET_TOKEN_SIZE, TransportError};
15
16use super::PathId;
17use super::spaces::PacketSpace;
18
19/// Perform key updates this many packets before the AEAD confidentiality limit.
20///
21/// Chosen arbitrarily, intended to be large enough to prevent spurious connection loss.
22const KEY_UPDATE_MARGIN: u64 = 10_000;
23
24pub(super) struct UnprotectHeaderResult {
25    /// The packet with the now unprotected header (`None` in the case of stateless reset packets
26    /// that fail to be decoded)
27    pub(super) packet: Option<Packet>,
28    /// Whether the packet was a stateless reset packet
29    pub(super) stateless_reset: bool,
30}
31
32pub(super) struct DecryptPacketResult {
33    /// The packet number
34    pub(super) packet_number: u64,
35    /// Whether a locally initiated key update has been acknowledged by the peer
36    pub(super) outgoing_key_update_acked: bool,
37    /// Whether the peer has initiated a key update
38    pub(super) incoming_key_update: bool,
39}
40
41pub(super) struct PrevCrypto {
42    /// The keys used for the previous key phase, temporarily retained to decrypt packets sent by
43    /// the peer prior to its own key update.
44    pub(super) crypto: KeyPair<Box<dyn PacketKey>>,
45    /// The incoming packet that ends the interval for which these keys are applicable, and the
46    /// time of its receipt.
47    ///
48    /// Incoming packets should be decrypted using these keys iff this is `None` or their packet
49    /// number is lower. `None` indicates that we have not yet received a packet using newer keys,
50    /// which implies that the update was locally initiated.
51    pub(super) end_packet: Option<(u64, Instant)>,
52    /// Whether the following key phase is from a remotely initiated update that we haven't acked
53    pub(super) update_unacked: bool,
54}
55
56pub(super) struct ZeroRttCrypto {
57    pub(super) header: Box<dyn HeaderKey>,
58    pub(super) packet: Box<dyn PacketKey>,
59}
60
61impl ZeroRttCrypto {
62    fn keys(&self) -> (&dyn HeaderKey, &dyn PacketKey) {
63        (self.header.as_ref(), self.packet.as_ref())
64    }
65}
66
67/// Consolidated crypto state for a connection.
68///
69/// This struct groups all cryptographic state together, including:
70/// - The TLS session
71/// - Per-space keys and crypto streams
72/// - Key update state (prev/next keys)
73/// - 0-RTT state
74pub(super) struct CryptoState {
75    /// Per encryption level crypto data (Initial, Handshake, Data).
76    pub(super) spaces: [CryptoSpace; 3],
77    /// The TLS session.
78    pub(super) session: Box<dyn crypto::Session>,
79
80    /*
81     * 0-RTT related fields
82     */
83    /// Whether 0-RTT was accepted.
84    pub(super) accepted_0rtt: bool,
85    /// Whether or not 0-RTT was enabled during the handshake. Does not imply acceptance.
86    pub(super) zero_rtt_enabled: bool,
87    /// 0-RTT crypto state, cleared when no longer needed.
88    pub(super) zero_rtt_crypto: Option<ZeroRttCrypto>,
89    /// Number of packets encrypted with 0-RTT keys. Client only.
90    sent_with_zero_rtt: u64,
91
92    /*
93     * State to manage 1-RTT key updates
94     */
95    /// 1-RTT keys to be used for the next key update.
96    ///
97    /// These are generated in advance to prevent timing attacks and/or DoS by third-party
98    /// attackers spoofing key updates.
99    pub(super) next_crypto: Option<KeyPair<Box<dyn PacketKey>>>,
100    /// 1-RTT keys used prior to a key update.
101    pub(super) prev_crypto: Option<PrevCrypto>,
102    /// Current key phase, toggled on each 1-RTT key update.
103    pub(super) key_phase: bool,
104    /// How many packets are in the current key phase. Used only for `Data` space.
105    pub(super) key_phase_size: u64,
106}
107
108impl CryptoState {
109    pub(super) fn new(
110        session: Box<dyn crypto::Session>,
111        init_cid: ConnectionId,
112        side: Side,
113        rng: &mut impl CryptoRng,
114    ) -> Self {
115        let initial_keys = session.initial_keys(init_cid, side);
116        let initial_space = CryptoSpace {
117            keys: Some(initial_keys),
118            ..Default::default()
119        };
120        Self {
121            spaces: [initial_space, Default::default(), Default::default()],
122            session,
123            next_crypto: None,
124            prev_crypto: None,
125            accepted_0rtt: false,
126            zero_rtt_enabled: false,
127            zero_rtt_crypto: None,
128            sent_with_zero_rtt: 0,
129            key_phase: false,
130            // A small initial key phase size ensures peers that don't handle key updates correctly
131            // fail sooner rather than later. It's okay for both peers to do this, as the first one
132            // to perform an update will reset the other's key phase size in `update_keys`, and a
133            // simultaneous key update by both is just like a regular key update with a really fast
134            // response. Inspired by quic-go's similar behavior of performing the first key update
135            // at the 100th short-header packet.
136            key_phase_size: rng.random_range(10..1000),
137        }
138    }
139
140    /// Removes header protection of a packet, or returns `None` if the packet was dropped.
141    pub(super) fn unprotect_header(
142        &self,
143        partial_decode: PartialDecode,
144        stateless_reset_token: Option<ResetToken>,
145    ) -> Option<UnprotectHeaderResult> {
146        let encryption_level = partial_decode.encryption_level();
147        let header_crypto = match encryption_level {
148            Some(level) => match self.remote_crypto(level) {
149                Some(crypto) => Some(crypto.0),
150                None => {
151                    let bytes = partial_decode.len();
152                    debug!(?encryption_level, bytes, "dropping unexpected packet");
153                    return None;
154                }
155            },
156            // Unprotected packet
157            None => None,
158        };
159
160        let packet = partial_decode.data();
161        let stateless_reset = packet.len() >= RESET_TOKEN_SIZE + 5
162            && stateless_reset_token.as_deref() == Some(&packet[packet.len() - RESET_TOKEN_SIZE..]);
163
164        match partial_decode.finish(header_crypto) {
165            Ok(packet) => Some(UnprotectHeaderResult {
166                packet: Some(packet),
167                stateless_reset,
168            }),
169            Err(_) if stateless_reset => Some(UnprotectHeaderResult {
170                packet: None,
171                stateless_reset: true,
172            }),
173            Err(e) => {
174                trace!("unable to complete packet decoding: {}", e);
175                None
176            }
177        }
178    }
179
180    /// Decrypts a packet's body in-place.
181    pub(super) fn decrypt_packet_body(
182        &self,
183        packet: &mut Packet,
184        path_id: PathId,
185        spaces: &[PacketSpace; 3],
186    ) -> Result<Option<DecryptPacketResult>, Option<TransportError>> {
187        let conn_key_phase = self.key_phase;
188        if !packet.header.is_protected() {
189            // Unprotected packets also don't have packet numbers
190            return Ok(None);
191        }
192        let space = packet.header.space();
193
194        if path_id != PathId::ZERO && space != SpaceKind::Data {
195            // do not try to decrypt illegal multipath packets
196            return Err(Some(TransportError::PROTOCOL_VIOLATION(
197                "multipath packet on non Data packet number space",
198            )));
199        }
200        // Packets that do not belong to known path ids are valid as long as they can be decrypted.
201        // If we didn't have a path, that's for the purposes of this function equivalent to not
202        // having received packets on that path yet. So both of these cases are represented by
203        // `None`.
204        let rx_packet_number = spaces[space]
205            .path_space(path_id)
206            .and_then(|s| s.largest_received_packet_number);
207        let packet_number = packet
208            .header
209            .number()
210            .ok_or(None)?
211            .expand(rx_packet_number.map(|n| n + 1).unwrap_or_default());
212        let packet_key_phase = packet.header.key_phase();
213
214        let mut crypto_update = false;
215        let crypto = if packet.header.is_0rtt() {
216            let (_, packet) = self.remote_crypto(EncryptionLevel::ZeroRtt).unwrap();
217            packet
218        } else if packet_key_phase == conn_key_phase || space != SpaceKind::Data {
219            let (_, packet) = self.remote_crypto(space.encryption_level()).unwrap();
220            packet
221        } else if let Some(prev) = self.prev_crypto.as_ref().filter(|crypto| {
222            // If this packet comes prior to acknowledgment of the key update by the peer,
223            // use the previous keys. Otherwise this must be a remotely-initiated key update
224            // and we let this fall through to the final case.
225            crypto.end_packet.is_none_or(|(pn, _)| packet_number < pn)
226        }) {
227            &*prev.crypto.remote
228        } else {
229            // We're in the Data space with a key phase mismatch and either there is no locally
230            // initiated key update or the locally initiated key update was acknowledged by a
231            // lower-numbered packet. The key phase mismatch must therefore represent a new
232            // remotely-initiated key update.
233            crypto_update = true;
234            &*self.next_crypto.as_ref().unwrap().remote
235        };
236
237        crypto
238            .decrypt(
239                path_id,
240                packet_number,
241                &packet.header_data,
242                &mut packet.payload,
243            )
244            .map_err(|_| {
245                trace!("decryption failed with packet number {}", packet_number);
246                None
247            })?;
248
249        if !packet.reserved_bits_valid() {
250            return Err(Some(TransportError::PROTOCOL_VIOLATION(
251                "reserved bits set",
252            )));
253        }
254
255        let mut outgoing_key_update_acked = false;
256        if let Some(ref prev) = self.prev_crypto
257            && prev.end_packet.is_none()
258            && packet_key_phase == conn_key_phase
259        {
260            outgoing_key_update_acked = true;
261        }
262
263        if crypto_update {
264            // Validate incoming key update
265            // If `rx_packet` is `None`, then either the path is entirely new, or we haven't
266            // received any packets on this path yet. In that case, having the first
267            // packet be a crypto update is fine.
268            let invalid_packet_number =
269                rx_packet_number.is_some_and(|rx_packet| packet_number <= rx_packet);
270            if invalid_packet_number || self.prev_crypto.as_ref().is_some_and(|x| x.update_unacked)
271            {
272                trace!(?packet_number, ?rx_packet_number, %path_id, "crypto update failed");
273                return Err(Some(TransportError::KEY_UPDATE_ERROR("")));
274            }
275        }
276
277        Ok(Some(DecryptPacketResult {
278            packet_number,
279            outgoing_key_update_acked,
280            incoming_key_update: crypto_update,
281        }))
282    }
283
284    /// Check if keys are available for the given encryption level.
285    pub(super) fn has_keys(&self, level: EncryptionLevel) -> bool {
286        match level {
287            EncryptionLevel::Initial => self.spaces[0].keys.is_some(),
288            EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.is_some(),
289            EncryptionLevel::Handshake => self.spaces[1].keys.is_some(),
290            EncryptionLevel::OneRtt => self.spaces[2].keys.is_some(),
291        }
292    }
293
294    /// Discard temporary key state (0-RTT and previous keys).
295    pub(super) fn discard_temporary_keys(&mut self) {
296        self.zero_rtt_crypto = None;
297        self.prev_crypto = None;
298    }
299
300    /// Enable 0-RTT crypto with the given keys.
301    pub(super) fn enable_zero_rtt(
302        &mut self,
303        header: Box<dyn HeaderKey>,
304        packet: Box<dyn PacketKey>,
305    ) {
306        self.zero_rtt_enabled = true;
307        self.zero_rtt_crypto = Some(ZeroRttCrypto { header, packet });
308    }
309
310    /// Discard 0-RTT crypto keys.
311    pub(super) fn discard_zero_rtt(&mut self) {
312        self.zero_rtt_crypto = None;
313    }
314
315    /// Get the integrity limit for the given space's local packet keys.
316    pub(super) fn integrity_limit(&self, space: SpaceKind) -> Option<u64> {
317        let keys = self.spaces[space].keys.as_ref()?;
318        Some(keys.packet.local.integrity_limit())
319    }
320
321    /// Get local (sending) crypto keys for the given encryption level.
322    ///
323    /// Use this only when sure the keys are allowed to be used. [`Self::encryption_keys`] should
324    /// be preferred otherwise.
325    pub(super) fn local_crypto(
326        &self,
327        level: EncryptionLevel,
328    ) -> Option<(&dyn HeaderKey, &dyn PacketKey)> {
329        match level {
330            EncryptionLevel::Initial => self.spaces[0].keys.as_ref().map(Keys::local),
331            EncryptionLevel::Handshake => self.spaces[1].keys.as_ref().map(Keys::local),
332            EncryptionLevel::OneRtt => self.spaces[2].keys.as_ref().map(Keys::local),
333            // 0-RTT uses the same keys for both directions
334            EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys),
335        }
336    }
337
338    /// Get remote (receiving) crypto keys for the given encryption level.
339    ///
340    /// Returns header and packet keys used for decrypting incoming packets.
341    fn remote_crypto(&self, level: EncryptionLevel) -> Option<(&dyn HeaderKey, &dyn PacketKey)> {
342        match level {
343            EncryptionLevel::Initial => self.spaces[0].keys.as_ref().map(Keys::remote),
344            EncryptionLevel::Handshake => self.spaces[1].keys.as_ref().map(Keys::remote),
345            EncryptionLevel::OneRtt => self.spaces[2].keys.as_ref().map(Keys::remote),
346            // 0-RTT uses the same keys for both directions
347            EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys),
348        }
349    }
350
351    /// Get local (sending) crypto keys and the actual encryption level for a given space.
352    ///
353    /// This method takes a [`SpaceKind`] and resolves the encryption level automatically: for the
354    /// [`SpaceKind::Data`] space on the client side, it falls back to 0-RTT keys when 1-RTT keys
355    /// are not yet available. Resolving the appropriate encryption keys makes this method
356    /// preferable to [`Self::local_crypto`] in general.
357    ///
358    /// Returns `None` if no keys are available.
359    pub(super) fn encryption_keys(
360        &self,
361        kind: SpaceKind,
362        side: Side,
363    ) -> Option<(&dyn HeaderKey, &dyn PacketKey, EncryptionLevel)> {
364        let mut keys = self.spaces[kind].keys.as_ref().map(Keys::local);
365        let mut level = match kind {
366            SpaceKind::Initial => EncryptionLevel::Initial,
367            SpaceKind::Handshake => EncryptionLevel::Handshake,
368            SpaceKind::Data => EncryptionLevel::OneRtt,
369        };
370
371        // Clients use 0-RTT keys if 1-RTT keys are not available. Servers never encrypt 0-RTT
372        if keys.is_none() && kind == SpaceKind::Data && side.is_client() {
373            keys = self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys);
374            level = EncryptionLevel::ZeroRtt;
375        }
376
377        keys.map(|(header_keys, packet_keys)| (header_keys, packet_keys, level))
378    }
379
380    /// Perform a 1-RTT key update.
381    ///
382    /// Generates the next set of keys, rotates current keys into previous, and installs the new
383    /// keys. Updates `key_phase` and `key_phase_size` accordingly.
384    ///
385    /// PANICS: If 1-RTT keys are missing.
386    pub(super) fn update_keys(&mut self, end_packet: Option<(u64, Instant)>, remote: bool) {
387        trace!("executing key update");
388
389        let new = self
390            .session
391            .next_1rtt_keys()
392            .expect("only called for `Data` packets");
393        let confidentiality_limit = new.local.confidentiality_limit();
394        let old = mem::replace(
395            &mut self.spaces[SpaceKind::Data]
396                .keys
397                .as_mut()
398                .unwrap() // safe because update_keys() can only be triggered by short packets
399                .packet,
400            mem::replace(self.next_crypto.as_mut().unwrap(), new),
401        );
402        self.prev_crypto = Some(PrevCrypto {
403            crypto: old,
404            end_packet,
405            update_unacked: remote,
406        });
407
408        self.key_phase_size = confidentiality_limit.saturating_sub(KEY_UPDATE_MARGIN);
409        self.key_phase = !self.key_phase;
410        self.spaces[2].sent_with_keys = 0;
411    }
412
413    /// Number of packets encrypted with the current set of keys at `level`.
414    ///
415    /// For [`EncryptionLevel::OneRtt`], this counter resets to zero on every key update (see
416    /// [`Self::update_keys`]).
417    pub(crate) fn sent_with_keys(&self, level: EncryptionLevel) -> u64 {
418        match level {
419            EncryptionLevel::Initial => self.spaces[0].sent_with_keys,
420            EncryptionLevel::ZeroRtt => self.sent_with_zero_rtt,
421            EncryptionLevel::Handshake => self.spaces[1].sent_with_keys,
422            EncryptionLevel::OneRtt => self.spaces[2].sent_with_keys,
423        }
424    }
425
426    /// Number of packets that may still be sent before the AEAD confidentiality limit is reached
427    /// at the given encryption level.
428    ///
429    /// For [`EncryptionLevel::OneRtt`] the effective limit is the minimum of the AEAD
430    /// confidentiality limit and the current key-phase size. For all other levels the raw AEAD
431    /// confidentiality limit is used.
432    ///
433    /// Returns `None` when no keys are available for `level`.
434    pub(crate) fn remaining_packet_budget(&self, level: EncryptionLevel) -> Option<u64> {
435        let sent_with_keys = self.sent_with_keys(level);
436        let (_header_keys, packet_keys) = self.local_crypto(level)?;
437        let limit = match level {
438            EncryptionLevel::OneRtt => self.key_phase_size.min(packet_keys.confidentiality_limit()),
439            _ => packet_keys.confidentiality_limit(),
440        };
441
442        Some(limit.saturating_sub(sent_with_keys))
443    }
444
445    /// Record that a packet has been encrypted at the given level.
446    pub(crate) fn inc_sent_with_keys(&mut self, level: EncryptionLevel) {
447        let count = match level {
448            EncryptionLevel::Initial => &mut self.spaces[0].sent_with_keys,
449            EncryptionLevel::ZeroRtt => &mut self.sent_with_zero_rtt,
450            EncryptionLevel::Handshake => &mut self.spaces[1].sent_with_keys,
451            EncryptionLevel::OneRtt => &mut self.spaces[2].sent_with_keys,
452        };
453        *count = count.saturating_add(1u64);
454    }
455}
456
457/// Per space kind cryptographic state.
458#[derive(Default)]
459pub(super) struct CryptoSpace {
460    /// Packet protection keys for this space.
461    pub(super) keys: Option<Keys>,
462    /// Incoming cryptographic handshake stream.
463    pub(super) crypto_stream: Assembler,
464    /// Current offset of outgoing cryptographic handshake stream.
465    pub(super) crypto_offset: u64,
466    /// Number of packets encrypted with the current set of keys.
467    pub(super) sent_with_keys: u64,
468}
469
470/// QUIC packet protection levels (RFC 9001).
471#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
472pub(crate) enum EncryptionLevel {
473    /// Initial packets (client and server).
474    Initial,
475    /// Early data (0-RTT), client only.
476    ZeroRtt,
477    /// Handshake packets.
478    Handshake,
479    /// Application data (1-RTT).
480    OneRtt,
481}
482
483impl From<SpaceKind> for crate::packet::SpaceId {
484    fn from(kind: SpaceKind) -> Self {
485        match kind {
486            SpaceKind::Initial => Self::Initial,
487            SpaceKind::Handshake => Self::Handshake,
488            SpaceKind::Data => Self::Data,
489        }
490    }
491}
492
493impl IndexMut<SpaceKind> for [CryptoSpace; 3] {
494    fn index_mut(&mut self, index: SpaceKind) -> &mut Self::Output {
495        &mut self[index as usize]
496    }
497}
498
499impl Index<SpaceKind> for [CryptoSpace; 3] {
500    type Output = CryptoSpace;
501
502    fn index(&self, index: SpaceKind) -> &Self::Output {
503        &self[index as usize]
504    }
505}