1use serde::{Deserialize, Serialize};
4
5use crate::{
6 capabilities::Capabilities,
7 crypto::{Keypair, PublicKey, Signature},
8 namespaces::PUBKY_AUTH,
9 timestamp::Timestamp,
10};
11
12const CURRENT_VERSION: u8 = 0;
13const TIMESTAMP_WINDOW: i64 = 180 * 1_000_000;
15
16mod signature_serde {
17 use core::fmt;
18
19 use serde::{
20 de::{self, SeqAccess, Visitor},
21 ser::SerializeTuple,
22 Deserializer, Serializer,
23 };
24
25 use crate::crypto::Signature;
26
27 pub fn serialize<S: Serializer>(
28 signature: &Signature,
29 serializer: S,
30 ) -> Result<S::Ok, S::Error> {
31 let mut tuple = serializer.serialize_tuple(Signature::BYTE_SIZE)?;
32
33 for byte in signature.to_bytes() {
34 tuple.serialize_element(&byte)?;
35 }
36
37 tuple.end()
38 }
39
40 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Signature, D::Error> {
41 struct SignatureVisitor;
42
43 impl<'de> Visitor<'de> for SignatureVisitor {
44 type Value = Signature;
45
46 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47 formatter.write_str("a 64-byte Ed25519 signature")
48 }
49
50 fn visit_seq<A: SeqAccess<'de>>(
51 self,
52 mut sequence: A,
53 ) -> Result<Self::Value, A::Error> {
54 let mut bytes = [0; Signature::BYTE_SIZE];
55
56 for (index, byte) in bytes.iter_mut().enumerate() {
57 *byte = sequence
58 .next_element()?
59 .ok_or_else(|| de::Error::invalid_length(index, &self))?;
60 }
61
62 Ok(Signature::from_bytes(&bytes))
63 }
64 }
65
66 deserializer.deserialize_tuple(Signature::BYTE_SIZE, SignatureVisitor)
67 }
68}
69
70#[derive(Debug, PartialEq, Serialize, Deserialize)]
71pub struct AuthToken {
73 #[serde(with = "signature_serde")]
75 signature: Signature,
76 namespace: [u8; 10],
79 version: u8,
86 timestamp: Timestamp,
88 public_key: PublicKey,
90 capabilities: Capabilities,
92}
93
94impl AuthToken {
95 pub fn sign(keypair: &Keypair, capabilities: impl Into<Capabilities>) -> Self {
97 let timestamp = Timestamp::now();
98
99 let mut token = Self {
100 signature: Signature::from_bytes(&[0; 64]),
101 namespace: *PUBKY_AUTH,
102 version: 0,
103 timestamp,
104 public_key: keypair.public_key(),
105 capabilities: capabilities.into(),
106 };
107
108 let serialized = token.serialize();
109
110 token.signature = keypair.sign(&serialized[65..]);
111
112 token
113 }
114
115 pub fn public_key(&self) -> &PublicKey {
119 &self.public_key
120 }
121
122 pub fn capabilities(&self) -> &Capabilities {
124 &self.capabilities
125 }
126
127 pub fn timestamp(&self) -> Timestamp {
129 self.timestamp
130 }
131
132 pub fn verify(bytes: &[u8]) -> Result<Self, Error> {
136 if bytes[74] > CURRENT_VERSION {
137 return Err(Error::UnknownVersion);
138 }
139
140 let token = AuthToken::deserialize(bytes)?;
141
142 match token.version {
143 0 => {
144 let now = Timestamp::now();
145
146 let diff = token.timestamp.as_u64() as i64 - now.as_u64() as i64;
148 if diff > TIMESTAMP_WINDOW {
149 return Err(Error::TooFarInTheFuture);
150 }
151 if diff < -TIMESTAMP_WINDOW {
152 return Err(Error::Expired);
153 }
154
155 token
156 .public_key
157 .verify(AuthToken::signable(token.version, bytes), &token.signature)
158 .map_err(|_| Error::InvalidSignature)?;
159
160 Ok(token)
161 }
162 _ => unreachable!(),
163 }
164 }
165
166 pub fn serialize(&self) -> Vec<u8> {
168 postcard::to_allocvec(self).unwrap()
169 }
170
171 pub fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
173 Ok(postcard::from_bytes(bytes)?)
174 }
175
176 fn signable(version: u8, bytes: &[u8]) -> &[u8] {
177 match version {
178 0 => bytes[65..].into(),
179 _ => unreachable!(),
180 }
181 }
182}
183
184#[derive(thiserror::Error, Debug, PartialEq, Eq)]
185pub enum Error {
187 #[error("Unknown version")]
188 UnknownVersion,
190 #[error("AuthToken has a timestamp that is more than 3 minutes in the future")]
191 TooFarInTheFuture,
193 #[error("AuthToken has a timestamp that is more than 3 minutes in the past")]
194 Expired,
196 #[error("Invalid Signature")]
197 InvalidSignature,
199 #[error(transparent)]
200 Parsing(#[from] postcard::Error),
202 #[error("AuthToken already used")]
203 AlreadyUsed,
205}
206
207#[cfg(test)]
208mod tests {
209 use crate::{
210 auth::auth_token::TIMESTAMP_WINDOW, capabilities::Capability, crypto::Keypair,
211 timestamp::Timestamp,
212 };
213
214 use super::*;
215
216 #[test]
217 fn sign_verify() {
218 let signer = Keypair::random();
219 let capabilities = vec![Capability::root()];
220
221 let token = AuthToken::sign(&signer, capabilities.clone());
222
223 let serialized = &token.serialize();
224 assert_eq!(serialized[..64], token.signature.to_bytes());
225 assert_eq!(&serialized[64..74], PUBKY_AUTH);
226 assert_eq!(serialized[74], CURRENT_VERSION);
227
228 let verified = AuthToken::verify(serialized).unwrap();
229
230 assert_eq!(verified.capabilities, capabilities.into());
231 }
232
233 #[test]
234 fn expired() {
235 let signer = Keypair::random();
236 let timestamp = (Timestamp::now()) - (TIMESTAMP_WINDOW as u64);
237 let token = sign_with_timestamp(&signer, timestamp);
238
239 let result = AuthToken::verify(&token.serialize());
240
241 assert_eq!(result, Err(Error::Expired));
242 }
243
244 fn sign_with_timestamp(signer: &Keypair, timestamp: Timestamp) -> AuthToken {
246 let mut token = AuthToken {
247 signature: Signature::from_bytes(&[0; 64]),
248 namespace: *PUBKY_AUTH,
249 version: 0,
250 timestamp,
251 public_key: signer.public_key(),
252 capabilities: Capabilities::from(vec![Capability::root()]),
253 };
254
255 let serialized = token.serialize();
256 token.signature = signer.sign(&serialized[65..]);
257
258 token
259 }
260
261 #[test]
262 fn too_far_in_future() {
263 let signer = Keypair::random();
264
265 let timestamp = Timestamp::now() + (TIMESTAMP_WINDOW as u64 + 5_000_000);
266 let token = sign_with_timestamp(&signer, timestamp);
267
268 assert_eq!(
269 AuthToken::verify(&token.serialize()),
270 Err(Error::TooFarInTheFuture)
271 );
272 }
273
274 #[test]
275 fn within_window() {
276 let signer = Keypair::random();
277
278 let past_token = sign_with_timestamp(
280 &signer,
281 Timestamp::now() - (TIMESTAMP_WINDOW as u64 - 5_000_000),
282 );
283 AuthToken::verify(&past_token.serialize()).unwrap();
284
285 let future_token = sign_with_timestamp(
287 &signer,
288 Timestamp::now() + (TIMESTAMP_WINDOW as u64 - 5_000_000),
289 );
290 AuthToken::verify(&future_token.serialize()).unwrap();
291 }
292
293 #[test]
294 fn unknown_version() {
295 let signer = Keypair::random();
296 let token = AuthToken {
297 signature: Signature::from_bytes(&[0; 64]),
298 namespace: *PUBKY_AUTH,
299 version: 1,
300 timestamp: Timestamp::now(),
301 public_key: signer.public_key(),
302 capabilities: Capabilities::from(vec![Capability::root()]),
303 };
304 let serialized = token.serialize();
305
306 assert_eq!(AuthToken::verify(&serialized), Err(Error::UnknownVersion));
307 }
308}