jwtk 0.5.0

JWT signing (JWS) and verification, with first class JWK and JWK Set (JWKS) support.
Documentation
use openssl::{hash::MessageDigest, memcmp, pkey::PKey, rand::rand_bytes, sign::Signer};
use smallvec::{smallvec, SmallVec};

use crate::{Error, Result, SigningKey, VerificationKey};

#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HmacAlgorithm {
    HS256,
    HS384,
    HS512,
}

impl HmacAlgorithm {
    fn name(self) -> &'static str {
        use HmacAlgorithm::*;
        match self {
            HS256 => "HS256",
            HS384 => "HS384",
            HS512 => "HS512",
        }
    }

    fn digest(self) -> MessageDigest {
        use HmacAlgorithm::*;
        match self {
            HS256 => MessageDigest::sha256(),
            HS384 => MessageDigest::sha384(),
            HS512 => MessageDigest::sha512(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct HmacKey {
    k: SmallVec<[u8; 32]>,
    algorithm: HmacAlgorithm,
}

impl HmacKey {
    #[inline]
    pub fn generate(algorithm: HmacAlgorithm) -> Result<Self> {
        let len = match algorithm {
            HmacAlgorithm::HS256 => 32,
            HmacAlgorithm::HS384 => 48,
            HmacAlgorithm::HS512 => 64,
        };

        let mut k = smallvec![0u8; len];
        rand_bytes(&mut k)?;

        Ok(Self { k, algorithm })
    }

    /// The key should have enough entropy. At least 32-byte of full entropy is
    /// recommended.
    #[inline]
    pub fn from_bytes(k: &[u8], algorithm: HmacAlgorithm) -> Self {
        Self {
            k: k.into(),
            algorithm,
        }
    }

    #[inline]
    pub fn serialize(&self) -> &[u8] {
        &self.k
    }
}

impl SigningKey for HmacKey {
    fn sign(&self, v: &[u8]) -> Result<SmallVec<[u8; 64]>> {
        let pk = PKey::hmac(&self.k)?;
        let mut signer = Signer::new(self.algorithm.digest(), pk.as_ref())?;

        let mut sig = smallvec![0u8; signer.len()?];
        signer.sign_oneshot(&mut sig, v)?;
        Ok(sig)
    }

    #[inline]
    fn alg(&self) -> &'static str {
        self.algorithm.name()
    }
}

impl VerificationKey for HmacKey {
    fn verify(&self, v: &[u8], sig: &[u8], alg: &str) -> Result<()> {
        if alg != self.algorithm.name() {
            return Err(Error::VerificationError);
        }

        let expected = self.sign(v)?;

        if memcmp::eq(sig, &expected) {
            Ok(())
        } else {
            Err(Error::VerificationError)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn conversion() -> Result<()> {
        let k = HmacKey::generate(HmacAlgorithm::HS384)?;
        assert_eq!(SigningKey::alg(&k), "HS384");
        let k1 = k.clone();
        let k1 = k1.serialize();
        HmacKey::from_bytes(k1, HmacAlgorithm::HS256);
        println!("{:?}", k);
        Ok(())
    }

    #[test]
    fn sign_and_verify() -> Result<()> {
        for alg in [
            HmacAlgorithm::HS256,
            HmacAlgorithm::HS384,
            HmacAlgorithm::HS512,
        ] {
            let k = HmacKey::from_bytes(b"key", alg);
            let sig = k.sign(b"...")?;
            assert!(k.verify(b"...", &sig, alg.name()).is_ok());
            assert!(k.verify(b"...", &sig, "WRONG ALG").is_err());
            assert!(k.verify(b"....", &sig, alg.name()).is_err());
        }
        Ok(())
    }
}