Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] Improvements to Token::sign and sign_randomized methods #23

Merged
merged 1 commit into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading