Skip to content

Commit 73430e3

Browse files
committed
WIP
1 parent 274a3e9 commit 73430e3

File tree

15 files changed

+249
-40
lines changed

15 files changed

+249
-40
lines changed

oid4vc-core/src/client_metadata.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use serde::{Deserialize, Serialize};
22
use serde_with::skip_serializing_none;
3+
use std::collections::HashMap;
34
use url::Url;
45

56
/// [`ClientMetadata`] is a request parameter used by a [`crate::RelyingParty`] to communicate its capabilities to a
@@ -16,6 +17,8 @@ pub enum ClientMetadataResource<T = ()> {
1617
/// expanded with Extensions and profiles.
1718
#[serde(flatten)]
1819
extension: T,
20+
#[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
21+
other: HashMap<String, serde_json::Value>,
1922
},
2023
ClientMetadataUri(String),
2124
}

oid4vc-core/src/openid4vc_extension.rs

+7
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ pub trait Extension: Serialize + PartialEq + Sized + std::fmt::Debug + Clone + S
4343
async { Err(anyhow::anyhow!("Not implemented.")) }
4444
}
4545

46+
fn get_relying_party_supported_syntax_types(
47+
_authorization_request: &<Self::RequestHandle as RequestHandle>::Parameters,
48+
) -> impl Future<Output = anyhow::Result<Vec<SubjectSyntaxType>>> {
49+
// Will be overwritten by the extension.
50+
async { Err(anyhow::anyhow!("Not implemented.")) }
51+
}
52+
4653
fn build_authorization_response(
4754
_jwts: Vec<String>,
4855
_user_input: <Self::ResponseHandle as ResponseHandle>::Input,

oid4vc-core/src/subject_syntax_type.rs

+8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ impl TryFrom<&str> for SubjectSyntaxType {
4040
}
4141
}
4242

43+
impl TryFrom<String> for SubjectSyntaxType {
44+
type Error = anyhow::Error;
45+
46+
fn try_from(value: String) -> Result<Self, Self::Error> {
47+
SubjectSyntaxType::from_str(value.as_str())
48+
}
49+
}
50+
4351
impl From<DidMethod> for SubjectSyntaxType {
4452
fn from(did_method: DidMethod) -> Self {
4553
SubjectSyntaxType::Did(did_method)

oid4vc-manager/src/managers/provider.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,36 @@ pub struct ProviderManager {
1818
impl ProviderManager {
1919
pub fn new(
2020
subject: Arc<dyn Subject>,
21-
default_subject_syntax_type: impl TryInto<SubjectSyntaxType>,
21+
supported_subject_syntax_types: Vec<impl TryInto<SubjectSyntaxType>>,
2222
supported_signing_algorithms: Vec<Algorithm>,
2323
) -> Result<Self> {
2424
Ok(Self {
25-
provider: Provider::new(subject, default_subject_syntax_type, supported_signing_algorithms)?,
25+
provider: Provider::new(subject, supported_subject_syntax_types, supported_signing_algorithms)?,
2626
})
2727
}
2828

2929
pub async fn validate_request(&self, authorization_request: String) -> Result<AuthorizationRequest<Object>> {
3030
self.provider.validate_request(authorization_request).await
3131
}
3232

33+
pub async fn get_matching_signing_algorithm<E: Extension>(
34+
&self,
35+
authorization_request: &AuthorizationRequest<Object<E>>,
36+
) -> Result<Algorithm> {
37+
self.provider
38+
.get_matching_signing_algorithm(authorization_request)
39+
.await
40+
}
41+
42+
pub async fn get_matching_subject_syntax_type<E: Extension>(
43+
&self,
44+
authorization_request: &AuthorizationRequest<Object<E>>,
45+
) -> Result<SubjectSyntaxType> {
46+
self.provider
47+
.get_matching_subject_syntax_type(authorization_request)
48+
.await
49+
}
50+
3351
pub async fn generate_response<E: Extension + OpenID4VC>(
3452
&self,
3553
authorization_request: &AuthorizationRequest<Object<E>>,
@@ -45,7 +63,7 @@ impl ProviderManager {
4563
self.provider.send_response(authorization_response).await
4664
}
4765

48-
pub fn default_subject_syntax_type(&self) -> &SubjectSyntaxType {
49-
&self.provider.default_subject_syntax_type
66+
pub fn default_subject_syntax_types(&self) -> &Vec<SubjectSyntaxType> {
67+
&self.provider.supported_subject_syntax_types
5068
}
5169
}

oid4vc-manager/src/methods/key_method.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ mod tests {
123123
let subject = KeySubject::new();
124124

125125
// Create a new provider manager.
126-
let provider_manager = ProviderManager::new(Arc::new(subject), "did:key", vec![Algorithm::EdDSA]).unwrap();
126+
let provider_manager =
127+
ProviderManager::new(Arc::new(subject), vec!["did:key"], vec![Algorithm::EdDSA]).unwrap();
127128

128129
// Get a new SIOP authorization_request with response mode `direct_post` for cross-device communication.
129130
let request_url = "\

oid4vc-manager/tests/oid4vci/authorization_code.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async fn test_authorization_code_flow() {
4242
let subject_did = subject.identifier("did:key", Algorithm::EdDSA).await.unwrap();
4343

4444
// Create a new wallet.
45-
let wallet: Wallet = Wallet::new(Arc::new(subject), "did:key", vec![Algorithm::EdDSA]).unwrap();
45+
let wallet: Wallet = Wallet::new(Arc::new(subject), vec!["did:key"], vec![Algorithm::EdDSA]).unwrap();
4646

4747
// Get the credential issuer url.
4848
let credential_issuer_url = credential_issuer

oid4vc-manager/tests/oid4vci/pre_authorized_code.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference
4646
let subject_did = subject.identifier("did:key", Algorithm::EdDSA).await.unwrap();
4747

4848
// Create a new wallet.
49-
let wallet: Wallet = Wallet::new(Arc::new(subject), "did:key", vec![Algorithm::EdDSA]).unwrap();
49+
let wallet: Wallet = Wallet::new(Arc::new(subject), vec!["did:key"], vec![Algorithm::EdDSA]).unwrap();
5050

5151
// Get the credential offer url.
5252
let credential_offer_query = credential_issuer

oid4vc-manager/tests/oid4vp/implicit.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use oid4vp::{
1919
ClaimFormatDesignation, ClaimFormatProperty, PresentationDefinition,
2020
};
2121
use serde_json::json;
22-
use std::sync::Arc;
22+
use std::{collections::HashMap, sync::Arc};
2323

2424
lazy_static! {
2525
pub static ref PRESENTATION_DEFINITION: PresentationDefinition = serde_json::from_value(json!(
@@ -109,13 +109,17 @@ async fn test_implicit_flow() {
109109
.into_iter()
110110
.collect(),
111111
},
112+
other: HashMap::from_iter(vec![(
113+
"subject_syntax_types_supported".to_string(),
114+
json!(vec!["did:key".to_string(),]),
115+
)]),
112116
})
113117
.nonce("nonce".to_string())
114118
.build()
115119
.unwrap();
116120

117121
// Create a provider manager and validate the authorization request.
118-
let provider_manager = ProviderManager::new(subject, "did:key", vec![Algorithm::EdDSA]).unwrap();
122+
let provider_manager = ProviderManager::new(subject, vec!["did:key"], vec![Algorithm::EdDSA]).unwrap();
119123

120124
// Create a new verifiable credential.
121125
let verifiable_credential = VerifiableCredentialJwt::builder()

oid4vc-manager/tests/siopv2/implicit.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ async fn test_implicit_flow(#[case] did_method: &str) {
141141
subject_syntax_types_supported: vec![SubjectSyntaxType::Did(DidMethod::from_str(did_method).unwrap())],
142142
id_token_signed_response_alg: Some(Algorithm::EdDSA),
143143
},
144+
other: Default::default(),
144145
})
145146
.claims(
146147
r#"{
@@ -185,7 +186,7 @@ async fn test_implicit_flow(#[case] did_method: &str) {
185186
};
186187

187188
// Create a new provider manager.
188-
let provider_manager = ProviderManager::new(Arc::new(subject), did_method, vec![Algorithm::EdDSA]).unwrap();
189+
let provider_manager = ProviderManager::new(Arc::new(subject), vec![did_method], vec![Algorithm::EdDSA]).unwrap();
189190

190191
// Create a new RequestUrl which includes a `request_uri` pointing to the mock server's `request_uri` endpoint.
191192
let authorization_request = AuthorizationRequest::<ByReference> {

oid4vci/src/wallet/mod.rs

+58-11
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::str::FromStr;
2+
13
use crate::authorization_details::AuthorizationDetailsObject;
24
use crate::authorization_request::AuthorizationRequest;
35
use crate::authorization_response::AuthorizationResponse;
@@ -11,7 +13,7 @@ use crate::credential_request::{BatchCredentialRequest, CredentialRequest};
1113
use crate::credential_response::BatchCredentialResponse;
1214
use crate::proof::{KeyProofType, ProofType};
1315
use crate::{credential_response::CredentialResponse, token_request::TokenRequest, token_response::TokenResponse};
14-
use anyhow::Result;
16+
use anyhow::{anyhow, Result};
1517
use jsonwebtoken::Algorithm;
1618
use oid4vc_core::authentication::subject::SigningSubject;
1719
use oid4vc_core::SubjectSyntaxType;
@@ -26,7 +28,7 @@ where
2628
CFC: CredentialFormatCollection,
2729
{
2830
pub subject: SigningSubject,
29-
pub default_subject_syntax_type: SubjectSyntaxType,
31+
pub supported_subject_syntax_types: Vec<SubjectSyntaxType>,
3032
pub client: ClientWithMiddleware,
3133
pub proof_signing_alg_values_supported: Vec<Algorithm>,
3234
phantom: std::marker::PhantomData<CFC>,
@@ -35,7 +37,7 @@ where
3537
impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
3638
pub fn new(
3739
subject: SigningSubject,
38-
default_subject_syntax_type: impl TryInto<SubjectSyntaxType>,
40+
supported_subject_syntax_types: Vec<impl TryInto<SubjectSyntaxType>>,
3941
proof_signing_alg_values_supported: Vec<Algorithm>,
4042
) -> anyhow::Result<Self> {
4143
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5);
@@ -44,9 +46,14 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
4446
.build();
4547
Ok(Self {
4648
subject,
47-
default_subject_syntax_type: default_subject_syntax_type
48-
.try_into()
49-
.map_err(|_| anyhow::anyhow!("Invalid did method"))?,
49+
supported_subject_syntax_types: supported_subject_syntax_types
50+
.into_iter()
51+
.map(|subject_syntax_type| {
52+
subject_syntax_type
53+
.try_into()
54+
.map_err(|_| anyhow::anyhow!("Invalid did method."))
55+
})
56+
.collect::<Result<_>>()?,
5057
client,
5158
proof_signing_alg_values_supported,
5259
phantom: std::marker::PhantomData,
@@ -121,7 +128,11 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
121128
client_id: self
122129
.subject
123130
.identifier(
124-
&self.default_subject_syntax_type.to_string(),
131+
&self
132+
.supported_subject_syntax_types
133+
.first()
134+
.map(ToString::to_string)
135+
.ok_or(anyhow!("No supported subject syntax types found."))?,
125136
self.proof_signing_alg_values_supported[0],
126137
)
127138
.await?,
@@ -163,6 +174,22 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
163174
))?
164175
.proof_signing_alg_values_supported;
165176

177+
let credential_issuer_cryptographic_binding_methods_supported: Vec<SubjectSyntaxType> =
178+
credential_configuration
179+
.cryptographic_binding_methods_supported
180+
.iter()
181+
.filter_map(|binding_method| SubjectSyntaxType::from_str(binding_method).ok())
182+
.collect();
183+
184+
let subject_syntax_type = self
185+
.supported_subject_syntax_types
186+
.iter()
187+
.find(|supported_syntax_type| {
188+
credential_issuer_cryptographic_binding_methods_supported.contains(supported_syntax_type)
189+
})
190+
.or(self.supported_subject_syntax_types.first())
191+
.ok_or(anyhow::anyhow!("No supported subject syntax types found."))?;
192+
166193
let signing_algorithm = self
167194
.proof_signing_alg_values_supported
168195
.iter()
@@ -179,10 +206,11 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
179206
.signer(self.subject.clone())
180207
.iss(
181208
self.subject
182-
.identifier(&self.default_subject_syntax_type.to_string(), *signing_algorithm)
209+
.identifier(&subject_syntax_type.to_string(), *signing_algorithm)
183210
.await?,
184211
)
185212
.aud(credential_issuer_metadata.credential_issuer)
213+
// TODO: Use current time.
186214
.iat(1571324800)
187215
// TODO: so is this REQUIRED or OPTIONAL?
188216
.nonce(
@@ -192,7 +220,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
192220
.ok_or(anyhow::anyhow!("No c_nonce found."))?
193221
.clone(),
194222
)
195-
.subject_syntax_type(self.default_subject_syntax_type.to_string())
223+
.subject_syntax_type(&subject_syntax_type.to_string())
196224
.build()
197225
.await?,
198226
),
@@ -226,6 +254,24 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
226254
))?
227255
.proof_signing_alg_values_supported;
228256

257+
let credential_issuer_cryptographic_binding_methods_supported: Vec<SubjectSyntaxType> =
258+
credential_configurations
259+
.first()
260+
.ok_or(anyhow::anyhow!("No credential configurations found."))?
261+
.cryptographic_binding_methods_supported
262+
.iter()
263+
.filter_map(|binding_method| SubjectSyntaxType::from_str(binding_method).ok())
264+
.collect();
265+
266+
let subject_syntax_type = self
267+
.supported_subject_syntax_types
268+
.iter()
269+
.find(|supported_syntax_type| {
270+
credential_issuer_cryptographic_binding_methods_supported.contains(supported_syntax_type)
271+
})
272+
.or(self.supported_subject_syntax_types.first())
273+
.ok_or(anyhow::anyhow!("No supported subject syntax types found."))?;
274+
229275
let signing_algorithm = self
230276
.proof_signing_alg_values_supported
231277
.iter()
@@ -241,10 +287,11 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
241287
.signer(self.subject.clone())
242288
.iss(
243289
self.subject
244-
.identifier(&self.default_subject_syntax_type.to_string(), *signing_algorithm)
290+
.identifier(&subject_syntax_type.to_string(), *signing_algorithm)
245291
.await?,
246292
)
247293
.aud(credential_issuer_metadata.credential_issuer)
294+
// TODO: Use current time.
248295
.iat(1571324800)
249296
// TODO: so is this REQUIRED or OPTIONAL?
250297
.nonce(
@@ -254,7 +301,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
254301
.ok_or(anyhow::anyhow!("No c_nonce found."))?
255302
.clone(),
256303
)
257-
.subject_syntax_type(self.default_subject_syntax_type.to_string())
304+
.subject_syntax_type(subject_syntax_type.to_string())
258305
.build()
259306
.await?,
260307
);

oid4vp/src/authorization_request.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ mod tests {
222222
]
223223
.into_iter()
224224
.collect()
225-
}
225+
},
226+
other: HashMap::from_iter(vec![("application_type".to_string(), serde_json::json!("web"))]),
226227
}),
227228
},
228229
json_example::<ExampleAuthorizationRequest>("tests/examples/client_metadata/client_client_id_did.json")

oid4vp/src/oid4vp.rs

+43
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use reqwest_middleware::ClientBuilder;
2121
use reqwest_retry::policies::ExponentialBackoff;
2222
use reqwest_retry::RetryTransientMiddleware;
2323
use serde::{Deserialize, Serialize};
24+
use std::str::FromStr;
2425
use std::sync::Arc;
2526

2627
/// This is the [`RequestHandle`] for the [`OID4VP`] extension.
@@ -121,6 +122,48 @@ impl Extension for OID4VP {
121122
}
122123
}
123124

125+
async fn get_relying_party_supported_syntax_types(
126+
authorization_request: &<Self::RequestHandle as RequestHandle>::Parameters,
127+
) -> anyhow::Result<Vec<SubjectSyntaxType>> {
128+
let client_metadata = match &authorization_request.client_metadata {
129+
ClientMetadataResource::ClientMetadataUri(client_metadata_uri) => {
130+
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5);
131+
let client = ClientBuilder::new(reqwest::Client::new())
132+
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
133+
.build();
134+
let client_metadata: ClientMetadataResource<ClientMetadataParameters> =
135+
client.get(client_metadata_uri).send().await?.json().await?;
136+
client_metadata
137+
}
138+
client_metadata => client_metadata.clone(),
139+
};
140+
141+
match client_metadata {
142+
ClientMetadataResource::ClientMetadataUri(_) => unreachable!(),
143+
ClientMetadataResource::ClientMetadata { other, .. } => {
144+
let subject_syntax_types_supported: Vec<SubjectSyntaxType> = other
145+
.get("subject_syntax_types_supported")
146+
.and_then(|subject_syntax_types_supported| {
147+
subject_syntax_types_supported
148+
.as_array()
149+
.and_then(|subject_syntax_types_supported| {
150+
subject_syntax_types_supported
151+
.into_iter()
152+
.map(|subject_syntax_type| {
153+
subject_syntax_type.as_str().map(|subject_syntax_type| {
154+
SubjectSyntaxType::from_str(subject_syntax_type).unwrap()
155+
})
156+
})
157+
.collect()
158+
})
159+
})
160+
.unwrap_or_default();
161+
162+
Ok(subject_syntax_types_supported)
163+
}
164+
}
165+
}
166+
124167
fn build_authorization_response(
125168
jwts: Vec<String>,
126169
user_input: <Self::ResponseHandle as ResponseHandle>::Input,

siopv2/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ serde_urlencoded = "0.7.1"
3131
serde_with.workspace = true
3232
tokio.workspace = true
3333
url = { version = "2.3.1", features = ["serde"] }
34+
log = "0.4"
3435

3536
[dev-dependencies]
3637
oid4vc-core = { path = "../oid4vc-core", features = ["test-utils"] }

0 commit comments

Comments
 (0)