jwt-lab 0.1.1

JWT crate for Rust: decode, verify, sign, mutate, JWK/JWKS, algorithm selection, time validation, and secure APIs.
Documentation
use crate::{types::Algorithm, Error, Result};
use serde::{Deserialize, Serialize};
use base64ct::Encoding;

/// Cryptographic keys for JWT operations
#[derive(Debug, Clone)]
pub enum Key {
    /// HMAC shared secret key
    Hs(Vec<u8>),
    /// RSA public key in PEM format
    RsaPublicPem(Vec<u8>),
    /// RSA private key in PEM format
    RsaPrivatePem(Vec<u8>),
    /// ECDSA public key in PEM format
    EcPublicPem(Vec<u8>),
    /// ECDSA private key in PEM format
    EcPrivatePem(Vec<u8>),
    /// EdDSA public key in PEM format
    EdPublicPem(Vec<u8>),
    /// EdDSA private key in PEM format
    EdPrivatePem(Vec<u8>),
}
impl Key {
    /// Create an HMAC key from a shared secret
    pub fn hs(secret: impl AsRef<[u8]>) -> Self { Key::Hs(secret.as_ref().to_vec()) }
    /// Create an RSA public key from PEM data
    pub fn rsa_public_pem(pem: impl AsRef<[u8]>) -> Self { Key::RsaPublicPem(pem.as_ref().to_vec()) }
    /// Create an RSA private key from PEM data
    pub fn rsa_private_pem(pem: impl AsRef<[u8]>) -> Self { Key::RsaPrivatePem(pem.as_ref().to_vec()) }
    /// Create an ECDSA public key from PEM data
    pub fn ec_public_pem(pem: impl AsRef<[u8]>) -> Self { Key::EcPublicPem(pem.as_ref().to_vec()) }
    /// Create an ECDSA private key from PEM data
    pub fn ec_private_pem(pem: impl AsRef<[u8]>) -> Self { Key::EcPrivatePem(pem.as_ref().to_vec()) }
    /// Create an EdDSA public key from PEM data
    pub fn ed_public_pem(pem: impl AsRef<[u8]>) -> Self { Key::EdPublicPem(pem.as_ref().to_vec()) }
    /// Create an EdDSA private key from PEM data
    pub fn ed_private_pem(pem: impl AsRef<[u8]>) -> Self { Key::EdPrivatePem(pem.as_ref().to_vec()) }
}

/// JSON Web Key (JWK) representation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Jwk {
    /// Key type (e.g., "RSA", "EC", "OKP", "oct")
    pub kty: String,
    /// Key ID
    #[serde(default)]
    pub kid: Option<String>,
    /// Algorithm
    #[serde(default)]
    pub alg: Option<String>,
    /// Key use
    #[serde(default, rename = "use")]
    pub use_: Option<String>,
    /// RSA modulus
    #[serde(default)] pub n: Option<String>,
    /// RSA exponent
    #[serde(default)] pub e: Option<String>,
    /// Private exponent (RSA, EC, OKP)
    #[serde(default)] pub d: Option<String>,
    /// RSA first prime factor
    #[serde(default)] pub p: Option<String>,
    /// RSA second prime factor
    #[serde(default)] pub q: Option<String>,
    /// RSA first factor CRT exponent
    #[serde(default)] pub dp: Option<String>,
    /// RSA second factor CRT exponent
    #[serde(default)] pub dq: Option<String>,
    /// RSA first CRT coefficient
    #[serde(default)] pub qi: Option<String>,
    /// Curve name (EC, OKP)
    #[serde(default)] pub crv: Option<String>,
    /// X coordinate (EC, OKP)
    #[serde(default)] pub x: Option<String>,
    /// Y coordinate (EC)
    #[serde(default)] pub y: Option<String>,
    /// Octet key value
    #[serde(default)] pub k: Option<String>,
}

/// JSON Web Key Set (JWKS) containing multiple JWKs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Jwks {
    /// List of JSON Web Keys
    pub keys: Vec<Jwk>
}

impl Jwks {
    /// Parse a JWKS from a JSON string
    ///
    /// # Arguments
    ///
    /// * `s` - JSON string containing the JWKS
    ///
    /// # Returns
    ///
    /// Returns a `Result` containing the parsed JWKS or an error
    pub fn from_str(s: &str) -> Result<Self> {
        serde_json::from_str(s).map_err(|e| Error::Json(e.to_string()))
    }
    /// Select a key from the JWKS by kid and algorithm
    ///
    /// # Arguments
    ///
    /// * `kid` - Key ID to search for
    /// * `alg` - Algorithm to use for key conversion
    ///
    /// # Returns
    ///
    /// Returns a `Result` containing the selected key or an error
    ///
    /// # Errors
    ///
    /// - `Error::KidNotFound` if no key with the given kid is found
    /// - `Error::Key` if key conversion fails
    pub fn select_for(&self, kid: &str, alg: crate::types::Algorithm) -> Result<Key> {
        let jwk = self.keys.iter().find(|k| k.kid.as_deref() == Some(kid)).ok_or(Error::KidNotFound)?;
        jwk.to_key_for(alg)
    }
}

impl Jwk {
    /// Convert this JWK to a Key for the given algorithm
    ///
    /// # Arguments
    ///
    /// * `alg` - The algorithm to use for key conversion
    ///
    /// # Returns
    ///
    /// Returns a `Result` containing the converted key or an error
    ///
    /// # Errors
    ///
    /// - `Error::Key` if the JWK is invalid or conversion fails
    ///
    /// # Note
    ///
    /// Currently only supports octet keys (HMAC). RSA, EC, and EdDSA JWK to PEM
    /// conversion is not implemented and will return an error.
    pub fn to_key_for(&self, alg: Algorithm) -> Result<Key> {
        match self.kty.as_str() {
            "oct" => {
                let k = self.k.as_ref().ok_or_else(|| Error::Key("oct missing k".into()))?;
                let bytes = base64ct::Base64UrlUnpadded::decode_vec(k).map_err(|e| Error::Key(e.to_string()))?;
                Ok(Key::Hs(bytes))
            }
            "RSA" => match alg {
                Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => {
                    Err(Error::Key("RSA JWK to PEM conversion not implemented".into()))
                }
                _ => Err(Error::Key("RSA JWK used with non-RS alg".into())),
            }
            "EC" => match (self.crv.as_deref(), alg) {
                (Some("P-256"), Algorithm::ES256) |
                (Some("P-384"), Algorithm::ES384) |
                (Some("P-521"), Algorithm::ES512) => {
                    Err(Error::Key("EC JWK to PEM conversion not implemented".into()))
                }
                _ => Err(Error::Key("EC JWK and alg mismatch or unsupported curve".into())),
            }
            "OKP" => match (self.crv.as_deref(), alg) {
                (Some("Ed25519"), Algorithm::EdDSA) => {
                    Err(Error::Key("EdDSA JWK to PEM conversion not implemented".into()))
                }
                _ => Err(Error::Key("OKP JWK not Ed25519".into())),
            }
            _ => Err(Error::Key("unsupported kty".into())),
        }
    }
}

/// Key source selection strategy
#[derive(Debug, Clone, Copy)]
pub enum KeySource {
    /// Automatically select the appropriate key source
    Auto
}