From 598d296bcb38830078a65b810e31162970579e9e Mon Sep 17 00:00:00 2001 From: Nander Stabel Date: Thu, 4 Apr 2024 17:04:23 +0200 Subject: [PATCH 1/4] feat: add support for multiple DID methods for `Subject` --- oid4vc-core/src/authentication/sign.rs | 4 +- oid4vc-core/src/authentication/subject.rs | 12 +--- oid4vc-core/src/authentication/validator.rs | 10 ++- oid4vc-core/src/decoder.rs | 29 -------- oid4vc-core/src/jwt.rs | 8 +-- oid4vc-core/src/lib.rs | 2 - oid4vc-core/src/openid4vc_extension.rs | 5 +- oid4vc-core/src/test_utils.rs | 6 +- oid4vc-manager/src/managers/provider.rs | 43 ++---------- oid4vc-manager/src/managers/relying_party.rs | 23 ++----- oid4vc-manager/src/methods/key_method.rs | 11 +-- .../src/servers/credential_issuer.rs | 8 +-- oid4vc-manager/tests/common/memory_storage.rs | 1 + oid4vc-manager/tests/common/mod.rs | 6 +- .../tests/oid4vci/authorization_code.rs | 4 +- .../tests/oid4vci/pre_authorized_code.rs | 4 +- oid4vc-manager/tests/oid4vp/implicit.rs | 11 +-- oid4vc-manager/tests/siopv2/implicit.rs | 68 ++++++++++++++++--- oid4vci/src/credential_issuer/mod.rs | 6 +- oid4vci/src/proof.rs | 5 ++ oid4vci/src/wallet/mod.rs | 12 ++-- oid4vp/src/oid4vp.rs | 16 ++--- siopv2/src/provider.rs | 43 +++++------- siopv2/src/relying_party.rs | 10 +-- siopv2/src/siopv2.rs | 14 ++-- siopv2/src/test_utils.rs | 6 +- 26 files changed, 175 insertions(+), 192 deletions(-) delete mode 100644 oid4vc-core/src/decoder.rs diff --git a/oid4vc-core/src/authentication/sign.rs b/oid4vc-core/src/authentication/sign.rs index eeb36cd9..021987d0 100644 --- a/oid4vc-core/src/authentication/sign.rs +++ b/oid4vc-core/src/authentication/sign.rs @@ -4,8 +4,8 @@ use std::sync::Arc; pub trait Sign: Send + Sync { // TODO: add this? // fn jwt_alg_name() -> &'static str; - fn key_id(&self) -> Option; - fn sign(&self, message: &str) -> Result>; + fn key_id(&self, did_method: &str) -> Option; + fn sign(&self, message: &str, did_method: &str) -> Result>; fn external_signer(&self) -> Option>; } diff --git a/oid4vc-core/src/authentication/subject.rs b/oid4vc-core/src/authentication/subject.rs index 9002972e..8830fa89 100644 --- a/oid4vc-core/src/authentication/subject.rs +++ b/oid4vc-core/src/authentication/subject.rs @@ -7,22 +7,14 @@ pub type SigningSubject = Arc; // TODO: Use a URI of some sort. /// This [`Subject`] trait is used to sign and verify JWTs. pub trait Subject: Sign + Verify + Send + Sync { - fn identifier(&self) -> Result; + fn identifier(&self, did_method: &str) -> Result; fn type_(&self) -> Result { - SubjectSyntaxType::from_str(&self.identifier()?) + SubjectSyntaxType::from_str(&self.identifier("FIX_THIS")?) } } pub type Subjects = Collection; -impl Subjects { - pub fn get_subject(&self, subject_syntax_type: SubjectSyntaxType) -> Option> { - self.iter() - .find(|&subject| *subject.0 == subject_syntax_type) - .map(|subject| subject.1.clone()) - } -} - impl TryFrom<[Arc; N]> for Subjects { type Error = anyhow::Error; diff --git a/oid4vc-core/src/authentication/validator.rs b/oid4vc-core/src/authentication/validator.rs index ed989bb0..effd5164 100644 --- a/oid4vc-core/src/authentication/validator.rs +++ b/oid4vc-core/src/authentication/validator.rs @@ -1,5 +1,6 @@ -use crate::{Collection, Subject, Subjects, Verify}; +use crate::{jwt, Collection, Subject, Subjects, Verify}; use anyhow::Result; +use serde::de::DeserializeOwned; use std::sync::Arc; pub type Validators = Collection; @@ -32,4 +33,11 @@ impl Validator { Validator::Verifier(verifier) => verifier.public_key(kid).await, } } + + pub async fn decode(&self, jwt: String) -> Result { + let (kid, algorithm) = jwt::extract_header(&jwt)?; + + let public_key = self.public_key(&kid).await?; + jwt::decode(&jwt, public_key, algorithm) + } } diff --git a/oid4vc-core/src/decoder.rs b/oid4vc-core/src/decoder.rs deleted file mode 100644 index 59a28b2d..00000000 --- a/oid4vc-core/src/decoder.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{jwt, Subjects, Validators}; -use anyhow::{anyhow, Result}; -use serde::de::DeserializeOwned; - -pub struct Decoder { - pub validators: Validators, -} - -impl Decoder { - pub async fn decode(&self, jwt: String) -> Result { - let (kid, algorithm) = jwt::extract_header(&jwt)?; - // TODO: decode for JWK Thumbprint - for validator in &self.validators.0 { - if let Ok(public_key) = validator.1.public_key(&kid).await { - return jwt::decode(&jwt, public_key, algorithm); - } - } - - Err(anyhow!("No validator found for this signed JWT.")) - } -} - -impl From<&Subjects> for Decoder { - fn from(subjects: &Subjects) -> Self { - Self { - validators: Validators::from(subjects), - } - } -} diff --git a/oid4vc-core/src/jwt.rs b/oid4vc-core/src/jwt.rs index 07f96586..a5f27942 100644 --- a/oid4vc-core/src/jwt.rs +++ b/oid4vc-core/src/jwt.rs @@ -50,18 +50,18 @@ where Ok(jsonwebtoken::decode::(jwt, &key, &validation)?.claims) } -pub fn encode(signer: Arc, header: Header, claims: C) -> Result +pub fn encode(signer: Arc, header: Header, claims: C, did_method: &str) -> Result where C: Serialize, S: Sign + ?Sized, { - let kid = signer.key_id().ok_or(anyhow!("No key identifier found."))?; + let kid = signer.key_id(did_method).ok_or(anyhow!("No key identifier found."))?; let jwt = JsonWebToken::new(header, claims).kid(kid); let message = [base64_url_encode(&jwt.header)?, base64_url_encode(&jwt.payload)?].join("."); - let proof_value = signer.sign(&message)?; + let proof_value = signer.sign(&message, did_method)?; let signature = base64_url::encode(proof_value.as_slice()); let message = [message, signature].join("."); Ok(message) @@ -95,7 +95,7 @@ mod tests { "nonce": "nonce", }); let subject = TestSubject::new("did:test:123".to_string(), "key_id".to_string()).unwrap(); - let encoded = encode(Arc::new(subject), Header::new(Algorithm::EdDSA), claims).unwrap(); + let encoded = encode(Arc::new(subject), Header::new(Algorithm::EdDSA), claims, "did:test").unwrap(); let verifier = MockVerifier::new(); let (kid, algorithm) = extract_header(&encoded).unwrap(); diff --git a/oid4vc-core/src/lib.rs b/oid4vc-core/src/lib.rs index 59da59b0..fabd704e 100644 --- a/oid4vc-core/src/lib.rs +++ b/oid4vc-core/src/lib.rs @@ -3,7 +3,6 @@ pub mod authorization_request; pub mod authorization_response; pub mod client_metadata; pub mod collection; -pub mod decoder; pub mod jwt; pub mod openid4vc_extension; pub mod rfc7519_claims; @@ -17,7 +16,6 @@ pub use authentication::{ verify::Verify, }; pub use collection::Collection; -pub use decoder::Decoder; use rand::{distributions::Alphanumeric, Rng}; pub use rfc7519_claims::RFC7519Claims; use serde::Serialize; diff --git a/oid4vc-core/src/openid4vc_extension.rs b/oid4vc-core/src/openid4vc_extension.rs index fd10ad7c..1859a498 100644 --- a/oid4vc-core/src/openid4vc_extension.rs +++ b/oid4vc-core/src/openid4vc_extension.rs @@ -1,4 +1,4 @@ -use crate::{authorization_response::AuthorizationResponse, Decoder, Subject}; +use crate::{authorization_response::AuthorizationResponse, Subject, Validator}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{future::Future, sync::Arc}; @@ -28,6 +28,7 @@ pub trait Extension: Serialize + PartialEq + Sized + std::fmt::Debug + Clone + S _client_id: &str, _extension_parameters: &::Parameters, _user_input: &::Input, + _did_method: &str, ) -> anyhow::Result> { // Will be overwritten by the extension. Err(anyhow::anyhow!("Not implemented.")) @@ -44,7 +45,7 @@ pub trait Extension: Serialize + PartialEq + Sized + std::fmt::Debug + Clone + S } fn decode_authorization_response( - _decoder: Decoder, + _validator: Validator, _authorization_response: &AuthorizationResponse, ) -> impl Future::ResponseItem>> + Send { // Will be overwritten by the extension. diff --git a/oid4vc-core/src/test_utils.rs b/oid4vc-core/src/test_utils.rs index 0ffca333..436d1d29 100644 --- a/oid4vc-core/src/test_utils.rs +++ b/oid4vc-core/src/test_utils.rs @@ -31,11 +31,11 @@ impl TestSubject { } impl Sign for TestSubject { - fn key_id(&self) -> Option { + fn key_id(&self, _did_method: &str) -> Option { Some(self.key_id.clone()) } - fn sign(&self, message: &str) -> Result> { + fn sign(&self, message: &str, _did_method: &str) -> Result> { let signature: Signature = TEST_KEYPAIR.sign(message.as_bytes()); Ok(signature.to_bytes().to_vec()) } @@ -53,7 +53,7 @@ impl Verify for TestSubject { } impl Subject for TestSubject { - fn identifier(&self) -> Result { + fn identifier(&self, _default_did_method: &str) -> Result { Ok(self.did.to_string()) } } diff --git a/oid4vc-manager/src/managers/provider.rs b/oid4vc-manager/src/managers/provider.rs index f3532bbc..a40ef4c9 100644 --- a/oid4vc-manager/src/managers/provider.rs +++ b/oid4vc-manager/src/managers/provider.rs @@ -3,30 +3,26 @@ use oid4vc_core::{ authorization_request::{AuthorizationRequest, Object}, authorization_response::AuthorizationResponse, openid4vc_extension::{Extension, OpenID4VC, ResponseHandle}, - Decoder, Subject, SubjectSyntaxType, Subjects, + Subject, SubjectSyntaxType, }; use reqwest::StatusCode; -use siopv2::{siopv2::SIOPv2, Provider}; +use siopv2::Provider; use std::sync::Arc; /// Manager struct for [`siopv2::Provider`]. pub struct ProviderManager { pub provider: Provider, - subjects: Subjects, } impl ProviderManager { - pub fn new(subjects: [Arc; N]) -> Result { + pub fn new(subjects: [Arc; N], default_did_method: String) -> Result { Ok(Self { - provider: Provider::new(subjects[0].clone())?, - subjects: Subjects::try_from(subjects)?, + provider: Provider::new(subjects[0].clone(), default_did_method)?, }) } pub async fn validate_request(&self, authorization_request: String) -> Result> { - self.provider - .validate_request(authorization_request, Decoder::from(&self.subjects)) - .await + self.provider.validate_request(authorization_request).await } pub fn generate_response( @@ -47,33 +43,4 @@ impl ProviderManager { pub fn current_subject_syntax_type(&self) -> Result { self.provider.subject.type_() } - - pub fn set_subject_syntax_type(&mut self, subject_syntax_type: SubjectSyntaxType) -> Result<()> { - self.provider.subject = self - .subjects - .get_subject(subject_syntax_type) - .ok_or_else(|| anyhow::anyhow!("No subject with the given syntax type found."))?; - Ok(()) - } - - pub fn subject_syntax_types_supported(&self) -> Vec { - self.subjects.iter().map(|subject| subject.0.to_owned()).collect() - } - - pub fn matching_subject_syntax_types( - &self, - authorization_request: &AuthorizationRequest>, - ) -> Option> { - let supported_types = authorization_request - .body - .extension - .subject_syntax_types_supported() - .map_or(Vec::new(), |types| { - types - .iter() - .filter(|sst| self.subject_syntax_types_supported().contains(sst)) - .collect() - }); - (!supported_types.is_empty()).then_some(supported_types.iter().map(|&sst| sst.clone()).collect()) - } } diff --git a/oid4vc-manager/src/managers/relying_party.rs b/oid4vc-manager/src/managers/relying_party.rs index 8aedf8a9..ac0d5fbc 100644 --- a/oid4vc-manager/src/managers/relying_party.rs +++ b/oid4vc-manager/src/managers/relying_party.rs @@ -3,7 +3,7 @@ use oid4vc_core::{ authorization_request::{AuthorizationRequest, Object}, authorization_response::AuthorizationResponse, openid4vc_extension::{Extension, ResponseHandle}, - Decoder, Subject, SubjectSyntaxType, Subjects, + Subject, SubjectSyntaxType, }; use siopv2::RelyingParty; use std::sync::Arc; @@ -11,14 +11,15 @@ use std::sync::Arc; /// Manager struct for [`siopv2::RelyingParty`]. pub struct RelyingPartyManager { pub relying_party: RelyingParty, - subjects: Subjects, } impl RelyingPartyManager { - pub fn new(subjects: [Arc; N]) -> Result { + pub fn new(subjects: [Arc; N], default_did_method: String) -> Result { Ok(Self { - relying_party: RelyingParty::new(subjects.get(0).ok_or_else(|| anyhow!("No subjects found."))?.clone())?, - subjects: Subjects::try_from(subjects)?, + relying_party: RelyingParty::new( + subjects.get(0).ok_or_else(|| anyhow!("No subjects found."))?.clone(), + default_did_method, + )?, }) } @@ -30,20 +31,10 @@ impl RelyingPartyManager { &self, authorization_response: &AuthorizationResponse, ) -> Result<::ResponseItem> { - self.relying_party - .validate_response(authorization_response, Decoder::from(&self.subjects)) - .await + self.relying_party.validate_response(authorization_response).await } pub fn current_subject_syntax_type(&self) -> Result { self.relying_party.subject.type_() } - - pub fn set_subject_syntax_type(&mut self, subject_syntax_type: SubjectSyntaxType) -> Result<()> { - self.relying_party.subject = self - .subjects - .get_subject(subject_syntax_type) - .ok_or_else(|| anyhow::anyhow!("No subject with the given syntax type found."))?; - Ok(()) - } } diff --git a/oid4vc-manager/src/methods/key_method.rs b/oid4vc-manager/src/methods/key_method.rs index ad0bc440..4191dcda 100644 --- a/oid4vc-manager/src/methods/key_method.rs +++ b/oid4vc-manager/src/methods/key_method.rs @@ -43,14 +43,14 @@ impl Default for KeySubject { #[async_trait] impl Sign for KeySubject { - fn key_id(&self) -> Option { + fn key_id(&self, _did_method: &str) -> Option { self.document .authentication .as_ref() .and_then(|authentication_methods| authentication_methods.get(0).cloned()) } - fn sign(&self, message: &str) -> Result> { + fn sign(&self, message: &str, _did_method: &str) -> Result> { match self.external_signer() { Some(external_signer) => external_signer.sign(message), None => Ok(self.keypair.sign(message.as_bytes())), @@ -70,7 +70,7 @@ impl Verify for KeySubject { } impl Subject for KeySubject { - fn identifier(&self) -> Result { + fn identifier(&self, _did_method: &str) -> Result { Ok(self.document.id.clone()) } } @@ -120,7 +120,7 @@ mod tests { let subject = KeySubject::new(); // Create a new provider manager. - let provider_manager = ProviderManager::new([Arc::new(subject)]).unwrap(); + let provider_manager = ProviderManager::new([Arc::new(subject)], "did:key".to_string()).unwrap(); // Get a new SIOP authorization_request with response mode `direct_post` for cross-device communication. let request_url = "\ @@ -151,7 +151,8 @@ mod tests { .unwrap(); // Let the relying party validate the authorization_response. - let relying_party_manager = RelyingPartyManager::new([Arc::new(KeySubject::new())]).unwrap(); + let relying_party_manager = + RelyingPartyManager::new([Arc::new(KeySubject::new())], "did:key".to_string()).unwrap(); assert!(relying_party_manager .validate_response(&authorization_response) .await diff --git a/oid4vc-manager/src/servers/credential_issuer.rs b/oid4vc-manager/src/servers/credential_issuer.rs index d3f72643..a15ec528 100644 --- a/oid4vc-manager/src/servers/credential_issuer.rs +++ b/oid4vc-manager/src/servers/credential_issuer.rs @@ -10,7 +10,7 @@ use axum::{ Form, Json, Router, }; use axum_auth::AuthBearer; -use oid4vc_core::{Decoder, Subjects}; +use oid4vc_core::Validator; use oid4vci::{ authorization_request::AuthorizationRequest, credential_format_profiles::CredentialFormatCollection, @@ -178,7 +178,7 @@ async fn credential, CFC: CredentialFormatCollection>( .credential_issuer .validate_proof( credential_request.proof.unwrap(), - Decoder::from(&Subjects::try_from([credential_issuer_manager.credential_issuer.subject.clone()]).unwrap()), + Validator::Subject(credential_issuer_manager.credential_issuer.subject.clone()), ) .await .unwrap(); @@ -216,9 +216,7 @@ async fn batch_credential, CFC: CredentialFormatCollection>( .credential_issuer .validate_proof( credential_request.proof.unwrap(), - Decoder::from( - &Subjects::try_from([credential_issuer_manager.credential_issuer.subject.clone()]).unwrap(), - ), + Validator::Subject(credential_issuer_manager.credential_issuer.subject.clone()), ) .await .unwrap(); diff --git a/oid4vc-manager/tests/common/memory_storage.rs b/oid4vc-manager/tests/common/memory_storage.rs index 8e6bcf29..56eefc88 100644 --- a/oid4vc-manager/tests/common/memory_storage.rs +++ b/oid4vc-manager/tests/common/memory_storage.rs @@ -125,6 +125,7 @@ impl Storage for Memory .verifiable_credential(verifiable_credential) .build() .ok(), + "did:key", ) .ok(), ) diff --git a/oid4vc-manager/tests/common/mod.rs b/oid4vc-manager/tests/common/mod.rs index 82227401..1276e0fe 100644 --- a/oid4vc-manager/tests/common/mod.rs +++ b/oid4vc-manager/tests/common/mod.rs @@ -37,11 +37,11 @@ impl TestSubject { #[async_trait] impl Sign for TestSubject { - fn key_id(&self) -> Option { + fn key_id(&self, _did_method: &str) -> Option { Some(self.key_id.clone()) } - fn sign(&self, message: &str) -> Result> { + fn sign(&self, message: &str, _did_method: &str) -> Result> { let signature: Signature = TEST_KEYPAIR.sign(message.as_bytes()); Ok(signature.to_bytes().to_vec()) } @@ -59,7 +59,7 @@ impl Verify for TestSubject { } impl Subject for TestSubject { - fn identifier(&self) -> Result { + fn identifier(&self, _did_method: &str) -> Result { Ok(self.did.to_string()) } } diff --git a/oid4vc-manager/tests/oid4vci/authorization_code.rs b/oid4vc-manager/tests/oid4vci/authorization_code.rs index a26962b8..de20ce6f 100644 --- a/oid4vc-manager/tests/oid4vci/authorization_code.rs +++ b/oid4vc-manager/tests/oid4vci/authorization_code.rs @@ -38,10 +38,10 @@ async fn test_authorization_code_flow() { // Create a new subject. let subject = KeySubject::new(); - let subject_did = subject.identifier().unwrap(); + let subject_did = subject.identifier("did:key").unwrap(); // Create a new wallet. - let wallet = Wallet::new(Arc::new(subject)); + let wallet = Wallet::new(Arc::new(subject), "did:key".to_string()); // 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 830c5723..17c709b2 100644 --- a/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs +++ b/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs @@ -42,10 +42,10 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference // Create a new subject. let subject = KeySubject::new(); - let subject_did = subject.identifier().unwrap(); + let subject_did = subject.identifier("did:key").unwrap(); // Create a new wallet. - let wallet = Wallet::new(Arc::new(subject)); + let wallet = Wallet::new(Arc::new(subject), "did:key".to_string()); // 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 3bef6895..e4ca8628 100644 --- a/oid4vc-manager/tests/oid4vp/implicit.rs +++ b/oid4vc-manager/tests/oid4vp/implicit.rs @@ -77,19 +77,19 @@ async fn test_implicit_flow() { )), None, ); - let issuer_did = issuer.identifier().unwrap(); + let issuer_did = issuer.identifier("did:key").unwrap(); // Create a new subject. let subject = Arc::new(KeySubject::from_keypair( generate::(Some("this-is-a-very-UNSAFE-secret-key".as_bytes().try_into().unwrap())), None, )); - let subject_did = subject.identifier().unwrap(); + let subject_did = subject.identifier("did:key").unwrap(); // Create a new relying party. let relying_party = Arc::new(KeySubject::new()); - let relying_party_did = relying_party.identifier().unwrap(); - let relying_party_manager = RelyingPartyManager::new([relying_party]).unwrap(); + let relying_party_did = relying_party.identifier("did:key").unwrap(); + let relying_party_manager = RelyingPartyManager::new([relying_party], "did:key".to_string()).unwrap(); // Create authorization request with response_type `id_token vp_token` let authorization_request = AuthorizationRequest::>::builder() @@ -101,7 +101,7 @@ async fn test_implicit_flow() { .unwrap(); // Create a provider manager and validate the authorization request. - let provider_manager = ProviderManager::new([subject]).unwrap(); + let provider_manager = ProviderManager::new([subject], "did:key".to_string()).unwrap(); // Create a new verifiable credential. let verifiable_credential = VerifiableCredentialJwt::builder() @@ -146,6 +146,7 @@ async fn test_implicit_flow() { ..Default::default() }, &verifiable_credential, + "did:key", ) .unwrap(); diff --git a/oid4vc-manager/tests/siopv2/implicit.rs b/oid4vc-manager/tests/siopv2/implicit.rs index 66b6076b..2da4fd77 100644 --- a/oid4vc-manager/tests/siopv2/implicit.rs +++ b/oid4vc-manager/tests/siopv2/implicit.rs @@ -1,13 +1,16 @@ use crate::common::{MemoryStorage, Storage, TestSubject}; +use axum::async_trait; +use did_key::{generate, Ed25519KeyPair}; use lazy_static::lazy_static; use oid4vc_core::{ + authentication::sign::ExternalSign, authorization_request::{AuthorizationRequest, ByReference, Object}, authorization_response::AuthorizationResponse, client_metadata::ClientMetadataResource, scope::{Scope, ScopeValue}, - DidMethod, SubjectSyntaxType, + DidMethod, Sign, Subject, SubjectSyntaxType, Verify, }; -use oid4vc_manager::{ProviderManager, RelyingPartyManager}; +use oid4vc_manager::{methods::key_method::KeySubject, ProviderManager, RelyingPartyManager}; use siopv2::{ authorization_request::ClientMetadataParameters, claims::{Address, IndividualClaimRequest}, @@ -21,6 +24,50 @@ use wiremock::{ Mock, MockServer, ResponseTemplate, }; +pub struct MultiDidMethodSubject { + pub test_subject: TestSubject, + pub key_subject: KeySubject, +} + +impl Sign for MultiDidMethodSubject { + fn key_id(&self, did_method: &str) -> Option { + match did_method { + "did:test" => self.test_subject.key_id(did_method), + "did:key" => self.key_subject.key_id(did_method), + _ => None, + } + } + + fn sign(&self, message: &str, _did_method: &str) -> anyhow::Result> { + self.test_subject.sign(message, _did_method) + } + + fn external_signer(&self) -> Option> { + None + } +} + +#[async_trait] +impl Verify for MultiDidMethodSubject { + async fn public_key(&self, kid: &str) -> anyhow::Result> { + match kid { + _ if kid.contains("did:test") => self.test_subject.public_key(kid).await, + _ if kid.contains("did:key") => self.key_subject.public_key(kid).await, + _ => Err(anyhow::anyhow!("Unsupported DID method.")), + } + } +} + +impl Subject for MultiDidMethodSubject { + fn identifier(&self, did_method: &str) -> anyhow::Result { + match did_method { + "did:test" => self.test_subject.identifier(did_method), + "did:key" => self.key_subject.identifier(did_method), + _ => Err(anyhow::anyhow!("Unsupported DID method.")), + } + } +} + lazy_static! { pub static ref USER_CLAIMS: serde_json::Value = serde_json::json!( { @@ -55,14 +102,17 @@ async fn test_implicit_flow() { let server_url = mock_server.uri(); // Create a new subject. - let subject = TestSubject::new( - "did:test:relying_party".to_string(), - "did:test:relying_party#key_id".to_string(), - ) - .unwrap(); + let subject = MultiDidMethodSubject { + test_subject: TestSubject::new( + "did:test:relying_party".to_string(), + "did:test:relying_party#key_id".to_string(), + ) + .unwrap(), + key_subject: KeySubject::from_keypair(generate::(None), None), + }; // Create a new relying party manager. - let relying_party_manager = RelyingPartyManager::new([Arc::new(subject)]).unwrap(); + let relying_party_manager = RelyingPartyManager::new([Arc::new(subject)], "did:test".to_string()).unwrap(); // Create a new RequestUrl with response mode `direct_post` for cross-device communication. let authorization_request: AuthorizationRequest> = AuthorizationRequest::>::builder() @@ -116,7 +166,7 @@ async fn test_implicit_flow() { let subject = TestSubject::new("did:test:subject".to_string(), "did:test:subject#key_id".to_string()).unwrap(); // Create a new provider manager. - let provider_manager = ProviderManager::new([Arc::new(subject)]).unwrap(); + let provider_manager = ProviderManager::new([Arc::new(subject)], "did:test".to_string()).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/credential_issuer/mod.rs b/oid4vci/src/credential_issuer/mod.rs index ac6facfa..5181bb8c 100644 --- a/oid4vci/src/credential_issuer/mod.rs +++ b/oid4vci/src/credential_issuer/mod.rs @@ -6,7 +6,7 @@ use self::{ authorization_server_metadata::AuthorizationServerMetadata, credential_issuer_metadata::CredentialIssuerMetadata, }; use crate::{credential_format_profiles::CredentialFormatCollection, proof::ProofOfPossession, Proof}; -use oid4vc_core::{authentication::subject::SigningSubject, Decoder}; +use oid4vc_core::{authentication::subject::SigningSubject, Validator}; #[derive(Clone)] pub struct CredentialIssuer @@ -19,9 +19,9 @@ where } impl CredentialIssuer { - pub async fn validate_proof(&self, proof: Proof, decoder: Decoder) -> anyhow::Result { + pub async fn validate_proof(&self, proof: Proof, validator: Validator) -> anyhow::Result { match proof { - Proof::Jwt { jwt, .. } => decoder.decode(jwt).await, + Proof::Jwt { jwt, .. } => validator.decode(jwt).await, Proof::Cwt { .. } => unimplemented!("CWT is not supported yet"), } } diff --git a/oid4vci/src/proof.rs b/oid4vci/src/proof.rs index 353ead1d..8de63ea9 100644 --- a/oid4vci/src/proof.rs +++ b/oid4vci/src/proof.rs @@ -32,6 +32,7 @@ pub struct ProofBuilder { rfc7519_claims: RFC7519Claims, nonce: Option, signer: Option>, + did_method: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -47,6 +48,8 @@ impl ProofBuilder { anyhow::ensure!(self.rfc7519_claims.iat.is_some(), "iat claim is required"); anyhow::ensure!(self.nonce.is_some(), "nonce claim is required"); + let did_method = self.did_method.ok_or(anyhow::anyhow!("did_method is required"))?; + match self.proof_type { Some(ProofType::Jwt) => Ok(Proof::Jwt { jwt: jwt::encode( @@ -60,6 +63,7 @@ impl ProofBuilder { rfc7519_claims: self.rfc7519_claims, nonce: self.nonce.ok_or(anyhow::anyhow!("No nonce found"))?, }, + &did_method, )?, }), Some(ProofType::Cwt) => todo!(), @@ -79,4 +83,5 @@ impl ProofBuilder { builder_fn!(rfc7519_claims, exp, i64); builder_fn!(rfc7519_claims, iat, i64); builder_fn!(nonce, String); + builder_fn!(did_method, String); } diff --git a/oid4vci/src/wallet/mod.rs b/oid4vci/src/wallet/mod.rs index d95faf3a..20b2956e 100644 --- a/oid4vci/src/wallet/mod.rs +++ b/oid4vci/src/wallet/mod.rs @@ -23,18 +23,20 @@ where CFC: CredentialFormatCollection, { pub subject: SigningSubject, + pub default_did_method: String, pub client: ClientWithMiddleware, phantom: std::marker::PhantomData, } impl Wallet { - pub fn new(subject: SigningSubject) -> Self { + pub fn new(subject: SigningSubject, default_did_method: String) -> Self { 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(); Self { subject, + default_did_method, client, phantom: std::marker::PhantomData, } @@ -105,7 +107,7 @@ impl Wallet { // TODO: must be `form`, but `AuthorizationRequest needs to be able to serilalize properly. .json(&AuthorizationRequest { response_type: "code".to_string(), - client_id: self.subject.identifier()?, + client_id: self.subject.identifier(&self.default_did_method)?, redirect_uri: None, scope: None, state: None, @@ -141,7 +143,7 @@ impl Wallet { Proof::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) - .iss(self.subject.identifier()?) + .iss(self.subject.identifier(&self.default_did_method)?) .aud(credential_issuer_metadata.credential_issuer) .iat(1571324800) .exp(9999999999i64) @@ -153,6 +155,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) + .did_method(&self.default_did_method) .build()?, ), }; @@ -178,7 +181,7 @@ impl Wallet { Proof::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) - .iss(self.subject.identifier()?) + .iss(self.subject.identifier(&self.default_did_method)?) .aud(credential_issuer_metadata.credential_issuer) .iat(1571324800) .exp(9999999999i64) @@ -190,6 +193,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) + .did_method(&self.default_did_method) .build()?, ); diff --git a/oid4vp/src/oid4vp.rs b/oid4vp/src/oid4vp.rs index 53682966..ae9ecbe5 100644 --- a/oid4vp/src/oid4vp.rs +++ b/oid4vp/src/oid4vp.rs @@ -10,9 +10,8 @@ use futures::{executor::block_on, future::join_all}; use identity_credential::{credential::Jwt, presentation::Presentation}; use jsonwebtoken::{Algorithm, Header}; use oid4vc_core::openid4vc_extension::{OpenID4VC, RequestHandle, ResponseHandle}; -use oid4vc_core::{ - authorization_response::AuthorizationResponse, jwt, openid4vc_extension::Extension, Decoder, Subject, -}; +use oid4vc_core::Validator; +use oid4vc_core::{authorization_response::AuthorizationResponse, jwt, openid4vc_extension::Extension, Subject}; use oid4vci::VerifiableCredentialJwt; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -47,8 +46,9 @@ impl Extension for OID4VP { client_id: &str, extension_parameters: &::Parameters, user_input: &::Input, + did_method: &str, ) -> anyhow::Result> { - let subject_identifier = subject.identifier()?; + let subject_identifier = subject.identifier(did_method)?; let vp_token = VpToken::builder() .iss(subject_identifier.clone()) @@ -61,7 +61,7 @@ impl Extension for OID4VP { .verifiable_presentation(user_input.verifiable_presentation.clone()) .build()?; - let jwt = jwt::encode(subject.clone(), Header::new(Algorithm::EdDSA), vp_token)?; + let jwt = jwt::encode(subject.clone(), Header::new(Algorithm::EdDSA), vp_token, did_method)?; Ok(vec![jwt]) } @@ -84,12 +84,12 @@ impl Extension for OID4VP { } async fn decode_authorization_response( - decoder: Decoder, + validator: Validator, response: &AuthorizationResponse, ) -> anyhow::Result<::ResponseItem> { let vp_token: VpToken = match &response.extension.oid4vp_parameters { Oid4vpParams::Jwt { .. } => todo!(), - Oid4vpParams::Params { vp_token, .. } => block_on(decoder.decode(vp_token.to_owned()))?, + Oid4vpParams::Params { vp_token, .. } => block_on(validator.decode(vp_token.to_owned()))?, }; join_all( @@ -97,7 +97,7 @@ impl Extension for OID4VP { .verifiable_presentation() .verifiable_credential .iter() - .map(|vc| decoder.decode(vc.as_str().to_owned())) + .map(|vc| validator.decode(vc.as_str().to_owned())) .collect::>(), ) .await diff --git a/siopv2/src/provider.rs b/siopv2/src/provider.rs index 8721e3e8..8dddec09 100644 --- a/siopv2/src/provider.rs +++ b/siopv2/src/provider.rs @@ -6,7 +6,7 @@ use oid4vc_core::{ authorization_request::{AuthorizationRequest, Body, ByReference, ByValue, Object}, authorization_response::AuthorizationResponse, openid4vc_extension::{Extension, ResponseHandle}, - Decoder, + Validator, }; use reqwest::StatusCode; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; @@ -17,28 +17,31 @@ use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; /// the user who is trying to authenticate. pub struct Provider { pub subject: SigningSubject, + pub default_did_method: String, client: ClientWithMiddleware, } impl Provider { // TODO: Use ProviderBuilder instead. - pub fn new(subject: SigningSubject) -> Result { + pub fn new(subject: SigningSubject, default_did_method: String) -> 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 }) + Ok(Provider { + subject, + client, + default_did_method, + }) } /// TODO: Add more validation rules. /// Takes a String and tries to parse it into an [`AuthorizationRequest`]. If the parsing fails, it tries to /// parse the [`AuthorizationRequest`] from the `request` parameter of the [`AuthorizationRequest`] /// or from the `request_uri` parameter of the [`AuthorizationRequest`]. - pub async fn validate_request( - &self, - authorization_request: String, - decoder: Decoder, - ) -> Result> { + pub async fn validate_request(&self, authorization_request: String) -> Result> { + let validator = Validator::Subject(self.subject.clone()); + let authorization_request = if let Ok(authorization_request) = authorization_request.parse::>() { @@ -47,7 +50,7 @@ impl Provider { let (client_id, authorization_request) = if let Ok(authorization_request) = AuthorizationRequest::::from_str(&authorization_request) { let client_id = authorization_request.body.client_id().clone(); - let authorization_request: AuthorizationRequest = decoder + let authorization_request: AuthorizationRequest = validator .decode(authorization_request.body.request.to_owned()) .await .unwrap(); @@ -59,7 +62,7 @@ impl Provider { let client_id = authorization_request.body.client_id().clone(); let builder = self.client.get(authorization_request.body.request_uri.clone()); let request_value = builder.send().await?.text().await?; - let authorization_request: AuthorizationRequest = decoder.decode(request_value).await?; + let authorization_request: AuthorizationRequest = validator.decode(request_value).await?; (client_id, authorization_request) } else { @@ -90,6 +93,7 @@ impl Provider { &authorization_request.body.client_id, &authorization_request.body.extension, &input, + &self.default_did_method, )?; E::build_authorization_response(jwts, input, redirect_uri, state) @@ -113,8 +117,7 @@ impl Provider { mod tests { use super::*; use crate::{siopv2::SIOPv2, test_utils::TestSubject}; - use oid4vc_core::{Subject, SubjectSyntaxType, Validator, Validators}; - use std::{str::FromStr, sync::Arc}; + use std::sync::Arc; #[tokio::test] async fn test_provider() { @@ -122,7 +125,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)).unwrap(); + let provider = Provider::new(Arc::new(subject), "did:test".to_string()).unwrap(); // Get a new SIOP authorization_request with response mode `direct_post` for cross-device communication. let request_url = "\ @@ -139,18 +142,8 @@ mod tests { "; // Let the provider validate the authorization_request. - let authorization_request: AuthorizationRequest = provider - .validate_request( - request_url.to_string(), - Decoder { - validators: Validators::from([( - SubjectSyntaxType::from_str("did:test").unwrap(), - Arc::new(Validator::Subject(Arc::new(TestSubject::default()) as Arc)), - )]), - }, - ) - .await - .unwrap(); + let authorization_request: AuthorizationRequest = + provider.validate_request(request_url.to_string()).await.unwrap(); let authorization_request = AuthorizationRequest::>::from_generic(&authorization_request).unwrap(); diff --git a/siopv2/src/relying_party.rs b/siopv2/src/relying_party.rs index f126d99c..12de5aa2 100644 --- a/siopv2/src/relying_party.rs +++ b/siopv2/src/relying_party.rs @@ -7,7 +7,7 @@ use oid4vc_core::{ authorization_response::AuthorizationResponse, jwt, openid4vc_extension::{Extension, ResponseHandle}, - Decoder, + Validator, }; use std::collections::HashMap; @@ -15,14 +15,16 @@ pub struct RelyingParty { // TODO: Strictly speaking a relying party doesn't need to have a [`Subject`]. It just needs methods to // sign and verify tokens. For simplicity we use a [`Subject`] here for now but we should consider a cleaner solution. pub subject: SigningSubject, + pub default_did_method: String, pub sessions: HashMap<(String, String), AuthorizationRequest>>, } impl RelyingParty { // TODO: Use RelyingPartyBuilder instead. - pub fn new(subject: SigningSubject) -> Result { + pub fn new(subject: SigningSubject, default_did_method: String) -> Result { Ok(RelyingParty { subject, + default_did_method, sessions: HashMap::new(), }) } @@ -32,6 +34,7 @@ impl RelyingParty { self.subject.clone(), Header::new(Algorithm::EdDSA), authorization_request, + &self.default_did_method, ) } @@ -40,8 +43,7 @@ impl RelyingParty { pub async fn validate_response( &self, authorization_response: &AuthorizationResponse, - decoder: Decoder, ) -> Result<::ResponseItem> { - E::decode_authorization_response(decoder, authorization_response).await + E::decode_authorization_response(Validator::Subject(self.subject.clone()), authorization_response).await } } diff --git a/siopv2/src/siopv2.rs b/siopv2/src/siopv2.rs index d6316feb..7b58ab02 100644 --- a/siopv2/src/siopv2.rs +++ b/siopv2/src/siopv2.rs @@ -4,9 +4,8 @@ use crate::token::id_token::IdToken; use chrono::{Duration, Utc}; use jsonwebtoken::{Algorithm, Header}; use oid4vc_core::openid4vc_extension::{OpenID4VC, RequestHandle, ResponseHandle}; -use oid4vc_core::{ - authorization_response::AuthorizationResponse, jwt, openid4vc_extension::Extension, Decoder, Subject, -}; +use oid4vc_core::Validator; +use oid4vc_core::{authorization_response::AuthorizationResponse, jwt, openid4vc_extension::Extension, Subject}; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -40,8 +39,9 @@ impl Extension for SIOPv2 { client_id: &str, extension_parameters: &::Parameters, user_input: &::Input, + did_method: &str, ) -> anyhow::Result> { - let subject_identifier = subject.identifier()?; + let subject_identifier = subject.identifier(did_method)?; let id_token = IdToken::builder() .iss(subject_identifier.clone()) @@ -54,7 +54,7 @@ impl Extension for SIOPv2 { .claims(user_input.clone()) .build()?; - let jwt = jwt::encode(subject.clone(), Header::new(Algorithm::EdDSA), id_token)?; + let jwt = jwt::encode(subject.clone(), Header::new(Algorithm::EdDSA), id_token, did_method)?; Ok(vec![jwt]) } @@ -77,11 +77,11 @@ impl Extension for SIOPv2 { } async fn decode_authorization_response( - decoder: Decoder, + validator: Validator, authorization_response: &AuthorizationResponse, ) -> anyhow::Result<::ResponseItem> { let token = authorization_response.extension.id_token.clone(); - decoder.decode(token).await + validator.decode(token).await } } diff --git a/siopv2/src/test_utils.rs b/siopv2/src/test_utils.rs index f3a2cd6a..40fad0a6 100644 --- a/siopv2/src/test_utils.rs +++ b/siopv2/src/test_utils.rs @@ -31,11 +31,11 @@ impl TestSubject { } impl Sign for TestSubject { - fn key_id(&self) -> Option { + fn key_id(&self, _did_method: &str) -> Option { Some(self.key_id.clone()) } - fn sign(&self, message: &str) -> Result> { + fn sign(&self, message: &str, _did_method: &str) -> Result> { let signature: Signature = TEST_KEYPAIR.sign(message.as_bytes()); Ok(signature.to_bytes().to_vec()) } @@ -53,7 +53,7 @@ impl Verify for TestSubject { } impl Subject for TestSubject { - fn identifier(&self) -> Result { + fn identifier(&self, _did_method: &str) -> Result { Ok(self.did.to_string()) } } From 3a9dc39448df382ea823b9a05b7c135b1d3e0443 Mon Sep 17 00:00:00 2001 From: Nander Stabel Date: Thu, 4 Apr 2024 21:54:17 +0200 Subject: [PATCH 2/4] fix: remove `Subjects` type --- oid4vc-core/src/authentication/sign.rs | 4 +- oid4vc-core/src/authentication/subject.rs | 24 ++---------- oid4vc-core/src/authentication/validator.rs | 20 +--------- oid4vc-core/src/collection.rs | 37 ------------------- oid4vc-core/src/jwt.rs | 8 ++-- oid4vc-core/src/lib.rs | 9 +---- oid4vc-core/src/openid4vc_extension.rs | 4 +- oid4vc-core/src/subject_syntax_type.rs | 17 +++++++++ oid4vc-core/src/test_utils.rs | 6 +-- .../src/managers/credential_issuer.rs | 17 +++------ oid4vc-manager/src/managers/provider.rs | 11 +++--- oid4vc-manager/src/managers/relying_party.rs | 15 ++------ oid4vc-manager/src/methods/key_method.rs | 10 ++--- oid4vc-manager/tests/common/mod.rs | 6 +-- .../tests/oid4vci/authorization_code.rs | 4 +- .../tests/oid4vci/pre_authorized_code.rs | 4 +- oid4vc-manager/tests/oid4vp/implicit.rs | 4 +- oid4vc-manager/tests/siopv2/implicit.rs | 24 ++++++------ oid4vci/src/proof.rs | 10 +++-- oid4vci/src/wallet/mod.rs | 16 ++++---- oid4vp/src/oid4vp.rs | 17 +++++++-- siopv2/src/provider.rs | 14 ++++--- siopv2/src/relying_party.rs | 8 ++-- siopv2/src/siopv2.rs | 17 +++++++-- siopv2/src/test_utils.rs | 6 +-- 25 files changed, 129 insertions(+), 183 deletions(-) delete mode 100644 oid4vc-core/src/collection.rs diff --git a/oid4vc-core/src/authentication/sign.rs b/oid4vc-core/src/authentication/sign.rs index 021987d0..6284a6dd 100644 --- a/oid4vc-core/src/authentication/sign.rs +++ b/oid4vc-core/src/authentication/sign.rs @@ -4,8 +4,8 @@ use std::sync::Arc; pub trait Sign: Send + Sync { // TODO: add this? // fn jwt_alg_name() -> &'static str; - fn key_id(&self, did_method: &str) -> Option; - fn sign(&self, message: &str, did_method: &str) -> Result>; + fn key_id(&self, subject_syntax_type: &str) -> Option; + fn sign(&self, message: &str, subject_syntax_type: &str) -> Result>; fn external_signer(&self) -> Option>; } diff --git a/oid4vc-core/src/authentication/subject.rs b/oid4vc-core/src/authentication/subject.rs index 8830fa89..54ddc2fc 100644 --- a/oid4vc-core/src/authentication/subject.rs +++ b/oid4vc-core/src/authentication/subject.rs @@ -1,29 +1,11 @@ -use crate::{Collection, Sign, SubjectSyntaxType, Verify}; +use crate::{Sign, Verify}; use anyhow::Result; -use std::{str::FromStr, sync::Arc}; +use std::sync::Arc; pub type SigningSubject = Arc; // TODO: Use a URI of some sort. /// This [`Subject`] trait is used to sign and verify JWTs. pub trait Subject: Sign + Verify + Send + Sync { - fn identifier(&self, did_method: &str) -> Result; - fn type_(&self) -> Result { - SubjectSyntaxType::from_str(&self.identifier("FIX_THIS")?) - } -} - -pub type Subjects = Collection; - -impl TryFrom<[Arc; N]> for Subjects { - type Error = anyhow::Error; - - fn try_from(subjects: [Arc; N]) -> Result { - Ok(Self::from( - subjects - .iter() - .map(|subject| subject.type_().map(|subject_type| (subject_type, subject.clone()))) - .collect::>>()?, - )) - } + fn identifier(&self, subject_syntax_type: &str) -> Result; } diff --git a/oid4vc-core/src/authentication/validator.rs b/oid4vc-core/src/authentication/validator.rs index effd5164..4bb6678e 100644 --- a/oid4vc-core/src/authentication/validator.rs +++ b/oid4vc-core/src/authentication/validator.rs @@ -1,26 +1,8 @@ -use crate::{jwt, Collection, Subject, Subjects, Verify}; +use crate::{jwt, Subject, Verify}; use anyhow::Result; use serde::de::DeserializeOwned; use std::sync::Arc; -pub type Validators = Collection; - -impl From<&Subjects> for Validators { - fn from(subjects: &Subjects) -> Self { - Self::from( - subjects - .iter() - .map(|(subject_syntax_type, subject)| { - ( - subject_syntax_type.clone(), - Arc::new(Validator::Subject(subject.clone())), - ) - }) - .collect::>(), - ) - } -} - pub enum Validator { Subject(Arc), Verifier(Arc), diff --git a/oid4vc-core/src/collection.rs b/oid4vc-core/src/collection.rs deleted file mode 100644 index e7d76c29..00000000 --- a/oid4vc-core/src/collection.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::SubjectSyntaxType; -use std::{collections::HashMap, sync::Arc}; - -#[derive(Clone)] -pub struct Collection(pub HashMap>); - -impl Collection { - pub fn get(&self, subject_syntax_type: &SubjectSyntaxType) -> Option<&Arc> { - self.0.get(subject_syntax_type) - } - - pub fn add(&mut self, subject_syntax_type: SubjectSyntaxType, subject: Arc) { - self.0.insert(subject_syntax_type, subject); - } - - pub fn iter(&self) -> impl Iterator)> { - self.0.iter() - } -} - -impl Default for Collection { - fn default() -> Self { - Collection(HashMap::new()) - } -} - -impl From<[(SubjectSyntaxType, Arc); N]> for Collection { - fn from(items: [(SubjectSyntaxType, Arc); N]) -> Self { - Collection(items.iter().cloned().collect()) - } -} - -impl From)>> for Collection { - fn from(items: Vec<(SubjectSyntaxType, Arc)>) -> Self { - Collection(items.iter().cloned().collect()) - } -} diff --git a/oid4vc-core/src/jwt.rs b/oid4vc-core/src/jwt.rs index a5f27942..c0c2c640 100644 --- a/oid4vc-core/src/jwt.rs +++ b/oid4vc-core/src/jwt.rs @@ -50,18 +50,20 @@ where Ok(jsonwebtoken::decode::(jwt, &key, &validation)?.claims) } -pub fn encode(signer: Arc, header: Header, claims: C, did_method: &str) -> Result +pub fn encode(signer: Arc, header: Header, claims: C, subject_syntax_type: &str) -> Result where C: Serialize, S: Sign + ?Sized, { - let kid = signer.key_id(did_method).ok_or(anyhow!("No key identifier found."))?; + let kid = signer + .key_id(subject_syntax_type) + .ok_or(anyhow!("No key identifier found."))?; let jwt = JsonWebToken::new(header, claims).kid(kid); let message = [base64_url_encode(&jwt.header)?, base64_url_encode(&jwt.payload)?].join("."); - let proof_value = signer.sign(&message, did_method)?; + let proof_value = signer.sign(&message, subject_syntax_type)?; let signature = base64_url::encode(proof_value.as_slice()); let message = [message, signature].join("."); Ok(message) diff --git a/oid4vc-core/src/lib.rs b/oid4vc-core/src/lib.rs index fabd704e..fa3b6205 100644 --- a/oid4vc-core/src/lib.rs +++ b/oid4vc-core/src/lib.rs @@ -2,20 +2,13 @@ pub mod authentication; pub mod authorization_request; pub mod authorization_response; pub mod client_metadata; -pub mod collection; pub mod jwt; pub mod openid4vc_extension; pub mod rfc7519_claims; pub mod scope; pub mod subject_syntax_type; -pub use authentication::{ - sign::Sign, - subject::{Subject, Subjects}, - validator::{Validator, Validators}, - verify::Verify, -}; -pub use collection::Collection; +pub use authentication::{sign::Sign, subject::Subject, validator::Validator, verify::Verify}; use rand::{distributions::Alphanumeric, Rng}; pub use rfc7519_claims::RFC7519Claims; use serde::Serialize; diff --git a/oid4vc-core/src/openid4vc_extension.rs b/oid4vc-core/src/openid4vc_extension.rs index 1859a498..3c46c947 100644 --- a/oid4vc-core/src/openid4vc_extension.rs +++ b/oid4vc-core/src/openid4vc_extension.rs @@ -1,4 +1,4 @@ -use crate::{authorization_response::AuthorizationResponse, Subject, Validator}; +use crate::{authorization_response::AuthorizationResponse, Subject, SubjectSyntaxType, Validator}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{future::Future, sync::Arc}; @@ -28,7 +28,7 @@ pub trait Extension: Serialize + PartialEq + Sized + std::fmt::Debug + Clone + S _client_id: &str, _extension_parameters: &::Parameters, _user_input: &::Input, - _did_method: &str, + _subject_syntax_type: impl TryInto, ) -> anyhow::Result> { // Will be overwritten by the extension. Err(anyhow::anyhow!("Not implemented.")) diff --git a/oid4vc-core/src/subject_syntax_type.rs b/oid4vc-core/src/subject_syntax_type.rs index 0727bc03..50fc1950 100644 --- a/oid4vc-core/src/subject_syntax_type.rs +++ b/oid4vc-core/src/subject_syntax_type.rs @@ -21,6 +21,23 @@ impl FromStr for SubjectSyntaxType { } } +impl Display for SubjectSyntaxType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SubjectSyntaxType::JwkThumbprint => write!(f, "urn:ietf:params:oauth:jwk-thumbprint"), + SubjectSyntaxType::Did(did_method) => write!(f, "{}", did_method), + } + } +} + +impl TryFrom<&str> for SubjectSyntaxType { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result { + SubjectSyntaxType::from_str(value) + } +} + impl From for SubjectSyntaxType { fn from(did_method: DidMethod) -> Self { SubjectSyntaxType::Did(did_method) diff --git a/oid4vc-core/src/test_utils.rs b/oid4vc-core/src/test_utils.rs index 436d1d29..09adbf64 100644 --- a/oid4vc-core/src/test_utils.rs +++ b/oid4vc-core/src/test_utils.rs @@ -31,11 +31,11 @@ impl TestSubject { } impl Sign for TestSubject { - fn key_id(&self, _did_method: &str) -> Option { + fn key_id(&self, _subject_syntax_type: &str) -> Option { Some(self.key_id.clone()) } - fn sign(&self, message: &str, _did_method: &str) -> Result> { + fn sign(&self, message: &str, _subject_syntax_type: &str) -> Result> { let signature: Signature = TEST_KEYPAIR.sign(message.as_bytes()); Ok(signature.to_bytes().to_vec()) } @@ -53,7 +53,7 @@ impl Verify for TestSubject { } impl Subject for TestSubject { - fn identifier(&self, _default_did_method: &str) -> Result { + fn identifier(&self, _subject_syntax_type: &str) -> Result { Ok(self.did.to_string()) } } diff --git a/oid4vc-manager/src/managers/credential_issuer.rs b/oid4vc-manager/src/managers/credential_issuer.rs index 2a2f24a9..1fed9430 100644 --- a/oid4vc-manager/src/managers/credential_issuer.rs +++ b/oid4vc-manager/src/managers/credential_issuer.rs @@ -1,6 +1,6 @@ use crate::storage::Storage; use anyhow::Result; -use oid4vc_core::{Subject, Subjects}; +use oid4vc_core::Subject; use oid4vci::{ credential_format_profiles::CredentialFormatCollection, credential_issuer::{ @@ -15,26 +15,19 @@ use std::{net::TcpListener, sync::Arc}; #[derive(Clone)] pub struct CredentialIssuerManager, CFC: CredentialFormatCollection> { pub credential_issuer: CredentialIssuer, - pub subjects: Arc, + pub subject: Arc, pub storage: S, pub listener: Arc, } impl, CFC: CredentialFormatCollection> CredentialIssuerManager { - pub fn new( - listener: Option, - storage: S, - subjects: [Arc; N], - ) -> Result { + pub fn new(listener: Option, storage: S, subject: Arc) -> Result { // `TcpListener::bind("127.0.0.1:0")` will bind to a random port. let listener = listener.unwrap_or_else(|| TcpListener::bind("127.0.0.1:0").unwrap()); let issuer_url: Url = format!("http://{:?}", listener.local_addr()?).parse()?; Ok(Self { credential_issuer: CredentialIssuer { - subject: subjects - .first() - .ok_or_else(|| anyhow::anyhow!("No subjects found."))? - .clone(), + subject: subject.clone(), metadata: CredentialIssuerMetadata { credential_issuer: issuer_url.clone(), authorization_servers: vec![], @@ -56,7 +49,7 @@ impl, CFC: CredentialFormatCollection> CredentialIssuerManager(subjects: [Arc; N], default_did_method: String) -> Result { + pub fn new( + subject: Arc, + default_subject_syntax_type: impl TryInto, + ) -> Result { Ok(Self { - provider: Provider::new(subjects[0].clone(), default_did_method)?, + provider: Provider::new(subject, default_subject_syntax_type)?, }) } @@ -39,8 +42,4 @@ impl ProviderManager { ) -> Result { self.provider.send_response(authorization_response).await } - - pub fn current_subject_syntax_type(&self) -> Result { - self.provider.subject.type_() - } } diff --git a/oid4vc-manager/src/managers/relying_party.rs b/oid4vc-manager/src/managers/relying_party.rs index ac0d5fbc..4f38a8d8 100644 --- a/oid4vc-manager/src/managers/relying_party.rs +++ b/oid4vc-manager/src/managers/relying_party.rs @@ -1,9 +1,9 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use oid4vc_core::{ authorization_request::{AuthorizationRequest, Object}, authorization_response::AuthorizationResponse, openid4vc_extension::{Extension, ResponseHandle}, - Subject, SubjectSyntaxType, + Subject, }; use siopv2::RelyingParty; use std::sync::Arc; @@ -14,12 +14,9 @@ pub struct RelyingPartyManager { } impl RelyingPartyManager { - pub fn new(subjects: [Arc; N], default_did_method: String) -> Result { + pub fn new(subject: Arc, subject_syntax_type: String) -> Result { Ok(Self { - relying_party: RelyingParty::new( - subjects.get(0).ok_or_else(|| anyhow!("No subjects found."))?.clone(), - default_did_method, - )?, + relying_party: RelyingParty::new(subject, subject_syntax_type)?, }) } @@ -33,8 +30,4 @@ impl RelyingPartyManager { ) -> Result<::ResponseItem> { self.relying_party.validate_response(authorization_response).await } - - pub fn current_subject_syntax_type(&self) -> Result { - self.relying_party.subject.type_() - } } diff --git a/oid4vc-manager/src/methods/key_method.rs b/oid4vc-manager/src/methods/key_method.rs index e1f787c7..89f6a3c2 100644 --- a/oid4vc-manager/src/methods/key_method.rs +++ b/oid4vc-manager/src/methods/key_method.rs @@ -43,14 +43,14 @@ impl Default for KeySubject { #[async_trait] impl Sign for KeySubject { - fn key_id(&self, _did_method: &str) -> Option { + fn key_id(&self, _subject_syntax_type: &str) -> Option { self.document .authentication .as_ref() .and_then(|authentication_methods| authentication_methods.first().cloned()) } - fn sign(&self, message: &str, _did_method: &str) -> Result> { + fn sign(&self, message: &str, _subject_syntax_type: &str) -> Result> { match self.external_signer() { Some(external_signer) => external_signer.sign(message), None => Ok(self.keypair.sign(message.as_bytes())), @@ -70,7 +70,7 @@ impl Verify for KeySubject { } impl Subject for KeySubject { - fn identifier(&self, _did_method: &str) -> Result { + fn identifier(&self, _subject_syntax_type: &str) -> Result { Ok(self.document.id.clone()) } } @@ -120,7 +120,7 @@ mod tests { let subject = KeySubject::new(); // Create a new provider manager. - let provider_manager = ProviderManager::new([Arc::new(subject)], "did:key".to_string()).unwrap(); + let provider_manager = ProviderManager::new(Arc::new(subject), "did:key").unwrap(); // Get a new SIOP authorization_request with response mode `direct_post` for cross-device communication. let request_url = "\ @@ -152,7 +152,7 @@ mod tests { // Let the relying party validate the authorization_response. let relying_party_manager = - RelyingPartyManager::new([Arc::new(KeySubject::new())], "did:key".to_string()).unwrap(); + RelyingPartyManager::new(Arc::new(KeySubject::new()), "did:key".to_string()).unwrap(); assert!(relying_party_manager .validate_response(&authorization_response) .await diff --git a/oid4vc-manager/tests/common/mod.rs b/oid4vc-manager/tests/common/mod.rs index 1276e0fe..cdab8196 100644 --- a/oid4vc-manager/tests/common/mod.rs +++ b/oid4vc-manager/tests/common/mod.rs @@ -37,11 +37,11 @@ impl TestSubject { #[async_trait] impl Sign for TestSubject { - fn key_id(&self, _did_method: &str) -> Option { + fn key_id(&self, _subject_syntax_type: &str) -> Option { Some(self.key_id.clone()) } - fn sign(&self, message: &str, _did_method: &str) -> Result> { + fn sign(&self, message: &str, _subject_syntax_type: &str) -> Result> { let signature: Signature = TEST_KEYPAIR.sign(message.as_bytes()); Ok(signature.to_bytes().to_vec()) } @@ -59,7 +59,7 @@ impl Verify for TestSubject { } impl Subject for TestSubject { - fn identifier(&self, _did_method: &str) -> Result { + fn identifier(&self, _subject_syntax_type: &str) -> Result { Ok(self.did.to_string()) } } diff --git a/oid4vc-manager/tests/oid4vci/authorization_code.rs b/oid4vc-manager/tests/oid4vci/authorization_code.rs index 9f922ad1..4d6bc891 100644 --- a/oid4vc-manager/tests/oid4vci/authorization_code.rs +++ b/oid4vc-manager/tests/oid4vci/authorization_code.rs @@ -22,12 +22,12 @@ async fn test_authorization_code_flow() { CredentialIssuerManager::<_, CredentialFormats>::new( None, MemoryStorage, - [Arc::new(KeySubject::from_keypair( + Arc::new(KeySubject::from_keypair( generate::(Some( "this-is-a-very-UNSAFE-issuer-secret-key".as_bytes().try_into().unwrap(), )), None, - ))], + )), ) .unwrap(), None, diff --git a/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs b/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs index c18cb32a..05d21a0f 100644 --- a/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs +++ b/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs @@ -26,12 +26,12 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference CredentialIssuerManager::new( None, MemoryStorage, - [Arc::new(KeySubject::from_keypair( + Arc::new(KeySubject::from_keypair( generate::(Some( "this-is-a-very-UNSAFE-issuer-secret-key".as_bytes().try_into().unwrap(), )), None, - ))], + )), ) .unwrap(), None, diff --git a/oid4vc-manager/tests/oid4vp/implicit.rs b/oid4vc-manager/tests/oid4vp/implicit.rs index e4ca8628..316105ee 100644 --- a/oid4vc-manager/tests/oid4vp/implicit.rs +++ b/oid4vc-manager/tests/oid4vp/implicit.rs @@ -89,7 +89,7 @@ async fn test_implicit_flow() { // Create a new relying party. let relying_party = Arc::new(KeySubject::new()); let relying_party_did = relying_party.identifier("did:key").unwrap(); - let relying_party_manager = RelyingPartyManager::new([relying_party], "did:key".to_string()).unwrap(); + let relying_party_manager = RelyingPartyManager::new(relying_party, "did:key".to_string()).unwrap(); // Create authorization request with response_type `id_token vp_token` let authorization_request = AuthorizationRequest::>::builder() @@ -101,7 +101,7 @@ async fn test_implicit_flow() { .unwrap(); // Create a provider manager and validate the authorization request. - let provider_manager = ProviderManager::new([subject], "did:key".to_string()).unwrap(); + let provider_manager = ProviderManager::new(subject, "did:key").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 6aacda83..24046c92 100644 --- a/oid4vc-manager/tests/siopv2/implicit.rs +++ b/oid4vc-manager/tests/siopv2/implicit.rs @@ -30,16 +30,16 @@ pub struct MultiDidMethodSubject { } impl Sign for MultiDidMethodSubject { - fn key_id(&self, did_method: &str) -> Option { - match did_method { - "did:test" => self.test_subject.key_id(did_method), - "did:key" => self.key_subject.key_id(did_method), + fn key_id(&self, subject_syntax_type: &str) -> Option { + match subject_syntax_type { + "did:test" => self.test_subject.key_id(subject_syntax_type), + "did:key" => self.key_subject.key_id(subject_syntax_type), _ => None, } } - fn sign(&self, message: &str, _did_method: &str) -> anyhow::Result> { - self.test_subject.sign(message, _did_method) + fn sign(&self, message: &str, _subject_syntax_type: &str) -> anyhow::Result> { + self.test_subject.sign(message, _subject_syntax_type) } fn external_signer(&self) -> Option> { @@ -59,10 +59,10 @@ impl Verify for MultiDidMethodSubject { } impl Subject for MultiDidMethodSubject { - fn identifier(&self, did_method: &str) -> anyhow::Result { - match did_method { - "did:test" => self.test_subject.identifier(did_method), - "did:key" => self.key_subject.identifier(did_method), + fn identifier(&self, subject_syntax_type: &str) -> anyhow::Result { + match subject_syntax_type { + "did:test" => self.test_subject.identifier(subject_syntax_type), + "did:key" => self.key_subject.identifier(subject_syntax_type), _ => Err(anyhow::anyhow!("Unsupported DID method.")), } } @@ -112,7 +112,7 @@ async fn test_implicit_flow() { }; // Create a new relying party manager. - let relying_party_manager = RelyingPartyManager::new([Arc::new(subject)], "did:test".to_string()).unwrap(); + let relying_party_manager = RelyingPartyManager::new(Arc::new(subject), "did:test".to_string()).unwrap(); // Create a new RequestUrl with response mode `direct_post` for cross-device communication. let authorization_request: AuthorizationRequest> = AuthorizationRequest::>::builder() @@ -166,7 +166,7 @@ async fn test_implicit_flow() { let subject = TestSubject::new("did:test:subject".to_string(), "did:test:subject#key_id".to_string()).unwrap(); // Create a new provider manager. - let provider_manager = ProviderManager::new([Arc::new(subject)], "did:test".to_string()).unwrap(); + let provider_manager = ProviderManager::new(Arc::new(subject), "did:test").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/proof.rs b/oid4vci/src/proof.rs index 7b78f510..c3f7e7b0 100644 --- a/oid4vci/src/proof.rs +++ b/oid4vci/src/proof.rs @@ -38,7 +38,7 @@ pub struct ProofBuilder { rfc7519_claims: RFC7519Claims, nonce: Option, signer: Option>, - did_method: Option, + subject_syntax_type: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -54,7 +54,9 @@ impl ProofBuilder { anyhow::ensure!(self.rfc7519_claims.iat.is_some(), "iat claim is required"); anyhow::ensure!(self.nonce.is_some(), "nonce claim is required"); - let did_method = self.did_method.ok_or(anyhow::anyhow!("did_method is required"))?; + let subject_syntax_type = self + .subject_syntax_type + .ok_or(anyhow::anyhow!("subject_syntax_type is required"))?; match self.proof_type { Some(ProofType::Jwt) => Ok(KeyProofType::Jwt { @@ -69,7 +71,7 @@ impl ProofBuilder { rfc7519_claims: self.rfc7519_claims, nonce: self.nonce.ok_or(anyhow::anyhow!("No nonce found"))?, }, - &did_method, + &subject_syntax_type, )?, }), Some(ProofType::Cwt) => todo!(), @@ -89,5 +91,5 @@ impl ProofBuilder { builder_fn!(rfc7519_claims, exp, i64); builder_fn!(rfc7519_claims, iat, i64); builder_fn!(nonce, String); - builder_fn!(did_method, String); + builder_fn!(subject_syntax_type, String); } diff --git a/oid4vci/src/wallet/mod.rs b/oid4vci/src/wallet/mod.rs index 2067c628..2f913860 100644 --- a/oid4vci/src/wallet/mod.rs +++ b/oid4vci/src/wallet/mod.rs @@ -23,20 +23,20 @@ where CFC: CredentialFormatCollection, { pub subject: SigningSubject, - pub default_did_method: String, + pub default_subject_syntax_type: String, pub client: ClientWithMiddleware, phantom: std::marker::PhantomData, } impl Wallet { - pub fn new(subject: SigningSubject, default_did_method: String) -> Self { + pub fn new(subject: SigningSubject, default_subject_syntax_type: String) -> Self { 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(); Self { subject, - default_did_method, + default_subject_syntax_type, client, phantom: std::marker::PhantomData, } @@ -107,7 +107,7 @@ impl Wallet { // TODO: must be `form`, but `AuthorizationRequest needs to be able to serilalize properly. .json(&AuthorizationRequest { response_type: "code".to_string(), - client_id: self.subject.identifier(&self.default_did_method)?, + client_id: self.subject.identifier(&self.default_subject_syntax_type)?, redirect_uri: None, scope: None, state: None, @@ -143,7 +143,7 @@ impl Wallet { KeyProofType::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) - .iss(self.subject.identifier(&self.default_did_method)?) + .iss(self.subject.identifier(&self.default_subject_syntax_type)?) .aud(credential_issuer_metadata.credential_issuer) .iat(1571324800) .exp(9999999999i64) @@ -155,7 +155,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) - .did_method(&self.default_did_method) + .subject_syntax_type(&self.default_subject_syntax_type) .build()?, ), }; @@ -181,7 +181,7 @@ impl Wallet { KeyProofType::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) - .iss(self.subject.identifier(&self.default_did_method)?) + .iss(self.subject.identifier(&self.default_subject_syntax_type)?) .aud(credential_issuer_metadata.credential_issuer) .iat(1571324800) .exp(9999999999i64) @@ -193,7 +193,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) - .did_method(&self.default_did_method) + .subject_syntax_type(&self.default_subject_syntax_type) .build()?, ); diff --git a/oid4vp/src/oid4vp.rs b/oid4vp/src/oid4vp.rs index ecd6c12d..57bc668a 100644 --- a/oid4vp/src/oid4vp.rs +++ b/oid4vp/src/oid4vp.rs @@ -10,8 +10,8 @@ use futures::{executor::block_on, future::join_all}; use identity_credential::{credential::Jwt, presentation::Presentation}; use jsonwebtoken::{Algorithm, Header}; use oid4vc_core::openid4vc_extension::{OpenID4VC, RequestHandle, ResponseHandle}; -use oid4vc_core::Validator; use oid4vc_core::{authorization_response::AuthorizationResponse, jwt, openid4vc_extension::Extension, Subject}; +use oid4vc_core::{SubjectSyntaxType, Validator}; use oid4vci::VerifiableCredentialJwt; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -46,9 +46,13 @@ impl Extension for OID4VP { client_id: &str, extension_parameters: &::Parameters, user_input: &::Input, - did_method: &str, + subject_syntax_type: impl TryInto, ) -> anyhow::Result> { - let subject_identifier = subject.identifier(did_method)?; + let subject_syntax_type_string = subject_syntax_type + .try_into() + .map_err(|_| anyhow::anyhow!("Failed to convert the subject syntax type"))? + .to_string(); + let subject_identifier = subject.identifier(&subject_syntax_type_string)?; let vp_token = VpToken::builder() .iss(subject_identifier.clone()) @@ -61,7 +65,12 @@ impl Extension for OID4VP { .verifiable_presentation(user_input.verifiable_presentation.clone()) .build()?; - let jwt = jwt::encode(subject.clone(), Header::new(Algorithm::EdDSA), vp_token, did_method)?; + let jwt = jwt::encode( + subject.clone(), + Header::new(Algorithm::EdDSA), + vp_token, + &subject_syntax_type_string, + )?; Ok(vec![jwt]) } diff --git a/siopv2/src/provider.rs b/siopv2/src/provider.rs index 8dddec09..b9482d62 100644 --- a/siopv2/src/provider.rs +++ b/siopv2/src/provider.rs @@ -6,7 +6,7 @@ use oid4vc_core::{ authorization_request::{AuthorizationRequest, Body, ByReference, ByValue, Object}, authorization_response::AuthorizationResponse, openid4vc_extension::{Extension, ResponseHandle}, - Validator, + SubjectSyntaxType, Validator, }; use reqwest::StatusCode; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; @@ -17,13 +17,13 @@ use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; /// the user who is trying to authenticate. pub struct Provider { pub subject: SigningSubject, - pub default_did_method: String, + pub default_subject_syntax_type: SubjectSyntaxType, client: ClientWithMiddleware, } impl Provider { // TODO: Use ProviderBuilder instead. - pub fn new(subject: SigningSubject, default_did_method: String) -> Result { + pub fn new(subject: SigningSubject, default_subject_syntax_type: impl TryInto) -> 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)) @@ -31,7 +31,9 @@ impl Provider { Ok(Provider { subject, client, - default_did_method, + default_subject_syntax_type: default_subject_syntax_type + .try_into() + .map_err(|_| anyhow::anyhow!("Invalid did method."))?, }) } @@ -93,7 +95,7 @@ impl Provider { &authorization_request.body.client_id, &authorization_request.body.extension, &input, - &self.default_did_method, + self.default_subject_syntax_type.clone(), )?; E::build_authorization_response(jwts, input, redirect_uri, state) @@ -125,7 +127,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".to_string()).unwrap(); + let provider = Provider::new(Arc::new(subject), "did:test").unwrap(); // Get a new SIOP authorization_request with response mode `direct_post` for cross-device communication. let request_url = "\ diff --git a/siopv2/src/relying_party.rs b/siopv2/src/relying_party.rs index 12de5aa2..a965d6d9 100644 --- a/siopv2/src/relying_party.rs +++ b/siopv2/src/relying_party.rs @@ -15,16 +15,16 @@ pub struct RelyingParty { // TODO: Strictly speaking a relying party doesn't need to have a [`Subject`]. It just needs methods to // sign and verify tokens. For simplicity we use a [`Subject`] here for now but we should consider a cleaner solution. pub subject: SigningSubject, - pub default_did_method: String, + pub default_subject_syntax_type: String, pub sessions: HashMap<(String, String), AuthorizationRequest>>, } impl RelyingParty { // TODO: Use RelyingPartyBuilder instead. - pub fn new(subject: SigningSubject, default_did_method: String) -> Result { + pub fn new(subject: SigningSubject, default_subject_syntax_type: String) -> Result { Ok(RelyingParty { subject, - default_did_method, + default_subject_syntax_type, sessions: HashMap::new(), }) } @@ -34,7 +34,7 @@ impl RelyingParty { self.subject.clone(), Header::new(Algorithm::EdDSA), authorization_request, - &self.default_did_method, + &self.default_subject_syntax_type, ) } diff --git a/siopv2/src/siopv2.rs b/siopv2/src/siopv2.rs index 7b58ab02..fe875035 100644 --- a/siopv2/src/siopv2.rs +++ b/siopv2/src/siopv2.rs @@ -4,8 +4,8 @@ use crate::token::id_token::IdToken; use chrono::{Duration, Utc}; use jsonwebtoken::{Algorithm, Header}; use oid4vc_core::openid4vc_extension::{OpenID4VC, RequestHandle, ResponseHandle}; -use oid4vc_core::Validator; use oid4vc_core::{authorization_response::AuthorizationResponse, jwt, openid4vc_extension::Extension, Subject}; +use oid4vc_core::{SubjectSyntaxType, Validator}; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -39,9 +39,13 @@ impl Extension for SIOPv2 { client_id: &str, extension_parameters: &::Parameters, user_input: &::Input, - did_method: &str, + subject_syntax_type: impl TryInto, ) -> anyhow::Result> { - let subject_identifier = subject.identifier(did_method)?; + let subject_syntax_type_string = subject_syntax_type + .try_into() + .map_err(|_| anyhow::anyhow!("Failed to convert the subject syntax type"))? + .to_string(); + let subject_identifier = subject.identifier(&subject_syntax_type_string)?; let id_token = IdToken::builder() .iss(subject_identifier.clone()) @@ -54,7 +58,12 @@ impl Extension for SIOPv2 { .claims(user_input.clone()) .build()?; - let jwt = jwt::encode(subject.clone(), Header::new(Algorithm::EdDSA), id_token, did_method)?; + let jwt = jwt::encode( + subject.clone(), + Header::new(Algorithm::EdDSA), + id_token, + &subject_syntax_type_string, + )?; Ok(vec![jwt]) } diff --git a/siopv2/src/test_utils.rs b/siopv2/src/test_utils.rs index 40fad0a6..d3c93608 100644 --- a/siopv2/src/test_utils.rs +++ b/siopv2/src/test_utils.rs @@ -31,11 +31,11 @@ impl TestSubject { } impl Sign for TestSubject { - fn key_id(&self, _did_method: &str) -> Option { + fn key_id(&self, _subject_syntax_type: &str) -> Option { Some(self.key_id.clone()) } - fn sign(&self, message: &str, _did_method: &str) -> Result> { + fn sign(&self, message: &str, _subject_syntax_type: &str) -> Result> { let signature: Signature = TEST_KEYPAIR.sign(message.as_bytes()); Ok(signature.to_bytes().to_vec()) } @@ -53,7 +53,7 @@ impl Verify for TestSubject { } impl Subject for TestSubject { - fn identifier(&self, _did_method: &str) -> Result { + fn identifier(&self, _subject_syntax_type: &str) -> Result { Ok(self.did.to_string()) } } From 986af6e5f445baaafeb83946068bef51f787d23b Mon Sep 17 00:00:00 2001 From: Nander Stabel Date: Tue, 9 Apr 2024 09:35:37 +0200 Subject: [PATCH 3/4] feat: add `default_subject_syntax_type` --- oid4vc-manager/src/managers/presentation.rs | 2 +- oid4vc-manager/src/managers/relying_party.rs | 9 +++-- 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 | 4 +-- oid4vc-manager/tests/siopv2/implicit.rs | 33 +++++++++++++------ .../credential_configurations_supported.rs | 2 +- oid4vci/src/wallet/mod.rs | 26 +++++++++------ siopv2/src/relying_party.rs | 12 ++++--- 10 files changed, 59 insertions(+), 36 deletions(-) diff --git a/oid4vc-manager/src/managers/presentation.rs b/oid4vc-manager/src/managers/presentation.rs index cb705ba6..acd6b595 100644 --- a/oid4vc-manager/src/managers/presentation.rs +++ b/oid4vc-manager/src/managers/presentation.rs @@ -9,7 +9,7 @@ use oid4vp::{ // TODO: make VP/VC fromat agnostic. In current form only jwt_vp_json + jwt_vc_json are supported. pub fn create_presentation_submission( presentation_definition: &PresentationDefinition, - credentials: Vec, + credentials: &[serde_json::Value], ) -> Result { let id = "Submission ID".to_string(); let definition_id = presentation_definition.id().clone(); diff --git a/oid4vc-manager/src/managers/relying_party.rs b/oid4vc-manager/src/managers/relying_party.rs index 4f38a8d8..3e63b818 100644 --- a/oid4vc-manager/src/managers/relying_party.rs +++ b/oid4vc-manager/src/managers/relying_party.rs @@ -3,7 +3,7 @@ use oid4vc_core::{ authorization_request::{AuthorizationRequest, Object}, authorization_response::AuthorizationResponse, openid4vc_extension::{Extension, ResponseHandle}, - Subject, + Subject, SubjectSyntaxType, }; use siopv2::RelyingParty; use std::sync::Arc; @@ -14,9 +14,12 @@ pub struct RelyingPartyManager { } impl RelyingPartyManager { - pub fn new(subject: Arc, subject_syntax_type: String) -> Result { + pub fn new( + subject: Arc, + default_subject_syntax_type: impl TryInto, + ) -> Result { Ok(Self { - relying_party: RelyingParty::new(subject, subject_syntax_type)?, + relying_party: RelyingParty::new(subject, default_subject_syntax_type)?, }) } diff --git a/oid4vc-manager/src/methods/key_method.rs b/oid4vc-manager/src/methods/key_method.rs index 89f6a3c2..d693421f 100644 --- a/oid4vc-manager/src/methods/key_method.rs +++ b/oid4vc-manager/src/methods/key_method.rs @@ -151,8 +151,7 @@ mod tests { .unwrap(); // Let the relying party validate the authorization_response. - let relying_party_manager = - RelyingPartyManager::new(Arc::new(KeySubject::new()), "did:key".to_string()).unwrap(); + let relying_party_manager = RelyingPartyManager::new(Arc::new(KeySubject::new()), "did:key").unwrap(); assert!(relying_party_manager .validate_response(&authorization_response) .await diff --git a/oid4vc-manager/tests/oid4vci/authorization_code.rs b/oid4vc-manager/tests/oid4vci/authorization_code.rs index 4d6bc891..94d8fadc 100644 --- a/oid4vc-manager/tests/oid4vci/authorization_code.rs +++ b/oid4vc-manager/tests/oid4vci/authorization_code.rs @@ -41,7 +41,7 @@ async fn test_authorization_code_flow() { let subject_did = subject.identifier("did:key").unwrap(); // Create a new wallet. - let wallet = Wallet::new(Arc::new(subject), "did:key".to_string()); + let wallet = Wallet::new(Arc::new(subject), "did:key").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 05d21a0f..6aa6d3ac 100644 --- a/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs +++ b/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs @@ -45,7 +45,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference let subject_did = subject.identifier("did:key").unwrap(); // Create a new wallet. - let wallet: Wallet = Wallet::new(Arc::new(subject), "did:key".to_string()); + let wallet: Wallet = Wallet::new(Arc::new(subject), "did:key").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 316105ee..308e0898 100644 --- a/oid4vc-manager/tests/oid4vp/implicit.rs +++ b/oid4vc-manager/tests/oid4vp/implicit.rs @@ -89,7 +89,7 @@ async fn test_implicit_flow() { // Create a new relying party. let relying_party = Arc::new(KeySubject::new()); let relying_party_did = relying_party.identifier("did:key").unwrap(); - let relying_party_manager = RelyingPartyManager::new(relying_party, "did:key".to_string()).unwrap(); + let relying_party_manager = RelyingPartyManager::new(relying_party, "did:key").unwrap(); // Create authorization request with response_type `id_token vp_token` let authorization_request = AuthorizationRequest::>::builder() @@ -134,7 +134,7 @@ async fn test_implicit_flow() { // Create presentation submission using the presentation definition and the verifiable credential. let presentation_submission = create_presentation_submission( &PRESENTATION_DEFINITION, - vec![serde_json::to_value(&verifiable_credential).unwrap()], + &vec![serde_json::to_value(&verifiable_credential).unwrap()], ) .unwrap(); diff --git a/oid4vc-manager/tests/siopv2/implicit.rs b/oid4vc-manager/tests/siopv2/implicit.rs index 24046c92..c1962177 100644 --- a/oid4vc-manager/tests/siopv2/implicit.rs +++ b/oid4vc-manager/tests/siopv2/implicit.rs @@ -24,6 +24,7 @@ use wiremock::{ Mock, MockServer, ResponseTemplate, }; +/// A Subject that can sign and verify messages with multiple different DID Methods. pub struct MultiDidMethodSubject { pub test_subject: TestSubject, pub key_subject: KeySubject, @@ -38,8 +39,12 @@ impl Sign for MultiDidMethodSubject { } } - fn sign(&self, message: &str, _subject_syntax_type: &str) -> anyhow::Result> { - self.test_subject.sign(message, _subject_syntax_type) + fn sign(&self, message: &str, subject_syntax_type: &str) -> anyhow::Result> { + match subject_syntax_type { + "did:test" => self.test_subject.sign(message, subject_syntax_type), + "did:key" => self.key_subject.sign(message, subject_syntax_type), + _ => Err(anyhow::anyhow!("Unsupported DID method.")), + } } fn external_signer(&self) -> Option> { @@ -95,8 +100,11 @@ lazy_static! { ); } +#[rstest::rstest] +#[case("did:key")] +#[case("did:test")] #[tokio::test] -async fn test_implicit_flow() { +async fn test_implicit_flow(#[case] did_method: &str) { // Create a new mock server and retreive it's url. let mock_server = MockServer::start().await; let server_url = mock_server.uri(); @@ -111,12 +119,14 @@ async fn test_implicit_flow() { key_subject: KeySubject::from_keypair(generate::(None), None), }; + let client_id = subject.identifier(did_method).unwrap(); + // Create a new relying party manager. - let relying_party_manager = RelyingPartyManager::new(Arc::new(subject), "did:test".to_string()).unwrap(); + let relying_party_manager = RelyingPartyManager::new(Arc::new(subject), did_method).unwrap(); // Create a new RequestUrl with response mode `direct_post` for cross-device communication. let authorization_request: AuthorizationRequest> = AuthorizationRequest::>::builder() - .client_id("did:test:relyingparty".to_string()) + .client_id(&client_id) .scope(Scope::from(vec![ScopeValue::OpenId, ScopeValue::Phone])) .redirect_uri(format!("{server_url}/redirect_uri").parse::().unwrap()) .response_mode("direct_post".to_string()) @@ -124,7 +134,7 @@ async fn test_implicit_flow() { client_name: None, logo_uri: None, extension: ClientMetadataParameters { - subject_syntax_types_supported: vec![SubjectSyntaxType::Did(DidMethod::from_str("did:test").unwrap())], + subject_syntax_types_supported: vec![SubjectSyntaxType::Did(DidMethod::from_str(did_method).unwrap())], }, }) .claims( @@ -162,17 +172,20 @@ async fn test_implicit_flow() { // Create a new storage for the user's claims. let storage = MemoryStorage::new(serde_json::from_value(USER_CLAIMS.clone()).unwrap()); - // Create a new subject and validator. - let subject = TestSubject::new("did:test:subject".to_string(), "did:test:subject#key_id".to_string()).unwrap(); + // Create a new subject. + let subject = MultiDidMethodSubject { + test_subject: TestSubject::new("did:test:subject".to_string(), "did:test:subject#key_id".to_string()).unwrap(), + key_subject: KeySubject::from_keypair(generate::(None), None), + }; // Create a new provider manager. - let provider_manager = ProviderManager::new(Arc::new(subject), "did:test").unwrap(); + let provider_manager = ProviderManager::new(Arc::new(subject), did_method).unwrap(); // Create a new RequestUrl which includes a `request_uri` pointing to the mock server's `request_uri` endpoint. let authorization_request = AuthorizationRequest:: { custom_url_scheme: "openid".to_string(), body: ByReference { - client_id: "did:test:relyingparty".to_string(), + client_id, request_uri: format!("{server_url}/request_uri").parse::().unwrap(), }, }; diff --git a/oid4vci/src/credential_issuer/credential_configurations_supported.rs b/oid4vci/src/credential_issuer/credential_configurations_supported.rs index 1e49106b..c16153f1 100644 --- a/oid4vci/src/credential_issuer/credential_configurations_supported.rs +++ b/oid4vci/src/credential_issuer/credential_configurations_supported.rs @@ -10,7 +10,7 @@ use serde_with::skip_serializing_none; /// Credentials Supported object as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-11.2.3-2.11.1 #[skip_serializing_none] -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] pub struct CredentialConfigurationsSupportedObject> where CFC: CredentialFormatCollection, diff --git a/oid4vci/src/wallet/mod.rs b/oid4vci/src/wallet/mod.rs index 2f913860..128b6cc6 100644 --- a/oid4vci/src/wallet/mod.rs +++ b/oid4vci/src/wallet/mod.rs @@ -12,6 +12,7 @@ use crate::proof::{KeyProofType, ProofType}; use crate::{credential_response::CredentialResponse, token_request::TokenRequest, token_response::TokenResponse}; use anyhow::Result; use oid4vc_core::authentication::subject::SigningSubject; +use oid4vc_core::SubjectSyntaxType; use reqwest::Url; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_retry::policies::ExponentialBackoff; @@ -23,23 +24,28 @@ where CFC: CredentialFormatCollection, { pub subject: SigningSubject, - pub default_subject_syntax_type: String, + pub default_subject_syntax_type: SubjectSyntaxType, pub client: ClientWithMiddleware, phantom: std::marker::PhantomData, } impl Wallet { - pub fn new(subject: SigningSubject, default_subject_syntax_type: String) -> Self { + pub fn new( + subject: SigningSubject, + default_subject_syntax_type: impl TryInto, + ) -> anyhow::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(); - Self { + Ok(Self { subject, - default_subject_syntax_type, + default_subject_syntax_type: default_subject_syntax_type + .try_into() + .map_err(|_| anyhow::anyhow!("Invalid did method"))?, client, phantom: std::marker::PhantomData, - } + }) } pub async fn get_credential_offer(&self, credential_offer_uri: Url) -> Result { @@ -107,7 +113,7 @@ impl Wallet { // TODO: must be `form`, but `AuthorizationRequest needs to be able to serilalize properly. .json(&AuthorizationRequest { response_type: "code".to_string(), - client_id: self.subject.identifier(&self.default_subject_syntax_type)?, + client_id: self.subject.identifier(&self.default_subject_syntax_type.to_string())?, redirect_uri: None, scope: None, state: None, @@ -143,7 +149,7 @@ impl Wallet { KeyProofType::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) - .iss(self.subject.identifier(&self.default_subject_syntax_type)?) + .iss(self.subject.identifier(&self.default_subject_syntax_type.to_string())?) .aud(credential_issuer_metadata.credential_issuer) .iat(1571324800) .exp(9999999999i64) @@ -155,7 +161,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) - .subject_syntax_type(&self.default_subject_syntax_type) + .subject_syntax_type(self.default_subject_syntax_type.to_string()) .build()?, ), }; @@ -181,7 +187,7 @@ impl Wallet { KeyProofType::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) - .iss(self.subject.identifier(&self.default_subject_syntax_type)?) + .iss(self.subject.identifier(&self.default_subject_syntax_type.to_string())?) .aud(credential_issuer_metadata.credential_issuer) .iat(1571324800) .exp(9999999999i64) @@ -193,7 +199,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) - .subject_syntax_type(&self.default_subject_syntax_type) + .subject_syntax_type(self.default_subject_syntax_type.to_string()) .build()?, ); diff --git a/siopv2/src/relying_party.rs b/siopv2/src/relying_party.rs index a965d6d9..3f30304c 100644 --- a/siopv2/src/relying_party.rs +++ b/siopv2/src/relying_party.rs @@ -7,7 +7,7 @@ use oid4vc_core::{ authorization_response::AuthorizationResponse, jwt, openid4vc_extension::{Extension, ResponseHandle}, - Validator, + SubjectSyntaxType, Validator, }; use std::collections::HashMap; @@ -15,16 +15,18 @@ pub struct RelyingParty { // TODO: Strictly speaking a relying party doesn't need to have a [`Subject`]. It just needs methods to // sign and verify tokens. For simplicity we use a [`Subject`] here for now but we should consider a cleaner solution. pub subject: SigningSubject, - pub default_subject_syntax_type: String, + pub default_subject_syntax_type: SubjectSyntaxType, pub sessions: HashMap<(String, String), AuthorizationRequest>>, } impl RelyingParty { // TODO: Use RelyingPartyBuilder instead. - pub fn new(subject: SigningSubject, default_subject_syntax_type: String) -> Result { + pub fn new(subject: SigningSubject, default_subject_syntax_type: impl TryInto) -> Result { Ok(RelyingParty { subject, - default_subject_syntax_type, + default_subject_syntax_type: default_subject_syntax_type + .try_into() + .map_err(|_| anyhow::anyhow!("Invalid did method."))?, sessions: HashMap::new(), }) } @@ -34,7 +36,7 @@ impl RelyingParty { self.subject.clone(), Header::new(Algorithm::EdDSA), authorization_request, - &self.default_subject_syntax_type, + &self.default_subject_syntax_type.to_string(), ) } From 6b078e2e8ed02c2464eafd91f0b7a41fef180f4c Mon Sep 17 00:00:00 2001 From: Nander Stabel Date: Fri, 19 Apr 2024 09:38:34 +0200 Subject: [PATCH 4/4] feat: add `default_subject_syntax_type` getter for `RelyingPartyManager` and `ProviderManager` --- oid4vc-manager/src/managers/provider.rs | 4 ++++ oid4vc-manager/src/managers/relying_party.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/oid4vc-manager/src/managers/provider.rs b/oid4vc-manager/src/managers/provider.rs index 12d3ec46..e462581b 100644 --- a/oid4vc-manager/src/managers/provider.rs +++ b/oid4vc-manager/src/managers/provider.rs @@ -42,4 +42,8 @@ impl ProviderManager { ) -> Result { self.provider.send_response(authorization_response).await } + + pub fn default_subject_syntax_type(&self) -> &SubjectSyntaxType { + &self.provider.default_subject_syntax_type + } } diff --git a/oid4vc-manager/src/managers/relying_party.rs b/oid4vc-manager/src/managers/relying_party.rs index 3e63b818..54a4945d 100644 --- a/oid4vc-manager/src/managers/relying_party.rs +++ b/oid4vc-manager/src/managers/relying_party.rs @@ -33,4 +33,8 @@ impl RelyingPartyManager { ) -> Result<::ResponseItem> { self.relying_party.validate_response(authorization_response).await } + + pub fn default_subject_syntax_type(&self) -> &SubjectSyntaxType { + &self.relying_party.default_subject_syntax_type + } }