quinn_proto/
token.rs

1use std::{
2    fmt, io,
3    net::{IpAddr, SocketAddr},
4    time::{Duration, SystemTime, UNIX_EPOCH},
5};
6
7use bytes::{Buf, BufMut};
8
9use crate::{
10    coding::{BufExt, BufMutExt},
11    crypto::{CryptoError, HandshakeTokenKey, HmacKey},
12    shared::ConnectionId,
13    RESET_TOKEN_SIZE,
14};
15
16pub(crate) struct RetryToken {
17    /// The destination connection ID set in the very first packet from the client
18    pub(crate) orig_dst_cid: ConnectionId,
19    /// The time at which this token was issued
20    pub(crate) issued: SystemTime,
21}
22
23impl RetryToken {
24    pub(crate) fn encode(
25        &self,
26        key: &dyn HandshakeTokenKey,
27        address: &SocketAddr,
28        retry_src_cid: &ConnectionId,
29    ) -> Vec<u8> {
30        let aead_key = key.aead_from_hkdf(retry_src_cid);
31
32        let mut buf = Vec::new();
33        encode_addr(&mut buf, address);
34        self.orig_dst_cid.encode_long(&mut buf);
35        buf.write::<u64>(
36            self.issued
37                .duration_since(UNIX_EPOCH)
38                .map(|x| x.as_secs())
39                .unwrap_or(0),
40        );
41
42        aead_key.seal(&mut buf, &[]).unwrap();
43
44        buf
45    }
46
47    pub(crate) fn from_bytes(
48        key: &dyn HandshakeTokenKey,
49        address: &SocketAddr,
50        retry_src_cid: &ConnectionId,
51        raw_token_bytes: &[u8],
52    ) -> Result<Self, TokenDecodeError> {
53        let aead_key = key.aead_from_hkdf(retry_src_cid);
54        let mut sealed_token = raw_token_bytes.to_vec();
55
56        let data = aead_key.open(&mut sealed_token, &[])?;
57        let mut reader = io::Cursor::new(data);
58        let token_addr = decode_addr(&mut reader).ok_or(TokenDecodeError::UnknownToken)?;
59        if token_addr != *address {
60            return Err(TokenDecodeError::WrongAddress);
61        }
62        let orig_dst_cid =
63            ConnectionId::decode_long(&mut reader).ok_or(TokenDecodeError::UnknownToken)?;
64        let issued = UNIX_EPOCH
65            + Duration::new(
66                reader
67                    .get::<u64>()
68                    .map_err(|_| TokenDecodeError::UnknownToken)?,
69                0,
70            );
71
72        Ok(Self {
73            orig_dst_cid,
74            issued,
75        })
76    }
77}
78
79fn encode_addr(buf: &mut Vec<u8>, address: &SocketAddr) {
80    match address.ip() {
81        IpAddr::V4(x) => {
82            buf.put_u8(0);
83            buf.put_slice(&x.octets());
84        }
85        IpAddr::V6(x) => {
86            buf.put_u8(1);
87            buf.put_slice(&x.octets());
88        }
89    }
90    buf.put_u16(address.port());
91}
92
93fn decode_addr<B: Buf>(buf: &mut B) -> Option<SocketAddr> {
94    let ip = match buf.get_u8() {
95        0 => IpAddr::V4(buf.get().ok()?),
96        1 => IpAddr::V6(buf.get().ok()?),
97        _ => return None,
98    };
99    let port = buf.get_u16();
100    Some(SocketAddr::new(ip, port))
101}
102
103/// Reasons why a retry token might fail to validate a client's address
104#[derive(Debug, Copy, Clone)]
105pub(crate) enum TokenDecodeError {
106    /// Token was not recognized. It should be silently ignored.
107    UnknownToken,
108    /// Token was well-formed but associated with an incorrect address. The connection cannot be
109    /// established.
110    WrongAddress,
111}
112
113impl From<CryptoError> for TokenDecodeError {
114    fn from(CryptoError: CryptoError) -> Self {
115        Self::UnknownToken
116    }
117}
118
119/// Stateless reset token
120///
121/// Used for an endpoint to securely communicate that it has lost state for a connection.
122#[allow(clippy::derived_hash_with_manual_eq)] // Custom PartialEq impl matches derived semantics
123#[derive(Debug, Copy, Clone, Hash)]
124pub(crate) struct ResetToken([u8; RESET_TOKEN_SIZE]);
125
126impl ResetToken {
127    pub(crate) fn new(key: &dyn HmacKey, id: &ConnectionId) -> Self {
128        let mut signature = vec![0; key.signature_len()];
129        key.sign(id, &mut signature);
130        // TODO: Server ID??
131        let mut result = [0; RESET_TOKEN_SIZE];
132        result.copy_from_slice(&signature[..RESET_TOKEN_SIZE]);
133        result.into()
134    }
135}
136
137impl PartialEq for ResetToken {
138    fn eq(&self, other: &Self) -> bool {
139        crate::constant_time::eq(&self.0, &other.0)
140    }
141}
142
143impl Eq for ResetToken {}
144
145impl From<[u8; RESET_TOKEN_SIZE]> for ResetToken {
146    fn from(x: [u8; RESET_TOKEN_SIZE]) -> Self {
147        Self(x)
148    }
149}
150
151impl std::ops::Deref for ResetToken {
152    type Target = [u8];
153    fn deref(&self) -> &[u8] {
154        &self.0
155    }
156}
157
158impl fmt::Display for ResetToken {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        for byte in self.iter() {
161            write!(f, "{byte:02x}")?;
162        }
163        Ok(())
164    }
165}
166
167#[cfg(test)]
168mod test {
169    #[cfg(feature = "ring")]
170    #[test]
171    fn token_sanity() {
172        use super::*;
173        use crate::cid_generator::{ConnectionIdGenerator, RandomConnectionIdGenerator};
174        use crate::MAX_CID_SIZE;
175
176        use rand::RngCore;
177        use std::{
178            net::Ipv6Addr,
179            time::{Duration, UNIX_EPOCH},
180        };
181
182        let rng = &mut rand::thread_rng();
183
184        let mut master_key = [0; 64];
185        rng.fill_bytes(&mut master_key);
186
187        let prk = ring::hkdf::Salt::new(ring::hkdf::HKDF_SHA256, &[]).extract(&master_key);
188
189        let addr = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 4433);
190        let retry_src_cid = RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid();
191        let token = RetryToken {
192            orig_dst_cid: RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid(),
193            issued: UNIX_EPOCH + Duration::new(42, 0), // Fractional seconds would be lost
194        };
195        let encoded = token.encode(&prk, &addr, &retry_src_cid);
196
197        let decoded = RetryToken::from_bytes(&prk, &addr, &retry_src_cid, &encoded)
198            .expect("token didn't validate");
199        assert_eq!(token.orig_dst_cid, decoded.orig_dst_cid);
200        assert_eq!(token.issued, decoded.issued);
201    }
202
203    #[cfg(feature = "ring")]
204    #[test]
205    fn invalid_token_returns_err() {
206        use super::*;
207        use crate::cid_generator::{ConnectionIdGenerator, RandomConnectionIdGenerator};
208        use crate::MAX_CID_SIZE;
209        use rand::RngCore;
210        use std::net::Ipv6Addr;
211
212        let rng = &mut rand::thread_rng();
213
214        let mut master_key = [0; 64];
215        rng.fill_bytes(&mut master_key);
216
217        let prk = ring::hkdf::Salt::new(ring::hkdf::HKDF_SHA256, &[]).extract(&master_key);
218
219        let addr = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 4433);
220        let retry_src_cid = RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid();
221
222        let mut invalid_token = Vec::new();
223
224        let mut random_data = [0; 32];
225        rand::thread_rng().fill_bytes(&mut random_data);
226        invalid_token.put_slice(&random_data);
227
228        // Assert: garbage sealed data returns err
229        assert!(RetryToken::from_bytes(&prk, &addr, &retry_src_cid, &invalid_token).is_err());
230    }
231}