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;
mod signature_serde {
use core::fmt;
use serde::{
de::{self, SeqAccess, Visitor},
ser::SerializeTuple,
Deserializer, Serializer,
};
use crate::crypto::Signature;
pub fn serialize<S: Serializer>(
signature: &Signature,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut tuple = serializer.serialize_tuple(Signature::BYTE_SIZE)?;
for byte in signature.to_bytes() {
tuple.serialize_element(&byte)?;
}
tuple.end()
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Signature, D::Error> {
struct SignatureVisitor;
impl<'de> Visitor<'de> for SignatureVisitor {
type Value = Signature;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a 64-byte Ed25519 signature")
}
fn visit_seq<A: SeqAccess<'de>>(
self,
mut sequence: A,
) -> Result<Self::Value, A::Error> {
let mut bytes = [0; Signature::BYTE_SIZE];
for (index, byte) in bytes.iter_mut().enumerate() {
*byte = sequence
.next_element()?
.ok_or_else(|| de::Error::invalid_length(index, &self))?;
}
Ok(Signature::from_bytes(&bytes))
}
}
deserializer.deserialize_tuple(Signature::BYTE_SIZE, SignatureVisitor)
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct AuthToken {
#[serde(with = "signature_serde")]
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();
assert_eq!(serialized[..64], token.signature.to_bytes());
assert_eq!(&serialized[64..74], PUBKY_AUTH);
assert_eq!(serialized[74], CURRENT_VERSION);
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));
}
}