Skip to main content

simoom_quic/
token.rs

1//! Address-validation tokens for Retry and NEW_TOKEN (RFC 9000 ยง8.1), authenticated with
2//! HMAC-SHA256 under the endpoint's token key.
3//!
4//! Layout: `type(1) | age_base_ms(8) | odcid_len(1) odcid | rscid_len(1) rscid | mac(16)`.
5//! The MAC also covers the client address (IP and port for Retry, IP only for NEW_TOKEN, so
6//! a client keeps its token across port changes).
7
8use crate::config::Config;
9use crate::crypto::ct_eq;
10use crate::params::Cid;
11use std::net::SocketAddr;
12use std::time::{Duration, Instant};
13
14const RETRY: u8 = 1;
15const NEW_TOKEN: u8 = 2;
16const MAC_LEN: usize = 16;
17
18fn addr_bytes(a: &SocketAddr, with_port: bool, out: &mut Vec<u8>) {
19    match a {
20        SocketAddr::V4(v) => out.extend_from_slice(&v.ip().octets()),
21        SocketAddr::V6(v) => out.extend_from_slice(&v.ip().octets()),
22    }
23    if with_port {
24        out.extend_from_slice(&a.port().to_be_bytes());
25    }
26}
27
28fn millis(cfg: &Config, now: Instant) -> u64 {
29    let epoch = *cfg.epoch.get_or_init(|| now);
30    now.saturating_duration_since(epoch).as_millis() as u64
31}
32
33fn mac(cfg: &Config, body: &[u8], addr: &SocketAddr, with_port: bool) -> [u8; 32] {
34    let mut a = Vec::with_capacity(18);
35    addr_bytes(addr, with_port, &mut a);
36    cfg.provider.hmac(&cfg.token_key, &[b"token", body, &a])
37}
38
39fn seal(cfg: &Config, ty: u8, now: Instant, odcid: &[u8], rscid: &[u8], addr: &SocketAddr) -> Vec<u8> {
40    let mut t = Vec::with_capacity(11 + odcid.len() + rscid.len() + MAC_LEN);
41    t.push(ty);
42    t.extend_from_slice(&millis(cfg, now).to_be_bytes());
43    t.push(odcid.len() as u8);
44    t.extend_from_slice(odcid);
45    t.push(rscid.len() as u8);
46    t.extend_from_slice(rscid);
47    let m = mac(cfg, &t, addr, ty == RETRY);
48    t.extend_from_slice(&m[..MAC_LEN]);
49    t
50}
51
52/// A Retry token binding the client address and the original and Retry connection IDs.
53pub fn retry_token(cfg: &Config, addr: SocketAddr, now: Instant, odcid: &[u8], rscid: &[u8]) -> Vec<u8> {
54    seal(cfg, RETRY, now, odcid, rscid, &addr)
55}
56
57/// A NEW_TOKEN token for future connections from the same IP address.
58pub fn new_token(cfg: &Config, addr: SocketAddr, now: Instant) -> Vec<u8> {
59    seal(cfg, NEW_TOKEN, now, &[], &[], &addr)
60}
61
62/// A token that passed validation.
63#[derive(Debug, PartialEq, Eq)]
64pub enum Validated {
65    /// From a Retry: the original destination CID and the Retry source CID.
66    Retry { odcid: Cid, rscid: Cid },
67    NewToken,
68}
69
70/// Validates a token received in a client Initial.
71pub fn validate(cfg: &Config, token: &[u8], addr: SocketAddr, now: Instant) -> Option<Validated> {
72    if token.len() < 11 + MAC_LEN {
73        return None;
74    }
75    let (body, m) = token.split_at(token.len() - MAC_LEN);
76    let ty = body[0];
77    if ty != RETRY && ty != NEW_TOKEN {
78        return None;
79    }
80    let expect = mac(cfg, body, &addr, ty == RETRY);
81    if !ct_eq(&expect[..MAC_LEN], m) {
82        return None;
83    }
84    let issued = u64::from_be_bytes(body[1..9].try_into().unwrap());
85    let lifetime = if ty == RETRY {
86        cfg.retry_token_lifetime
87    } else {
88        cfg.new_token_lifetime.unwrap_or(Duration::ZERO)
89    };
90    let age = millis(cfg, now).checked_sub(issued)?;
91    if age > lifetime.as_millis() as u64 {
92        return None;
93    }
94    if ty == NEW_TOKEN {
95        return Some(Validated::NewToken);
96    }
97    let rest = &body[9..];
98    let ol = *rest.first()? as usize;
99    if ol > 20 || rest.len() < 1 + ol + 1 {
100        return None;
101    }
102    let odcid = Cid::new(&rest[1..1 + ol]);
103    let rl = rest[1 + ol] as usize;
104    if rl > 20 || rest.len() != 2 + ol + rl {
105        return None;
106    }
107    let rscid = Cid::new(&rest[2 + ol..]);
108    Some(Validated::Retry { odcid, rscid })
109}