diff --git a/oid4vc-core/src/authentication/sign.rs b/oid4vc-core/src/authentication/sign.rs index eeb36cd9..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) -> Option; - fn sign(&self, message: &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 9002972e..54ddc2fc 100644 --- a/oid4vc-core/src/authentication/subject.rs +++ b/oid4vc-core/src/authentication/subject.rs @@ -1,37 +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) -> Result; - fn type_(&self) -> Result { - SubjectSyntaxType::from_str(&self.identifier()?) - } -} - -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; - - 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 ed989bb0..4bb6678e 100644 --- a/oid4vc-core/src/authentication/validator.rs +++ b/oid4vc-core/src/authentication/validator.rs @@ -1,25 +1,8 @@ -use crate::{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), @@ -32,4 +15,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/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/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..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) -> 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().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)?; + 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) @@ -95,7 +97,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..fa3b6205 100644 --- a/oid4vc-core/src/lib.rs +++ b/oid4vc-core/src/lib.rs @@ -2,22 +2,13 @@ pub mod authentication; 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; 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 decoder::Decoder; +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 fd10ad7c..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, Decoder, Subject}; +use crate::{authorization_response::AuthorizationResponse, Subject, SubjectSyntaxType, 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, + _subject_syntax_type: impl TryInto, ) -> 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/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 0ffca333..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) -> Option { + fn key_id(&self, _subject_syntax_type: &str) -> Option { Some(self.key_id.clone()) } - fn sign(&self, message: &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) -> 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, + 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/provider.rs b/oid4vc-manager/src/managers/provider.rs index f3532bbc..e462581b 100644 --- a/oid4vc-manager/src/managers/provider.rs +++ b/oid4vc-manager/src/managers/provider.rs @@ -3,30 +3,29 @@ 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( + subject: Arc, + default_subject_syntax_type: impl TryInto, + ) -> Result { Ok(Self { - provider: Provider::new(subjects[0].clone())?, - subjects: Subjects::try_from(subjects)?, + provider: Provider::new(subject, default_subject_syntax_type)?, }) } 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( @@ -44,36 +43,7 @@ impl ProviderManager { self.provider.send_response(authorization_response).await } - 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()) + 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 221e1515..54a4945d 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}, - 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( + subject: Arc, + default_subject_syntax_type: impl TryInto, + ) -> Result { Ok(Self { - relying_party: RelyingParty::new(subjects.first().ok_or_else(|| anyhow!("No subjects found."))?.clone())?, - subjects: Subjects::try_from(subjects)?, + relying_party: RelyingParty::new(subject, default_subject_syntax_type)?, }) } @@ -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(()) + pub fn default_subject_syntax_type(&self) -> &SubjectSyntaxType { + &self.relying_party.default_subject_syntax_type } } diff --git a/oid4vc-manager/src/methods/key_method.rs b/oid4vc-manager/src/methods/key_method.rs index 8530b910..d693421f 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, _subject_syntax_type: &str) -> Option { self.document .authentication .as_ref() .and_then(|authentication_methods| authentication_methods.first().cloned()) } - fn sign(&self, message: &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) -> 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)]).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 = "\ @@ -151,7 +151,7 @@ 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").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 d579707a..9d7b1dd6 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 12fba4ef..41ea08d3 100644 --- a/oid4vc-manager/tests/common/memory_storage.rs +++ b/oid4vc-manager/tests/common/memory_storage.rs @@ -134,6 +134,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..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) -> Option { + fn key_id(&self, _subject_syntax_type: &str) -> Option { Some(self.key_id.clone()) } - fn sign(&self, message: &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) -> 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 62a8c0c1..94d8fadc 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, @@ -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").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 622038cf..6aa6d3ac 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, @@ -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> = Wallet::new(Arc::new(subject)); + 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 3bef6895..308e0898 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").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").unwrap(); // Create a new verifiable credential. let verifiable_credential = VerifiableCredentialJwt::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(); @@ -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 0cb214de..c1962177 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,55 @@ 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, +} + +impl Sign for MultiDidMethodSubject { + 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, 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> { + 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, 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.")), + } + } +} + lazy_static! { pub static ref USER_CLAIMS: serde_json::Value = serde_json::json!( { @@ -48,25 +100,33 @@ 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(); // 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), + }; + + let client_id = subject.identifier(did_method).unwrap(); // 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_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()) @@ -74,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( @@ -112,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)]).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/credential_issuer/mod.rs b/oid4vci/src/credential_issuer/mod.rs index 9ce0d89e..8c9010a7 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, KeyProofType}; -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: KeyProofType, decoder: Decoder) -> anyhow::Result { + pub async fn validate_proof(&self, proof: KeyProofType, validator: Validator) -> anyhow::Result { match proof { - KeyProofType::Jwt { jwt, .. } => decoder.decode(jwt).await, + KeyProofType::Jwt { jwt, .. } => validator.decode(jwt).await, KeyProofType::Cwt { .. } => unimplemented!("CWT is not supported yet"), } } diff --git a/oid4vci/src/proof.rs b/oid4vci/src/proof.rs index 8cd0cd82..c3f7e7b0 100644 --- a/oid4vci/src/proof.rs +++ b/oid4vci/src/proof.rs @@ -38,6 +38,7 @@ pub struct ProofBuilder { rfc7519_claims: RFC7519Claims, nonce: Option, signer: Option>, + subject_syntax_type: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -53,6 +54,10 @@ 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 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 { jwt: jwt::encode( @@ -66,6 +71,7 @@ impl ProofBuilder { rfc7519_claims: self.rfc7519_claims, nonce: self.nonce.ok_or(anyhow::anyhow!("No nonce found"))?, }, + &subject_syntax_type, )?, }), Some(ProofType::Cwt) => todo!(), @@ -85,4 +91,5 @@ impl ProofBuilder { builder_fn!(rfc7519_claims, exp, i64); builder_fn!(rfc7519_claims, iat, i64); builder_fn!(nonce, String); + builder_fn!(subject_syntax_type, String); } diff --git a/oid4vci/src/wallet/mod.rs b/oid4vci/src/wallet/mod.rs index 6fc7df9f..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,21 +24,28 @@ where CFC: CredentialFormatCollection, { pub subject: SigningSubject, + pub default_subject_syntax_type: SubjectSyntaxType, pub client: ClientWithMiddleware, phantom: std::marker::PhantomData, } impl Wallet { - pub fn new(subject: SigningSubject) -> 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 + .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 { @@ -105,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()?, + client_id: self.subject.identifier(&self.default_subject_syntax_type.to_string())?, redirect_uri: None, scope: None, state: None, @@ -141,7 +149,7 @@ impl Wallet { KeyProofType::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) - .iss(self.subject.identifier()?) + .iss(self.subject.identifier(&self.default_subject_syntax_type.to_string())?) .aud(credential_issuer_metadata.credential_issuer) .iat(1571324800) .exp(9999999999i64) @@ -153,6 +161,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) + .subject_syntax_type(self.default_subject_syntax_type.to_string()) .build()?, ), }; @@ -178,7 +187,7 @@ impl Wallet { KeyProofType::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) - .iss(self.subject.identifier()?) + .iss(self.subject.identifier(&self.default_subject_syntax_type.to_string())?) .aud(credential_issuer_metadata.credential_issuer) .iat(1571324800) .exp(9999999999i64) @@ -190,6 +199,7 @@ impl Wallet { .ok_or(anyhow::anyhow!("No c_nonce found."))? .clone(), ) + .subject_syntax_type(self.default_subject_syntax_type.to_string()) .build()?, ); diff --git a/oid4vp/src/oid4vp.rs b/oid4vp/src/oid4vp.rs index 2c1524e6..57bc668a 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::{authorization_response::AuthorizationResponse, jwt, openid4vc_extension::Extension, Subject}; +use oid4vc_core::{SubjectSyntaxType, Validator}; use oid4vci::VerifiableCredentialJwt; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -47,8 +46,13 @@ impl Extension for OID4VP { client_id: &str, extension_parameters: &::Parameters, user_input: &::Input, + subject_syntax_type: impl TryInto, ) -> anyhow::Result> { - let subject_identifier = subject.identifier()?; + 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)?; + let jwt = jwt::encode( + subject.clone(), + Header::new(Algorithm::EdDSA), + vp_token, + &subject_syntax_type_string, + )?; Ok(vec![jwt]) } @@ -84,12 +93,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 +106,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..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}, - Decoder, + SubjectSyntaxType, Validator, }; use reqwest::StatusCode; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; @@ -17,28 +17,33 @@ 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, client: ClientWithMiddleware, } impl Provider { // TODO: Use ProviderBuilder instead. - pub fn new(subject: SigningSubject) -> 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)) .build(); - Ok(Provider { subject, client }) + Ok(Provider { + subject, + client, + default_subject_syntax_type: default_subject_syntax_type + .try_into() + .map_err(|_| anyhow::anyhow!("Invalid 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 +52,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 +64,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 +95,7 @@ impl Provider { &authorization_request.body.client_id, &authorization_request.body.extension, &input, + self.default_subject_syntax_type.clone(), )?; E::build_authorization_response(jwts, input, redirect_uri, state) @@ -113,8 +119,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 +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)).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 = "\ @@ -139,18 +144,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..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}, - Decoder, + SubjectSyntaxType, Validator, }; use std::collections::HashMap; @@ -15,14 +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: SubjectSyntaxType, pub sessions: HashMap<(String, String), AuthorizationRequest>>, } impl RelyingParty { // TODO: Use RelyingPartyBuilder instead. - pub fn new(subject: SigningSubject) -> Result { + pub fn new(subject: SigningSubject, default_subject_syntax_type: impl TryInto) -> Result { Ok(RelyingParty { subject, + default_subject_syntax_type: default_subject_syntax_type + .try_into() + .map_err(|_| anyhow::anyhow!("Invalid did method."))?, sessions: HashMap::new(), }) } @@ -32,6 +36,7 @@ impl RelyingParty { self.subject.clone(), Header::new(Algorithm::EdDSA), authorization_request, + &self.default_subject_syntax_type.to_string(), ) } @@ -40,8 +45,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..fe875035 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::{authorization_response::AuthorizationResponse, jwt, openid4vc_extension::Extension, Subject}; +use oid4vc_core::{SubjectSyntaxType, Validator}; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -40,8 +39,13 @@ impl Extension for SIOPv2 { client_id: &str, extension_parameters: &::Parameters, user_input: &::Input, + subject_syntax_type: impl TryInto, ) -> anyhow::Result> { - let subject_identifier = subject.identifier()?; + 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)?; + let jwt = jwt::encode( + subject.clone(), + Header::new(Algorithm::EdDSA), + id_token, + &subject_syntax_type_string, + )?; Ok(vec![jwt]) } @@ -77,11 +86,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..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) -> Option { + fn key_id(&self, _subject_syntax_type: &str) -> Option { Some(self.key_id.clone()) } - fn sign(&self, message: &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) -> Result { + fn identifier(&self, _subject_syntax_type: &str) -> Result { Ok(self.did.to_string()) } }