ts-token 0.8.0

JSON web token library for my projects
Documentation
//! A verifier for a token.

use core::time::Duration;

use base64ct::{Base64UrlUnpadded, Encoding};
use ts_crypto::{
    EdwardsVerifyingKey, EllipticVerifyingKey, RsaVerifyingKey, Sha256, Sha384, Sha512,
    VerifyingKey,
    rsa::{Pkcs1v15, Pss},
};

use crate::{
    JsonWebKey, JsonWebToken,
    jwt::{Claims, Header},
};

/// A verifier for a token.
pub struct TokenVerifier {
    /// The JSON web key for this verifier.
    pub jwk: JsonWebKey,
    /// The verifying key.
    pub key: VerifyingKey,
}

impl TokenVerifier {
    /// Create a new token verifier from a JSON web key.
    pub fn new(jwk: JsonWebKey) -> Option<Self> {
        let key = match &jwk.kty {
            crate::KeyType::Ec { x, y } => {
                let x = Base64UrlUnpadded::decode_vec(x).ok()?;
                let y = Base64UrlUnpadded::decode_vec(y).ok()?;
                let key = EllipticVerifyingKey::from_coordinates(&x, &y)?;
                VerifyingKey::Elliptic(key)
            }
            crate::KeyType::Rsa { n, e } => {
                let n = Base64UrlUnpadded::decode_vec(n).ok()?;
                let e = Base64UrlUnpadded::decode_vec(e).ok()?;
                let key = RsaVerifyingKey::from_parameters(&n, &e)?;
                VerifyingKey::Rsa(key)
            }
            crate::KeyType::Okp { x } => {
                let raw_key = Base64UrlUnpadded::decode_vec(x).ok()?;
                let key = EdwardsVerifyingKey::from_raw_key(&raw_key)?;
                VerifyingKey::Edwards(key)
            }
        };
        // TODO verify the algorithm matches the key.

        Some(Self { jwk, key })
    }

    /// Verify a signed JSON web token, returning the verified token.
    pub fn verify(&self, jws: &str) -> Option<JsonWebToken> {
        let mut parts = jws.split('.');
        let header_str = parts.next()?;
        let claims_str = parts.next()?;
        let signature = parts.next()?;

        let header = Base64UrlUnpadded::decode_vec(header_str).ok()?;
        let header: Header = serde_json::from_slice(&header).ok()?;

        let claims = Base64UrlUnpadded::decode_vec(claims_str).ok()?;
        let claims: Claims = serde_json::from_slice(&claims).ok()?;

        let signature = Base64UrlUnpadded::decode_vec(signature).ok()?;

        if header.kid != self.jwk.kid {
            return None;
        }

        let message = [header_str, claims_str].join(".");

        // Verify the signature.
        match self.jwk.alg.as_str() {
            "RS256" => {
                let VerifyingKey::Rsa(key) = &self.key else {
                    return None;
                };
                if !key.verifies::<Pkcs1v15, Sha256>(&signature, message.as_bytes()) {
                    return None;
                }
            }
            "RS384" => {
                let VerifyingKey::Rsa(key) = &self.key else {
                    return None;
                };
                if !key.verifies::<Pkcs1v15, Sha384>(&signature, message.as_bytes()) {
                    return None;
                }
            }
            "RS512" => {
                let VerifyingKey::Rsa(key) = &self.key else {
                    return None;
                };
                if !key.verifies::<Pkcs1v15, Sha512>(&signature, message.as_bytes()) {
                    return None;
                }
            }

            "PS256" => {
                let VerifyingKey::Rsa(key) = &self.key else {
                    return None;
                };
                if !key.verifies::<Pss, Sha256>(&signature, message.as_bytes()) {
                    return None;
                }
            }
            "PS384" => {
                let VerifyingKey::Rsa(key) = &self.key else {
                    return None;
                };
                if !key.verifies::<Pss, Sha384>(&signature, message.as_bytes()) {
                    return None;
                }
            }
            "PS512" => {
                let VerifyingKey::Rsa(key) = &self.key else {
                    return None;
                };
                if !key.verifies::<Pss, Sha512>(&signature, message.as_bytes()) {
                    return None;
                }
            }

            "ES256" => {
                let VerifyingKey::Elliptic(key) = &self.key else {
                    return None;
                };
                if !key.verifies::<Sha256>(&signature, message.as_bytes()) {
                    return None;
                }
            }
            "ES384" => {
                let VerifyingKey::Elliptic(key) = &self.key else {
                    return None;
                };
                if !key.verifies::<Sha384>(&signature, message.as_bytes()) {
                    return None;
                }
            }
            "ES512" => {
                let VerifyingKey::Elliptic(key) = &self.key else {
                    return None;
                };
                if !key.verifies::<Sha512>(&signature, message.as_bytes()) {
                    return None;
                }
            }

            "Ed25519" | "Ed448" | "EdDSA" => {
                let VerifyingKey::Edwards(key) = &self.key else {
                    return None;
                };
                if !key.verifies(&signature, message.as_bytes()) {
                    return None;
                }
            }

            _ => return None,
        }

        // Check the token is still valid.
        let exp = Duration::from_secs(claims.exp);
        let iat = Duration::from_secs(claims.iat);
        let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) else {
            return None;
        };
        if exp < now || iat > now {
            return None;
        }

        Some(JsonWebToken { header, claims })
    }
}