1use std::{
2 fmt,
3 net::{IpAddr, SocketAddr},
4};
5
6use bytes::{Buf, BufMut, Bytes};
7use rand::Rng;
8
9use crate::{
10 Duration, RESET_TOKEN_SIZE, ServerConfig, SystemTime, UNIX_EPOCH,
11 coding::{BufExt, BufMutExt},
12 crypto::{HandshakeTokenKey, HmacKey},
13 packet::InitialHeader,
14 shared::ConnectionId,
15};
16
17pub trait TokenLog: Send + Sync {
31 fn check_and_insert(
58 &self,
59 nonce: u128,
60 issued: SystemTime,
61 lifetime: Duration,
62 ) -> Result<(), TokenReuseError>;
63}
64
65pub struct TokenReuseError;
67
68pub struct NoneTokenLog;
70
71impl TokenLog for NoneTokenLog {
72 fn check_and_insert(&self, _: u128, _: SystemTime, _: Duration) -> Result<(), TokenReuseError> {
73 Err(TokenReuseError)
74 }
75}
76
77pub trait TokenStore: Send + Sync {
80 fn insert(&self, server_name: &str, token: Bytes);
84
85 fn take(&self, server_name: &str) -> Option<Bytes>;
92}
93
94pub struct NoneTokenStore;
96
97impl TokenStore for NoneTokenStore {
98 fn insert(&self, _: &str, _: Bytes) {}
99 fn take(&self, _: &str) -> Option<Bytes> {
100 None
101 }
102}
103
104#[derive(Debug)]
106pub(crate) struct IncomingToken {
107 pub(crate) retry_src_cid: Option<ConnectionId>,
108 pub(crate) orig_dst_cid: ConnectionId,
109 pub(crate) validated: bool,
110}
111
112impl IncomingToken {
113 pub(crate) fn from_header(
116 header: &InitialHeader,
117 server_config: &ServerConfig,
118 remote_address: SocketAddr,
119 ) -> Result<Self, InvalidRetryTokenError> {
120 let unvalidated = Self {
121 retry_src_cid: None,
122 orig_dst_cid: header.dst_cid,
123 validated: false,
124 };
125
126 if header.token.is_empty() {
128 return Ok(unvalidated);
129 }
130
131 let Some(retry) = Token::decode(&*server_config.token_key, &header.token) else {
141 return Ok(unvalidated);
142 };
143
144 match retry.payload {
146 TokenPayload::Retry {
147 address,
148 orig_dst_cid,
149 issued,
150 } => {
151 if address != remote_address {
152 return Err(InvalidRetryTokenError);
153 }
154 if issued + server_config.retry_token_lifetime < server_config.time_source.now() {
155 return Err(InvalidRetryTokenError);
156 }
157
158 Ok(Self {
159 retry_src_cid: Some(header.dst_cid),
160 orig_dst_cid,
161 validated: true,
162 })
163 }
164 TokenPayload::Validation { ip, issued } => {
165 if ip != remote_address.ip() {
166 return Ok(unvalidated);
167 }
168 if issued + server_config.validation_token.lifetime
169 < server_config.time_source.now()
170 {
171 return Ok(unvalidated);
172 }
173 if server_config
174 .validation_token
175 .log
176 .check_and_insert(retry.nonce, issued, server_config.validation_token.lifetime)
177 .is_err()
178 {
179 return Ok(unvalidated);
180 }
181
182 Ok(Self {
183 retry_src_cid: None,
184 orig_dst_cid: header.dst_cid,
185 validated: true,
186 })
187 }
188 }
189 }
190}
191
192pub(crate) struct InvalidRetryTokenError;
196
197pub(crate) struct Token {
199 pub(crate) payload: TokenPayload,
201 nonce: u128,
203}
204
205impl Token {
206 pub(crate) fn new(payload: TokenPayload, rng: &mut impl Rng) -> Self {
208 Self {
209 nonce: rng.random(),
210 payload,
211 }
212 }
213
214 pub(crate) fn encode(&self, key: &dyn HandshakeTokenKey) -> Vec<u8> {
216 let mut buf = Vec::new();
217
218 match self.payload {
220 TokenPayload::Retry {
221 address,
222 orig_dst_cid,
223 issued,
224 } => {
225 buf.put_u8(TokenType::Retry as u8);
226 encode_addr(&mut buf, address);
227 orig_dst_cid.encode_long(&mut buf);
228 encode_unix_secs(&mut buf, issued);
229 }
230 TokenPayload::Validation { ip, issued } => {
231 buf.put_u8(TokenType::Validation as u8);
232 encode_ip(&mut buf, ip);
233 encode_unix_secs(&mut buf, issued);
234 }
235 }
236
237 key.seal(self.nonce, &mut buf).unwrap();
239 buf.extend(&self.nonce.to_le_bytes());
240
241 buf
242 }
243
244 fn decode(key: &dyn HandshakeTokenKey, raw_token_bytes: &[u8]) -> Option<Self> {
246 let (sealed_token, nonce_bytes) = raw_token_bytes.split_last_chunk()?;
249
250 let nonce = u128::from_le_bytes(*nonce_bytes);
251
252 let mut sealed_token = sealed_token.to_vec();
253 let mut data = key.open(nonce, &mut sealed_token).ok()?;
254
255 let payload = match TokenType::from_byte((&mut data).get::<u8>().ok()?)? {
257 TokenType::Retry => TokenPayload::Retry {
258 address: decode_addr(&mut data)?,
259 orig_dst_cid: ConnectionId::decode_long(&mut data)?,
260 issued: decode_unix_secs(&mut data)?,
261 },
262 TokenType::Validation => TokenPayload::Validation {
263 ip: decode_ip(&mut data)?,
264 issued: decode_unix_secs(&mut data)?,
265 },
266 };
267
268 if !data.is_empty() {
269 return None;
271 }
272
273 Some(Self { nonce, payload })
274 }
275}
276
277pub(crate) enum TokenPayload {
279 Retry {
281 address: SocketAddr,
283 orig_dst_cid: ConnectionId,
285 issued: SystemTime,
287 },
288 Validation {
290 ip: IpAddr,
292 issued: SystemTime,
294 },
295}
296
297#[derive(Copy, Clone)]
299#[repr(u8)]
300enum TokenType {
301 Retry = 0,
302 Validation = 1,
303}
304
305impl TokenType {
306 fn from_byte(n: u8) -> Option<Self> {
307 use TokenType::*;
308 [Retry, Validation].into_iter().find(|ty| *ty as u8 == n)
309 }
310}
311
312fn encode_addr(buf: &mut Vec<u8>, address: SocketAddr) {
313 encode_ip(buf, address.ip());
314 buf.put_u16(address.port());
315}
316
317fn decode_addr<B: Buf>(buf: &mut B) -> Option<SocketAddr> {
318 let ip = decode_ip(buf)?;
319 let port = buf.get().ok()?;
320 Some(SocketAddr::new(ip, port))
321}
322
323fn encode_ip(buf: &mut Vec<u8>, ip: IpAddr) {
324 match ip {
325 IpAddr::V4(x) => {
326 buf.put_u8(0);
327 buf.put_slice(&x.octets());
328 }
329 IpAddr::V6(x) => {
330 buf.put_u8(1);
331 buf.put_slice(&x.octets());
332 }
333 }
334}
335
336fn decode_ip<B: Buf>(buf: &mut B) -> Option<IpAddr> {
337 match buf.get::<u8>().ok()? {
338 0 => buf.get().ok().map(IpAddr::V4),
339 1 => buf.get().ok().map(IpAddr::V6),
340 _ => None,
341 }
342}
343
344fn encode_unix_secs(buf: &mut Vec<u8>, time: SystemTime) {
345 buf.write::<u64>(
346 time.duration_since(UNIX_EPOCH)
347 .unwrap_or_default()
348 .as_secs(),
349 );
350}
351
352fn decode_unix_secs<B: Buf>(buf: &mut B) -> Option<SystemTime> {
353 Some(UNIX_EPOCH + Duration::from_secs(buf.get::<u64>().ok()?))
354}
355
356#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Debug, Copy, Clone, Hash)]
361pub(crate) struct ResetToken([u8; RESET_TOKEN_SIZE]);
362
363impl ResetToken {
364 pub(crate) fn new(key: &dyn HmacKey, id: ConnectionId) -> Self {
365 let mut signature = vec![0; key.signature_len()];
366 key.sign(&id, &mut signature);
367 let mut result = [0; RESET_TOKEN_SIZE];
369 result.copy_from_slice(&signature[..RESET_TOKEN_SIZE]);
370 result.into()
371 }
372}
373
374impl PartialEq for ResetToken {
375 fn eq(&self, other: &Self) -> bool {
376 crate::constant_time::eq(&self.0, &other.0)
377 }
378}
379
380impl Eq for ResetToken {}
381
382impl From<[u8; RESET_TOKEN_SIZE]> for ResetToken {
383 fn from(x: [u8; RESET_TOKEN_SIZE]) -> Self {
384 Self(x)
385 }
386}
387
388impl std::ops::Deref for ResetToken {
389 type Target = [u8];
390 fn deref(&self) -> &[u8] {
391 &self.0
392 }
393}
394
395impl fmt::Display for ResetToken {
396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397 for byte in self.iter() {
398 write!(f, "{byte:02x}")?;
399 }
400 Ok(())
401 }
402}
403
404#[cfg(all(test, any(feature = "aws-lc-rs", feature = "ring")))]
405mod test {
406 use crate::crypto::ring_like::RetryTokenKey;
407
408 use super::*;
409 use rand::prelude::*;
410
411 fn token_round_trip(payload: TokenPayload) -> TokenPayload {
412 let rng = &mut rand::rng();
413 let token = Token::new(payload, rng);
414 let master_key = RetryTokenKey::new(rng);
415 let encoded = token.encode(&master_key);
416 let decoded = Token::decode(&master_key, &encoded).expect("token didn't decrypt / decode");
417 assert_eq!(token.nonce, decoded.nonce);
418 decoded.payload
419 }
420
421 #[test]
422 fn retry_token_sanity() {
423 use crate::MAX_CID_SIZE;
424 use crate::cid_generator::{ConnectionIdGenerator, RandomConnectionIdGenerator};
425 use crate::{Duration, UNIX_EPOCH};
426
427 use std::net::Ipv6Addr;
428
429 let address_1 = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 4433);
430 let orig_dst_cid_1 = RandomConnectionIdGenerator::new(MAX_CID_SIZE).generate_cid();
431 let issued_1 = UNIX_EPOCH + Duration::from_secs(42); let payload_1 = TokenPayload::Retry {
433 address: address_1,
434 orig_dst_cid: orig_dst_cid_1,
435 issued: issued_1,
436 };
437 let TokenPayload::Retry {
438 address: address_2,
439 orig_dst_cid: orig_dst_cid_2,
440 issued: issued_2,
441 } = token_round_trip(payload_1)
442 else {
443 panic!("token decoded as wrong variant");
444 };
445
446 assert_eq!(address_1, address_2);
447 assert_eq!(orig_dst_cid_1, orig_dst_cid_2);
448 assert_eq!(issued_1, issued_2);
449 }
450
451 #[test]
452 fn validation_token_sanity() {
453 use crate::{Duration, UNIX_EPOCH};
454
455 use std::net::Ipv6Addr;
456
457 let ip_1 = Ipv6Addr::LOCALHOST.into();
458 let issued_1 = UNIX_EPOCH + Duration::from_secs(42); let payload_1 = TokenPayload::Validation {
461 ip: ip_1,
462 issued: issued_1,
463 };
464 let TokenPayload::Validation {
465 ip: ip_2,
466 issued: issued_2,
467 } = token_round_trip(payload_1)
468 else {
469 panic!("token decoded as wrong variant");
470 };
471
472 assert_eq!(ip_1, ip_2);
473 assert_eq!(issued_1, issued_2);
474 }
475
476 #[test]
477 fn invalid_token_returns_err() {
478 use super::*;
479
480 let master_key = RetryTokenKey::new(&mut rand::rng());
481
482 let mut invalid_token = Vec::new();
483
484 let mut random_data = [0; 32];
485 rand::rng().fill_bytes(&mut random_data);
486 invalid_token.put_slice(&random_data);
487
488 assert!(Token::decode(&master_key, &invalid_token).is_none());
490 }
491}