From d095db0b943fa208baae298d2e1172e34aa995bc Mon Sep 17 00:00:00 2001 From: Nander Stabel Date: Wed, 29 May 2024 10:40:14 +0200 Subject: [PATCH] feat: add selelect functionality for subject syntax type --- oid4vc-core/src/client_metadata.rs | 3 + oid4vc-core/src/openid4vc_extension.rs | 7 ++ oid4vc-core/src/subject_syntax_type.rs | 8 ++ oid4vc-manager/src/managers/provider.rs | 26 ++++- oid4vc-manager/src/methods/key_method.rs | 3 +- .../tests/oid4vci/authorization_code.rs | 2 +- .../tests/oid4vci/pre_authorized_code.rs | 2 +- oid4vc-manager/tests/oid4vp/implicit.rs | 8 +- oid4vc-manager/tests/siopv2/implicit.rs | 3 +- oid4vci/src/wallet/mod.rs | 103 ++++++++++++------ oid4vp/src/authorization_request.rs | 3 +- oid4vp/src/oid4vp.rs | 49 ++++++++- siopv2/src/provider.rs | 64 ++++++++--- siopv2/src/siopv2.rs | 32 ++++++ 14 files changed, 247 insertions(+), 66 deletions(-) diff --git a/oid4vc-core/src/client_metadata.rs b/oid4vc-core/src/client_metadata.rs index a5aea690..59d8dab2 100644 --- a/oid4vc-core/src/client_metadata.rs +++ b/oid4vc-core/src/client_metadata.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use std::collections::HashMap; use url::Url; /// [`ClientMetadata`] is a request parameter used by a [`crate::RelyingParty`] to communicate its capabilities to a @@ -16,6 +17,8 @@ pub enum ClientMetadataResource { /// expanded with Extensions and profiles. #[serde(flatten)] extension: T, + #[serde(flatten, skip_serializing_if = "HashMap::is_empty")] + other: HashMap, }, ClientMetadataUri(String), } diff --git a/oid4vc-core/src/openid4vc_extension.rs b/oid4vc-core/src/openid4vc_extension.rs index 32f8807e..7499943e 100644 --- a/oid4vc-core/src/openid4vc_extension.rs +++ b/oid4vc-core/src/openid4vc_extension.rs @@ -43,6 +43,13 @@ pub trait Extension: Serialize + PartialEq + Sized + std::fmt::Debug + Clone + S async { Err(anyhow::anyhow!("Not implemented.")) } } + fn get_relying_party_supported_syntax_types( + _authorization_request: &::Parameters, + ) -> impl Future>> { + // Will be overwritten by the extension. + async { Err(anyhow::anyhow!("Not implemented.")) } + } + fn build_authorization_response( _jwts: Vec, _user_input: ::Input, diff --git a/oid4vc-core/src/subject_syntax_type.rs b/oid4vc-core/src/subject_syntax_type.rs index 9640b4e1..d79e4f02 100644 --- a/oid4vc-core/src/subject_syntax_type.rs +++ b/oid4vc-core/src/subject_syntax_type.rs @@ -40,6 +40,14 @@ impl TryFrom<&str> for SubjectSyntaxType { } } +impl TryFrom for SubjectSyntaxType { + type Error = anyhow::Error; + + fn try_from(value: String) -> Result { + SubjectSyntaxType::from_str(value.as_str()) + } +} + impl From for SubjectSyntaxType { fn from(did_method: DidMethod) -> Self { SubjectSyntaxType::Did(did_method) diff --git a/oid4vc-manager/src/managers/provider.rs b/oid4vc-manager/src/managers/provider.rs index 5f07f25b..aac51e60 100644 --- a/oid4vc-manager/src/managers/provider.rs +++ b/oid4vc-manager/src/managers/provider.rs @@ -18,11 +18,11 @@ pub struct ProviderManager { impl ProviderManager { pub fn new( subject: Arc, - default_subject_syntax_type: impl TryInto, + supported_subject_syntax_types: Vec>, supported_signing_algorithms: Vec, ) -> Result { Ok(Self { - provider: Provider::new(subject, default_subject_syntax_type, supported_signing_algorithms)?, + provider: Provider::new(subject, supported_subject_syntax_types, supported_signing_algorithms)?, }) } @@ -30,6 +30,24 @@ impl ProviderManager { self.provider.validate_request(authorization_request).await } + pub async fn get_matching_signing_algorithm( + &self, + authorization_request: &AuthorizationRequest>, + ) -> Result { + self.provider + .get_matching_signing_algorithm(authorization_request) + .await + } + + pub async fn get_matching_subject_syntax_type( + &self, + authorization_request: &AuthorizationRequest>, + ) -> Result { + self.provider + .get_matching_subject_syntax_type(authorization_request) + .await + } + pub async fn generate_response( &self, authorization_request: &AuthorizationRequest>, @@ -45,7 +63,7 @@ impl ProviderManager { self.provider.send_response(authorization_response).await } - pub fn default_subject_syntax_type(&self) -> &SubjectSyntaxType { - &self.provider.default_subject_syntax_type + pub fn default_subject_syntax_types(&self) -> &Vec { + &self.provider.supported_subject_syntax_types } } diff --git a/oid4vc-manager/src/methods/key_method.rs b/oid4vc-manager/src/methods/key_method.rs index d20d4011..7c59f7e4 100644 --- a/oid4vc-manager/src/methods/key_method.rs +++ b/oid4vc-manager/src/methods/key_method.rs @@ -123,7 +123,8 @@ mod tests { let subject = KeySubject::new(); // Create a new provider manager. - let provider_manager = ProviderManager::new(Arc::new(subject), "did:key", vec![Algorithm::EdDSA]).unwrap(); + let provider_manager = + ProviderManager::new(Arc::new(subject), vec!["did:key"], vec![Algorithm::EdDSA]).unwrap(); // Get a new SIOP authorization_request with response mode `direct_post` for cross-device communication. let request_url = "\ diff --git a/oid4vc-manager/tests/oid4vci/authorization_code.rs b/oid4vc-manager/tests/oid4vci/authorization_code.rs index 53d8344b..57e24663 100644 --- a/oid4vc-manager/tests/oid4vci/authorization_code.rs +++ b/oid4vc-manager/tests/oid4vci/authorization_code.rs @@ -42,7 +42,7 @@ async fn test_authorization_code_flow() { let subject_did = subject.identifier("did:key", Algorithm::EdDSA).await.unwrap(); // Create a new wallet. - let wallet: Wallet = Wallet::new(Arc::new(subject), "did:key", vec![Algorithm::EdDSA]).unwrap(); + let wallet: Wallet = Wallet::new(Arc::new(subject), vec!["did:key"], vec![Algorithm::EdDSA]).unwrap(); // Get the credential issuer url. let credential_issuer_url = credential_issuer diff --git a/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs b/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs index 976362dd..5cc41eb5 100644 --- a/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs +++ b/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs @@ -46,7 +46,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference let subject_did = subject.identifier("did:key", Algorithm::EdDSA).await.unwrap(); // Create a new wallet. - let wallet: Wallet = Wallet::new(Arc::new(subject), "did:key", vec![Algorithm::EdDSA]).unwrap(); + let wallet: Wallet = Wallet::new(Arc::new(subject), vec!["did:key"], vec![Algorithm::EdDSA]).unwrap(); // Get the credential offer url. let credential_offer_query = credential_issuer diff --git a/oid4vc-manager/tests/oid4vp/implicit.rs b/oid4vc-manager/tests/oid4vp/implicit.rs index fc62f91f..c0a9f9f6 100644 --- a/oid4vc-manager/tests/oid4vp/implicit.rs +++ b/oid4vc-manager/tests/oid4vp/implicit.rs @@ -19,7 +19,7 @@ use oid4vp::{ ClaimFormatDesignation, ClaimFormatProperty, PresentationDefinition, }; use serde_json::json; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; lazy_static! { pub static ref PRESENTATION_DEFINITION: PresentationDefinition = serde_json::from_value(json!( @@ -109,13 +109,17 @@ async fn test_implicit_flow() { .into_iter() .collect(), }, + other: HashMap::from_iter(vec![( + "subject_syntax_types_supported".to_string(), + json!(vec!["did:key".to_string(),]), + )]), }) .nonce("nonce".to_string()) .build() .unwrap(); // Create a provider manager and validate the authorization request. - let provider_manager = ProviderManager::new(subject, "did:key", vec![Algorithm::EdDSA]).unwrap(); + let provider_manager = ProviderManager::new(subject, vec!["did:key"], vec![Algorithm::EdDSA]).unwrap(); // Create a new verifiable credential. let verifiable_credential = VerifiableCredentialJwt::builder() diff --git a/oid4vc-manager/tests/siopv2/implicit.rs b/oid4vc-manager/tests/siopv2/implicit.rs index 2782df19..3e895756 100644 --- a/oid4vc-manager/tests/siopv2/implicit.rs +++ b/oid4vc-manager/tests/siopv2/implicit.rs @@ -141,6 +141,7 @@ async fn test_implicit_flow(#[case] did_method: &str) { subject_syntax_types_supported: vec![SubjectSyntaxType::Did(DidMethod::from_str(did_method).unwrap())], id_token_signed_response_alg: Some(Algorithm::EdDSA), }, + other: Default::default(), }) .claims( r#"{ @@ -185,7 +186,7 @@ async fn test_implicit_flow(#[case] did_method: &str) { }; // Create a new provider manager. - let provider_manager = ProviderManager::new(Arc::new(subject), did_method, vec![Algorithm::EdDSA]).unwrap(); + let provider_manager = ProviderManager::new(Arc::new(subject), vec![did_method], vec![Algorithm::EdDSA]).unwrap(); // Create a new RequestUrl which includes a `request_uri` pointing to the mock server's `request_uri` endpoint. let authorization_request = AuthorizationRequest:: { diff --git a/oid4vci/src/wallet/mod.rs b/oid4vci/src/wallet/mod.rs index 6c6ab471..f2895c8e 100644 --- a/oid4vci/src/wallet/mod.rs +++ b/oid4vci/src/wallet/mod.rs @@ -11,7 +11,7 @@ use crate::credential_request::{BatchCredentialRequest, CredentialRequest}; use crate::credential_response::BatchCredentialResponse; use crate::proof::{KeyProofType, ProofType}; use crate::{credential_response::CredentialResponse, token_request::TokenRequest, token_response::TokenResponse}; -use anyhow::Result; +use anyhow::{anyhow, Result}; use jsonwebtoken::Algorithm; use oid4vc_core::authentication::subject::SigningSubject; use oid4vc_core::SubjectSyntaxType; @@ -20,13 +20,14 @@ use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::RetryTransientMiddleware; use serde::de::DeserializeOwned; +use std::str::FromStr; pub struct Wallet> where CFC: CredentialFormatCollection, { pub subject: SigningSubject, - pub default_subject_syntax_type: SubjectSyntaxType, + pub supported_subject_syntax_types: Vec, pub client: ClientWithMiddleware, pub proof_signing_alg_values_supported: Vec, phantom: std::marker::PhantomData, @@ -35,7 +36,7 @@ where impl Wallet { pub fn new( subject: SigningSubject, - default_subject_syntax_type: impl TryInto, + supported_subject_syntax_types: Vec>, proof_signing_alg_values_supported: Vec, ) -> anyhow::Result { let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5); @@ -44,9 +45,14 @@ impl Wallet { .build(); Ok(Self { subject, - default_subject_syntax_type: default_subject_syntax_type - .try_into() - .map_err(|_| anyhow::anyhow!("Invalid did method"))?, + supported_subject_syntax_types: supported_subject_syntax_types + .into_iter() + .map(|subject_syntax_type| { + subject_syntax_type + .try_into() + .map_err(|_| anyhow::anyhow!("Invalid did method.")) + }) + .collect::>()?, client, proof_signing_alg_values_supported, phantom: std::marker::PhantomData, @@ -121,7 +127,11 @@ impl Wallet { client_id: self .subject .identifier( - &self.default_subject_syntax_type.to_string(), + &self + .supported_subject_syntax_types + .first() + .map(ToString::to_string) + .ok_or(anyhow!("No supported subject syntax types found."))?, self.proof_signing_alg_values_supported[0], ) .await?, @@ -148,13 +158,10 @@ impl Wallet { .map_err(|e| e.into()) } - pub async fn get_credential( + fn select_signing_algorithm( &self, - credential_issuer_metadata: CredentialIssuerMetadata, - token_response: &TokenResponse, credential_configuration: &CredentialConfigurationsSupportedObject, - ) -> Result { - let credential_format = credential_configuration.credential_format.to_owned(); + ) -> Result { let credential_issuer_proof_signing_alg_values_supported = &credential_configuration .proof_types_supported .get(&ProofType::Jwt) @@ -163,26 +170,60 @@ impl Wallet { ))? .proof_signing_alg_values_supported; - let signing_algorithm = self - .proof_signing_alg_values_supported + self.proof_signing_alg_values_supported .iter() .find(|supported_algorithm| { credential_issuer_proof_signing_alg_values_supported.contains(supported_algorithm) }) - .ok_or(anyhow::anyhow!("No supported signing algorithm found."))?; + .cloned() + .ok_or(anyhow::anyhow!("No supported signing algorithm found.")) + } + + fn select_subject_syntax_type( + &self, + credential_configuration: &CredentialConfigurationsSupportedObject, + ) -> Result { + let credential_issuer_cryptographic_binding_methods_supported: Vec = + credential_configuration + .cryptographic_binding_methods_supported + .iter() + .filter_map(|binding_method| SubjectSyntaxType::from_str(binding_method).ok()) + .collect(); + + self.supported_subject_syntax_types + .iter() + .find(|supported_syntax_type| { + credential_issuer_cryptographic_binding_methods_supported.contains(supported_syntax_type) + }) + .cloned() + .ok_or(anyhow::anyhow!("No supported subject syntax types found.")) + } + + pub async fn get_credential( + &self, + credential_issuer_metadata: CredentialIssuerMetadata, + token_response: &TokenResponse, + credential_configuration: &CredentialConfigurationsSupportedObject, + ) -> Result { + let credential_format = credential_configuration.credential_format.to_owned(); + + let signing_algorithm = self.select_signing_algorithm(credential_configuration)?; + let subject_syntax_type = self.select_subject_syntax_type(credential_configuration)?; + let credential_request = CredentialRequest { credential_format, proof: Some( KeyProofType::builder() .proof_type(ProofType::Jwt) - .algorithm(*signing_algorithm) + .algorithm(signing_algorithm) .signer(self.subject.clone()) .iss( self.subject - .identifier(&self.default_subject_syntax_type.to_string(), *signing_algorithm) + .identifier(&subject_syntax_type.to_string(), signing_algorithm) .await?, ) .aud(credential_issuer_metadata.credential_issuer) + // TODO: Use current time. .iat(1571324800) // TODO: so is this REQUIRED or OPTIONAL? .nonce( @@ -192,7 +233,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) - .subject_syntax_type(self.default_subject_syntax_type.to_string()) + .subject_syntax_type(&subject_syntax_type.to_string()) .build() .await?, ), @@ -216,35 +257,25 @@ impl Wallet { credential_configurations: &[CredentialConfigurationsSupportedObject], ) -> Result { // TODO: This needs to be fixed since this current implementation assumes that for all credentials the same Proof Type is supported. - let credential_issuer_proof_signing_alg_values_supported = &credential_configurations + let credential_configuration = credential_configurations .first() - .ok_or(anyhow::anyhow!("No credential configurations found."))? - .proof_types_supported - .get(&ProofType::Jwt) - .ok_or(anyhow::anyhow!( - "`jwt` proof type is missing from the `proof_types_supported` parameter" - ))? - .proof_signing_alg_values_supported; + .ok_or(anyhow::anyhow!("No credential configurations found."))?; - let signing_algorithm = self - .proof_signing_alg_values_supported - .iter() - .find(|supported_algorithm| { - credential_issuer_proof_signing_alg_values_supported.contains(supported_algorithm) - }) - .ok_or(anyhow::anyhow!("No supported signing algorithm found."))?; + let signing_algorithm = self.select_signing_algorithm(credential_configuration)?; + let subject_syntax_type = self.select_subject_syntax_type(credential_configuration)?; let proof = Some( KeyProofType::builder() .proof_type(ProofType::Jwt) - .algorithm(*signing_algorithm) + .algorithm(signing_algorithm) .signer(self.subject.clone()) .iss( self.subject - .identifier(&self.default_subject_syntax_type.to_string(), *signing_algorithm) + .identifier(&subject_syntax_type.to_string(), signing_algorithm) .await?, ) .aud(credential_issuer_metadata.credential_issuer) + // TODO: Use current time. .iat(1571324800) // TODO: so is this REQUIRED or OPTIONAL? .nonce( @@ -254,7 +285,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) - .subject_syntax_type(self.default_subject_syntax_type.to_string()) + .subject_syntax_type(subject_syntax_type.to_string()) .build() .await?, ); diff --git a/oid4vp/src/authorization_request.rs b/oid4vp/src/authorization_request.rs index 68c96744..49dd9f24 100644 --- a/oid4vp/src/authorization_request.rs +++ b/oid4vp/src/authorization_request.rs @@ -222,7 +222,8 @@ mod tests { ] .into_iter() .collect() - } + }, + other: HashMap::from_iter(vec![("application_type".to_string(), serde_json::json!("web"))]), }), }, json_example::("tests/examples/client_metadata/client_client_id_did.json") diff --git a/oid4vp/src/oid4vp.rs b/oid4vp/src/oid4vp.rs index eda06eb6..0e231ea9 100644 --- a/oid4vp/src/oid4vp.rs +++ b/oid4vp/src/oid4vp.rs @@ -21,6 +21,7 @@ use reqwest_middleware::ClientBuilder; use reqwest_retry::policies::ExponentialBackoff; use reqwest_retry::RetryTransientMiddleware; use serde::{Deserialize, Serialize}; +use std::str::FromStr; use std::sync::Arc; /// This is the [`RequestHandle`] for the [`OID4VP`] extension. @@ -89,10 +90,12 @@ impl Extension for OID4VP { Ok(vec![jwt]) } + // TODO: combine this function with `get_relying_party_supported_syntax_types`. async fn get_relying_party_supported_algorithms( authorization_request: &::Parameters, ) -> anyhow::Result> { let client_metadata = match &authorization_request.client_metadata { + // Fetch the client metadata from the given URI. ClientMetadataResource::ClientMetadataUri(client_metadata_uri) => { let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5); let client = ClientBuilder::new(reqwest::Client::new()) @@ -106,8 +109,10 @@ impl Extension for OID4VP { }; // TODO: in this current solution we assume that if there is a`ClaimFormatDesignation::JwtVcJson` `alg` present - // in the client_metadata that this same `alg` will apply for the signing of all the credentials and the VP. + // in the client_metadata that this same `alg` will apply for the signing of all the credentials and the VP as + // well as the Proof of Possession. match client_metadata { + // Fetch the client metadata from the given URI. ClientMetadataResource::ClientMetadataUri(_) => unreachable!(), ClientMetadataResource::ClientMetadata { extension, .. } => extension .vp_formats @@ -121,6 +126,48 @@ impl Extension for OID4VP { } } + async fn get_relying_party_supported_syntax_types( + authorization_request: &::Parameters, + ) -> anyhow::Result> { + let client_metadata = match &authorization_request.client_metadata { + ClientMetadataResource::ClientMetadataUri(client_metadata_uri) => { + let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5); + let client = ClientBuilder::new(reqwest::Client::new()) + .with(RetryTransientMiddleware::new_with_policy(retry_policy)) + .build(); + let client_metadata: ClientMetadataResource = + client.get(client_metadata_uri).send().await?.json().await?; + client_metadata + } + client_metadata => client_metadata.clone(), + }; + + match client_metadata { + ClientMetadataResource::ClientMetadataUri(_) => unreachable!(), + ClientMetadataResource::ClientMetadata { other, .. } => { + let subject_syntax_types_supported: Vec = other + .get("subject_syntax_types_supported") + .and_then(|subject_syntax_types_supported| { + subject_syntax_types_supported + .as_array() + .and_then(|subject_syntax_types_supported| { + subject_syntax_types_supported + .iter() + .map(|subject_syntax_type| { + subject_syntax_type.as_str().map(|subject_syntax_type| { + SubjectSyntaxType::from_str(subject_syntax_type).unwrap() + }) + }) + .collect() + }) + }) + .unwrap_or_default(); + + Ok(subject_syntax_types_supported) + } + } + } + fn build_authorization_response( jwts: Vec, user_input: ::Input, diff --git a/siopv2/src/provider.rs b/siopv2/src/provider.rs index 4c0c7ac8..86282c0f 100644 --- a/siopv2/src/provider.rs +++ b/siopv2/src/provider.rs @@ -18,8 +18,8 @@ use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; /// the user who is trying to authenticate. pub struct Provider { pub subject: SigningSubject, - pub default_subject_syntax_type: SubjectSyntaxType, - pub supported_sigining_alogrithms: Vec, + pub supported_subject_syntax_types: Vec, + pub supported_signing_algorithms: Vec, client: ClientWithMiddleware, } @@ -27,20 +27,26 @@ impl Provider { // TODO: Use ProviderBuilder instead. pub fn new( subject: SigningSubject, - default_subject_syntax_type: impl TryInto, + supported_subject_syntax_types: Vec>, supported_sigining_alogrithms: Vec, ) -> Result { let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5); let client = ClientBuilder::new(reqwest::Client::new()) .with(RetryTransientMiddleware::new_with_policy(retry_policy)) .build(); + Ok(Provider { subject, client, - default_subject_syntax_type: default_subject_syntax_type - .try_into() - .map_err(|_| anyhow::anyhow!("Invalid did method."))?, - supported_sigining_alogrithms, + supported_subject_syntax_types: supported_subject_syntax_types + .into_iter() + .map(|subject_syntax_type| { + subject_syntax_type + .try_into() + .map_err(|_| anyhow::anyhow!("Invalid did method.")) + }) + .collect::>()?, + supported_signing_algorithms: supported_sigining_alogrithms, }) } @@ -87,6 +93,34 @@ impl Provider { Ok(authorization_request) } + pub async fn get_matching_signing_algorithm( + &self, + authorization_request: &AuthorizationRequest>, + ) -> Result { + let relying_party_supported_algorithms = + E::get_relying_party_supported_algorithms(&authorization_request.body.extension).await?; + + self.supported_signing_algorithms + .iter() + .find(|supported_algorithm| relying_party_supported_algorithms.contains(supported_algorithm)) + .cloned() + .ok_or(anyhow::anyhow!("No supported signing algorithms found.")) + } + + pub async fn get_matching_subject_syntax_type( + &self, + authorization_request: &AuthorizationRequest>, + ) -> Result { + let relying_party_supported_syntax_types = + E::get_relying_party_supported_syntax_types(&authorization_request.body.extension).await?; + + self.supported_subject_syntax_types + .iter() + .find(|supported_syntax_type| relying_party_supported_syntax_types.contains(supported_syntax_type)) + .cloned() + .ok_or(anyhow::anyhow!("No supported subject syntax types found.")) + } + /// Generates an [`AuthorizationResponse`] in response to an [`AuthorizationRequest`] and the user's claims. The [`AuthorizationResponse`] /// contains an [`IdToken`], which is signed by the [`Subject`] of the [`Provider`]. pub async fn generate_response( @@ -97,22 +131,16 @@ impl Provider { let redirect_uri = authorization_request.body.redirect_uri.to_string(); let state = authorization_request.body.state.clone(); - let relying_party_supported_algorithms = - E::get_relying_party_supported_algorithms(&authorization_request.body.extension).await?; - - let signing_algorithm = self - .supported_sigining_alogrithms - .iter() - .find(|supported_algorithm| relying_party_supported_algorithms.contains(supported_algorithm)) - .ok_or(anyhow::anyhow!("No supported signing algorithms found."))?; + let signing_algorithm = self.get_matching_signing_algorithm(authorization_request).await?; + let subject_syntax_type = self.get_matching_subject_syntax_type(authorization_request).await?; let jwts = E::generate_token( self.subject.clone(), &authorization_request.body.client_id, &authorization_request.body.extension, &input, - self.default_subject_syntax_type.clone(), - *signing_algorithm, + subject_syntax_type.clone(), + signing_algorithm, ) .await?; @@ -145,7 +173,7 @@ mod tests { let subject = TestSubject::new("did:test:123".to_string(), "key_id".to_string()).unwrap(); // Create a new provider. - let provider = Provider::new(Arc::new(subject), "did:test", vec![Algorithm::EdDSA]).unwrap(); + let provider = Provider::new(Arc::new(subject), vec!["did:test"], vec![Algorithm::EdDSA]).unwrap(); // Get a new SIOP authorization_request with response mode `direct_post` for cross-device communication. let request_url = "\ diff --git a/siopv2/src/siopv2.rs b/siopv2/src/siopv2.rs index b4070517..e0db9dff 100644 --- a/siopv2/src/siopv2.rs +++ b/siopv2/src/siopv2.rs @@ -83,10 +83,12 @@ impl Extension for SIOPv2 { Ok(vec![jwt]) } + // TODO: combine this function with `get_relying_party_supported_syntax_types`. async fn get_relying_party_supported_algorithms( authorization_request: &::Parameters, ) -> anyhow::Result> { let client_metadata = match &authorization_request.client_metadata { + // Fetch the client metadata from the given URI. ClientMetadataResource::ClientMetadataUri(client_metadata_uri) => { let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5); let client = ClientBuilder::new(reqwest::Client::new()) @@ -111,6 +113,36 @@ impl Extension for SIOPv2 { } } + async fn get_relying_party_supported_syntax_types( + authorization_request: &::Parameters, + ) -> anyhow::Result> { + let client_metadata = match &authorization_request.client_metadata { + // Fetch the client metadata from the given URI. + ClientMetadataResource::ClientMetadataUri(client_metadata_uri) => { + let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5); + let client = ClientBuilder::new(reqwest::Client::new()) + .with(RetryTransientMiddleware::new_with_policy(retry_policy)) + .build(); + let client_metadata: ClientMetadataResource = + client.get(client_metadata_uri).send().await?.json().await?; + client_metadata + } + client_metadata => client_metadata.clone(), + }; + + match client_metadata { + ClientMetadataResource::ClientMetadataUri(_) => unreachable!(), + ClientMetadataResource::ClientMetadata { + extension: + ClientMetadataParameters { + subject_syntax_types_supported, + .. + }, + .. + } => Ok(subject_syntax_types_supported), + } + } + fn build_authorization_response( jwts: Vec, _user_input: ::Input,