Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
nanderstabel committed May 29, 2024
1 parent 274a3e9 commit 111ced1
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 40 deletions.
3 changes: 3 additions & 0 deletions oid4vc-core/src/client_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::collections::HashMap;
use url::Url;

/// [`ClientMetadata`] is a request parameter used by a [`crate::RelyingParty`] to communicate its capabilities to a
Expand All @@ -16,6 +17,8 @@ pub enum ClientMetadataResource<T = ()> {
/// expanded with Extensions and profiles.
#[serde(flatten)]
extension: T,
#[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
other: HashMap<String, serde_json::Value>,
},
ClientMetadataUri(String),
}
7 changes: 7 additions & 0 deletions oid4vc-core/src/openid4vc_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ pub trait Extension: Serialize + PartialEq + Sized + std::fmt::Debug + Clone + S
async { Err(anyhow::anyhow!("Not implemented.")) }
}

fn get_relying_party_supported_syntax_types(
_authorization_request: &<Self::RequestHandle as RequestHandle>::Parameters,
) -> impl Future<Output = anyhow::Result<Vec<SubjectSyntaxType>>> {
// Will be overwritten by the extension.
async { Err(anyhow::anyhow!("Not implemented.")) }
}

fn build_authorization_response(
_jwts: Vec<String>,
_user_input: <Self::ResponseHandle as ResponseHandle>::Input,
Expand Down
8 changes: 8 additions & 0 deletions oid4vc-core/src/subject_syntax_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ impl TryFrom<&str> for SubjectSyntaxType {
}
}

impl TryFrom<String> for SubjectSyntaxType {
type Error = anyhow::Error;

fn try_from(value: String) -> Result<Self, Self::Error> {
SubjectSyntaxType::from_str(value.as_str())
}
}

impl From<DidMethod> for SubjectSyntaxType {
fn from(did_method: DidMethod) -> Self {
SubjectSyntaxType::Did(did_method)
Expand Down
26 changes: 22 additions & 4 deletions oid4vc-manager/src/managers/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,36 @@ pub struct ProviderManager {
impl ProviderManager {
pub fn new(
subject: Arc<dyn Subject>,
default_subject_syntax_type: impl TryInto<SubjectSyntaxType>,
supported_subject_syntax_types: Vec<impl TryInto<SubjectSyntaxType>>,
supported_signing_algorithms: Vec<Algorithm>,
) -> Result<Self> {
Ok(Self {
provider: Provider::new(subject, default_subject_syntax_type, supported_signing_algorithms)?,
provider: Provider::new(subject, supported_subject_syntax_types, supported_signing_algorithms)?,
})
}

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

pub async fn get_matching_signing_algorithm<E: Extension>(
&self,
authorization_request: &AuthorizationRequest<Object<E>>,
) -> Result<Algorithm> {
self.provider
.get_matching_signing_algorithm(authorization_request)
.await
}

pub async fn get_matching_subject_syntax_type<E: Extension>(
&self,
authorization_request: &AuthorizationRequest<Object<E>>,
) -> Result<SubjectSyntaxType> {
self.provider
.get_matching_subject_syntax_type(authorization_request)
.await
}

pub async fn generate_response<E: Extension + OpenID4VC>(
&self,
authorization_request: &AuthorizationRequest<Object<E>>,
Expand All @@ -45,7 +63,7 @@ impl ProviderManager {
self.provider.send_response(authorization_response).await
}

pub fn default_subject_syntax_type(&self) -> &SubjectSyntaxType {
&self.provider.default_subject_syntax_type
pub fn default_subject_syntax_types(&self) -> &Vec<SubjectSyntaxType> {
&self.provider.supported_subject_syntax_types
}
}
3 changes: 2 additions & 1 deletion oid4vc-manager/src/methods/key_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ mod tests {
let subject = KeySubject::new();

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

// Get a new SIOP authorization_request with response mode `direct_post` for cross-device communication.
let request_url = "\
Expand Down
2 changes: 1 addition & 1 deletion oid4vc-manager/tests/oid4vci/authorization_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async fn test_authorization_code_flow() {
let subject_did = subject.identifier("did:key", Algorithm::EdDSA).await.unwrap();

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

// Get the credential issuer url.
let credential_issuer_url = credential_issuer
Expand Down
2 changes: 1 addition & 1 deletion oid4vc-manager/tests/oid4vci/pre_authorized_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference
let subject_did = subject.identifier("did:key", Algorithm::EdDSA).await.unwrap();

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

// Get the credential offer url.
let credential_offer_query = credential_issuer
Expand Down
8 changes: 6 additions & 2 deletions oid4vc-manager/tests/oid4vp/implicit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use oid4vp::{
ClaimFormatDesignation, ClaimFormatProperty, PresentationDefinition,
};
use serde_json::json;
use std::sync::Arc;
use std::{collections::HashMap, sync::Arc};

lazy_static! {
pub static ref PRESENTATION_DEFINITION: PresentationDefinition = serde_json::from_value(json!(
Expand Down Expand Up @@ -109,13 +109,17 @@ async fn test_implicit_flow() {
.into_iter()
.collect(),
},
other: HashMap::from_iter(vec![(
"subject_syntax_types_supported".to_string(),
json!(vec!["did:key".to_string(),]),
)]),
})
.nonce("nonce".to_string())
.build()
.unwrap();

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

// Create a new verifiable credential.
let verifiable_credential = VerifiableCredentialJwt::builder()
Expand Down
3 changes: 2 additions & 1 deletion oid4vc-manager/tests/siopv2/implicit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ async fn test_implicit_flow(#[case] did_method: &str) {
subject_syntax_types_supported: vec![SubjectSyntaxType::Did(DidMethod::from_str(did_method).unwrap())],
id_token_signed_response_alg: Some(Algorithm::EdDSA),
},
other: Default::default(),
})
.claims(
r#"{
Expand Down Expand Up @@ -185,7 +186,7 @@ async fn test_implicit_flow(#[case] did_method: &str) {
};

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

// Create a new RequestUrl which includes a `request_uri` pointing to the mock server's `request_uri` endpoint.
let authorization_request = AuthorizationRequest::<ByReference> {
Expand Down
69 changes: 58 additions & 11 deletions oid4vci/src/wallet/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::str::FromStr;

use crate::authorization_details::AuthorizationDetailsObject;
use crate::authorization_request::AuthorizationRequest;
use crate::authorization_response::AuthorizationResponse;
Expand All @@ -11,7 +13,7 @@ use crate::credential_request::{BatchCredentialRequest, CredentialRequest};
use crate::credential_response::BatchCredentialResponse;
use crate::proof::{KeyProofType, ProofType};
use crate::{credential_response::CredentialResponse, token_request::TokenRequest, token_response::TokenResponse};
use anyhow::Result;
use anyhow::{anyhow, Result};
use jsonwebtoken::Algorithm;
use oid4vc_core::authentication::subject::SigningSubject;
use oid4vc_core::SubjectSyntaxType;
Expand All @@ -26,7 +28,7 @@ where
CFC: CredentialFormatCollection,
{
pub subject: SigningSubject,
pub default_subject_syntax_type: SubjectSyntaxType,
pub supported_subject_syntax_types: Vec<SubjectSyntaxType>,
pub client: ClientWithMiddleware,
pub proof_signing_alg_values_supported: Vec<Algorithm>,
phantom: std::marker::PhantomData<CFC>,
Expand All @@ -35,7 +37,7 @@ where
impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
pub fn new(
subject: SigningSubject,
default_subject_syntax_type: impl TryInto<SubjectSyntaxType>,
supported_subject_syntax_types: Vec<impl TryInto<SubjectSyntaxType>>,
proof_signing_alg_values_supported: Vec<Algorithm>,
) -> anyhow::Result<Self> {
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5);
Expand All @@ -44,9 +46,14 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
.build();
Ok(Self {
subject,
default_subject_syntax_type: default_subject_syntax_type
.try_into()
.map_err(|_| anyhow::anyhow!("Invalid did method"))?,
supported_subject_syntax_types: supported_subject_syntax_types
.into_iter()
.map(|subject_syntax_type| {
subject_syntax_type
.try_into()
.map_err(|_| anyhow::anyhow!("Invalid did method."))
})
.collect::<Result<_>>()?,
client,
proof_signing_alg_values_supported,
phantom: std::marker::PhantomData,
Expand Down Expand Up @@ -121,7 +128,11 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
client_id: self
.subject
.identifier(
&self.default_subject_syntax_type.to_string(),
&self
.supported_subject_syntax_types
.first()
.map(ToString::to_string)
.ok_or(anyhow!("No supported subject syntax types found."))?,
self.proof_signing_alg_values_supported[0],
)
.await?,
Expand Down Expand Up @@ -163,6 +174,22 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
))?
.proof_signing_alg_values_supported;

let credential_issuer_cryptographic_binding_methods_supported: Vec<SubjectSyntaxType> =
credential_configuration
.cryptographic_binding_methods_supported
.iter()
.filter_map(|binding_method| SubjectSyntaxType::from_str(binding_method).ok())
.collect();

let subject_syntax_type = self
.supported_subject_syntax_types
.iter()
.find(|supported_syntax_type| {
credential_issuer_cryptographic_binding_methods_supported.contains(supported_syntax_type)
})
.or(self.supported_subject_syntax_types.first())
.ok_or(anyhow::anyhow!("No supported subject syntax types found."))?;

let signing_algorithm = self
.proof_signing_alg_values_supported
.iter()
Expand All @@ -179,10 +206,11 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
.signer(self.subject.clone())
.iss(
self.subject
.identifier(&self.default_subject_syntax_type.to_string(), *signing_algorithm)
.identifier(&subject_syntax_type.to_string(), *signing_algorithm)
.await?,
)
.aud(credential_issuer_metadata.credential_issuer)
// TODO: Use current time.
.iat(1571324800)
// TODO: so is this REQUIRED or OPTIONAL?
.nonce(
Expand All @@ -192,7 +220,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
.ok_or(anyhow::anyhow!("No c_nonce found."))?
.clone(),
)
.subject_syntax_type(self.default_subject_syntax_type.to_string())
.subject_syntax_type(&subject_syntax_type.to_string())
.build()
.await?,
),
Expand Down Expand Up @@ -226,6 +254,24 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
))?
.proof_signing_alg_values_supported;

let credential_issuer_cryptographic_binding_methods_supported: Vec<SubjectSyntaxType> =
credential_configurations
.first()
.ok_or(anyhow::anyhow!("No credential configurations found."))?
.cryptographic_binding_methods_supported
.iter()
.filter_map(|binding_method| SubjectSyntaxType::from_str(binding_method).ok())
.collect();

let subject_syntax_type = self
.supported_subject_syntax_types
.iter()
.find(|supported_syntax_type| {
credential_issuer_cryptographic_binding_methods_supported.contains(supported_syntax_type)
})
.or(self.supported_subject_syntax_types.first())
.ok_or(anyhow::anyhow!("No supported subject syntax types found."))?;

let signing_algorithm = self
.proof_signing_alg_values_supported
.iter()
Expand All @@ -241,10 +287,11 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
.signer(self.subject.clone())
.iss(
self.subject
.identifier(&self.default_subject_syntax_type.to_string(), *signing_algorithm)
.identifier(&subject_syntax_type.to_string(), *signing_algorithm)
.await?,
)
.aud(credential_issuer_metadata.credential_issuer)
// TODO: Use current time.
.iat(1571324800)
// TODO: so is this REQUIRED or OPTIONAL?
.nonce(
Expand All @@ -254,7 +301,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
.ok_or(anyhow::anyhow!("No c_nonce found."))?
.clone(),
)
.subject_syntax_type(self.default_subject_syntax_type.to_string())
.subject_syntax_type(subject_syntax_type.to_string())
.build()
.await?,
);
Expand Down
3 changes: 2 additions & 1 deletion oid4vp/src/authorization_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ mod tests {
]
.into_iter()
.collect()
}
},
other: HashMap::from_iter(vec![("application_type".to_string(), serde_json::json!("web"))]),
}),
},
json_example::<ExampleAuthorizationRequest>("tests/examples/client_metadata/client_client_id_did.json")
Expand Down
43 changes: 43 additions & 0 deletions oid4vp/src/oid4vp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use reqwest_middleware::ClientBuilder;
use reqwest_retry::policies::ExponentialBackoff;
use reqwest_retry::RetryTransientMiddleware;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use std::sync::Arc;

/// This is the [`RequestHandle`] for the [`OID4VP`] extension.
Expand Down Expand Up @@ -121,6 +122,48 @@ impl Extension for OID4VP {
}
}

async fn get_relying_party_supported_syntax_types(
authorization_request: &<Self::RequestHandle as RequestHandle>::Parameters,
) -> anyhow::Result<Vec<SubjectSyntaxType>> {
let client_metadata = match &authorization_request.client_metadata {
ClientMetadataResource::ClientMetadataUri(client_metadata_uri) => {
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5);
let client = ClientBuilder::new(reqwest::Client::new())
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
.build();
let client_metadata: ClientMetadataResource<ClientMetadataParameters> =
client.get(client_metadata_uri).send().await?.json().await?;
client_metadata
}
client_metadata => client_metadata.clone(),
};

match client_metadata {
ClientMetadataResource::ClientMetadataUri(_) => unreachable!(),
ClientMetadataResource::ClientMetadata { other, .. } => {
let subject_syntax_types_supported: Vec<SubjectSyntaxType> = other
.get("subject_syntax_types_supported")
.and_then(|subject_syntax_types_supported| {
subject_syntax_types_supported
.as_array()
.and_then(|subject_syntax_types_supported| {
subject_syntax_types_supported
.into_iter()
.map(|subject_syntax_type| {
subject_syntax_type.as_str().map(|subject_syntax_type| {
SubjectSyntaxType::from_str(subject_syntax_type).unwrap()
})
})
.collect()
})
})
.unwrap_or_default();

Ok(subject_syntax_types_supported)
}
}
}

fn build_authorization_response(
jwts: Vec<String>,
user_input: <Self::ResponseHandle as ResponseHandle>::Input,
Expand Down
Loading

0 comments on commit 111ced1

Please sign in to comment.