mod err;
pub mod jwk;
mod key;
mod raw;
use std::{borrow::Cow, fmt, time::SystemTime};
use serde::Deserialize;
pub use self::{
err::Error,
key::VerifyingKey,
jwk::{Jwks, Jwk},
raw::RawJwt,
};
make_string_enum!(
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub enum Alg<'a> {
HS256,
HS384,
HS512,
RS256,
RS384,
RS512,
PS256,
PS384,
PS512,
ES256,
ES384,
ES512,
ES256K,
EdDSA,
}
);
#[derive(Debug, Deserialize)]
pub struct Header<'a, E = ()> {
pub typ: Option<Cow<'a, str>>,
pub alg: Alg<'a>,
pub kid: Option<Cow<'a, str>>,
#[serde(flatten)]
pub extra_fields: E,
}
impl<'a, E> Header<'a, E> {
pub fn from_str(s: &'a str) -> Result<Self, Error>
where
E: Deserialize<'a>,
{
serde_json::from_str(s).map_err(Error::InvalidJson)
}
}
#[derive(Debug, Deserialize)]
pub struct Payload<E = ()> {
pub exp: Option<NumericDate>,
pub nbf: Option<NumericDate>,
pub iat: Option<NumericDate>,
#[serde(flatten)]
pub extra_fields: E,
}
impl<E> Payload<E> {
pub fn from_str<'de>(s: &'de str) -> Result<Self, Error>
where
E: Deserialize<'de>,
{
serde_json::from_str(s).map_err(Error::InvalidJson)
}
}
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct NumericDate(
pub u64
);
impl NumericDate {
pub fn now() -> Self {
let secs = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("system time is before unix epoch, interesting")
.as_secs();
Self(secs)
}
}
pub trait Validator<H, P> {
fn validate(&self, header: &Header<H>, payload: &Payload<P>) -> Result<(), Error>;
}
pub struct BasicValidator {
pub allowed_clock_skew: u32,
}
impl Default for BasicValidator {
fn default() -> Self {
Self { allowed_clock_skew: 3 }
}
}
impl<H, P> Validator<H, P> for BasicValidator {
fn validate(&self, _header: &Header<H>, payload: &Payload<P>) -> Result<(), Error> {
let now = NumericDate::now();
let allowed_clock_skew = u64::from(self.allowed_clock_skew);
let Some(exp) = payload.exp else {
return Err(Error::ExpMissing);
};
if exp.0 + allowed_clock_skew < now.0 {
return Err(Error::Expired);
}
if payload.nbf.is_some_and(|nbf| nbf.0 > now.0 + allowed_clock_skew) {
return Err(Error::NotValidYet);
}
Ok(())
}
}
pub struct SignatureValid(());
impl SignatureValid {
pub fn unchecked_create_proof() -> Self {
Self(())
}
}
pub trait SignatureVerifier<H> {
#[allow(async_fn_in_trait)]
async fn verify(
&self,
header: &Header<'_, H>,
message: &str,
signature: &[u8],
) -> Result<SignatureValid, Error>;
}
impl<H> SignatureVerifier<H> for VerifyingKey {
async fn verify(
&self,
header: &Header<'_, H>,
message: &str,
signature: &[u8],
) -> Result<SignatureValid, Error> {
self.verify(header, message, signature)
}
}
macro_rules! impl_signature_verifier_for_collection {
($ty:ty) => {
impl<H> SignatureVerifier<H> for $ty {
async fn verify(
&self,
header: &Header<'_, H>,
message: &str,
signature: &[u8],
) -> Result<SignatureValid, Error> {
verify_with_key_iter(self, header, message, signature)
}
}
};
}
impl_signature_verifier_for_collection!([VerifyingKey]);
impl_signature_verifier_for_collection!(Vec<VerifyingKey>);
fn verify_with_key_iter<'i, H, I: IntoIterator<Item = &'i VerifyingKey>>(
keys: I,
header: &Header<'_, H>,
message: &str,
signature: &[u8],
) -> Result<SignatureValid, Error> {
let mut suitable_key_found = false;
for key in keys {
suitable_key_found |= key.supports_alg(&header.alg);
if let Ok(proof) = key.verify(header, message, signature) {
return Ok(proof);
}
}
Err(if suitable_key_found { Error::InvalidSignature } else { Error::NoSuitableKey })
}
pub async fn decode<H, P, R>(
jwt: &str,
signature_verifier: &(impl ?Sized + SignatureVerifier<H>),
validator: &impl Validator<H, P>,
callback: impl FnOnce(Header<H>, Payload<P>) -> R,
) -> Result<R, Error>
where
H: for<'de> Deserialize<'de>,
P: for<'de> Deserialize<'de>,
{
let raw = RawJwt::new(jwt)?;
raw.decode(signature_verifier, validator, callback).await
}
macro_rules! make_string_enum {
(
$(#[$($attr:tt)*])*
pub enum $name:ident<'a> {
$(
$variant:ident $( = $val:literal)?,
)*
}
) => {
$(
#[$($attr)*]
)*
pub enum $name<'a> {
$(
$(
#[doc = concat!("`\"", $val, "\"`")]
#[serde(rename = $val)]
)?
$variant,
)*
#[serde(untagged)] Other(Cow<'a, str>),
}
impl<'a> $name<'a> {
pub fn from_str(s: &'a str) -> Self {
match s {
$(
$crate::make_string_enum!(@s $variant $($val)?) => Self::$variant,
)*
other => Self::Other(other.into()),
}
}
pub fn as_str(&self) -> &str {
match self {
$(
Self::$variant => $crate::make_string_enum!(@s $variant $($val)?),
)*
Self::Other(cow) => cow,
}
}
}
impl fmt::Display for $name<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
};
(@s $variant:ident ) => { stringify!($variant) };
(@s $variant:ident $val:literal) => { $val };
}
pub(crate) use make_string_enum;