1use std::sync::{Arc, Mutex};
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 capabilities::Capabilities,
9 crypto::{Keypair, PublicKey, Signature},
10 namespaces::PUBKY_AUTH,
11 timestamp::Timestamp,
12};
13
14const CURRENT_VERSION: u8 = 0;
15const TIMESTAMP_WINDOW: i64 = 180 * 1_000_000;
17
18#[derive(Debug, PartialEq, Serialize, Deserialize)]
19pub struct AuthToken {
21 signature: Signature,
23 namespace: [u8; 10],
26 version: u8,
33 timestamp: Timestamp,
35 public_key: PublicKey,
37 capabilities: Capabilities,
39}
40
41impl AuthToken {
42 pub fn sign(keypair: &Keypair, capabilities: impl Into<Capabilities>) -> Self {
44 let timestamp = Timestamp::now();
45
46 let mut token = Self {
47 signature: Signature::from_bytes(&[0; 64]),
48 namespace: *PUBKY_AUTH,
49 version: 0,
50 timestamp,
51 public_key: keypair.public_key(),
52 capabilities: capabilities.into(),
53 };
54
55 let serialized = token.serialize();
56
57 token.signature = keypair.sign(&serialized[65..]);
58
59 token
60 }
61
62 pub fn public_key(&self) -> &PublicKey {
66 &self.public_key
67 }
68
69 pub fn capabilities(&self) -> &Capabilities {
71 &self.capabilities
72 }
73
74 pub fn timestamp(&self) -> Timestamp {
76 self.timestamp
77 }
78
79 pub fn verify(bytes: &[u8]) -> Result<Self, Error> {
83 if bytes[74] > CURRENT_VERSION {
84 return Err(Error::UnknownVersion);
85 }
86
87 let token = AuthToken::deserialize(bytes)?;
88
89 match token.version {
90 0 => {
91 let now = Timestamp::now();
92
93 let diff = token.timestamp.as_u64() as i64 - now.as_u64() as i64;
95 if diff > TIMESTAMP_WINDOW {
96 return Err(Error::TooFarInTheFuture);
97 }
98 if diff < -TIMESTAMP_WINDOW {
99 return Err(Error::Expired);
100 }
101
102 token
103 .public_key
104 .verify(AuthToken::signable(token.version, bytes), &token.signature)
105 .map_err(|_| Error::InvalidSignature)?;
106
107 Ok(token)
108 }
109 _ => unreachable!(),
110 }
111 }
112
113 pub fn serialize(&self) -> Vec<u8> {
115 postcard::to_allocvec(self).unwrap()
116 }
117
118 pub fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
120 Ok(postcard::from_bytes(bytes)?)
121 }
122
123 fn signable(version: u8, bytes: &[u8]) -> &[u8] {
124 match version {
125 0 => bytes[65..].into(),
126 _ => unreachable!(),
127 }
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
133struct TokenId {
134 timestamp: Timestamp,
135 public_key: PublicKey,
136}
137
138impl Ord for TokenId {
139 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
140 self.timestamp
141 .cmp(&other.timestamp)
142 .then_with(|| self.public_key.as_bytes().cmp(other.public_key.as_bytes()))
143 }
144}
145
146impl PartialOrd for TokenId {
147 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
148 Some(self.cmp(other))
149 }
150}
151
152#[derive(Debug, Clone, Default)]
157struct ReplayGuard {
158 seen: Vec<TokenId>,
159}
160
161impl ReplayGuard {
162 fn check_and_track(&mut self, id: TokenId) -> Result<(), Error> {
164 match self.seen.binary_search(&id) {
165 Ok(_) => Err(Error::AlreadyUsed),
166 Err(index) => {
167 self.seen.insert(index, id);
168 Ok(())
169 }
170 }
171 }
172
173 fn gc(&mut self) {
176 let cutoff = Timestamp::now() - 2 * TIMESTAMP_WINDOW as u64;
177
178 let expired_count = self.seen.partition_point(|id| id.timestamp < cutoff);
179
180 self.seen.drain(..expired_count);
181 }
182}
183
184#[derive(Debug, Clone, Default)]
185pub struct AuthVerifier {
187 replay_guard: Arc<Mutex<ReplayGuard>>,
188}
189
190impl AuthVerifier {
191 pub fn verify(&self, bytes: &[u8]) -> Result<AuthToken, Error> {
194 let token = AuthToken::verify(bytes)?;
195
196 let id = TokenId {
197 timestamp: token.timestamp,
198 public_key: token.public_key.clone(),
199 };
200
201 let mut guard = self.replay_guard.lock().unwrap();
202 guard.gc();
203 guard.check_and_track(id)?;
204
205 Ok(token)
206 }
207}
208
209#[derive(thiserror::Error, Debug, PartialEq, Eq)]
210pub enum Error {
212 #[error("Unknown version")]
213 UnknownVersion,
215 #[error("AuthToken has a timestamp that is more than 3 minutes in the future")]
216 TooFarInTheFuture,
218 #[error("AuthToken has a timestamp that is more than 3 minutes in the past")]
219 Expired,
221 #[error("Invalid Signature")]
222 InvalidSignature,
224 #[error(transparent)]
225 Parsing(#[from] postcard::Error),
227 #[error("AuthToken already used")]
228 AlreadyUsed,
230}
231
232#[cfg(test)]
233mod tests {
234 use crate::{
235 auth::TIMESTAMP_WINDOW, capabilities::Capability, crypto::Keypair, timestamp::Timestamp,
236 };
237
238 use super::*;
239
240 #[test]
241 fn sign_verify() {
242 let signer = Keypair::random();
243 let capabilities = vec![Capability::root()];
244
245 let verifier = AuthVerifier::default();
246
247 let token = AuthToken::sign(&signer, capabilities.clone());
248
249 let serialized = &token.serialize();
250
251 verifier.verify(serialized).unwrap();
252
253 assert_eq!(token.capabilities, capabilities.into());
254 }
255
256 #[test]
257 fn expired() {
258 let signer = Keypair::random();
259 let capabilities = Capabilities::from(vec![Capability::root()]);
260
261 let verifier = AuthVerifier::default();
262
263 let timestamp = (Timestamp::now()) - (TIMESTAMP_WINDOW as u64);
264
265 let mut signable = vec![];
266 signable.extend_from_slice(signer.public_key().as_bytes());
267 signable.extend_from_slice(&postcard::to_allocvec(&capabilities).unwrap());
268
269 let signature = signer.sign(&signable);
270
271 let token = AuthToken {
272 signature,
273 namespace: *PUBKY_AUTH,
274 version: 0,
275 timestamp,
276 public_key: signer.public_key(),
277 capabilities,
278 };
279
280 let serialized = token.serialize();
281
282 let result = verifier.verify(&serialized);
283
284 assert_eq!(result, Err(Error::Expired));
285 }
286
287 #[test]
288 fn already_used() {
289 let signer = Keypair::random();
290 let capabilities = vec![Capability::root()];
291
292 let verifier = AuthVerifier::default();
293
294 let token = AuthToken::sign(&signer, capabilities.clone());
295
296 let serialized = &token.serialize();
297
298 verifier.verify(serialized).unwrap();
299
300 assert_eq!(token.capabilities, capabilities.into());
301
302 assert_eq!(verifier.verify(serialized), Err(Error::AlreadyUsed));
303 }
304
305 fn sign_with_timestamp(signer: &Keypair, timestamp: Timestamp) -> AuthToken {
307 let mut token = AuthToken {
308 signature: Signature::from_bytes(&[0; 64]),
309 namespace: *PUBKY_AUTH,
310 version: 0,
311 timestamp,
312 public_key: signer.public_key(),
313 capabilities: Capabilities::from(vec![Capability::root()]),
314 };
315
316 let serialized = token.serialize();
317 token.signature = signer.sign(&serialized[65..]);
318
319 token
320 }
321
322 #[test]
323 fn too_far_in_future() {
324 let signer = Keypair::random();
325 let verifier = AuthVerifier::default();
326
327 let timestamp = Timestamp::now() + (TIMESTAMP_WINDOW as u64 + 5_000_000);
328 let token = sign_with_timestamp(&signer, timestamp);
329
330 assert_eq!(
331 verifier.verify(&token.serialize()),
332 Err(Error::TooFarInTheFuture)
333 );
334 }
335
336 #[test]
337 fn within_window() {
338 let signer = Keypair::random();
339 let verifier = AuthVerifier::default();
340
341 let past_token = sign_with_timestamp(
343 &signer,
344 Timestamp::now() - (TIMESTAMP_WINDOW as u64 - 5_000_000),
345 );
346 verifier.verify(&past_token.serialize()).unwrap();
347
348 let future_token = sign_with_timestamp(
350 &signer,
351 Timestamp::now() + (TIMESTAMP_WINDOW as u64 - 5_000_000),
352 );
353 verifier.verify(&future_token.serialize()).unwrap();
354 }
355
356 #[test]
357 fn replay_guard_gc() {
358 let mut guard = ReplayGuard::default();
359 let signer = Keypair::random();
360 let now = Timestamp::now();
361
362 let old_id = TokenId {
364 timestamp: now - 3 * TIMESTAMP_WINDOW as u64,
365 public_key: signer.public_key(),
366 };
367 guard.check_and_track(old_id).unwrap();
368
369 let recent_id = TokenId {
371 timestamp: now,
372 public_key: signer.public_key(),
373 };
374 guard.check_and_track(recent_id.clone()).unwrap();
375
376 assert_eq!(guard.seen.len(), 2);
377
378 guard.gc();
380
381 assert_eq!(guard.seen.len(), 1);
382 assert_eq!(guard.seen[0], recent_id);
383 }
384
385 #[test]
386 fn unknown_version() {
387 let signer = Keypair::random();
388 let token = AuthToken {
389 signature: Signature::from_bytes(&[0; 64]),
390 namespace: *PUBKY_AUTH,
391 version: 1,
392 timestamp: Timestamp::now(),
393 public_key: signer.public_key(),
394 capabilities: Capabilities::from(vec![Capability::root()]),
395 };
396 let serialized = token.serialize();
397
398 assert_eq!(AuthToken::verify(&serialized), Err(Error::UnknownVersion));
399 }
400}