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 TIME_INTERVAL: u64 = 30 * 1_000_000;
16
17const CURRENT_VERSION: u8 = 0;
18const TIMESTAMP_WINDOW: i64 = 45 * 1_000_000;
20
21#[derive(Debug, PartialEq, Serialize, Deserialize)]
22pub struct AuthToken {
24 signature: Signature,
26 namespace: [u8; 10],
29 version: u8,
36 timestamp: Timestamp,
38 public_key: PublicKey,
40 capabilities: Capabilities,
42}
43
44impl AuthToken {
45 pub fn sign(keypair: &Keypair, capabilities: impl Into<Capabilities>) -> Self {
47 let timestamp = Timestamp::now();
48
49 let mut token = Self {
50 signature: Signature::from_bytes(&[0; 64]),
51 namespace: *PUBKY_AUTH,
52 version: 0,
53 timestamp,
54 public_key: keypair.public_key(),
55 capabilities: capabilities.into(),
56 };
57
58 let serialized = token.serialize();
59
60 token.signature = keypair.sign(&serialized[65..]);
61
62 token
63 }
64
65 pub fn public_key(&self) -> &PublicKey {
69 &self.public_key
70 }
71
72 pub fn capabilities(&self) -> &Capabilities {
74 &self.capabilities
75 }
76
77 pub fn verify(bytes: &[u8]) -> Result<Self, Error> {
81 if bytes[75] > CURRENT_VERSION {
82 return Err(Error::UnknownVersion);
83 }
84
85 let token = AuthToken::deserialize(bytes)?;
86
87 match token.version {
88 0 => {
89 let now = Timestamp::now();
90
91 let diff = token.timestamp.as_u64() as i64 - now.as_u64() as i64;
93 if diff > TIMESTAMP_WINDOW {
94 return Err(Error::TooFarInTheFuture);
95 }
96 if diff < -TIMESTAMP_WINDOW {
97 return Err(Error::Expired);
98 }
99
100 token
101 .public_key
102 .verify(AuthToken::signable(token.version, bytes), &token.signature)
103 .map_err(|_| Error::InvalidSignature)?;
104
105 Ok(token)
106 }
107 _ => unreachable!(),
108 }
109 }
110
111 pub fn serialize(&self) -> Vec<u8> {
113 postcard::to_allocvec(self).unwrap()
114 }
115
116 pub fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
118 Ok(postcard::from_bytes(bytes)?)
119 }
120
121 fn id(version: u8, bytes: &[u8]) -> Box<[u8]> {
126 match version {
127 0 => bytes[75..115].into(),
128 _ => unreachable!(),
129 }
130 }
131
132 fn signable(version: u8, bytes: &[u8]) -> &[u8] {
133 match version {
134 0 => bytes[65..].into(),
135 _ => unreachable!(),
136 }
137 }
138}
139
140#[derive(Debug, Clone, Default)]
141pub struct AuthVerifier {
143 seen: Arc<Mutex<Vec<Box<[u8]>>>>,
144}
145
146impl AuthVerifier {
147 pub fn verify(&self, bytes: &[u8]) -> Result<AuthToken, Error> {
150 self.gc();
151
152 let token = AuthToken::verify(bytes)?;
153
154 let mut seen = self.seen.lock().unwrap();
155
156 let id = AuthToken::id(token.version, bytes);
157
158 match seen.binary_search_by(|element| element.cmp(&id)) {
159 Ok(_) => Err(Error::AlreadyUsed),
160 Err(index) => {
161 seen.insert(index, id);
162 Ok(token)
163 }
164 }
165 }
166
167 fn gc(&self) {
171 let threshold = ((Timestamp::now().as_u64() / TIME_INTERVAL) - 2).to_be_bytes();
172
173 let mut inner = self.seen.lock().unwrap();
174
175 match inner.binary_search_by(|element| element[0..8].cmp(&threshold)) {
176 Ok(index) | Err(index) => {
177 inner.drain(0..index);
178 }
179 }
180 }
181}
182
183#[derive(thiserror::Error, Debug, PartialEq, Eq)]
184pub enum Error {
186 #[error("Unknown version")]
187 UnknownVersion,
189 #[error("AuthToken has a timestamp that is more than 45 seconds in the future")]
190 TooFarInTheFuture,
192 #[error("AuthToken has a timestamp that is more than 45 seconds in the past")]
193 Expired,
195 #[error("Invalid Signature")]
196 InvalidSignature,
198 #[error(transparent)]
199 Parsing(#[from] postcard::Error),
201 #[error("AuthToken already used")]
202 AlreadyUsed,
204}
205
206#[cfg(test)]
207mod tests {
208 use crate::{
209 auth::TIMESTAMP_WINDOW, capabilities::Capability, crypto::Keypair, timestamp::Timestamp,
210 };
211
212 use super::*;
213
214 #[test]
215 fn v0_id_signable() {
216 let signer = Keypair::random();
217 let capabilities = vec![Capability::root()];
218
219 let token = AuthToken::sign(&signer, capabilities.clone());
220
221 let serialized = &token.serialize();
222
223 let mut id = vec![];
224 id.extend_from_slice(&token.timestamp.to_bytes());
225 id.extend_from_slice(signer.public_key().as_bytes());
226
227 assert_eq!(AuthToken::id(token.version, serialized), id.into());
228
229 assert_eq!(
230 AuthToken::signable(token.version, serialized),
231 &serialized[65..]
232 )
233 }
234
235 #[test]
236 fn sign_verify() {
237 let signer = Keypair::random();
238 let capabilities = vec![Capability::root()];
239
240 let verifier = AuthVerifier::default();
241
242 let token = AuthToken::sign(&signer, capabilities.clone());
243
244 let serialized = &token.serialize();
245
246 verifier.verify(serialized).unwrap();
247
248 assert_eq!(token.capabilities, capabilities.into());
249 }
250
251 #[test]
252 fn expired() {
253 let signer = Keypair::random();
254 let capabilities = Capabilities(vec![Capability::root()]);
255
256 let verifier = AuthVerifier::default();
257
258 let timestamp = (Timestamp::now()) - (TIMESTAMP_WINDOW as u64);
259
260 let mut signable = vec![];
261 signable.extend_from_slice(signer.public_key().as_bytes());
262 signable.extend_from_slice(&postcard::to_allocvec(&capabilities).unwrap());
263
264 let signature = signer.sign(&signable);
265
266 let token = AuthToken {
267 signature,
268 namespace: *PUBKY_AUTH,
269 version: 0,
270 timestamp,
271 public_key: signer.public_key(),
272 capabilities,
273 };
274
275 let serialized = token.serialize();
276
277 let result = verifier.verify(&serialized);
278
279 assert_eq!(result, Err(Error::Expired));
280 }
281
282 #[test]
283 fn already_used() {
284 let signer = Keypair::random();
285 let capabilities = vec![Capability::root()];
286
287 let verifier = AuthVerifier::default();
288
289 let token = AuthToken::sign(&signer, capabilities.clone());
290
291 let serialized = &token.serialize();
292
293 verifier.verify(serialized).unwrap();
294
295 assert_eq!(token.capabilities, capabilities.into());
296
297 assert_eq!(verifier.verify(serialized), Err(Error::AlreadyUsed));
298 }
299}