jwtea 0.1.0

Lean JWT library
Documentation
//! Lean JWT library for asymmetric signature schemes, using `aws-lc-rs`.
//!
//! **Note**: this currently only contains APIs for "verifying JWT", not for
//! generating JWTs.
//!
//! `jwtea` is a toolbox, not a framework. It provides functionality that are
//! always needed when dealing with JWTs, but you might still have to plug
//! things together. The API was still designed in a way to make it hard to
//! introduce security problems in your application.
//!
//! This library is also mindful of performance where it matters: creating and
//! validating JWTs. It does not deserialize unused header or payload fields,
//! and tries to avoid memory allocations as much as possible.
//!
//! This library is not for everyone: it does not and will not implement
//! symmetric signature schemes or JWEs, for example.

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!(
    /// Values for the `alg` field of a JWT or JWK.
    #[derive(Debug, Deserialize, PartialEq, Eq)]
    pub enum Alg<'a> {
        HS256,
        HS384,
        HS512,
        RS256,
        RS384,
        RS512,
        PS256,
        PS384,
        PS512,
        ES256,
        ES384,
        ES512,
        ES256K,
        EdDSA,
    }
);


/// A JWT header.
///
/// Only few fields are "built-in". Additional fields that you are interested in
/// must be added via `extra_fields`. The most commonly used fields are `alg`,
/// `typ` and `kid`, which are used by most applications. All other fields are
/// way less common and rarely used. That's why `jwtea` does not include them
/// by default.
///
/// See RFC 7515, section 4.
#[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> {
    /// Deserializes from a string containing JSON.
    pub fn from_str(s: &'a str) -> Result<Self, Error>
    where
        E: Deserialize<'a>,
    {
        serde_json::from_str(s).map_err(Error::InvalidJson)
    }
}

/// A JWT payload.
///
/// JWT payloads are basically arbitrary JSON. This type only contains a few
/// fields/claims that are used extremely often and do not require memory
/// allocation. Additional fields, which you most certainly need, must be added
/// via `extra_fields`/`E`.
#[derive(Debug, Deserialize)]
pub struct Payload<E = ()> {
    /// Expiration time. See RFC 7519, section 4.1.4.
    pub exp: Option<NumericDate>,
    /// "Not before". See RFC 7519, section 4.1.5.
    pub nbf: Option<NumericDate>,
    /// "Issued at". See RFC 7519, section 4.1.6.
    pub iat: Option<NumericDate>,

    #[serde(flatten)]
    pub extra_fields: E,
}

impl<E> Payload<E> {
    /// Deserializes from a string containing JSON.
    pub fn from_str<'de>(s: &'de str) -> Result<Self, Error>
    where
        E: Deserialize<'de>,
    {
        serde_json::from_str(s).map_err(Error::InvalidJson)
    }
}

/// A JWT `NumericDate` (see RFC 7519), e.g. used for `exp` and `nbf`.
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct NumericDate(
    /// Number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC
    /// date/time, ignoring leap seconds.
    pub u64
);

impl NumericDate {
    /// Returns the `NumericDate` representing now. Panics if system time is
    /// before the Unix epoch.
    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)
    }
}


/// Validator for JWT claims.
///
/// A validator's job is to check important claims to reject JWTs based on
/// some rules, e.g. expired JWTs. See [`BasicValidator`] for an example that
/// should  work for many cases.
pub trait Validator<H, P> {
    /// Inspect the header and payload to perform arbitrary checks.
    ///
    /// When the function is called (e.g. by [`decode`]), the signature check
    /// was already performed and thus header and payload can be trusted, i.e.
    /// they were crafted by the owner of the private key used for signing the
    /// JWT.
    fn validate(&self, header: &Header<H>, payload: &Payload<P>) -> Result<(), Error>;
}

/// A [`Validator`] that checks `exp` and `nbf` claims.
///
/// The `exp` claim is required, as JWTs without it are valid forever and should
/// be avoided. `exp` and `nbf` are compared with the current time (with a
/// configurable `allowed_clock_skew`, defaulting to 3s).
pub struct BasicValidator {
    /// How much leeway to allow when doing time based comparisons (e.g. with
    /// `exp` or `nbf`). In seconds.
    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(())
    }
}

/// A zero-sized type as a token to represent a valid signature check.
pub struct SignatureValid(());

impl SignatureValid {
    /// Returns a proof token. **Warning**: dangerous!
    ///
    /// Only use this function when you have actually performed a cryptographic
    /// signature check. Usually you don't need this function as
    /// `SignatureValid` is returned by `VerifyingKey::verify`, for example.
    pub fn unchecked_create_proof() -> Self {
        Self(())
    }
}

/// Something to verify a JWT's signature with.
///
/// This is implemented for `VerifyingKey` and `&[VerifyingKey]`. But it can be
/// implemented for custom types to implement custom logic, including `kid`
/// lookup and on-the-fly key fetching/refreshing.
///
/// **Warning**: incorrectly implementing this trait can lead to serious
/// security vulnerabilities! The API design, and in particular `SignatureValid`
/// should protect a bit.
pub trait SignatureVerifier<H> {
    /// Verifies the given signature.
    ///
    /// This function has access to the JWT's header, but it should take care
    /// to not fall for algorithm-downgrading attacks or similar bugs. Remember
    /// that the header cannot be trusted at this point and could be crafted by
    /// an attacker.
    // TODO: reconsider this lint. For now its fine.
    #[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 })
}


/// Decodes the given JWT, verifies its signature, validates its claims, and
/// finally giving you access to its payload.
///
/// If all checks pass, `callback` is called with the header and payload, and
/// its return value is returned by this function. The `signature_verifier` can
/// be a single [`VerifyingKey`], but see [`SignatureVerifier`] for more
/// information.
///
/// This function is a bit involved and doesn't work as one would intuitively
/// think for different reasons:
/// - There is a callback instead of returning the payload directly, as this can
///   be faster: the deserialized JSON can then borrow from function-local
///   buffers (the decoded base64).
/// - The API is designed in a way to be hard to misuse and introduce security
///   bugs. You only get access to the payload once everything about the JWT
///   is verified.
///
/// Also see [`RawJwt::decode`] if you already have a [`RawJwt`].
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;