Skip to main content

qv_core/
token.rs

1use crate::crypto::SuiteId;
2use crate::error::{QVError, QVResult};
3
4/// Token type byte.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6#[repr(u8)]
7pub enum TokenType {
8    Access = 0x01,
9    Refresh = 0x02,
10    Service = 0x03,
11}
12
13impl TokenType {
14    pub fn from_byte(b: u8) -> QVResult<Self> {
15        match b {
16            0x01 => Ok(TokenType::Access),
17            0x02 => Ok(TokenType::Refresh),
18            0x03 => Ok(TokenType::Service),
19            _ => Err(QVError::SerializationError(format!("unknown token type {b:#04x}"))),
20        }
21    }
22    pub fn as_byte(self) -> u8 { self as u8 }
23}
24
25/// Magic constant "QVLT" in big-endian.
26pub const MAGIC: u32 = 0x51564C54;
27pub const VERSION: u16 = 0x0300; // v3.0
28
29/// Decoded (plain-text) binary header before payload encryption.
30#[derive(Debug, Clone)]
31pub struct QVTokenHeader {
32    pub suite:       SuiteId,
33    pub token_type:  TokenType,
34    pub issued_at:   u64,  // microseconds since Unix epoch
35    pub ttl:         u32,  // seconds
36    pub nonce:       [u8; 32],
37    pub device_fp:   [u8; 32],
38    pub mutation_ctr: u64,
39}
40
41/// Full serialised token (header + encrypted payload + signature).
42///
43/// Wire format (all big-endian):
44/// +0    4B   MAGIC 0x51564C54
45/// +4    2B   VERSION 0x0300
46/// +6    1B   SUITE_ID
47/// +7    1B   TOKEN_TYPE
48/// +8    8B   ISSUED_AT (µs)
49/// +16   4B   TTL (s)
50/// +20  32B   NONCE
51/// +52  32B   DEVICE_FP
52/// +84   4B   PAYLOAD_LEN
53/// +88   var  ENCRYPTED_PAYLOAD (XChaCha20-Poly1305)
54/// +88+PL 8B  MUTATION_CTR
55/// +96+PL var SIGNATURE (ML-DSA-87 = 4595B for Suite 0x05)
56#[derive(Debug, Clone)]
57pub struct QVRawToken {
58    pub header:            QVTokenHeader,
59    pub encrypted_payload: Vec<u8>,
60    pub signature:         Vec<u8>,
61}
62
63impl QVRawToken {
64    /// Serialize to wire bytes.
65    pub fn to_bytes(&self) -> Vec<u8> {
66        let h = &self.header;
67        let pl = self.encrypted_payload.len() as u32;
68        let mut buf = Vec::with_capacity(96 + self.encrypted_payload.len() + self.signature.len());
69
70        buf.extend_from_slice(&MAGIC.to_be_bytes());
71        buf.extend_from_slice(&VERSION.to_be_bytes());
72        buf.push(h.suite.as_byte());
73        buf.push(h.token_type.as_byte());
74        buf.extend_from_slice(&h.issued_at.to_be_bytes());
75        buf.extend_from_slice(&h.ttl.to_be_bytes());
76        buf.extend_from_slice(&h.nonce);
77        buf.extend_from_slice(&h.device_fp);
78        buf.extend_from_slice(&pl.to_be_bytes());
79        buf.extend_from_slice(&self.encrypted_payload);
80        buf.extend_from_slice(&h.mutation_ctr.to_be_bytes());
81        buf.extend_from_slice(&self.signature);
82        buf
83    }
84
85    /// Deserialize from wire bytes.
86    pub fn from_bytes(data: &[u8]) -> QVResult<Self> {
87        macro_rules! need {
88            ($n:expr) => {
89                if data.len() < $n {
90                    return Err(QVError::BufferTooShort { need: $n, have: data.len() });
91                }
92            };
93        }
94
95        need!(88);
96
97        let magic = u32::from_be_bytes(data[0..4].try_into().unwrap());
98        if magic != MAGIC {
99            return Err(QVError::InvalidMagic);
100        }
101
102        let version = u16::from_be_bytes(data[4..6].try_into().unwrap());
103        if version != VERSION {
104            return Err(QVError::UnsupportedVersion(version));
105        }
106
107        let suite      = SuiteId::from_byte(data[6])?;
108        let token_type = TokenType::from_byte(data[7])?;
109        let issued_at  = u64::from_be_bytes(data[8..16].try_into().unwrap());
110        let ttl        = u32::from_be_bytes(data[16..20].try_into().unwrap());
111        let nonce: [u8; 32]     = data[20..52].try_into().unwrap();
112        let device_fp: [u8; 32] = data[52..84].try_into().unwrap();
113        let pl         = u32::from_be_bytes(data[84..88].try_into().unwrap()) as usize;
114
115        need!(88 + pl + 8);
116        let encrypted_payload = data[88..88 + pl].to_vec();
117
118        let mc_off     = 88 + pl;
119        let mutation_ctr = u64::from_be_bytes(data[mc_off..mc_off + 8].try_into().unwrap());
120
121        let sig_off    = mc_off + 8;
122        need!(sig_off + 1);
123        let signature  = data[sig_off..].to_vec();
124
125        Ok(QVRawToken {
126            header: QVTokenHeader { suite, token_type, issued_at, ttl, nonce, device_fp, mutation_ctr },
127            encrypted_payload,
128            signature,
129        })
130    }
131
132    /// The bytes that are covered by the signature (everything except the sig itself).
133    pub fn signed_bytes(&self) -> Vec<u8> {
134        let h = &self.header;
135        let pl = self.encrypted_payload.len() as u32;
136        let mut buf = Vec::new();
137        buf.extend_from_slice(&MAGIC.to_be_bytes());
138        buf.extend_from_slice(&VERSION.to_be_bytes());
139        buf.push(h.suite.as_byte());
140        buf.push(h.token_type.as_byte());
141        buf.extend_from_slice(&h.issued_at.to_be_bytes());
142        buf.extend_from_slice(&h.ttl.to_be_bytes());
143        buf.extend_from_slice(&h.nonce);
144        buf.extend_from_slice(&h.device_fp);
145        buf.extend_from_slice(&pl.to_be_bytes());
146        buf.extend_from_slice(&self.encrypted_payload);
147        buf.extend_from_slice(&h.mutation_ctr.to_be_bytes());
148        buf
149    }
150}