quinn_proto/
token.rs

1use std::{
2    fmt,
3    mem::size_of,
4    net::{IpAddr, SocketAddr},
5};
6
7use bytes::{Buf, BufMut, Bytes};
8use rand::Rng;
9
10use crate::{
11    Duration, RESET_TOKEN_SIZE, ServerConfig, SystemTime, UNIX_EPOCH,
12    coding::{BufExt, BufMutExt},
13    crypto::{HandshakeTokenKey, HmacKey},
14    packet::InitialHeader,
15    shared::ConnectionId,
16};
17
18/// Responsible for limiting clients' ability to reuse validation tokens
19///
20/// [_RFC 9000 § 8.1.4:_](https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1.4)
21///
22/// > Attackers could replay tokens to use servers as amplifiers in DDoS attacks. To protect
23/// > against such attacks, servers MUST ensure that replay of tokens is prevented or limited.
24/// > Servers SHOULD ensure that tokens sent in Retry packets are only accepted for a short time,
25/// > as they are returned immediately by clients. Tokens that are provided in NEW_TOKEN frames
26/// > (Section 19.7) need to be valid for longer but SHOULD NOT be accepted multiple times.
27/// > Servers are encouraged to allow tokens to be used only once, if possible; tokens MAY include
28/// > additional information about clients to further narrow applicability or reuse.
29///
30/// `TokenLog` pertains only to tokens provided in NEW_TOKEN frames.
31pub trait TokenLog: Send + Sync {
32    /// Record that the token was used and, ideally, return a token reuse error if the token may
33    /// have been already used previously
34    ///
35    /// False negatives and false positives are both permissible. Called when a client uses an
36    /// address validation token.
37    ///
38    /// Parameters:
39    /// - `nonce`: A server-generated random unique value for the token.
40    /// - `issued`: The time the server issued the token.
41    /// - `lifetime`: The expiration time of address validation tokens sent via NEW_TOKEN frames,
42    ///   as configured by [`ServerValidationTokenConfig::lifetime`][1].
43    ///
44    /// [1]: crate::ValidationTokenConfig::lifetime
45    ///
46    /// ## Security & Performance
47    ///
48    /// To the extent that it is possible to repeatedly trigger false negatives (returning `Ok` for
49    /// a token which has been reused), an attacker could use the server to perform [amplification
50    /// attacks][2]. The QUIC specification requires that this be limited, if not prevented fully.
51    ///
52    /// A false positive (returning `Err` for a token which has never been used) is not a security
53    /// vulnerability; it is permissible for a `TokenLog` to always return `Err`. A false positive
54    /// causes the token to be ignored, which may cause the transmission of some 0.5-RTT data to be
55    /// delayed until the handshake completes, if a sufficient amount of 0.5-RTT data it sent.
56    ///
57    /// [2]: https://en.wikipedia.org/wiki/Denial-of-service_attack#Amplification
58    fn check_and_insert(
59        &self,
60        nonce: u128,
61        issued: SystemTime,
62        lifetime: Duration,
63    ) -> Result<(), TokenReuseError>;
64}
65
66/// Error for when a validation token may have been reused
67pub struct TokenReuseError;
68
69/// Null implementation of [`TokenLog`], which never accepts tokens
70pub struct NoneTokenLog;
71
72impl TokenLog for NoneTokenLog {
73    fn check_and_insert(&self, _: u128, _: SystemTime, _: Duration) -> Result<(), TokenReuseError> {
74        Err(TokenReuseError)
75    }
76}
77
78/// Responsible for storing validation tokens received from servers and retrieving them for use in
79/// subsequent connections
80pub trait TokenStore: Send + Sync {
81    /// Potentially store a token for later one-time use
82    ///
83    /// Called when a NEW_TOKEN frame is received from the server.
84    fn insert(&self, server_name: &str, token: Bytes);
85
86    /// Try to find and take a token that was stored with the given server name
87    ///
88    /// The same token must never be returned from `take` twice, as doing so can be used to
89    /// de-anonymize a client's traffic.
90    ///
91    /// Called when trying to connect to a server. It is always ok for this to return `None`.
92    fn take(&self, server_name: &str) -> Option<Bytes>;
93}
94
95/// Null implementation of [`TokenStore`], which does not store any tokens
96pub struct NoneTokenStore;
97
98impl TokenStore for NoneTokenStore {
99    fn insert(&self, _: &str, _: Bytes) {}
100    fn take(&self, _: &str) -> Option<Bytes> {
101        None
102    }
103}
104
105/// State in an `Incoming` determined by a token or lack thereof
106#[derive(Debug)]
107pub(crate) struct IncomingToken {
108    pub(crate) retry_src_cid: Option<ConnectionId>,
109    pub(crate) orig_dst_cid: ConnectionId,
110    pub(crate) validated: bool,
111}
112
113impl IncomingToken {
114    /// Construct for an `Incoming` given the first packet header, or error if the connection
115    /// cannot be established
116    pub(crate) fn from_header(
117        header: &InitialHeader,
118        server_config: &ServerConfig,
119        remote_address: SocketAddr,
120    ) -> Result<Self, InvalidRetryTokenError> {
121        let unvalidated = Self {
122            retry_src_cid: None,
123            orig_dst_cid: header.dst_cid,
124            validated: false,
125        };
126
127        // Decode token or short-circuit
128        if header.token.is_empty() {
129            return Ok(unvalidated);
130        }
131
132        // In cases where a token cannot be decrypted/decoded, we must allow for the possibility
133        // that this is caused not by client malfeasance, but by the token having been generated by
134        // an incompatible endpoint, e.g. a different version or a neighbor behind the same load
135        // balancer. In such cases we proceed as if there was no token.
136        //
137        // [_RFC 9000 § 8.1.3:_](https://www.rfc-editor.org/rfc/rfc9000.html#section-8.1.3-10)
138        //
139        // > If the token is invalid, then the server SHOULD proceed as if the client did not have
140        // > a validated address, including potentially sending a Retry packet.
141        let Some(retry) = Token::decode(&*server_config.token_key, &header.token) else {
142            return Ok(unvalidated);
143        };
144
145        // Validate token, then convert into Self
146        match retry.payload {
147            TokenPayload::Retry {
148                address,
149                orig_dst_cid,
150                issued,
151            } => {
152                if address != remote_address {
153                    return Err(InvalidRetryTokenError);
154                }
155                if issued + server_config.retry_token_lifetime < server_config.time_source.now() {
156                    return Err(InvalidRetryTokenError);
157                }
158
159                Ok(Self {
160                    retry_src_cid: Some(header.dst_cid),
161                    orig_dst_cid,
162                    validated: true,
163                })
164            }
165            TokenPayload::Validation { ip, issued } => {
166                if ip != remote_address.ip() {
167                    return Ok(unvalidated);
168                }
169                if issued + server_config.validation_token.lifetime
170                    < server_config.time_source.now()
171                {
172                    return Ok(unvalidated);
173                }
174                if server_config
175                    .validation_token
176                    .log
177                    .check_and_insert(retry.nonce, issued, server_config.validation_token.lifetime)
178                    .is_err()
179                {
180                    return Ok(unvalidated);
181                }
182
183                Ok(Self {
184                    retry_src_cid: None,
185                    orig_dst_cid: header.dst_cid,
186                    validated: true,
187                })
188            }
189        }
190    }
191}
192
193/// Error for a token being unambiguously from a Retry packet, and not valid
194///
195/// The connection cannot be established.
196pub(crate) struct InvalidRetryTokenError;
197
198/// Retry or validation token
199pub(crate) struct Token {
200    /// Content that is encrypted from the client
201    pub(crate) payload: TokenPayload,
202    /// Randomly generated value, which must be unique, and is visible to the client
203    nonce: u128,
204}
205
206impl Token {
207    /// Construct with newly sampled randomness
208    pub(crate) fn new(payload: TokenPayload, rng: &mut impl Rng) -> Self {
209        Self {
210            nonce: rng.random(),
211            payload,
212        }
213    }
214
215    /// Encode and encrypt
216    pub(crate) fn encode(&self, key: &dyn HandshakeTokenKey) -> Vec<u8> {
217        let mut buf = Vec::new();
218
219        // Encode payload
220        match self.payload {
221            TokenPayload::Retry {
222                address,
223                orig_dst_cid,
224                issued,
225            } => {
226                buf.put_u8(TokenType::Retry as u8);
227                encode_addr(&mut buf, address);
228                orig_dst_cid.encode_long(&mut buf);
229                encode_unix_secs(&mut buf, issued);
230            }
231            TokenPayload::Validation { ip, issued } => {
232                buf.put_u8(TokenType::Validation as u8);
233                encode_ip(&mut buf, ip);
234                encode_unix_secs(&mut buf, issued);
235            }
236        }
237
238        // Encrypt
239        let aead_key = key.aead_from_hkdf(&self.nonce.to_le_bytes());
240        aead_key.seal(&mut buf, &[]).unwrap();
241        buf.extend(&self.nonce.to_le_bytes());
242
243        buf
244    }
245
246    /// Decode and decrypt
247    fn decode(key: &dyn HandshakeTokenKey, raw_token_bytes: &[u8]) -> Option<Self> {
248        // Decrypt
249
250        // MSRV: split_at_checked requires 1.80.0
251        let nonce_slice_start = raw_token_bytes.len().checked_sub(size_of::<u128>())?;
252        let (sealed_token, nonce_bytes) = raw_token_bytes.split_at(nonce_slice_start);
253
254        let nonce = u128::from_le_bytes(nonce_bytes.try_into().unwrap());
255
256        let aead_key = key.aead_from_hkdf(nonce_bytes);
257        let mut sealed_token = sealed_token.to_vec();
258        let data = aead_key.open(&mut sealed_token, &[]).ok()?;
259
260        // Decode payload
261        let mut reader = &data[..];
262        let payload = match TokenType::from_byte((&mut reader).get::<u8>().ok()?)? {
263            TokenType::Retry => TokenPayload::Retry {
264                address: decode_addr(&mut reader)?,
265                orig_dst_cid: ConnectionId::decode_long(&mut reader)?,
266                issued: decode_unix_secs(&mut reader)?,
267            },
268            TokenType::Validation => TokenPayload::Validation {
269                ip: decode_ip(&mut reader)?,
270                issued: decode_unix_secs(&mut reader)?,
271            },
272        };
273
274        if !reader.is_empty() {
275            // Consider extra bytes a decoding error (it may be from an incompatible endpoint)
276            return None;
277        }
278
279        Some(Self { nonce, payload })
280    }
281}
282
283/// Content of a [`Token`] that is encrypted from the client
284pub(crate) enum TokenPayload {
285    /// Token originating from a Retry packet
286    Retry {
287        /// The client's address
288        address: SocketAddr,
289        /// The destination connection ID set in the very first packet from the client
290        orig_dst_cid: ConnectionId,
291        /// The time at which this token was issued
292        issued: SystemTime,
293    },
294    /// Token originating from a NEW_TOKEN frame
295    Validation {
296        /// The client's IP address (its port is likely to change between sessions)
297        ip: IpAddr,
298        /// The time at which this token was issued
299        issued: SystemTime,
300    },
301}
302
303/// Variant tag for a [`TokenPayload`]
304#[derive(Copy, Clone)]
305#[repr(u8)]
306enum TokenType {
307    Retry = 0,
308    Validation = 1,
309}
310
311impl TokenType {
312    fn from_byte(n: u8) -> Option<Self> {
313        use TokenType::*;
314        [Retry, Validation].into_iter().find(|ty| *ty as u8 == n)
315    }
316}
317
318fn encode_addr(buf: &mut Vec<u8>, address: SocketAddr) {
319    encode_ip(buf, address.ip());
320    buf.put_u16(address.port());
321}
322
323fn decode_addr<B: Buf>(buf: &mut B) -> Option<SocketAddr> {
324    let ip = decode_ip(buf)?;
325    let port = buf.get().ok()?;
326    Some(SocketAddr::new(ip, port))
327}
328
329fn encode_ip(buf: &mut Vec<u8>, ip: IpAddr) {
330    match ip {
331        IpAddr::V4(x) => {
332            buf.put_u8(0);
333            buf.put_slice(&x.octets());
334        }
335        IpAddr::V6(x) => {
336            buf.put_u8(1);
337            buf.put_slice(&x.octets());
338        }
339    }
340}
341
342fn decode_ip<B: Buf>(buf: &mut B) -> Option<IpAddr> {
343    match buf.get::<u8>().ok()? {
344        0 => buf.get().ok().map(IpAddr::V4),
345        1 => buf.get().ok().map(IpAddr::V6),
346        _ => None,
347    }
348}
349
350fn encode_unix_secs(buf: &mut Vec<u8>, time: SystemTime) {
351    buf.write::<u64>(
352        time.duration_since(UNIX_EPOCH)
353            .unwrap_or_default()
354            .as_secs(),
355    );
356}
357
358fn decode_unix_secs<B: Buf>(buf: &mut B) -> Option<SystemTime> {
359    Some(UNIX_EPOCH + Duration::from_secs(buf.get::<u64>().ok()?))
360}
361
362/// Stateless reset token
363///
364/// Used for an endpoint to securely communicate that it has lost state for a connection.
365#[allow(clippy::derived_hash_with_manual_eq)] // Custom PartialEq impl matches derived semantics
366#[derive(Debug, Copy, Clone, Hash)]
367pub(crate) struct ResetToken([u8; RESET_TOKEN_SIZE]);
368
369impl ResetToken {
370    pub(crate) fn new(key: &dyn HmacKey, id: ConnectionId) -> Self {
371        let mut signature = vec![0; key.signature_len()];
372        key.sign(&id, &mut signature);
373        // TODO: Server ID??
374        let mut result = [0; RESET_TOKEN_SIZE];
375        result.copy_from_slice(&signature[..RESET_TOKEN_SIZE]);
376        result.into()
377    }
378}
379
380impl PartialEq for ResetToken {
381    fn eq(&self, other: &Self) -> bool {
382        crate::constant_time::eq(&self.0, &other.0)
383    }
384}
385
386impl Eq for ResetToken {}
387
388impl From<[u8; RESET_TOKEN_SIZE]> for ResetToken {
389    fn from(x: [u8; RESET_TOKEN_SIZE]) -> Self {
390        Self(x)
391    }
392}
393
394impl std::ops::Deref for ResetToken {
395    type Target = [u8];
396    fn deref(&self) -> &[u8] {
397        &self.0
398    }
399}
400
401impl fmt::Display for ResetToken {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        for byte in self.iter() {
404            write!(f, "{byte:02x}")?;
405        }
406        Ok(())
407    }
408}
409
410#[cfg(all(test, any(feature = "aws-lc-rs", feature = "ring")))]
411mod test {
412    use super::*;
413    #[cfg(all(feature = "aws-lc-rs", not(feature = "ring")))]
414    use aws_lc_rs::hkdf;
415    use rand::prelude::*;
416    #[cfg(feature = "ring")]
417    use ring::hkdf;
418
419    fn token_round_trip(payload: TokenPayload) -> TokenPayload {
420        let rng = &mut rand::rng();
421        let token = Token::new(payload, rng);
422        let mut master_key = [0; 64];
423        rng.fill_bytes(&mut master_key);
424        let prk = hkdf::Salt::new(hkdf::HKDF_SHA256, &[]).extract(&master_key);
425        let encoded = token.encode(&prk);
426        let decoded = Token::decode(&prk, &encoded).expect("token didn't decrypt / decode");
427        assert_eq!(token.nonce, decoded.nonce);
428        decoded.payload
429    }
430
431    #[test]
432    fn retry_token_sanity() {
433        use crate::MAX_CID_SIZE;
434        use crate::cid_generator::{ConnectionIdGenerator, RandomConnectionIdGenerator};
435        use crate::{Duration, UNIX_EPOCH};
436
437        use std::net::Ipv6Addr;
438
439        let address_1 = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 4433);
440        let orig_dst_cid_1 = RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid();
441        let issued_1 = UNIX_EPOCH + Duration::from_secs(42); // Fractional seconds would be lost
442        let payload_1 = TokenPayload::Retry {
443            address: address_1,
444            orig_dst_cid: orig_dst_cid_1,
445            issued: issued_1,
446        };
447        let TokenPayload::Retry {
448            address: address_2,
449            orig_dst_cid: orig_dst_cid_2,
450            issued: issued_2,
451        } = token_round_trip(payload_1)
452        else {
453            panic!("token decoded as wrong variant");
454        };
455
456        assert_eq!(address_1, address_2);
457        assert_eq!(orig_dst_cid_1, orig_dst_cid_2);
458        assert_eq!(issued_1, issued_2);
459    }
460
461    #[test]
462    fn validation_token_sanity() {
463        use crate::{Duration, UNIX_EPOCH};
464
465        use std::net::Ipv6Addr;
466
467        let ip_1 = Ipv6Addr::LOCALHOST.into();
468        let issued_1 = UNIX_EPOCH + Duration::from_secs(42); // Fractional seconds would be lost
469
470        let payload_1 = TokenPayload::Validation {
471            ip: ip_1,
472            issued: issued_1,
473        };
474        let TokenPayload::Validation {
475            ip: ip_2,
476            issued: issued_2,
477        } = token_round_trip(payload_1)
478        else {
479            panic!("token decoded as wrong variant");
480        };
481
482        assert_eq!(ip_1, ip_2);
483        assert_eq!(issued_1, issued_2);
484    }
485
486    #[test]
487    fn invalid_token_returns_err() {
488        use super::*;
489        use rand::RngCore;
490
491        let rng = &mut rand::rng();
492
493        let mut master_key = [0; 64];
494        rng.fill_bytes(&mut master_key);
495
496        let prk = hkdf::Salt::new(hkdf::HKDF_SHA256, &[]).extract(&master_key);
497
498        let mut invalid_token = Vec::new();
499
500        let mut random_data = [0; 32];
501        rand::rng().fill_bytes(&mut random_data);
502        invalid_token.put_slice(&random_data);
503
504        // Assert: garbage sealed data returns err
505        assert!(Token::decode(&prk, &invalid_token).is_none());
506    }
507}