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 time
46    /// 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 `None`.
203        let rx_packet_number = spaces[space]
204            .path_space(path_id)
205            .and_then(|s| s.largest_received_packet_number);
206        let packet_number = packet
207            .header
208            .number()
209            .ok_or(None)?
210            .expand(rx_packet_number.map(|n| n + 1).unwrap_or_default());
211        let packet_key_phase = packet.header.key_phase();
212
213        let mut crypto_update = false;
214        let crypto = if packet.header.is_0rtt() {
215            let (_, packet) = self.remote_crypto(EncryptionLevel::ZeroRtt).unwrap();
216            packet
217        } else if packet_key_phase == conn_key_phase || space != SpaceKind::Data {
218            let (_, packet) = self.remote_crypto(space.encryption_level()).unwrap();
219            packet
220        } else if let Some(prev) = self.prev_crypto.as_ref().filter(|crypto| {
221            // If this packet comes prior to acknowledgment of the key update by the peer,
222            // use the previous keys. Otherwise this must be a remotely-initiated key update
223            // and we let this fall through to the final case.
224            crypto.end_packet.is_none_or(|(pn, _)| packet_number < pn)
225        }) {
226            &*prev.crypto.remote
227        } else {
228            // We're in the Data space with a key phase mismatch and either there is no locally
229            // initiated key update or the locally initiated key update was acknowledged by a
230            // lower-numbered packet. The key phase mismatch must therefore represent a new
231            // remotely-initiated key update.
232            crypto_update = true;
233            &*self.next_crypto.as_ref().unwrap().remote
234        };
235
236        crypto
237            .decrypt(
238                path_id,
239                packet_number,
240                &packet.header_data,
241                &mut packet.payload,
242            )
243            .map_err(|_| {
244                trace!("decryption failed with packet number {}", packet_number);
245                None
246            })?;
247
248        if !packet.reserved_bits_valid() {
249            return Err(Some(TransportError::PROTOCOL_VIOLATION(
250                "reserved bits set",
251            )));
252        }
253
254        let mut outgoing_key_update_acked = false;
255        if let Some(ref prev) = self.prev_crypto
256            && prev.end_packet.is_none()
257            && packet_key_phase == conn_key_phase
258        {
259            outgoing_key_update_acked = true;
260        }
261
262        if crypto_update {
263            // Validate incoming key update
264            // If `rx_packet` is `None`, then either the path is entirely new, or we haven't received
265            // any packets on this path yet. In that case, having the first packet be a crypto update
266            // is fine.
267            let invalid_packet_number =
268                rx_packet_number.is_some_and(|rx_packet| packet_number <= rx_packet);
269            if invalid_packet_number || self.prev_crypto.as_ref().is_some_and(|x| x.update_unacked)
270            {
271                trace!(?packet_number, ?rx_packet_number, %path_id, "crypto update failed");
272                return Err(Some(TransportError::KEY_UPDATE_ERROR("")));
273            }
274        }
275
276        Ok(Some(DecryptPacketResult {
277            packet_number,
278            outgoing_key_update_acked,
279            incoming_key_update: crypto_update,
280        }))
281    }
282
283    /// Check if keys are available for the given encryption level.
284    pub(super) fn has_keys(&self, level: EncryptionLevel) -> bool {
285        match level {
286            EncryptionLevel::Initial => self.spaces[0].keys.is_some(),
287            EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.is_some(),
288            EncryptionLevel::Handshake => self.spaces[1].keys.is_some(),
289            EncryptionLevel::OneRtt => self.spaces[2].keys.is_some(),
290        }
291    }
292
293    /// Discard temporary key state (0-RTT and previous keys).
294    pub(super) fn discard_temporary_keys(&mut self) {
295        self.zero_rtt_crypto = None;
296        self.prev_crypto = None;
297    }
298
299    /// Enable 0-RTT crypto with the given keys.
300    pub(super) fn enable_zero_rtt(
301        &mut self,
302        header: Box<dyn HeaderKey>,
303        packet: Box<dyn PacketKey>,
304    ) {
305        self.zero_rtt_enabled = true;
306        self.zero_rtt_crypto = Some(ZeroRttCrypto { header, packet });
307    }
308
309    /// Discard 0-RTT crypto keys.
310    pub(super) fn discard_zero_rtt(&mut self) {
311        self.zero_rtt_crypto = None;
312    }
313
314    /// Get the integrity limit for the given space's local packet keys.
315    pub(super) fn integrity_limit(&self, space: SpaceKind) -> Option<u64> {
316        let keys = self.spaces[space].keys.as_ref()?;
317        Some(keys.packet.local.integrity_limit())
318    }
319
320    /// Get local (sending) crypto keys for the given encryption level.
321    ///
322    /// Use this only when sure the keys are allowed to be used. [`Self::encryption_keys`] should
323    /// be preferred otherwise.
324    pub(super) fn local_crypto(
325        &self,
326        level: EncryptionLevel,
327    ) -> Option<(&dyn HeaderKey, &dyn PacketKey)> {
328        match level {
329            EncryptionLevel::Initial => self.spaces[0].keys.as_ref().map(Keys::local),
330            EncryptionLevel::Handshake => self.spaces[1].keys.as_ref().map(Keys::local),
331            EncryptionLevel::OneRtt => self.spaces[2].keys.as_ref().map(Keys::local),
332            // 0-RTT uses the same keys for both directions
333            EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys),
334        }
335    }
336
337    /// Get remote (receiving) crypto keys for the given encryption level.
338    ///
339    /// Returns header and packet keys used for decrypting incoming packets.
340    fn remote_crypto(&self, level: EncryptionLevel) -> Option<(&dyn HeaderKey, &dyn PacketKey)> {
341        match level {
342            EncryptionLevel::Initial => self.spaces[0].keys.as_ref().map(Keys::remote),
343            EncryptionLevel::Handshake => self.spaces[1].keys.as_ref().map(Keys::remote),
344            EncryptionLevel::OneRtt => self.spaces[2].keys.as_ref().map(Keys::remote),
345            // 0-RTT uses the same keys for both directions
346            EncryptionLevel::ZeroRtt => self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys),
347        }
348    }
349
350    /// Get local (sending) crypto keys and the actual encryption level for a given space.
351    ///
352    /// This method takes a [`SpaceKind`] and resolves the encryption level automatically: for the
353    /// [`SpaceKind::Data`] space on the client side, it falls back to 0-RTT keys when 1-RTT keys
354    /// are not yet available. Resolving the appropriate encryption keys makes this method
355    /// preferable to [`Self::local_crypto`] in general.
356    ///
357    /// Returns `None` if no keys are available.
358    pub(super) fn encryption_keys(
359        &self,
360        kind: SpaceKind,
361        side: Side,
362    ) -> Option<(&dyn HeaderKey, &dyn PacketKey, EncryptionLevel)> {
363        let mut keys = self.spaces[kind].keys.as_ref().map(Keys::local);
364        let mut level = match kind {
365            SpaceKind::Initial => EncryptionLevel::Initial,
366            SpaceKind::Handshake => EncryptionLevel::Handshake,
367            SpaceKind::Data => EncryptionLevel::OneRtt,
368        };
369
370        // Clients use 0-RTT keys if 1-RTT keys are not available. Servers never encrypt 0-RTT
371        if keys.is_none() && kind == SpaceKind::Data && side.is_client() {
372            keys = self.zero_rtt_crypto.as_ref().map(ZeroRttCrypto::keys);
373            level = EncryptionLevel::ZeroRtt;
374        }
375
376        keys.map(|(header_keys, packet_keys)| (header_keys, packet_keys, level))
377    }
378
379    /// Perform a 1-RTT key update.
380    ///
381    /// Generates the next set of keys, rotates current keys into previous, and installs the new
382    /// keys. Updates `key_phase` and `key_phase_size` accordingly.
383    ///
384    /// PANICS: If 1-RTT keys are missing.
385    pub(super) fn update_keys(&mut self, end_packet: Option<(u64, Instant)>, remote: bool) {
386        trace!("executing key update");
387
388        let new = self
389            .session
390            .next_1rtt_keys()
391            .expect("only called for `Data` packets");
392        let confidentiality_limit = new.local.confidentiality_limit();
393        let old = mem::replace(
394            &mut self.spaces[SpaceKind::Data]
395                .keys
396                .as_mut()
397                .unwrap() // safe because update_keys() can only be triggered by short packets
398                .packet,
399            mem::replace(self.next_crypto.as_mut().unwrap(), new),
400        );
401        self.prev_crypto = Some(PrevCrypto {
402            crypto: old,
403            end_packet,
404            update_unacked: remote,
405        });
406
407        self.key_phase_size = confidentiality_limit.saturating_sub(KEY_UPDATE_MARGIN);
408        self.key_phase = !self.key_phase;
409        self.spaces[2].sent_with_keys = 0;
410    }
411
412    /// Number of packets encrypted with the current set of keys at `level`.
413    ///
414    /// For [`EncryptionLevel::OneRtt`], this counter resets to zero on every key update (see
415    /// [`Self::update_keys`]).
416    pub(crate) fn sent_with_keys(&self, level: EncryptionLevel) -> u64 {
417        match level {
418            EncryptionLevel::Initial => self.spaces[0].sent_with_keys,
419            EncryptionLevel::ZeroRtt => self.sent_with_zero_rtt,
420            EncryptionLevel::Handshake => self.spaces[1].sent_with_keys,
421            EncryptionLevel::OneRtt => self.spaces[2].sent_with_keys,
422        }
423    }
424
425    /// Number of packets that may still be sent before the AEAD confidentiality limit is reached
426    /// at the given encryption level.
427    ///
428    /// For [`EncryptionLevel::OneRtt`] the effective limit is the minimum of the AEAD
429    /// confidentiality limit and the current key-phase size. For all other levels the raw AEAD
430    /// confidentiality limit is used.
431    ///
432    /// Returns `None` when no keys are available for `level`.
433    pub(crate) fn remaining_packet_budget(&self, level: EncryptionLevel) -> Option<u64> {
434        let sent_with_keys = self.sent_with_keys(level);
435        let (_header_keys, packet_keys) = self.local_crypto(level)?;
436        let limit = match level {
437            EncryptionLevel::OneRtt => self.key_phase_size.min(packet_keys.confidentiality_limit()),
438            _ => packet_keys.confidentiality_limit(),
439        };
440
441        Some(limit.saturating_sub(sent_with_keys))
442    }
443
444    /// Record that a packet has been encrypted at the given level.
445    pub(crate) fn inc_sent_with_keys(&mut self, level: EncryptionLevel) {
446        let count = match level {
447            EncryptionLevel::Initial => &mut self.spaces[0].sent_with_keys,
448            EncryptionLevel::ZeroRtt => &mut self.sent_with_zero_rtt,
449            EncryptionLevel::Handshake => &mut self.spaces[1].sent_with_keys,
450            EncryptionLevel::OneRtt => &mut self.spaces[2].sent_with_keys,
451        };
452        *count = count.saturating_add(1u64);
453    }
454}
455
456/// Per space kind cryptographic state.
457#[derive(Default)]
458pub(super) struct CryptoSpace {
459    /// Packet protection keys for this space.
460    pub(super) keys: Option<Keys>,
461    /// Incoming cryptographic handshake stream.
462    pub(super) crypto_stream: Assembler,
463    /// Current offset of outgoing cryptographic handshake stream.
464    pub(super) crypto_offset: u64,
465    /// Number of packets encrypted with the current set of keys.
466    pub(super) sent_with_keys: u64,
467}
468
469/// QUIC packet protection levels (RFC 9001).
470#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
471pub(crate) enum EncryptionLevel {
472    /// Initial packets (client and server).
473    Initial,
474    /// Early data (0-RTT), client only.
475    ZeroRtt,
476    /// Handshake packets.
477    Handshake,
478    /// Application data (1-RTT).
479    OneRtt,
480}
481
482impl From<SpaceKind> for crate::packet::SpaceId {
483    fn from(kind: SpaceKind) -> Self {
484        match kind {
485            SpaceKind::Initial => Self::Initial,
486            SpaceKind::Handshake => Self::Handshake,
487            SpaceKind::Data => Self::Data,
488        }
489    }
490}
491
492impl IndexMut<SpaceKind> for [CryptoSpace; 3] {
493    fn index_mut(&mut self, index: SpaceKind) -> &mut Self::Output {
494        &mut self[index as usize]
495    }
496}
497
498impl Index<SpaceKind> for [CryptoSpace; 3] {
499    type Output = CryptoSpace;
500
501    fn index(&self, index: SpaceKind) -> &Self::Output {
502        &self[index as usize]
503    }
504}