Skip to content

Commit

Permalink
Support for randomized signatures
Browse files Browse the repository at this point in the history
Includes ECDSA support
  • Loading branch information
alexrudy committed Nov 29, 2023
1 parent 7eb9cc0 commit ae9b360
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 4 deletions.
27 changes: 27 additions & 0 deletions src/algorithms/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ use base64ct::Base64UrlUnpadded as Base64Url;
use base64ct::Encoding;
use bytes::Bytes;
use digest::generic_array::{ArrayLength, GenericArray};
use digest::Digest;
use ecdsa::EncodedPoint;
use elliptic_curve::{
ops::Invert,
Expand All @@ -89,6 +90,7 @@ use elliptic_curve::{
AffinePoint, Curve, CurveArithmetic, FieldBytes, FieldBytesSize, JwkParameters, PublicKey,
Scalar, SecretKey,
};
use signature::RandomizedDigestSigner;

#[cfg(feature = "p256")]
pub use p256::NistP256;
Expand Down Expand Up @@ -345,6 +347,31 @@ macro_rules! jose_ecdsa_algorithm {
};
}

impl<S, C> crate::algorithms::RandomizedTokenSigner<S> for ecdsa::SigningKey<C>
where
C: PrimeCurve + CurveArithmetic + JwkParameters + ecdsa::hazmat::DigestPrimitive,
Scalar<C>: Invert<Output = CtOption<Scalar<C>>> + SignPrimitive<C>,
SignatureSize<C>: ArrayLength<u8>,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
FieldBytesSize<C>: ModulusSize,
S: SignatureEncoding,
Self: RandomizedDigestSigner<C::Digest, S> + crate::algorithms::DynJsonWebAlgorithm,
{
fn try_sign_token(
&self,
header: &str,
payload: &str,
rng: &mut impl elliptic_curve::rand_core::CryptoRngCore,
) -> Result<S, signature::Error> {
let mut digest = C::Digest::new();
digest.update(header.as_bytes());
digest.update(b".");
digest.update(payload.as_bytes());

self.try_sign_digest_with_rng(rng, digest)
}
}

#[cfg(feature = "p256")]
jose_ecdsa_algorithm!(ES256, NistP256);

Expand Down
29 changes: 29 additions & 0 deletions src/algorithms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ use base64ct::Encoding;
use bytes::Bytes;
use digest::Digest;
use serde::{Deserialize, Serialize};
use signature::rand_core::CryptoRngCore;
use signature::SignatureEncoding;

#[cfg(any(feature = "p256", feature = "hmac", feature = "rsa"))]
Expand Down Expand Up @@ -224,6 +225,34 @@ where
}
}

/// A trait to represent an algorithm which can sign a JWT, with a source of
/// randomness.
pub trait RandomizedTokenSigner<S>: DynJsonWebAlgorithm + SerializePublicJWK
where
S: SignatureEncoding,
{
/// Sign the contents of the JWT, when provided with the base64url-encoded header
/// and payload. This is the JWS Signature value, and will be base64url-encoded
/// and appended to the compact representation of the JWT.
fn try_sign_token(
&self,
header: &str,
payload: &str,
rng: &mut impl CryptoRngCore,
) -> Result<S, signature::Error>;

/// Sign the contents of the JWT, when provided with the base64url-encoded header
/// and payload. This is the JWS Signature value, and will be base64url-encoded
/// and appended to the compact representation of the JWT.
///
/// # Panics
///
/// This function will panic if the signature cannot be computed.
fn sign_token(&self, header: &str, payload: &str, rng: &mut impl CryptoRngCore) -> S {
self.try_sign_token(header, payload, rng).unwrap()
}
}

/// A trait to represent an algorithm which can verify a JWT.
///
/// This trait should apply to the equivalent of public keys, which have enough information
Expand Down
6 changes: 3 additions & 3 deletions src/jose/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ use sha2::Sha256;
use signature::SignatureEncoding;
use url::Url;

use crate::algorithms::AlgorithmIdentifier;
use crate::algorithms::{AlgorithmIdentifier, DynJsonWebAlgorithm};
use crate::base64data::Base64JSON;

#[cfg(feature = "fmt")]
use crate::fmt;
use crate::key::{JsonWebKey, Thumbprint};
use crate::key::{JsonWebKey, SerializePublicJWK, Thumbprint};

mod derive;
mod rendered;
Expand Down Expand Up @@ -187,7 +187,7 @@ impl<H> Header<H, UnsignedHeader> {
key: &A,
) -> Result<Header<H, SignedHeader>, signature::Error>
where
A: crate::algorithms::TokenSigner<S> + ?Sized,
A: DynJsonWebAlgorithm + SerializePublicJWK + ?Sized,
S: SignatureEncoding,
{
let state = SignedHeader {
Expand Down
60 changes: 59 additions & 1 deletion src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ where
A: crate::algorithms::TokenSigner<S> + ?Sized,
S: SignatureEncoding,
{
let header = self.state.header.into_signed_header(algorithm)?;
let header = self.state.header.into_signed_header::<A, S>(algorithm)?;
let headers = Base64JSON(&header).serialized_value()?;
let payload = self.payload.serialized_value()?;
let signature = algorithm
Expand All @@ -448,6 +448,33 @@ where
fmt: self.fmt,
})
}

/// Sign this token using the given algorithm, and a random number generator.
pub fn sign_randomized<A, S>(
self,
algorithm: &A,
rng: &mut impl elliptic_curve::rand_core::CryptoRngCore,
) -> Result<Token<P, Signed<H, A, S>, Fmt>, TokenSigningError>

Check failure on line 457 in src/token/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

very complex type used. Consider factoring parts into `type` definitions

error: very complex type used. Consider factoring parts into `type` definitions --> src/token/mod.rs:457:10 | 457 | ) -> Result<Token<P, Signed<H, A, S>, Fmt>, TokenSigningError> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]`
where
A: crate::algorithms::RandomizedTokenSigner<S> + ?Sized,
S: SignatureEncoding,
{
let header = self.state.header.into_signed_header::<A, S>(algorithm)?;
let headers = Base64JSON(&header).serialized_value()?;
let payload = self.payload.serialized_value()?;
let signature = algorithm
.try_sign_token(&headers, &payload, rng)
.map_err(TokenSigningError::Signing)?;
Ok(Token {
payload: self.payload,
state: Signed {
header,
signature,
_phantom_key: PhantomData,
},
fmt: self.fmt,
})
}
}

impl<H, Fmt, P> Token<P, Unverified<H>, Fmt>
Expand Down Expand Up @@ -868,6 +895,37 @@ mod test_ecdsa {

assert_eq!(verified.payload(), Some(&"This is a signed message"));
}

#[test]
fn rfc7515_example_a3_randomized() {
let pkey = &json!({
"kty":"EC",
"crv":"P-256",
"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"
});

let key = ecdsa(pkey);

let token = Token::compact((), "This is a signed message");

let signed = token
.sign_randomized::<_, ::ecdsa::Signature<_>>(
&key,
&mut elliptic_curve::rand_core::OsRng,
)
.unwrap();

let verifying_key = key.verifying_key();

let verified = signed
.unverify()
.verify::<_, ::ecdsa::Signature<_>>(verifying_key)
.unwrap();

assert_eq!(verified.payload(), Some(&"This is a signed message"));
}
}

#[cfg(all(test, feature = "hmac"))]
Expand Down

0 comments on commit ae9b360

Please sign in to comment.