Skip to content

Commit

Permalink
Merge pull request #23 from alexrudy/docs/trait-documentation
Browse files Browse the repository at this point in the history
[docs] Improvements to Token::sign and sign_randomized methods
  • Loading branch information
alexrudy authored Dec 26, 2023
2 parents 440cab6 + 6f128f2 commit 2a34f5c
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 30 deletions.
36 changes: 20 additions & 16 deletions examples/dyn-key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,31 +11,29 @@ use rsa::pkcs8::DecodePrivateKey;
use serde_json::json;
use sha2::Sha256;

trait TokenSigningKey: TokenSigner<SignatureBytes> + SerializePublicJWK {}

impl<T> TokenSigningKey for T where
T: TokenSigner<SignatureBytes> + SerializeJWK + SerializePublicJWK
{
fn type_name_of_val<T>(_: &T) -> &'static str {
std::any::type_name::<T>()
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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<Sha256> =
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<dyn TokenSigningKey> =
Box::new(rsa::pkcs1v15::SigningKey::<Sha256>::new(key.clone()));
let verify_alg: Box<dyn TokenVerifier<SignatureBytes>> = Box::new(verify_key.clone());
let dyn_signing_key: Box<dyn TokenSigner<SignatureBytes>> = Box::new(
rsa::pkcs1v15::SigningKey::<Sha256>::new(signing_key.clone()),
);
let dyn_verify_key: Box<dyn TokenVerifier<SignatureBytes>> = Box::new(verify_key.clone());

// Claims can combine registered and custom fields. The claims object
// can be any type which implements [serde::Serialize].
Expand Down Expand Up @@ -71,7 +67,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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();

Expand All @@ -98,14 +96,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.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
Expand Down
2 changes: 1 addition & 1 deletion src/algorithms/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
58 changes: 46 additions & 12 deletions src/algorithms/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<S = SignatureBytes>: 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<S, sig::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.
/// and payload. See [`TokenSigner::try_sign_token`] for more details.
///
/// # Panics
///
Expand Down Expand Up @@ -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<S = SignatureBytes>:
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,
Expand All @@ -248,8 +274,7 @@ where
) -> Result<S, sig::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.
/// and payload. See [`RandomizedTokenSigner::try_sign_token`] for more details.
///
/// # Panics
///
Expand All @@ -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<S = SignatureBytes>: 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],
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -365,6 +397,8 @@ macro_rules! jose_algorithm {
};
}

pub(crate) use jose_algorithm;

#[cfg(test)]
mod test {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion src/algorithms/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
30 changes: 30 additions & 0 deletions src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::SigningKey<sha2::Sha256>, 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<A, S>(
self,
Expand Down

0 comments on commit 2a34f5c

Please sign in to comment.