diff --git a/src/jwk.rs b/src/jwk.rs index 314fb03..f3cbaf1 100644 --- a/src/jwk.rs +++ b/src/jwk.rs @@ -30,6 +30,7 @@ pub mod policy; mod private; mod public; pub(crate) mod serde_impl; +mod set; mod signer; pub(crate) mod thumbprint; mod verifier; @@ -42,6 +43,7 @@ pub use self::{ key_use::KeyUsage, private::{EcPrivate, OkpPrivate, Private}, public::{EcPublic, OkpPublic, Public}, + set::{CheckedJsonWebKeySet, JsonWebKeySet}, signer::{FromJwkError, JwkSigner}, symmetric::SymmetricJsonWebKey, thumbprint::Thumbprint, diff --git a/src/jwk/policy.rs b/src/jwk/policy.rs index 3a259a3..1f83e22 100644 --- a/src/jwk/policy.rs +++ b/src/jwk/policy.rs @@ -209,7 +209,7 @@ impl Policy for &P { } /// An error returned by the [`Policy`] trait -pub trait PolicyError { +pub trait PolicyError: core::error::Error { /// A custom error message fn custom(msg: T) -> Self where diff --git a/src/jwk/set.rs b/src/jwk/set.rs new file mode 100644 index 0000000..8204a25 --- /dev/null +++ b/src/jwk/set.rs @@ -0,0 +1,246 @@ +//! This module contains the JWK Set implementation. + +use alloc::vec::Vec; + +use serde::{Deserialize, Serialize}; + +use super::{ + policy::{Checkable, Checked, Policy}, + FromJwkError, JsonWebKey, JwkSigner, JwkVerifier, +}; + +/// A list of raw [`JsonWebKey`] objects, which is parsed according to [Section +/// 5 of RFC 7517](https://www.rfc-editor.org/rfc/rfc7517#section-5). +/// +/// ## Additional parameters +/// +/// The `A` type parameter can be used to specify additional parameters for all +/// the json web keys inside this set. +/// +/// ## Use key set for operations +/// +/// In order to use a key set for signing or verifying, you first have to +/// validate all keys inside the set. Similar to how to have to check a +/// [`JsonWebKey`] in order to get a [`Signer`] or [`Verifier`] instance. +/// +/// [`Signer`]: crate::jws::Signer +/// [`Verifier`]: crate::jws::Verifier +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct JsonWebKeySet { + keys: Vec>, +} + +impl JsonWebKeySet { + /// Tries to find the JWK with the given key ID paramter set. + pub fn find_by_keyid(&self, key_id: &str) -> Option<&JsonWebKey> { + self.keys + .iter() + .find(|key| key.key_id().is_some_and(|id| id == key_id)) + } + + /// Returns an iterator over all the JWKs in this set. + pub fn iter(&self) -> impl Iterator> { + self.keys.iter() + } + + /// Returns an iterator that allows modifying each JWK. + pub fn iter_mut(&mut self) -> impl Iterator> { + self.keys.iter_mut() + } + + /// Checks all keys inside this set using the given policy. + /// + /// ## Errors + /// + /// If any of the keys inside this set do not pass the policy check, this + /// function will return an error. + pub fn check(self, policy: P) -> Result, P::Error> + where + A: Checkable, + { + let mut validated = Vec::new(); + + for key in self.keys { + match key.check(policy.clone()) { + Ok(checked) => validated.push(checked), + Err((_, err)) => return Err(err), + } + } + + Ok(CheckedJsonWebKeySet { keys: validated }) + } +} + +impl<'a, A> IntoIterator for &'a mut JsonWebKeySet { + type IntoIter = core::slice::IterMut<'a, JsonWebKey>; + type Item = &'a mut JsonWebKey; + + fn into_iter(self) -> Self::IntoIter { + self.keys.iter_mut() + } +} + +impl<'a, A> IntoIterator for &'a JsonWebKeySet { + type IntoIter = core::slice::Iter<'a, JsonWebKey>; + type Item = &'a JsonWebKey; + + fn into_iter(self) -> Self::IntoIter { + self.keys.iter() + } +} + +impl IntoIterator for JsonWebKeySet { + type IntoIter = alloc::vec::IntoIter; + type Item = JsonWebKey; + + fn into_iter(self) -> Self::IntoIter { + self.keys.into_iter() + } +} + +impl From>> for JsonWebKeySet { + fn from(keys: Vec>) -> Self { + Self { keys } + } +} + +impl FromIterator> for JsonWebKeySet { + fn from_iter>>(iter: T) -> Self { + let keys = iter.into_iter().collect(); + Self { keys } + } +} + +impl From> for JsonWebKeySet { + fn from(checked: CheckedJsonWebKeySet) -> Self { + checked.into_jwk_set() + } +} + +/// A list of validated [`JsonWebKey`] objects. +/// +/// This is the version of a [`JsonWebKeySet`] whose keys have all been checked, +/// so it is safe now to use them for signing or verifying data. +#[derive(Debug, Default, Clone)] +pub struct CheckedJsonWebKeySet { + keys: Vec, P>>, +} + +impl CheckedJsonWebKeySet { + /// Converts this checked JWK set back into a normal [`JsonWebKeySet`]. + pub fn into_jwk_set(self) -> JsonWebKeySet { + let keys = self + .keys + .into_iter() + .map(|x| x.into_type()) + .collect::>(); + + JsonWebKeySet { keys } + } + + /// Tries to convert all keys in this set to [`JwkSigner`]s, in order to + /// sign a JWS using them. + /// + /// # Errors + /// + /// Fails if one of the JWKs could not be converted to a signer. + pub fn into_signers(self) -> Result, FromJwkError> { + let signers: Vec = self + .keys + .into_iter() + .map(|x| x.try_into()) + .collect::, _>>()?; + + Ok(signers) + } + + /// Tries to convert all keys in this set to [`JwkSigner`]s, in order to + /// sign a JWS using them. But instead of taking `self`, it will clone + /// all keys. + /// + /// # Errors + /// + /// Fails if one of the JWKs could not be converted to a signer. + pub fn signers(&self) -> Result, FromJwkError> + where + A: Clone, + P: Clone, + { + let signers: Vec = self + .keys + .iter() + .cloned() + .map(|x| x.try_into()) + .collect::, _>>()?; + + Ok(signers) + } + + /// Tries to convert all keys in this set to [`JwkVerifier`]s, in order to + /// verify a JWS using them. + /// + /// # Errors + /// + /// Fails if one of the JWKs could not be converted to a verifier. + pub fn into_verifiers(self) -> Result, FromJwkError> { + let verifiers: Vec = self + .keys + .into_iter() + .map(|x| x.try_into()) + .collect::, _>>()?; + + Ok(verifiers) + } + + /// Tries to convert all keys in this set to [`JwkVerifier`]s, in order to + /// verify a JWS using them. But instead of taking `self`, it will clone + /// all keys. + /// + /// # Errors + /// + /// Fails if one of the JWKs could not be converted to a verifier. + pub fn verifiers(&self) -> Result, FromJwkError> + where + A: Clone, + P: Clone, + { + let verifiers: Vec = self + .keys + .iter() + .cloned() + .map(|x| x.try_into()) + .collect::, _>>()?; + + Ok(verifiers) + } + + /// Tries to find the JWK for the given key id, and then converts that JWK + /// into a [`JwkSigner`]. + pub fn signer_for_key_id(&self, key_id: &str) -> Option> + where + A: Clone, + P: Clone, + { + let key = self + .keys + .iter() + .find(|key| key.key_id().is_some_and(|id| id == key_id))?; + + Some(JwkSigner::try_from(Checked::clone(key))) + } + + /// Tries to find the JWK for the given key id, and then converts that JWK + /// into a [`JwkVerifier`]. + pub fn verifier_for_key_id(&self, key_id: &str) -> Option> + where + A: Clone, + P: Clone, + { + let key = self + .keys + .iter() + .find(|key| key.key_id().is_some_and(|id| id == key_id))?; + + Some(JwkVerifier::try_from(Checked::clone(key))) + } +} diff --git a/src/jws.rs b/src/jws.rs index 1f83457..546fa01 100644 --- a/src/jws.rs +++ b/src/jws.rs @@ -298,6 +298,34 @@ impl JsonWebSignature { } impl JsonWebSignature { + /// Signs this JWS using multiple signers. + /// + /// Instead of taking a trait object as a signer, this method takes a + /// generic type which can avoid the requirement for manual coercion to + /// a trait object. + /// + /// You can use this method to avoid some unnecessary mappings. For example, + /// if you have a `Vec`, you can use + /// `sign_many_type(signers.iter_mut())` instead of having to map + /// `JwkSigner` to `&mut dyn Signer` first. + /// + /// # Errors + /// + /// Returns an error if the length of the given iterator of signers does + /// not match the number of headers in this JWS. + /// Otherwise, this method may return the same errors as the normal sign + /// operation. + #[inline] + pub fn sign_many_type<'s, S: AsRef<[u8]> + 's, SIGNER: Signer + 's>( + self, + signers: impl IntoIterator, + ) -> Result, SignError> { + self.sign_many(signers.into_iter().map(|s| { + let s: &mut dyn Signer = s; + s + })) + } + /// Signs this JWS using multiple signers. /// /// This is only supported when the JWS is in the [`JsonGeneral`] format. diff --git a/src/jws/verify.rs b/src/jws/verify.rs index c58e806..61c8c21 100644 --- a/src/jws/verify.rs +++ b/src/jws/verify.rs @@ -199,12 +199,38 @@ impl ManyUnverified { /// Verify this struct using the given verifies, returning a [`Verified`] /// representation of the inner type. /// + /// You can use this method to avoid some unnecessary mapping. For example, + /// if you have a `Vec`, you can use + /// `verify_many_type(signers.iter_mut())` instead of having to map + /// `JwkVerifier` to `&mut dyn Verifier` first. + /// + /// # Errors + /// + /// Returns an error if the number of verifiers does not match the number of + /// signatures, or if anything went wrong during a signature + /// verification or if one of the signatures is just invalid. + pub fn verify_many_type<'a, V: Verifier + 'a>( + self, + verifiers: impl IntoIterator, + ) -> Result, VerifyError> { + self.verify_many(verifiers.into_iter().map(|s| { + let s: &mut dyn Verifier = s; + s + })) + } + + /// Verify this struct using the given verifies, returning a [`Verified`] + /// representation of the inner type. + /// + /// Note that currently, the order of the verifiers iterator and signatures + /// in the JWS must match exactly. + /// /// # Errors /// /// Returns an error if the number of verifiers does not match the number of /// signatures, or if anything went wrong during a signature /// verification or if one of the signatures is just invalid. - // TODO: consider using a more specific error type to give the usermore + // TODO: consider using a more specific error type to give the user more // information about the error pub fn verify_many<'a>( self, diff --git a/tests/jwk.rs b/tests/jwk.rs index f2906f3..c670521 100644 --- a/tests/jwk.rs +++ b/tests/jwk.rs @@ -349,3 +349,27 @@ fn symmetric_thumbprint() { let p = Base64UrlString::encode(p); assert_eq!(&*p, "prDKy90VJzrDTpm8-W2Q_pv_kzrX_zyZ7ANjRAasDxc"); } + +pub mod set { + use jose::{jwk::JsonWebKeySet, JsonWebKey}; + + use crate::read_key_file; + + #[test] + fn construct_and_find() { + let set = read_key_file("set"); + let set: JsonWebKeySet = serde_json::from_str(&set).unwrap(); + + let ec_key = read_key_file("p256"); + let ec_key = serde_json::from_str::(&ec_key).unwrap(); + + let hmac_key = read_key_file("hs256"); + let hmac_key = serde_json::from_str::(&hmac_key).unwrap(); + + let found_ec_key = set.find_by_keyid("ec_key").unwrap(); + let found_hmac_key = set.find_by_keyid("hmac_key").unwrap(); + + assert_eq!(found_ec_key.key_type(), ec_key.key_type()); + assert_eq!(found_hmac_key.key_type(), hmac_key.key_type()); + } +} diff --git a/tests/jws.rs b/tests/jws.rs index 71d7957..d61422c 100644 --- a/tests/jws.rs +++ b/tests/jws.rs @@ -15,7 +15,7 @@ use jose::{ jwa::JsonWebSigningAlgorithm, jwk::{ policy::{Checkable, StandardPolicy}, - JwkSigner, JwkVerifier, + JsonWebKeySet, JwkSigner, JwkVerifier, }, jws::{ FromRawPayload, IntoPayload, IntoSigner, IntoVerifier, ManyUnverified, PayloadData, @@ -465,3 +465,32 @@ fn ed25519() { .verify(&mut verifier) .expect_err("signature made by different private key"); } + +#[test] +fn json_general_sign_with_set() { + let keys = std::fs::read_to_string(format!( + "{}/tests/keys/set.json", + env!("CARGO_MANIFEST_DIR"), + )) + .unwrap(); + let payload = StringPayload::from("Hello guys!"); + + let keys: JsonWebKeySet = serde_json::from_str(&keys).unwrap(); + let keys = keys.check(StandardPolicy::new()).unwrap(); + + let mut signers = keys.signers().unwrap(); + let jws = Jws::::builder() + .header(|b| b) + .header(|b| b) + .build(payload) + .unwrap() + .sign_many_type(signers.iter_mut()) + .unwrap() + .encode(); + + let mut verifiers = keys.verifiers().unwrap(); + let _parsed_jws = ManyUnverified::>::decode(jws) + .unwrap() + .verify_many_type(verifiers.iter_mut()) + .unwrap(); +} diff --git a/tests/keys/set.json b/tests/keys/set.json new file mode 100644 index 0000000..a0f14a9 --- /dev/null +++ b/tests/keys/set.json @@ -0,0 +1,21 @@ +{ + "keys": [ + { + "use": "sig", + "kid": "ec_key", + "kty": "EC", + "crv": "P-256", + "alg": "ES256", + "x": "-HGJKqKLCoB6z4zlNKef927CODDulLcHdxNi2iUTi5g", + "y": "GaVhYaBvIgSAaNLjXjVqOvtCGH56x5s4DnWMy9TXbTU", + "d": "C6AV5ZvCGQevYYMJT15frXWuKaqEDthnSMtuJKEKykI" + }, + { + "use": "sig", + "kid": "hmac_key", + "kty": "oct", + "alg": "HS256", + "k": "QXk0V2FZSWYyMFR3RVNMZmt4SFVwU3Z4MkRtNlpINE4" + } + ] +}