From 6f128f257156caa79cf8eb1cc375def9c6ff8b47 Mon Sep 17 00:00:00 2001 From: Alex Rudy Date: Tue, 26 Dec 2023 03:28:08 +0000 Subject: [PATCH] [docs] Improvements to Token::sign and sign_randomized methods Also removes jose_algorithm! macro from public interface. --- examples/dyn-key.rs | 36 +++++++++++++------------ src/algorithms/ecdsa.rs | 2 +- src/algorithms/mod.rs | 58 ++++++++++++++++++++++++++++++++--------- src/algorithms/rsa.rs | 2 +- src/token/mod.rs | 30 +++++++++++++++++++++ 5 files changed, 98 insertions(+), 30 deletions(-) diff --git a/examples/dyn-key.rs b/examples/dyn-key.rs index d510056..4326d03 100644 --- a/examples/dyn-key.rs +++ b/examples/dyn-key.rs @@ -2,8 +2,6 @@ use jaws::algorithms::SignatureBytes; use jaws::algorithms::TokenSigner; use jaws::algorithms::TokenVerifier; use jaws::key::DeserializeJWK; -use jaws::key::SerializeJWK; -use jaws::key::SerializePublicJWK; use jaws::token::Unverified; use jaws::Compact; use jaws::JWTFormat; @@ -13,31 +11,29 @@ use rsa::pkcs8::DecodePrivateKey; use serde_json::json; use sha2::Sha256; -trait TokenSigningKey: TokenSigner + SerializePublicJWK {} - -impl TokenSigningKey for T where - T: TokenSigner + SerializeJWK + SerializePublicJWK -{ +fn type_name_of_val(_: &T) -> &'static str { + std::any::type_name::() } fn main() -> Result<(), Box> { // This key is from RFC 7515, Appendix A.2. Provide your own key instead! // The key here is stored as a PKCS#8 PEM file, but you can leverage // RustCrypto to load a variety of other formats. - let key = rsa::RsaPrivateKey::from_pkcs8_pem(include_str!(concat!( + let signing_key = rsa::RsaPrivateKey::from_pkcs8_pem(include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/examples/rfc7515a2.pem" ))) .unwrap(); let verify_key: rsa::pkcs1v15::VerifyingKey = - rsa::pkcs1v15::VerifyingKey::new(key.to_public_key()); + rsa::pkcs1v15::VerifyingKey::new(signing_key.to_public_key()); // We will sign the JWT with a type-erased algorithm, and use a type-erased // verifier to verify it. This allows you to use a set of verifiers which // are not known at compile time. - let alg: Box = - Box::new(rsa::pkcs1v15::SigningKey::::new(key.clone())); - let verify_alg: Box> = Box::new(verify_key.clone()); + let dyn_signing_key: Box> = Box::new( + rsa::pkcs1v15::SigningKey::::new(signing_key.clone()), + ); + let dyn_verify_key: Box> = Box::new(verify_key.clone()); // Claims can combine registered and custom fields. The claims object // can be any type which implements [serde::Serialize]. @@ -71,7 +67,9 @@ fn main() -> Result<(), Box> { println!("{}", token.formatted()); // Sign the token with the algorithm, and print the result. - let signed = token.sign::<_, SignatureBytes>(alg.as_ref()).unwrap(); + let signed = token + .sign::<_, SignatureBytes>(dyn_signing_key.as_ref()) + .unwrap(); let rendered = signed.rendered().unwrap(); @@ -98,14 +96,20 @@ fn main() -> Result<(), Box> { .clone() .verify::<_, rsa::pkcs1v15::Signature>(&verify_key) .unwrap(); - println!("Verified with verify key (typed)"); + println!( + "Verified with verify key (typed): {}", + type_name_of_val(&verify_key) + ); // Check it against the verified key let verified = token .clone() - .verify::<_, SignatureBytes>(verify_alg.as_ref()) + .verify::<_, SignatureBytes>(dyn_verify_key.as_ref()) .unwrap(); - println!("Verified with dyn verify key"); + println!( + "Verified with dyn verify key: {}", + type_name_of_val(&dyn_verify_key) + ); // Check it against its own JWT token diff --git a/src/algorithms/ecdsa.rs b/src/algorithms/ecdsa.rs index be9f662..f88c1dc 100644 --- a/src/algorithms/ecdsa.rs +++ b/src/algorithms/ecdsa.rs @@ -340,7 +340,7 @@ where macro_rules! jose_ecdsa_algorithm { ($alg:ident, $curve:ty) => { - $crate::jose_algorithm!( + $crate::algorithms::jose_algorithm!( $alg, ecdsa::SigningKey<$curve>, ecdsa::VerifyingKey<$curve>, diff --git a/src/algorithms/mod.rs b/src/algorithms/mod.rs index 0dfe0a3..aece157 100644 --- a/src/algorithms/mod.rs +++ b/src/algorithms/mod.rs @@ -191,19 +191,30 @@ pub trait JsonWebAlgorithmDigest: JsonWebAlgorithm { /// A trait to represent an algorithm which can sign a JWT. /// -/// This trait should apply to signing keys. +/// This trait should be implemented by signing keys. It is not designed for direct +/// use by end-users of the JAWS library, rather it is designed to be easily implemented +/// by other [RustCrypto](https://github.com/RustCrypto) crates, such as [`rsa`] or [`ecdsa`]. pub trait TokenSigner: 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. + /// and payload. The header and payload are already serialized to JSON and then + /// base64url-encoded, so this function should not perform any additional encoding. + /// + /// The signature must implement [`SignatureEncoding`], and will be base64url-encoded + /// and appended to the compact representation of the JWT. Signatures should not be + /// pre-encoded, rather they should be in a format appropriate for verification. + /// + /// This method is not intended to be called directly, rather it is designed to be + /// easily implemented within the [RustCrypto](https://github.com/RustCrypto) ecosystem. + /// + /// To sign a token, use the [`Token::sign`](crate::token::Token::sign) method, which provides the correct wrapping + /// and format to produce a signed JWT. fn try_sign_token(&self, header: &str, payload: &str) -> Result; /// 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. + /// and payload. See [`TokenSigner::try_sign_token`] for more details. /// /// # Panics /// @@ -232,14 +243,29 @@ where #[cfg(feature = "rand")] /// A trait to represent an algorithm which can sign a JWT, with a source of /// randomness. +/// +/// This trait should be implemented by signing keys. It is not designed for direct +/// use by end-users of the JAWS library, rather it is designed to be easily implemented +/// by other [RustCrypto](https://github.com/RustCrypto) crates, such as [`rsa`] or [`ecdsa`]. pub trait RandomizedTokenSigner: 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. + /// and payload, and a source of randomness. The header and payload are already + /// serialized to JSON and then base64url-encoded, so this function should not perform + /// any additional encoding. + /// + /// The signature must implement [`SignatureEncoding`], and will be base64url-encoded + /// and appended to the compact representation of the JWT. Signatures should not be + /// pre-encoded, rather they should be in a format appropriate for verification. + /// + /// This method is not intended to be called directly, rather it is designed to be + /// easily implemented within the [RustCrypto](https://github.com/RustCrypto) ecosystem. + /// + /// To sign a token, use the [`Token::sign_randomized`](crate::token::Token::sign_randomized) method, which provides the correct + /// wrapping and format to produce a signed JWT. fn try_sign_token( &self, header: &str, @@ -248,8 +274,7 @@ where ) -> Result; /// 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. + /// and payload. See [`RandomizedTokenSigner::try_sign_token`] for more details. /// /// # Panics /// @@ -262,13 +287,21 @@ where /// 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 -/// to verify a JWT signature, but not necessarily to sing it. +/// to verify a JWT signature, but not necessarily to sign it. It is designed to be easily +/// implemented by other [RustCrypto](https://github.com/RustCrypto) crates, such as [`rsa`] +/// or [`ecdsa`]. pub trait TokenVerifier: DynJsonWebAlgorithm where S: SignatureEncoding, { /// Verify the signature of the JWT, when provided with the base64url-encoded header - /// and payload. + /// and payload, along side the signature. The header and payload are already encoded, + /// so this function should not perform any additional encoding. + /// + /// The signature must implement [`SignatureEncoding`], and will be base64url-encoded + /// and appended to the compact representation of the JWT. JAWS retains the signature + /// in the typed form returned by this method to preserve the ability to render the + /// JWS token. fn verify_token( &self, header: &[u8], @@ -304,7 +337,6 @@ where } /// A macro to implement the required traits for common JWS alogorithms. -#[macro_export] macro_rules! jose_algorithm { ($alg:ident, $signer:ty, $verifier:ty, $digest:ty, $signature:ty) => { impl $crate::algorithms::JsonWebAlgorithm for $signer { @@ -365,6 +397,8 @@ macro_rules! jose_algorithm { }; } +pub(crate) use jose_algorithm; + #[cfg(test)] mod test { use super::*; diff --git a/src/algorithms/rsa.rs b/src/algorithms/rsa.rs index 8c70a0a..22e7312 100644 --- a/src/algorithms/rsa.rs +++ b/src/algorithms/rsa.rs @@ -234,7 +234,7 @@ where macro_rules! jose_rsa_pkcs1v15_algorithm { ($alg:ident, $digest:ty) => { - $crate::jose_algorithm!( + $crate::algorithms::jose_algorithm!( $alg, rsa::pkcs1v15::SigningKey<$digest>, rsa::pkcs1v15::VerifyingKey<$digest>, diff --git a/src/token/mod.rs b/src/token/mod.rs index 38e8251..1cdaf61 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -423,6 +423,36 @@ where /// This method consumes the token and returns a new one with the signature attached. /// Once the signature is attached, the internal fields are no longer mutable (as that /// would invalidate the signature), but they are still recoverable. + /// + /// When using this method, you will need to specify the signature encoding type along + /// with the algorithm (i.e. both parameters A and S). The algorithm does not uniquely + /// identify the signature kind, and so it is not possible to infer the signature type + /// from just the algorithm type. For example, all included algorithms also implement + /// signing for the [`crate::algorithms::SignatureBytes`] type, which is a raw byte array, + /// suitable for use with any signature (and appropriate for use when you don't want to + /// statically specify the signature type, see the dyn-key example). + /// + #[cfg_attr( + feature = "rsa", + doc = r#" +# Example: Signing with a key + +```rust +# use jaws::token::Token; +# use signature::rand_core as rand; +let key = rsa::pkcs1v15::SigningKey::random(&mut rand::OsRng, 2048).unwrap(); +let token = Token::compact((), ()); + +// The only way to get a signed token is to sign an Unsigned token! +let signed = token.sign::, rsa::pkcs1v15::Signature>(&key).unwrap(); +println!("Token: {}", signed.rendered().unwrap()); +``` + +Signing often requires specifying the algorithm to use. In the example above, we use +`RS256`, which is the RSA-PKCS1-v1-5 signature algorithm with SHA-256. The algorithm is +specified by constraining the type of `key` when calling [`Token::sign`]. + "# + )] #[allow(clippy::type_complexity)] pub fn sign( self,