Skip to content

Commit

Permalink
feat: add selelect functionality for subject syntax type
Browse files Browse the repository at this point in the history
  • Loading branch information
nanderstabel committed May 29, 2024
1 parent 274a3e9 commit d095db0
Show file tree
Hide file tree
Showing 14 changed files with 247 additions and 66 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
103 changes: 67 additions & 36 deletions oid4vci/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::credential_request::{BatchCredentialRequest, CredentialRequest};
use crate::credential_response::BatchCredentialResponse;
use crate::proof::{KeyProofType, ProofType};
use crate::{credential_response::CredentialResponse, token_request::TokenRequest, token_response::TokenResponse};
use anyhow::Result;
use anyhow::{anyhow, Result};
use jsonwebtoken::Algorithm;
use oid4vc_core::authentication::subject::SigningSubject;
use oid4vc_core::SubjectSyntaxType;
Expand All @@ -20,13 +20,14 @@ use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_retry::policies::ExponentialBackoff;
use reqwest_retry::RetryTransientMiddleware;
use serde::de::DeserializeOwned;
use std::str::FromStr;

pub struct Wallet<CFC = CredentialFormats<WithParameters>>
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 +36,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 +45,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 +127,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 All @@ -148,13 +158,10 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
.map_err(|e| e.into())
}

pub async fn get_credential(
fn select_signing_algorithm(
&self,
credential_issuer_metadata: CredentialIssuerMetadata<CFC>,
token_response: &TokenResponse,
credential_configuration: &CredentialConfigurationsSupportedObject,
) -> Result<CredentialResponse> {
let credential_format = credential_configuration.credential_format.to_owned();
) -> Result<Algorithm> {
let credential_issuer_proof_signing_alg_values_supported = &credential_configuration
.proof_types_supported
.get(&ProofType::Jwt)
Expand All @@ -163,26 +170,60 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
))?
.proof_signing_alg_values_supported;

let signing_algorithm = self
.proof_signing_alg_values_supported
self.proof_signing_alg_values_supported
.iter()
.find(|supported_algorithm| {
credential_issuer_proof_signing_alg_values_supported.contains(supported_algorithm)
})
.ok_or(anyhow::anyhow!("No supported signing algorithm found."))?;
.cloned()
.ok_or(anyhow::anyhow!("No supported signing algorithm found."))
}

fn select_subject_syntax_type(
&self,
credential_configuration: &CredentialConfigurationsSupportedObject,
) -> Result<SubjectSyntaxType> {
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();

self.supported_subject_syntax_types
.iter()
.find(|supported_syntax_type| {
credential_issuer_cryptographic_binding_methods_supported.contains(supported_syntax_type)
})
.cloned()
.ok_or(anyhow::anyhow!("No supported subject syntax types found."))
}

pub async fn get_credential(
&self,
credential_issuer_metadata: CredentialIssuerMetadata<CFC>,
token_response: &TokenResponse,
credential_configuration: &CredentialConfigurationsSupportedObject,
) -> Result<CredentialResponse> {
let credential_format = credential_configuration.credential_format.to_owned();

let signing_algorithm = self.select_signing_algorithm(credential_configuration)?;
let subject_syntax_type = self.select_subject_syntax_type(credential_configuration)?;

let credential_request = CredentialRequest {
credential_format,
proof: Some(
KeyProofType::builder()
.proof_type(ProofType::Jwt)
.algorithm(*signing_algorithm)
.algorithm(signing_algorithm)
.signer(self.subject.clone())
.iss(
self.subject
.identifier(&self.default_subject_syntax_type.to_string(), *signing_algorithm)
.identifier(&subject_syntax_type.to_string(), signing_algorithm)
.await?,
)
.aud(credential_issuer_metadata.credential_issuer)
// TODO: Use current time.
.iat(1571324800)
// TODO: so is this REQUIRED or OPTIONAL?
.nonce(
Expand All @@ -192,7 +233,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 All @@ -216,35 +257,25 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
credential_configurations: &[CredentialConfigurationsSupportedObject],
) -> Result<BatchCredentialResponse> {
// TODO: This needs to be fixed since this current implementation assumes that for all credentials the same Proof Type is supported.
let credential_issuer_proof_signing_alg_values_supported = &credential_configurations
let credential_configuration = credential_configurations
.first()
.ok_or(anyhow::anyhow!("No credential configurations found."))?
.proof_types_supported
.get(&ProofType::Jwt)
.ok_or(anyhow::anyhow!(
"`jwt` proof type is missing from the `proof_types_supported` parameter"
))?
.proof_signing_alg_values_supported;
.ok_or(anyhow::anyhow!("No credential configurations found."))?;

let signing_algorithm = self
.proof_signing_alg_values_supported
.iter()
.find(|supported_algorithm| {
credential_issuer_proof_signing_alg_values_supported.contains(supported_algorithm)
})
.ok_or(anyhow::anyhow!("No supported signing algorithm found."))?;
let signing_algorithm = self.select_signing_algorithm(credential_configuration)?;
let subject_syntax_type = self.select_subject_syntax_type(credential_configuration)?;

let proof = Some(
KeyProofType::builder()
.proof_type(ProofType::Jwt)
.algorithm(*signing_algorithm)
.algorithm(signing_algorithm)
.signer(self.subject.clone())
.iss(
self.subject
.identifier(&self.default_subject_syntax_type.to_string(), *signing_algorithm)
.identifier(&subject_syntax_type.to_string(), signing_algorithm)
.await?,
)
.aud(credential_issuer_metadata.credential_issuer)
// TODO: Use current time.
.iat(1571324800)
// TODO: so is this REQUIRED or OPTIONAL?
.nonce(
Expand All @@ -254,7 +285,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
Loading

0 comments on commit d095db0

Please sign in to comment.