diff --git a/crates/handlers/src/oauth2/token.rs b/crates/handlers/src/oauth2/token.rs index 27c8f357..9ff32070 100644 --- a/crates/handlers/src/oauth2/token.rs +++ b/crates/handlers/src/oauth2/token.rs @@ -355,7 +355,7 @@ async fn authorization_code_grant( let header = JsonWebSignatureHeader::new(alg) .with_kid(key.kid().context("key has no `kid` for some reason")?); - let signer = key.params().signer_for_alg(alg)?; + let signer = key.params().signing_key_for_alg(alg)?; let id_token = Jwt::sign(header, claims, &signer)?; Some(id_token.as_str().to_owned()) diff --git a/crates/handlers/src/oauth2/userinfo.rs b/crates/handlers/src/oauth2/userinfo.rs index 9132ef18..c77151b6 100644 --- a/crates/handlers/src/oauth2/userinfo.rs +++ b/crates/handlers/src/oauth2/userinfo.rs @@ -82,7 +82,7 @@ pub async fn get( let header = JsonWebSignatureHeader::new(alg) .with_kid(key.kid().context("key has no `kid` for some reason")?); - let signer = key.params().signer_for_alg(alg)?; + let signer = key.params().signing_key_for_alg(alg)?; let user_info = SignedUserInfo { iss: url_builder.oidc_issuer().to_string(), diff --git a/crates/jose/src/constraints.rs b/crates/jose/src/constraints.rs index 57373452..51dfcbe9 100644 --- a/crates/jose/src/constraints.rs +++ b/crates/jose/src/constraints.rs @@ -76,11 +76,15 @@ pub enum ConstraintDecision { } pub trait Constrainable { - /// List of available algorithms for this key - fn algs(&self) -> Option> { + fn alg(&self) -> Option { None } + /// List of available algorithms for this key + fn algs(&self) -> &[JsonWebSignatureAlg] { + &[] + } + /// Key ID (`kid`) of this key fn kid(&self) -> Option<&str> { None @@ -99,25 +103,36 @@ impl<'a> Constraint<'a> { fn decide(&self, constrainable: &T) -> ConstraintDecision { match self { Constraint::Alg { constraint_alg } => { - if let Some(algs) = constrainable.algs() { - if algs.contains(constraint_alg) { + // If the constrainable has one specific alg defined, use that + if let Some(alg) = constrainable.alg() { + if alg == *constraint_alg { ConstraintDecision::Positive } else { ConstraintDecision::Negative } - } else { + // If not, check that the requested alg is valid for this + // constrainable + } else if constrainable.algs().contains(constraint_alg) { ConstraintDecision::Neutral + } else { + ConstraintDecision::Negative } } Constraint::Algs { constraint_algs } => { - if let Some(algs) = constrainable.algs() { - if algs.iter().any(|alg| constraint_algs.contains(alg)) { + if let Some(alg) = constrainable.alg() { + if constraint_algs.contains(&alg) { ConstraintDecision::Positive } else { ConstraintDecision::Negative } - } else { + } else if constrainable + .algs() + .iter() + .any(|alg| constraint_algs.contains(alg)) + { ConstraintDecision::Neutral + } else { + ConstraintDecision::Negative } } Constraint::Kid { constraint_kid } => { diff --git a/crates/jose/src/jwa/rsa.rs b/crates/jose/src/jwa/rsa.rs index e9b1b100..661c8c57 100644 --- a/crates/jose/src/jwa/rsa.rs +++ b/crates/jose/src/jwa/rsa.rs @@ -17,7 +17,7 @@ use super::signature::Signature; -pub trait RsaHashIdentifier { +pub(crate) trait RsaHashIdentifier { const HASH: rsa::Hash; } diff --git a/crates/jose/src/jwk/mod.rs b/crates/jose/src/jwk/mod.rs index 9c570079..baf96633 100644 --- a/crates/jose/src/jwk/mod.rs +++ b/crates/jose/src/jwk/mod.rs @@ -27,7 +27,7 @@ use serde_with::{ }; use url::Url; -use crate::constraints::Constrainable; +use crate::constraints::{Constrainable, Constraint, ConstraintSet}; pub(crate) mod private_parameters; pub(crate) mod public_parameters; @@ -39,9 +39,11 @@ pub use self::{ pub trait ParametersInfo { fn kty(&self) -> JsonWebKeyType; - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg]; + fn possible_algs(&self) -> &[JsonWebSignatureAlg]; } +/// An utilitary trait to figure out the [`JsonWebKeyEcEllipticCurve`] value for +/// elliptic curves trait JwkEcCurve { const CRV: JsonWebKeyEcEllipticCurve; } @@ -239,12 +241,12 @@ where self.parameters.kty() } - fn algs(&self) -> Option> { - if let Some(alg) = self.alg { - Some(vec![alg]) - } else { - Some(self.parameters.possible_algs().to_vec()) - } + fn algs(&self) -> &[JsonWebSignatureAlg] { + self.parameters.possible_algs() + } + + fn alg(&self) -> Option { + self.alg } fn use_(&self) -> Option { @@ -293,6 +295,55 @@ impl

JsonWebKeySet

{ pub fn new(keys: Vec>) -> Self { Self { keys } } + + /// Find the best key given the constraints + #[must_use] + pub fn find_key(&self, constraints: &ConstraintSet) -> Option<&JsonWebKey

> + where + P: ParametersInfo, + { + constraints.filter(&self.keys).pop() + } + + /// Find the list of keys which match the given constraints + #[must_use] + pub fn find_keys(&self, constraints: &ConstraintSet) -> Vec<&JsonWebKey

> + where + P: ParametersInfo, + { + constraints.filter(&self.keys) + } + + /// Find a key for the given algorithm. Returns `None` if no suitable key + /// was found. + #[must_use] + pub fn signing_key_for_algorithm(&self, alg: JsonWebSignatureAlg) -> Option<&JsonWebKey

> + where + P: ParametersInfo, + { + let constraints = ConstraintSet::new([ + Constraint::alg(alg), + Constraint::use_(mas_iana::jose::JsonWebKeyUse::Sig), + ]); + self.find_key(&constraints) + } + + /// Get a list of available signing algorithms for this [`Keystore`] + #[must_use] + pub fn available_signing_algorithms(&self) -> Vec + where + P: ParametersInfo, + { + let mut algs: Vec<_> = self + .keys + .iter() + .flat_map(|key| key.params().possible_algs()) + .copied() + .collect(); + algs.sort(); + algs.dedup(); + algs + } } impl

FromIterator> for JsonWebKeySet

{ diff --git a/crates/jose/src/jwk/private_parameters.rs b/crates/jose/src/jwk/private_parameters.rs index ee0c62aa..c86de9f1 100644 --- a/crates/jose/src/jwk/private_parameters.rs +++ b/crates/jose/src/jwk/private_parameters.rs @@ -87,7 +87,7 @@ impl ParametersInfo for JsonWebKeyPrivateParameters { } } - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg] { + fn possible_algs(&self) -> &[JsonWebSignatureAlg] { match self { JsonWebKeyPrivateParameters::Oct(p) => p.possible_algs(), JsonWebKeyPrivateParameters::Rsa(p) => p.possible_algs(), @@ -128,7 +128,7 @@ impl ParametersInfo for OctPrivateParameters { JsonWebKeyType::Oct } - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg] { + fn possible_algs(&self) -> &[JsonWebSignatureAlg] { &[ JsonWebSignatureAlg::Hs256, JsonWebSignatureAlg::Hs384, @@ -190,7 +190,7 @@ impl ParametersInfo for RsaPrivateParameters { JsonWebKeyType::Rsa } - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg] { + fn possible_algs(&self) -> &[JsonWebSignatureAlg] { &[ JsonWebSignatureAlg::Rs256, JsonWebSignatureAlg::Rs384, @@ -330,7 +330,7 @@ impl ParametersInfo for EcPrivateParameters { JsonWebKeyType::Ec } - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg] { + fn possible_algs(&self) -> &[JsonWebSignatureAlg] { match self.crv { JsonWebKeyEcEllipticCurve::P256 => &[JsonWebSignatureAlg::Es256], JsonWebKeyEcEllipticCurve::P384 => &[JsonWebSignatureAlg::Es384], @@ -471,7 +471,7 @@ impl ParametersInfo for OkpPrivateParameters { JsonWebKeyType::Okp } - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg] { + fn possible_algs(&self) -> &[JsonWebSignatureAlg] { &[JsonWebSignatureAlg::EdDsa] } } diff --git a/crates/jose/src/jwk/public_parameters.rs b/crates/jose/src/jwk/public_parameters.rs index ab3a875a..b448b49a 100644 --- a/crates/jose/src/jwk/public_parameters.rs +++ b/crates/jose/src/jwk/public_parameters.rs @@ -74,7 +74,7 @@ impl ParametersInfo for JsonWebKeyPublicParameters { } } - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg] { + fn possible_algs(&self) -> &[JsonWebSignatureAlg] { match self { JsonWebKeyPublicParameters::Rsa(p) => p.possible_algs(), JsonWebKeyPublicParameters::Ec(p) => p.possible_algs(), @@ -100,7 +100,7 @@ impl ParametersInfo for RsaPublicParameters { JsonWebKeyType::Rsa } - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg] { + fn possible_algs(&self) -> &[JsonWebSignatureAlg] { &[ JsonWebSignatureAlg::Rs256, JsonWebSignatureAlg::Rs384, @@ -147,7 +147,7 @@ impl ParametersInfo for EcPublicParameters { JsonWebKeyType::Ec } - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg] { + fn possible_algs(&self) -> &[JsonWebSignatureAlg] { match self.crv { JsonWebKeyEcEllipticCurve::P256 => &[JsonWebSignatureAlg::Es256], JsonWebKeyEcEllipticCurve::P384 => &[JsonWebSignatureAlg::Es384], @@ -172,7 +172,7 @@ impl ParametersInfo for OkpPublicParameters { JsonWebKeyType::Okp } - fn possible_algs(&self) -> &'static [JsonWebSignatureAlg] { + fn possible_algs(&self) -> &[JsonWebSignatureAlg] { &[JsonWebSignatureAlg::EdDsa] } } diff --git a/crates/keystore/src/lib.rs b/crates/keystore/src/lib.rs index c075a73a..968e0fed 100644 --- a/crates/keystore/src/lib.rs +++ b/crates/keystore/src/lib.rs @@ -23,13 +23,12 @@ )] #![warn(clippy::pedantic)] -use std::sync::Arc; +use std::{ops::Deref, sync::Arc}; use der::{zeroize::Zeroizing, Decode}; use mas_iana::jose::{JsonWebKeyType, JsonWebSignatureAlg}; pub use mas_jose::jwk::{JsonWebKey, JsonWebKeySet}; use mas_jose::{ - constraints::{Constraint, ConstraintSet}, jwa::{AsymmetricSigningKey, AsymmetricVerifyingKey}, jwk::{JsonWebKeyPublicParameters, ParametersInfo, PublicJsonWebKeySet}, }; @@ -395,7 +394,7 @@ impl PrivateKey { /// # Errors /// /// Returns an error if the key is not suited for the selected algorithm - pub fn verifier_for_alg( + pub fn verifying_key_for_alg( &self, alg: JsonWebSignatureAlg, ) -> Result { @@ -437,7 +436,7 @@ impl PrivateKey { /// # Errors /// /// Returns an error if the key is not suited for the selected algorithm - pub fn signer_for_alg( + pub fn signing_key_for_alg( &self, alg: JsonWebSignatureAlg, ) -> Result { @@ -593,44 +592,12 @@ impl Keystore { }) .collect() } +} - /// Find the best key given the constraints - #[must_use] - pub fn find_key(&self, constraints: &ConstraintSet) -> Option<&JsonWebKey> { - constraints.filter(self.keys.iter()).pop() - } +impl Deref for Keystore { + type Target = JsonWebKeySet; - /// Find the list of keys which match the givent constraints - #[must_use] - pub fn find_keys(&self, constraints: &ConstraintSet) -> Vec<&JsonWebKey> { - constraints.filter(self.keys.iter()) - } - - /// Find a key for the given algorithm. Returns `None` if no suitable key - /// was found. - #[must_use] - pub fn signing_key_for_algorithm( - &self, - alg: JsonWebSignatureAlg, - ) -> Option<&JsonWebKey> { - let constraints = ConstraintSet::new([ - Constraint::alg(alg), - Constraint::use_(mas_iana::jose::JsonWebKeyUse::Sig), - ]); - self.find_key(&constraints) - } - - /// Get a list of available signing algorithms for this [`Keystore`] - #[must_use] - pub fn available_signing_algorithms(&self) -> Vec { - let mut algs: Vec<_> = self - .keys - .iter() - .flat_map(|key| key.params().possible_algs()) - .copied() - .collect(); - algs.sort(); - algs.dedup(); - algs + fn deref(&self) -> &Self::Target { + &self.keys } } diff --git a/crates/keystore/tests/load.rs b/crates/keystore/tests/load.rs index c2fe4696..2d6d8a52 100644 --- a/crates/keystore/tests/load.rs +++ b/crates/keystore/tests/load.rs @@ -27,6 +27,18 @@ macro_rules! plain_test { let bytes = include_bytes!(concat!("./keys/", $path)); let key = PrivateKey::load(bytes).unwrap(); assert!(matches!(key, PrivateKey::$kind(_)), "wrong key type"); + + let algs = key.possible_algs(); + assert_ne!(algs.len(), 0); + + for &alg in algs { + let header = JsonWebSignatureHeader::new(alg); + let payload = "hello"; + let signer = key.signing_key_for_alg(alg).unwrap(); + let jwt = Jwt::sign(header, payload, &signer).unwrap(); + let verifier = key.verifying_key_for_alg(alg).unwrap(); + jwt.verify(&verifier).unwrap(); + } } }; } @@ -45,9 +57,9 @@ macro_rules! enc_test { for &alg in algs { let header = JsonWebSignatureHeader::new(alg); let payload = "hello"; - let signer = key.signer_for_alg(alg).unwrap(); + let signer = key.signing_key_for_alg(alg).unwrap(); let jwt = Jwt::sign(header, payload, &signer).unwrap(); - let verifier = key.verifier_for_alg(alg).unwrap(); + let verifier = key.verifying_key_for_alg(alg).unwrap(); jwt.verify(&verifier).unwrap(); } }