diff --git a/Cargo.lock b/Cargo.lock index c811f070..b0893b4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1571,6 +1571,7 @@ dependencies = [ "indoc", "mas-config", "mas-data-model", + "mas-iana", "mas-jose", "mas-static-files", "mas-storage", @@ -1631,6 +1632,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "hmac 0.12.0", + "mas-iana", "p256", "pkcs1", "pkcs8", @@ -1954,6 +1956,7 @@ dependencies = [ "indoc", "itertools", "language-tags", + "mas-iana", "parse-display", "serde", "serde_json", diff --git a/crates/handlers/Cargo.toml b/crates/handlers/Cargo.toml index 3b9d5c49..03523340 100644 --- a/crates/handlers/Cargo.toml +++ b/crates/handlers/Cargo.toml @@ -59,6 +59,7 @@ mas-static-files = { path = "../static-files" } mas-storage = { path = "../storage" } mas-warp-utils = { path = "../warp-utils" } mas-jose = { path = "../jose" } +mas-iana = { path = "../iana" } [dev-dependencies] indoc = "1.0.3" diff --git a/crates/handlers/src/oauth2/discovery.rs b/crates/handlers/src/oauth2/discovery.rs index 36603535..73642afc 100644 --- a/crates/handlers/src/oauth2/discovery.rs +++ b/crates/handlers/src/oauth2/discovery.rs @@ -15,15 +15,19 @@ use std::collections::HashSet; use mas_config::OAuth2Config; +use mas_jose::{JsonWebSignatureAlgorithm, SigningKeystore}; use oauth2_types::{ - oidc::{ClaimType, Metadata, SigningAlgorithm, SubjectType}, + oidc::{ClaimType, Metadata, SubjectType}, pkce::CodeChallengeMethod, requests::{ClientAuthenticationMethod, Display, GrantType, ResponseMode}, }; use warp::{filters::BoxedFilter, Filter, Reply}; #[allow(clippy::too_many_lines)] -pub(super) fn filter(config: &OAuth2Config) -> BoxedFilter<(Box,)> { +pub(super) fn filter( + key_store: impl SigningKeystore, + config: &OAuth2Config, +) -> BoxedFilter<(Box,)> { let base = config.issuer.clone(); // This is how clients can authenticate @@ -39,25 +43,17 @@ pub(super) fn filter(config: &OAuth2Config) -> BoxedFilter<(Box,)> { let client_auth_signing_alg_values_supported = Some({ let mut s = HashSet::new(); - s.insert(SigningAlgorithm::Hs256); - s.insert(SigningAlgorithm::Hs384); - s.insert(SigningAlgorithm::Hs512); - s.insert(SigningAlgorithm::Rs256); - s.insert(SigningAlgorithm::Rs384); - s.insert(SigningAlgorithm::Rs512); + s.insert(JsonWebSignatureAlgorithm::Hs256); + s.insert(JsonWebSignatureAlgorithm::Hs384); + s.insert(JsonWebSignatureAlgorithm::Hs512); + s.insert(JsonWebSignatureAlgorithm::Rs256); + s.insert(JsonWebSignatureAlgorithm::Rs384); + s.insert(JsonWebSignatureAlgorithm::Rs512); s }); // This is how we can sign stuff - // TODO: query the signing store - let jwt_signing_alg_values_supported = Some({ - let mut s = HashSet::new(); - s.insert(SigningAlgorithm::Rs256); - s.insert(SigningAlgorithm::Rs384); - s.insert(SigningAlgorithm::Rs512); - s.insert(SigningAlgorithm::Es256); - s - }); + let jwt_signing_alg_values_supported = Some(key_store.supported_algorithms()); // Prepare all the endpoints let issuer = Some(base.clone()); diff --git a/crates/handlers/src/oauth2/mod.rs b/crates/handlers/src/oauth2/mod.rs index 01572b75..760c8f01 100644 --- a/crates/handlers/src/oauth2/mod.rs +++ b/crates/handlers/src/oauth2/mod.rs @@ -43,7 +43,7 @@ pub fn filter( oauth2_config: &OAuth2Config, cookies_config: &CookiesConfig, ) -> BoxedFilter<(impl Reply,)> { - let discovery = discovery(oauth2_config); + let discovery = discovery(key_store.as_ref(), oauth2_config); let keys = keys(key_store); let authorization = authorization(pool, templates, oauth2_config, cookies_config); let userinfo = userinfo(pool, oauth2_config); diff --git a/crates/jose/Cargo.toml b/crates/jose/Cargo.toml index c932229c..b130a607 100644 --- a/crates/jose/Cargo.toml +++ b/crates/jose/Cargo.toml @@ -31,3 +31,5 @@ thiserror = "1.0.30" tokio = { version = "1.15.0", features = ["macros", "rt", "sync"] } url = { version = "2.2.2", features = ["serde"] } zeroize = "1.4.3" + +mas-iana = { path = "../iana" } diff --git a/crates/jose/src/keystore/shared_secret.rs b/crates/jose/src/keystore/shared_secret.rs index 9f4467e2..af53445d 100644 --- a/crates/jose/src/keystore/shared_secret.rs +++ b/crates/jose/src/keystore/shared_secret.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashSet; + use anyhow::bail; use async_trait::async_trait; use hmac::{Hmac, Mac}; @@ -34,6 +36,16 @@ impl<'a> SharedSecret<'a> { #[async_trait] impl<'a> SigningKeystore for &SharedSecret<'a> { + fn supported_algorithms(self) -> HashSet { + let mut algorithms = HashSet::with_capacity(3); + + algorithms.insert(JsonWebSignatureAlgorithm::Hs256); + algorithms.insert(JsonWebSignatureAlgorithm::Hs384); + algorithms.insert(JsonWebSignatureAlgorithm::Hs512); + + algorithms + } + async fn prepare_header(self, alg: JsonWebSignatureAlgorithm) -> anyhow::Result { if !matches!( alg, diff --git a/crates/jose/src/keystore/static_keystore.rs b/crates/jose/src/keystore/static_keystore.rs index ae2df453..2151f2eb 100644 --- a/crates/jose/src/keystore/static_keystore.rs +++ b/crates/jose/src/keystore/static_keystore.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use anyhow::bail; use async_trait::async_trait; @@ -126,6 +126,26 @@ impl StaticKeystore { #[async_trait] impl SigningKeystore for &StaticKeystore { + fn supported_algorithms(self) -> HashSet { + let has_rsa = !self.rsa_keys.is_empty(); + let has_es256 = !self.es256_keys.is_empty(); + + let capacity = (if has_rsa { 3 } else { 0 }) + (if has_es256 { 1 } else { 0 }); + let mut algorithms = HashSet::with_capacity(capacity); + + if has_rsa { + algorithms.insert(JsonWebSignatureAlgorithm::Rs256); + algorithms.insert(JsonWebSignatureAlgorithm::Rs384); + algorithms.insert(JsonWebSignatureAlgorithm::Rs512); + } + + if has_es256 { + algorithms.insert(JsonWebSignatureAlgorithm::Es256); + } + + algorithms + } + async fn prepare_header(self, alg: JsonWebSignatureAlgorithm) -> anyhow::Result { let header = JwtHeader::new(alg); diff --git a/crates/jose/src/keystore/traits.rs b/crates/jose/src/keystore/traits.rs index 9ebe7023..dfd55493 100644 --- a/crates/jose/src/keystore/traits.rs +++ b/crates/jose/src/keystore/traits.rs @@ -12,12 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashSet; + use async_trait::async_trait; use crate::{iana::JsonWebSignatureAlgorithm, JsonWebKeySet, JwtHeader}; #[async_trait] pub trait SigningKeystore { + fn supported_algorithms(self) -> HashSet; + async fn prepare_header(self, alg: JsonWebSignatureAlgorithm) -> anyhow::Result; async fn sign(self, header: &JwtHeader, msg: &[u8]) -> anyhow::Result>; diff --git a/crates/jose/src/lib.rs b/crates/jose/src/lib.rs index c97df8f9..16c7177b 100644 --- a/crates/jose/src/lib.rs +++ b/crates/jose/src/lib.rs @@ -19,8 +19,9 @@ #![allow(clippy::missing_errors_doc)] #![allow(clippy::module_name_repetitions)] +pub(crate) use mas_iana::jose as iana; + pub mod claims; -pub(crate) mod iana; pub(crate) mod jwk; pub(crate) mod jwt; mod keystore; diff --git a/crates/oauth2-types/Cargo.toml b/crates/oauth2-types/Cargo.toml index 3ef2c8e9..f34dbb4b 100644 --- a/crates/oauth2-types/Cargo.toml +++ b/crates/oauth2-types/Cargo.toml @@ -19,3 +19,5 @@ sha2 = "0.10.0" data-encoding = "2.3.2" thiserror = "1.0.30" itertools = "0.10.3" + +mas-iana = { path = "../iana" } diff --git a/crates/oauth2-types/src/oidc.rs b/crates/oauth2-types/src/oidc.rs index 2b5c2e97..d6aa1bb1 100644 --- a/crates/oauth2-types/src/oidc.rs +++ b/crates/oauth2-types/src/oidc.rs @@ -14,6 +14,7 @@ use std::collections::HashSet; +use mas_iana::jose::{JsonWebEncryptionAlgorithm, JsonWebSignatureAlgorithm}; use serde::Serialize; use serde_with::skip_serializing_none; use url::Url; @@ -38,28 +39,6 @@ pub enum ClaimType { Distributed, } -#[derive(Serialize, Clone, Copy, PartialEq, Eq, Hash)] -#[serde(rename_all = "UPPERCASE")] -pub enum SigningAlgorithm { - #[serde(rename = "none")] - None, - Hs256, - Hs384, - Hs512, - Ps256, - Ps384, - Ps512, - Rs256, - Rs384, - Rs512, - Es256, - Es256K, - Es384, - Es512, - #[serde(rename = "EcDSA")] - EcDsa, -} - /// Authorization server metadata, as described by the /// [IANA registry](https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#authorization-server-metadata) #[skip_serializing_none] @@ -104,7 +83,8 @@ pub struct Metadata { /// JSON array containing a list of the JWS signing algorithms supported by /// the token endpoint for the signature on the JWT used to authenticate the /// client at the token endpoint. - pub token_endpoint_auth_signing_alg_values_supported: Option>, + pub token_endpoint_auth_signing_alg_values_supported: + Option>, /// URL of a page containing human-readable information that developers /// might want or need to know when using the authorization server. @@ -135,7 +115,8 @@ pub struct Metadata { /// JSON array containing a list of the JWS signing algorithms supported by /// the revocation endpoint for the signature on the JWT used to /// authenticate the client at the revocation endpoint. - pub revocation_endpoint_auth_signing_alg_values_supported: Option>, + pub revocation_endpoint_auth_signing_alg_values_supported: + Option>, /// URL of the authorization server's OAuth 2.0 introspection endpoint. pub introspection_endpoint: Option, @@ -147,7 +128,8 @@ pub struct Metadata { /// JSON array containing a list of the JWS signing algorithms supported by /// the introspection endpoint for the signature on the JWT used to /// authenticate the client at the introspection endpoint. - pub introspection_endpoint_auth_signing_alg_values_supported: Option>, + pub introspection_endpoint_auth_signing_alg_values_supported: + Option>, /// PKCE code challenge methods supported by this authorization server. pub code_challenge_methods_supported: Option>, @@ -165,45 +147,39 @@ pub struct Metadata { /// JSON array containing a list of the JWS "alg" values supported by the OP /// for the ID Token. - pub id_token_signing_alg_values_supported: Option>, + pub id_token_signing_alg_values_supported: Option>, - // TODO: type /// JSON array containing a list of the JWE "alg" values supported by the OP /// for the ID Token. - pub id_token_encryption_alg_values_supported: Option>, + pub id_token_encryption_alg_values_supported: Option>, - // TODO: type /// JSON array containing a list of the JWE "enc" values supported by the OP /// for the ID Token. - pub id_token_encryption_enc_values_supported: Option>, + pub id_token_encryption_enc_values_supported: Option>, /// JSON array containing a list of the JWS "alg" values supported by the /// UserInfo Endpoint. - pub userinfo_signing_alg_values_supported: Option>, + pub userinfo_signing_alg_values_supported: Option>, - // TODO: type /// JSON array containing a list of the JWE "alg" values supported by the /// UserInfo Endpoint. - pub userinfo_encryption_alg_values_supported: Option>, + pub userinfo_encryption_alg_values_supported: Option>, - // TODO: type /// JSON array containing a list of the JWE "enc" values supported by the /// UserInfo Endpoint. - pub userinfo_encryption_enc_values_supported: Option>, + pub userinfo_encryption_enc_values_supported: Option>, /// JSON array containing a list of the JWS "alg" values supported by the OP /// for Request Objects. - pub request_object_signing_alg_values_supported: Option>, + pub request_object_signing_alg_values_supported: Option>, - // TODO: type /// JSON array containing a list of the JWE "alg" values supported by the OP /// for Request Objects. - pub request_object_encryption_alg_values_supported: Option>, + pub request_object_encryption_alg_values_supported: Option>, - // TODO: type /// JSON array containing a list of the JWE "enc" values supported by the OP /// for Request Objects. - pub request_object_encryption_enc_values_supported: Option>, + pub request_object_encryption_enc_values_supported: Option>, /// JSON array containing a list of the "display" parameter values that the /// OpenID Provider supports.