s2n_quic_core/stateless_reset/
token.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Defines the Stateless Reset token
5
6use s2n_codec::{
7    decoder_value,
8    zerocopy::{FromBytes, Immutable, IntoBytes, Unaligned},
9    Encoder, EncoderValue,
10};
11use subtle::ConstantTimeEq;
12
13//= https://www.rfc-editor.org/rfc/rfc9000#section-10.3
14//# Stateless Reset {
15//#   Fixed Bits (2) = 1,
16//#   Unpredictable Bits (38..),
17//#   Stateless Reset Token (128),
18//# }
19pub const LEN: usize = 128 / 8;
20
21// The implemented PartialEq will have the same results as
22// a derived version, except it is constant-time. Therefore
23// Hash can still be derived.
24#[allow(clippy::derived_hash_with_manual_eq)]
25#[derive(Copy, Clone, Debug, Eq, Hash, FromBytes, IntoBytes, Unaligned, Immutable)]
26#[cfg_attr(
27    any(test, feature = "generator"),
28    derive(bolero_generator::TypeGenerator)
29)]
30#[repr(C)]
31pub struct Token([u8; LEN]);
32
33impl Token {
34    /// A zeroed out stateless reset token
35    pub const ZEROED: Self = Self([0; LEN]);
36
37    /// Unwraps this token, returning the underlying array
38    pub fn into_inner(self) -> [u8; LEN] {
39        self.0
40    }
41}
42
43impl From<[u8; LEN]> for Token {
44    fn from(bytes: [u8; LEN]) -> Self {
45        Self(bytes)
46    }
47}
48
49impl TryFrom<&[u8]> for Token {
50    type Error = core::array::TryFromSliceError;
51
52    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
53        let bytes = bytes.try_into()?;
54        Ok(Self(bytes))
55    }
56}
57
58impl AsRef<[u8]> for Token {
59    fn as_ref(&self) -> &[u8] {
60        &self.0[..]
61    }
62}
63
64impl AsMut<[u8]> for Token {
65    fn as_mut(&mut self) -> &mut [u8] {
66        self.0.as_mut()
67    }
68}
69
70impl PartialEq for Token {
71    //= https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1
72    //# When comparing a datagram to stateless reset token values, endpoints
73    //# MUST perform the comparison without leaking information about the
74    //# value of the token.
75    fn eq(&self, other: &Self) -> bool {
76        self.0.ct_eq(&other.0).into()
77    }
78}
79
80decoder_value!(
81    impl<'a> Token {
82        fn decode(buffer: Buffer) -> Result<Self> {
83            let (value, buffer) = buffer.decode_slice(LEN)?;
84            let value: &[u8] = value.into_less_safe_slice();
85            let token = Token::try_from(value).expect("slice len already verified");
86
87            Ok((token, buffer))
88        }
89    }
90);
91
92impl EncoderValue for Token {
93    fn encoding_size(&self) -> usize {
94        LEN
95    }
96
97    fn encode<E: Encoder>(&self, encoder: &mut E) {
98        self.as_ref().encode(encoder)
99    }
100}
101
102// A generator for a stateless reset token
103pub trait Generator: 'static + Send {
104    /// If enabled, a stateless reset packet containing the token generated
105    /// by this Generator will be sent when a packet is received that cannot
106    /// be matched to an existing connection. Otherwise, the packet will be
107    /// dropped with no further action.
108    const ENABLED: bool = true;
109
110    /// Generates a stateless reset token.
111    ///
112    /// The stateless reset token MUST be difficult to guess.
113    ///
114    /// To enable stateless reset functionality, the stateless reset token must
115    /// be generated the same for a given `local_connection_id` before and after loss of state.
116    fn generate(&mut self, local_connection_id: &[u8]) -> Token;
117}
118
119#[cfg(any(test, feature = "testing"))]
120pub mod testing {
121    use crate::{
122        stateless_reset,
123        stateless_reset::token::{Token, LEN},
124    };
125
126    pub const TEST_TOKEN_1: Token = Token(11111111123456578987654321u128.to_be_bytes());
127    pub const TEST_TOKEN_2: Token = Token(222222222123456578987654321u128.to_be_bytes());
128    pub const TEST_TOKEN_3: Token = Token(333333333123456578987654321u128.to_be_bytes());
129    pub const TEST_TOKEN_4: Token = Token(444444444123456578987654321u128.to_be_bytes());
130
131    const KEY: u8 = 123;
132
133    #[derive(Debug, Default)]
134    pub struct Generator();
135
136    impl stateless_reset::token::Generator for Generator {
137        fn generate(&mut self, connection_id: &[u8]) -> Token {
138            let mut token = [0; LEN];
139
140            for (index, byte) in connection_id.as_ref().iter().enumerate() {
141                token[index] = byte ^ KEY;
142            }
143
144            token.into()
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use crate::stateless_reset::token::{testing::TEST_TOKEN_1, LEN};
152
153    //= https://www.rfc-editor.org/rfc/rfc9000#section-10.3.1
154    //= type=test
155    //# When comparing a datagram to stateless reset token values, endpoints
156    //# MUST perform the comparison without leaking information about the
157    //# value of the token.
158    #[test]
159    fn equality_test() {
160        let token_1 = TEST_TOKEN_1;
161        let token_2 = TEST_TOKEN_1;
162
163        assert_eq!(token_1, token_2);
164
165        for i in 0..LEN {
166            let mut token = TEST_TOKEN_1;
167            token.0[i] = !TEST_TOKEN_1.0[i];
168
169            assert_ne!(TEST_TOKEN_1, token);
170        }
171    }
172}