use serde::{Deserialize, Serialize};
use crate::{
capabilities::Capabilities,
crypto::{Keypair, PublicKey, Signature},
namespaces::PUBKY_AUTH,
timestamp::Timestamp,
};
const CURRENT_VERSION: u8 = 0;
const TIMESTAMP_WINDOW: i64 = 180 * 1_000_000;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct AuthToken {
signature: Signature,
namespace: [u8; 10],
version: u8,
timestamp: Timestamp,
public_key: PublicKey,
capabilities: Capabilities,
}
impl AuthToken {
pub fn sign(keypair: &Keypair, capabilities: impl Into<Capabilities>) -> Self {
let timestamp = Timestamp::now();
let mut token = Self {
signature: Signature::from_bytes(&[0; 64]),
namespace: *PUBKY_AUTH,
version: 0,
timestamp,
public_key: keypair.public_key(),
capabilities: capabilities.into(),
};
let serialized = token.serialize();
token.signature = keypair.sign(&serialized[65..]);
token
}
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
pub fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
pub fn timestamp(&self) -> Timestamp {
self.timestamp
}
pub fn verify(bytes: &[u8]) -> Result<Self, Error> {
if bytes[74] > CURRENT_VERSION {
return Err(Error::UnknownVersion);
}
let token = AuthToken::deserialize(bytes)?;
match token.version {
0 => {
let now = Timestamp::now();
let diff = token.timestamp.as_u64() as i64 - now.as_u64() as i64;
if diff > TIMESTAMP_WINDOW {
return Err(Error::TooFarInTheFuture);
}
if diff < -TIMESTAMP_WINDOW {
return Err(Error::Expired);
}
token
.public_key
.verify(AuthToken::signable(token.version, bytes), &token.signature)
.map_err(|_| Error::InvalidSignature)?;
Ok(token)
}
_ => unreachable!(),
}
}
pub fn serialize(&self) -> Vec<u8> {
postcard::to_allocvec(self).unwrap()
}
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
Ok(postcard::from_bytes(bytes)?)
}
fn signable(version: u8, bytes: &[u8]) -> &[u8] {
match version {
0 => bytes[65..].into(),
_ => unreachable!(),
}
}
}
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum Error {
#[error("Unknown version")]
UnknownVersion,
#[error("AuthToken has a timestamp that is more than 3 minutes in the future")]
TooFarInTheFuture,
#[error("AuthToken has a timestamp that is more than 3 minutes in the past")]
Expired,
#[error("Invalid Signature")]
InvalidSignature,
#[error(transparent)]
Parsing(#[from] postcard::Error),
#[error("AuthToken already used")]
AlreadyUsed,
}
#[cfg(test)]
mod tests {
use crate::{
auth::auth_token::TIMESTAMP_WINDOW, capabilities::Capability, crypto::Keypair,
timestamp::Timestamp,
};
use super::*;
#[test]
fn sign_verify() {
let signer = Keypair::random();
let capabilities = vec![Capability::root()];
let token = AuthToken::sign(&signer, capabilities.clone());
let serialized = &token.serialize();
let verified = AuthToken::verify(serialized).unwrap();
assert_eq!(verified.capabilities, capabilities.into());
}
#[test]
fn expired() {
let signer = Keypair::random();
let timestamp = (Timestamp::now()) - (TIMESTAMP_WINDOW as u64);
let token = sign_with_timestamp(&signer, timestamp);
let result = AuthToken::verify(&token.serialize());
assert_eq!(result, Err(Error::Expired));
}
fn sign_with_timestamp(signer: &Keypair, timestamp: Timestamp) -> AuthToken {
let mut token = AuthToken {
signature: Signature::from_bytes(&[0; 64]),
namespace: *PUBKY_AUTH,
version: 0,
timestamp,
public_key: signer.public_key(),
capabilities: Capabilities::from(vec![Capability::root()]),
};
let serialized = token.serialize();
token.signature = signer.sign(&serialized[65..]);
token
}
#[test]
fn too_far_in_future() {
let signer = Keypair::random();
let timestamp = Timestamp::now() + (TIMESTAMP_WINDOW as u64 + 5_000_000);
let token = sign_with_timestamp(&signer, timestamp);
assert_eq!(
AuthToken::verify(&token.serialize()),
Err(Error::TooFarInTheFuture)
);
}
#[test]
fn within_window() {
let signer = Keypair::random();
let past_token = sign_with_timestamp(
&signer,
Timestamp::now() - (TIMESTAMP_WINDOW as u64 - 5_000_000),
);
AuthToken::verify(&past_token.serialize()).unwrap();
let future_token = sign_with_timestamp(
&signer,
Timestamp::now() + (TIMESTAMP_WINDOW as u64 - 5_000_000),
);
AuthToken::verify(&future_token.serialize()).unwrap();
}
#[test]
fn unknown_version() {
let signer = Keypair::random();
let token = AuthToken {
signature: Signature::from_bytes(&[0; 64]),
namespace: *PUBKY_AUTH,
version: 1,
timestamp: Timestamp::now(),
public_key: signer.public_key(),
capabilities: Capabilities::from(vec![Capability::root()]),
};
let serialized = token.serialize();
assert_eq!(AuthToken::verify(&serialized), Err(Error::UnknownVersion));
}
}