diff --git a/Cargo.toml b/Cargo.toml index 9212a97..1a45cc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ p521 = { version = "0.13.0", optional = true } hmac = { version = "0.12.1", features = ["reset"], optional = true } [features] -default = ["fmt", "rsa", "ecdsa", "p256", "p384", "p521"] +default = ["fmt", "rsa", "ecdsa", "p256", "p384", "p521", "hmac"] fmt = [] rsa = ["dep:rsa"] hmac = ["dep:hmac"] diff --git a/justfile b/justfile index efb9b0a..345d6a6 100644 --- a/justfile +++ b/justfile @@ -4,6 +4,10 @@ test: @echo "Running tests..." cargo hack test --feature-powerset --group-features ecdsa,p256,p384,p521 +check: + @echo "Checking..." + cargo hack check --feature-powerset --group-features ecdsa,p256,p384,p521 + doc: @echo "Building docs..." cargo doc --all-features diff --git a/src/algorithms/ecdsa.rs b/src/algorithms/ecdsa.rs index e7aa0e8..aaea18c 100644 --- a/src/algorithms/ecdsa.rs +++ b/src/algorithms/ecdsa.rs @@ -2,7 +2,7 @@ //! //! This module provides implementations of the ECDSA signing algorithms for use with JSON Web Tokens. //! -//! It uses [elliptic_curve::SecretKey] as the base type, and provides implementations of the ECDSA +//! It uses [ecdsa::SigningKey] and [ecdsa::VerifyingKey] as the key types, and provides implementations of the ECDSA //! signing algorithms for the following curves: //! - P-256 (ES256) //! - P-384 (ES384) @@ -66,7 +66,8 @@ println!("{}", signed.formatted()); "# )] -use ::ecdsa::{hazmat::SignPrimitive, PrimeCurve, SignatureSize, SigningKey, VerifyingKey}; +use ::ecdsa::{hazmat::SignPrimitive, PrimeCurve, SignatureSize}; +pub use ::ecdsa::{SigningKey, VerifyingKey}; use base64ct::Encoding; use digest::generic_array::ArrayLength; use elliptic_curve::{ @@ -88,10 +89,6 @@ pub use p521::NistP521; impl crate::key::JWKeyType for VerifyingKey where C: PrimeCurve + CurveArithmetic + JwkParameters, - Scalar: Invert>> + SignPrimitive, - SignatureSize: ArrayLength, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, { const KEY_TYPE: &'static str = "EC"; } @@ -99,8 +96,6 @@ where impl crate::key::SerializeJWK for VerifyingKey where C: PrimeCurve + CurveArithmetic + JwkParameters, - Scalar: Invert>> + SignPrimitive, - SignatureSize: ArrayLength, AffinePoint: FromEncodedPoint + ToEncodedPoint, FieldBytesSize: ModulusSize, { @@ -146,8 +141,6 @@ where C: PrimeCurve + CurveArithmetic + JwkParameters, Scalar: Invert>> + SignPrimitive, SignatureSize: ArrayLength, - AffinePoint: FromEncodedPoint + ToEncodedPoint, - FieldBytesSize: ModulusSize, { const KEY_TYPE: &'static str = "EC"; } diff --git a/src/algorithms/hmac.rs b/src/algorithms/hmac.rs index 506002a..9669f37 100644 --- a/src/algorithms/hmac.rs +++ b/src/algorithms/hmac.rs @@ -1,16 +1,35 @@ //! HMAC Signing algorithms for use with JWS //! //! Based on the [hmac](https://crates.io/crates/hmac) crate. +//! +//! ## Usage +//! +//! Unlike the other algorithms, HMAC algorithms use special types provided +//! by the JOSE crate. HMAC is not really a "signature" algorithm in a strict sense, +//! as it does not use a private key. Instead, it uses a symmetric key, which is +//! shared between the signer and verifier. +//! +//! The [`HmacKey`] type is used to represent the key. This type is a wrapper around +//! a byte vector, and can be created from any type which can be converted into a +//! byte vector. The [`Hmac`] type is used to represent the algorithm, and is a wrapper +//! which combines the key with the digest algorithm. +//! +//! The [`Hmac`] type implements [`TokenSigner`] and [`TokenVerifier`], and can be used +//! to sign and verify tokens. Signatures are represented by the [`DigestSignature`] type, +//! which is a wrapper around the [`digest::Output`] type from the [`digest`](https://crates.io/crates/digest) +//! crate, but which provides the appropriate signature encoding behavior. use std::{marker::PhantomData, ops::Deref}; use base64ct::Encoding; -use digest::Digest; +use digest::{Digest, Mac}; use hmac::SimpleHmac; use signature::SignatureEncoding; use crate::key::{JWKeyType, SerializeJWK}; +use super::JoseAlgorithm; + /// A key used to seed an HMAC signature. #[derive(Debug, Clone, PartialEq, Eq, Hash, zeroize::Zeroize, zeroize::ZeroizeOnDrop, Default)] pub struct HmacKey { @@ -143,10 +162,6 @@ macro_rules! hmac_algorithm { crate::algorithms::AlgorithmIdentifier::$alg; type Signature = DigestSignature<$digest>; } - - impl crate::algorithms::JoseDigestAlgorithm for Hmac<$digest> { - type Digest = $digest; - } }; } @@ -154,6 +169,49 @@ hmac_algorithm!(HS256, sha2::Sha256); hmac_algorithm!(HS384, sha2::Sha384); hmac_algorithm!(HS512, sha2::Sha512); +impl super::TokenSigner for Hmac +where + Hmac: JoseAlgorithm>, + D: Digest + digest::core_api::BlockSizeUser, +{ + fn try_sign_token( + &self, + header: &str, + payload: &str, + ) -> Result { + let mut mac: SimpleHmac = + SimpleHmac::new_from_slice(self.key.as_ref()).expect("Valid key"); + mac.update(header.as_bytes()); + mac.update(b"."); + mac.update(payload.as_bytes()); + Ok(DigestSignature(mac.finalize().into_bytes())) + } +} + +impl super::TokenVerifier for Hmac +where + Hmac: JoseAlgorithm>, + D: Digest + digest::core_api::BlockSizeUser + Clone, +{ + fn verify_token( + &self, + header: &[u8], + payload: &[u8], + signature: &[u8], + ) -> Result { + let mut mac: SimpleHmac = + SimpleHmac::new_from_slice(self.key.as_ref()).expect("Valid key"); + mac.update(header); + mac.update(b"."); + mac.update(payload); + mac.clone() + .verify_slice(signature) + .map_err(|err| signature::Error::from_source(err))?; + + Ok(DigestSignature(mac.finalize().into_bytes())) + } +} + #[cfg(test)] mod test { @@ -195,13 +253,13 @@ mod test { let algorithm: Hmac = Hmac::new(key); - // let signature = algorithm.sign_token(&header, &payload); + let signature = algorithm.sign_token(&header, &payload); - // let sig = base64ct::Base64UrlUnpadded::encode_string(signature.to_bytes().as_ref()); + let sig = base64ct::Base64UrlUnpadded::encode_string(signature.to_bytes().as_ref()); - // assert_eq!( - // sig, - // strip_whitespace("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk") - // ); + assert_eq!( + sig, + strip_whitespace("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk") + ); } } diff --git a/src/algorithms/mod.rs b/src/algorithms/mod.rs index af3e9db..d24d9ca 100644 --- a/src/algorithms/mod.rs +++ b/src/algorithms/mod.rs @@ -4,6 +4,51 @@ //! //! See the submodules for specific algorithm implementations for signing. //! +//! # Algorithm Traits +//! +//! JOSE uses a few traits to define appropriate signing algorithms. [`JoseAlgorithm`] is the main trait, +//! which defines the algorithm identifier and the type of the signature. [`JoseDigestAlgorithm`] is a +//! subtrait which defines the digest algorithm used by the signature, for algorithms which use digest +//! signing (e.g. RSA-PSS, RSA-PKCS1-v1_5, ECDSA), and where a specific digest is specified by the algorithm +//! identifier. +//! +//! [`TokenSigner`] and [`TokenVerifier`] are traits which define the ability to sign and verify a JWT. +//! They are implemented for any [`JoseDigestAlgorithm`] which is also a [`DigestSigner`] or [`DigestVerifier`]. +//! +//! # Supported Algorithms +//! +//! ## HMAC +//! +//! - HS256: HMAC using SHA-256 +//! - HS384: HMAC using SHA-384 +//! - HS512: HMAC using SHA-512 +//! +//! ## RSA +//! +//! - RS256: RSASSA-PKCS1-v1_5 using SHA-256 +//! - RS384: RSASSA-PKCS1-v1_5 using SHA-384 +//! - RS512: RSASSA-PKCS1-v1_5 using SHA-512 +//! - PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256 +//! - PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384 +//! +//! ## ECDSA +//! +//! - ES256: ECDSA using P-256 and SHA-256 +//! - ES384: ECDSA using P-384 and SHA-384 +//! +//! # Unsupported algorithms +//! +//! This crate does not support signing or verification with the `none` algorithm, +//! as it is generally a security vulnerability. +//! +//! Currently, there is no support for the following algorithms: +//! +//! - EdDSA: EdDSA using Ed25519 is not yet supported. +//! - ES512: ECDSA using P-521 and SHA-512 is not yet supported. +//! +//! All of these algorithms could be supported by providing suitable implementations +//! of the [`JoseAlgorithm`] trait and the [`TokenSigner`] and [`TokenVerifier`] traits. +//! //! [RFC7518]: https://tools.ietf.org/html/rfc7518 use bytes::Bytes; @@ -11,6 +56,15 @@ use digest::Digest; use serde::{Deserialize, Serialize}; use signature::SignatureEncoding; +#[cfg(any(feature = "p256", feature = "hmac", feature = "rsa"))] +pub use sha2::Sha256; + +#[cfg(any(feature = "p348", feature = "hmac", feature = "rsa"))] +pub use sha2::Sha384; + +#[cfg(any(feature = "hmac", feature = "rsa"))] +pub use sha2::Sha512; + #[cfg(feature = "ecdsa")] pub mod ecdsa; diff --git a/src/algorithms/rsa.rs b/src/algorithms/rsa.rs index f8d05e1..85a1bac 100644 --- a/src/algorithms/rsa.rs +++ b/src/algorithms/rsa.rs @@ -2,8 +2,7 @@ //! //! # PKCS#1 v1.5 (RS256, RS384, RS512) //! This algorithm is used to sign and verify JSON Web Tokens using the RSASSA-PKCS1-v1_5. -//! A [rsa::pkcs1v15::SigningKey] signing key is used to provide the underlying signature -//! algorithm. +//! Use [rsa::pkcs1v15::SigningKey] to sign tokens, and [rsa::pkcs1v15::VerifyingKey] to verify tokens. //! //! A key of size 2048 bits or larger MUST be used with these algorithms. //! @@ -14,10 +13,15 @@ //! //! # PSS (PS256, PS384, PS512) //! This algorithm is used to sign and verify JSON Web Tokens using the RSASSA-PSS. +//! +//! Use [rsa::pss::BlindedSigningKey] to sign tokens, and [rsa::pss::VerifyingKey] to verify tokens. use base64ct::{Base64UrlUnpadded, Encoding}; use rsa::PublicKeyParts; +pub use rsa::pkcs1v15; +pub use rsa::pss; + impl crate::key::JWKeyType for rsa::RsaPublicKey { const KEY_TYPE: &'static str = "RSA"; } diff --git a/src/token/mod.rs b/src/token/mod.rs index 46e0eeb..12983e2 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -816,10 +816,10 @@ mod test_hmac { let token = Token::compact((), "This is an HMAC'd message"); - // let signed = token.sign(&algorithm).unwrap(); + let signed = token.sign(&algorithm).unwrap(); - // let verified = signed.unverify().verify(&algorithm).unwrap(); + let verified = signed.unverify().verify(&algorithm).unwrap(); - // assert_eq!(verified.payload(), Some(&"This is an HMAC'd message")); + assert_eq!(verified.payload(), Some(&"This is an HMAC'd message")); } }