1pub mod claims;
2pub mod crypto;
3pub mod error;
4#[cfg(feature = "falcon")]
5pub mod falcon;
6pub mod issuance;
7pub mod mutation;
8pub mod token;
9pub mod verify;
10
11pub use claims::Claims;
12pub use crypto::{QVSigningKey, QVVerifyingKey, SuiteId, generate_keypair};
13pub use error::{QVError, QVResult};
14pub use issuance::{IssueParams, issue_token};
15#[cfg(feature = "falcon")]
16pub use issuance::{issue_token_falcon512, issue_token_falcon1024};
17pub use mutation::MutationChain;
18pub use token::{QVRawToken, QVTokenHeader, TokenType, VERSION, MAGIC};
19pub use verify::{verify_token, VerifyOutput};
20#[cfg(feature = "falcon")]
21pub use verify::{verify_token_falcon512, verify_token_falcon1024};
22
23#[cfg(test)]
24mod tests {
25 use super::*;
26
27 fn test_encrypt_key() -> [u8; 32] {
28 let mut k = [0u8; 32];
29 for (i, b) in k.iter_mut().enumerate() { *b = i as u8; }
30 k
31 }
32
33 #[test]
34 fn roundtrip_issue_verify() {
35 let (sk, vk) = generate_keypair().expect("keygen");
36 let ek = test_encrypt_key();
37 let mut chain = MutationChain::new([0xAB; 32]);
38
39 let mut claims = Claims::new();
40 claims.insert("sub", "user-123");
41 claims.insert("role", "admin");
42
43 let params = IssueParams {
44 suite: SuiteId::Dilithium5,
45 token_type: TokenType::Access,
46 ttl_secs: 3600,
47 device_fp: None,
48 claims: &claims,
49 signing_key: &sk,
50 encrypt_key: &ek,
51 chain: &mut chain,
52 };
53
54 let raw = issue_token(params).expect("issue");
55 let bytes = raw.to_bytes();
56 let parsed = QVRawToken::from_bytes(&bytes).expect("parse");
57
58 let verify_chain = MutationChain::from_state([0xAB; 32], 0);
59 let out = verify_token(&parsed, &vk, &ek, &verify_chain).expect("verify");
60
61 assert_eq!(out.claims.get("sub"), Some("user-123"));
62 assert_eq!(out.claims.get("role"), Some("admin"));
63 }
64
65 #[test]
66 fn expired_token_rejected() {
67 let (sk, vk) = generate_keypair().expect("keygen");
68 let ek = test_encrypt_key();
69 let mut chain = MutationChain::new([0x11; 32]);
70
71 let mut claims = Claims::new();
72 claims.insert("sub", "test");
73
74 let params = IssueParams {
75 suite: SuiteId::Dilithium5,
76 token_type: TokenType::Access,
77 ttl_secs: 0, device_fp: None,
79 claims: &claims,
80 signing_key: &sk,
81 encrypt_key: &ek,
82 chain: &mut chain,
83 };
84
85 let raw = issue_token(params).expect("issue");
86 let bytes = raw.to_bytes();
87 let parsed = QVRawToken::from_bytes(&bytes).expect("parse");
88 let verify_chain = MutationChain::from_state([0x11; 32], 0);
89
90 std::thread::sleep(std::time::Duration::from_millis(1100));
92 let result = verify_token(&parsed, &vk, &ek, &verify_chain);
93 assert!(matches!(result, Err(QVError::Expired { .. })));
94 }
95
96 #[test]
97 fn tampered_signature_rejected() {
98 let (sk, vk) = generate_keypair().expect("keygen");
99 let ek = test_encrypt_key();
100 let mut chain = MutationChain::new([0x22; 32]);
101
102 let mut claims = Claims::new();
103 claims.insert("sub", "attacker");
104
105 let params = IssueParams {
106 suite: SuiteId::Dilithium5,
107 token_type: TokenType::Access,
108 ttl_secs: 3600,
109 device_fp: None,
110 claims: &claims,
111 signing_key: &sk,
112 encrypt_key: &ek,
113 chain: &mut chain,
114 };
115
116 let mut raw = issue_token(params).expect("issue");
117 raw.signature[100] ^= 0xFF;
119
120 let verify_chain = MutationChain::from_state([0x22; 32], 0);
121 let result = verify_token(&raw, &vk, &ek, &verify_chain);
122 assert!(matches!(result, Err(QVError::SignatureInvalid)));
123 }
124
125 #[test]
126 fn replay_rejected() {
127 let (sk, vk) = generate_keypair().expect("keygen");
128 let ek = test_encrypt_key();
129 let mut chain = MutationChain::new([0x33; 32]);
130
131 let mut claims = Claims::new();
132 claims.insert("sub", "user");
133
134 let params = IssueParams {
135 suite: SuiteId::Dilithium5,
136 token_type: TokenType::Access,
137 ttl_secs: 3600,
138 device_fp: None,
139 claims: &claims,
140 signing_key: &sk,
141 encrypt_key: &ek,
142 chain: &mut chain,
143 };
144
145 let raw = issue_token(params).expect("issue");
146
147 let advanced_chain = MutationChain::from_state([0x33; 32], 1);
149 let result = verify_token(&raw, &vk, &ek, &advanced_chain);
150 assert!(matches!(result, Err(QVError::ReplayDetected { .. })));
151 }
152
153 #[cfg(feature = "falcon")]
154 #[test]
155 fn falcon512_token_roundtrip() {
156 use crate::falcon::falcon512;
157
158 let (sk, vk) = falcon512::generate_keypair().expect("falcon keygen");
159 let ek = test_encrypt_key();
160 let mut chain = MutationChain::new([0x44; 32]);
161
162 let mut claims = Claims::new();
163 claims.insert("sub", "falcon-512-user");
164 claims.insert("role", "pilot");
165
166 let raw = issue_token_falcon512(
167 TokenType::Access, 3600, None, &claims, &sk, &ek, &mut chain,
168 ).expect("issue falcon-512");
169
170 assert!(raw.signature.len() <= 666, "falcon-512 sig {} > 666", raw.signature.len());
172 assert!(raw.signature.len() < 4627 / 5, "expected 5x smaller than ML-DSA-87");
173 assert_eq!(raw.header.suite, SuiteId::Falcon512);
174
175 let bytes = raw.to_bytes();
177 let parsed = QVRawToken::from_bytes(&bytes).expect("parse");
178 assert_eq!(parsed.header.suite, SuiteId::Falcon512);
179
180 let verify_chain = MutationChain::from_state([0x44; 32], 0);
181 let out = verify_token_falcon512(&parsed, &vk, &ek, &verify_chain).expect("verify");
182 assert_eq!(out.claims.get("sub"), Some("falcon-512-user"));
183 assert_eq!(out.claims.get("role"), Some("pilot"));
184 }
185
186 #[cfg(feature = "falcon")]
187 #[test]
188 fn falcon1024_token_roundtrip() {
189 use crate::falcon::falcon1024;
190
191 let (sk, vk) = falcon1024::generate_keypair().expect("falcon keygen");
192 let ek = test_encrypt_key();
193 let mut chain = MutationChain::new([0x55; 32]);
194
195 let mut claims = Claims::new();
196 claims.insert("sub", "falcon-1024-user");
197
198 let raw = issue_token_falcon1024(
199 TokenType::Service, 60, None, &claims, &sk, &ek, &mut chain,
200 ).expect("issue falcon-1024");
201
202 assert!(raw.signature.len() <= 1280);
203 assert_eq!(raw.header.suite, SuiteId::Falcon1024);
204
205 let bytes = raw.to_bytes();
206 let parsed = QVRawToken::from_bytes(&bytes).expect("parse");
207
208 let verify_chain = MutationChain::from_state([0x55; 32], 0);
209 let out = verify_token_falcon1024(&parsed, &vk, &ek, &verify_chain).expect("verify");
210 assert_eq!(out.claims.get("sub"), Some("falcon-1024-user"));
211 }
212
213 #[cfg(feature = "falcon")]
214 #[test]
215 fn falcon_suite_mismatch_rejected() {
216 use crate::falcon::falcon512;
217
218 let (sk, _vk) = falcon512::generate_keypair().expect("keygen");
219 let (_sk1024, vk1024) = crate::falcon::falcon1024::generate_keypair().expect("keygen");
220 let ek = test_encrypt_key();
221 let mut chain = MutationChain::new([0x66; 32]);
222
223 let mut claims = Claims::new();
224 claims.insert("sub", "x");
225
226 let raw = issue_token_falcon512(
227 TokenType::Access, 3600, None, &claims, &sk, &ek, &mut chain,
228 ).expect("issue");
229
230 let verify_chain = MutationChain::from_state([0x66; 32], 0);
231 let result = verify_token_falcon1024(&raw, &vk1024, &ek, &verify_chain);
233 assert!(matches!(result, Err(QVError::UnknownSuite(0x10))));
234 }
235
236 #[test]
237 fn claims_encode_decode_roundtrip() {
238 let mut c = Claims::new();
239 c.insert("iss", "qv.example.com");
240 c.insert("sub", "user-456");
241 c.insert("scope", "read:all");
242
243 let encoded = c.encode().expect("encode");
244 let decoded = Claims::decode(&encoded).expect("decode");
245
246 assert_eq!(decoded.get("iss"), Some("qv.example.com"));
247 assert_eq!(decoded.get("sub"), Some("user-456"));
248 assert_eq!(decoded.get("scope"), Some("read:all"));
249 }
250}