Skip to content

Commit

Permalink
Support algorithm traits with HMAC
Browse files Browse the repository at this point in the history
  • Loading branch information
alexrudy committed Nov 25, 2023
1 parent 4496aec commit 6e08d36
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 27 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
4 changes: 4 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 3 additions & 10 deletions src/algorithms/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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::{
Expand All @@ -88,19 +89,13 @@ pub use p521::NistP521;
impl<C> crate::key::JWKeyType for VerifyingKey<C>
where
C: PrimeCurve + CurveArithmetic + JwkParameters,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,
{
const KEY_TYPE: &'static str = "EC";
}

impl<C> crate::key::SerializeJWK for VerifyingKey<C>
where
C: PrimeCurve + CurveArithmetic + JwkParameters,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,
{
Expand Down Expand Up @@ -146,8 +141,6 @@ where
C: PrimeCurve + CurveArithmetic + JwkParameters,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,
{
const KEY_TYPE: &'static str = "EC";
}
Expand Down
80 changes: 69 additions & 11 deletions src/algorithms/hmac.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -143,17 +162,56 @@ macro_rules! hmac_algorithm {
crate::algorithms::AlgorithmIdentifier::$alg;
type Signature = DigestSignature<$digest>;
}

impl crate::algorithms::JoseDigestAlgorithm for Hmac<$digest> {
type Digest = $digest;
}
};
}

hmac_algorithm!(HS256, sha2::Sha256);
hmac_algorithm!(HS384, sha2::Sha384);
hmac_algorithm!(HS512, sha2::Sha512);

impl<D> super::TokenSigner for Hmac<D>
where
Hmac<D>: JoseAlgorithm<Signature = DigestSignature<D>>,
D: Digest + digest::core_api::BlockSizeUser,
{
fn try_sign_token(
&self,
header: &str,
payload: &str,
) -> Result<Self::Signature, signature::Error> {
let mut mac: SimpleHmac<D> =
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<D> super::TokenVerifier for Hmac<D>
where
Hmac<D>: JoseAlgorithm<Signature = DigestSignature<D>>,
D: Digest + digest::core_api::BlockSizeUser + Clone,
{
fn verify_token(
&self,
header: &[u8],
payload: &[u8],
signature: &[u8],
) -> Result<Self::Signature, signature::Error> {
let mut mac: SimpleHmac<D> =
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))?;

Check failure on line 209 in src/algorithms/hmac.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant closure

error: redundant closure --> src/algorithms/hmac.rs:209:22 | 209 | .map_err(|err| signature::Error::from_source(err))?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `signature::Error::from_source` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure = note: `-D clippy::redundant-closure` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]`

Ok(DigestSignature(mac.finalize().into_bytes()))
}
}

#[cfg(test)]
mod test {

Expand Down Expand Up @@ -195,13 +253,13 @@ mod test {

let algorithm: Hmac<Sha256> = 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")
);
}
}
54 changes: 54 additions & 0 deletions src/algorithms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,67 @@
//!
//! 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;
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;

Expand Down
8 changes: 6 additions & 2 deletions src/algorithms/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//!
Expand All @@ -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";
}
Expand Down
6 changes: 3 additions & 3 deletions src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
}

0 comments on commit 6e08d36

Please sign in to comment.