Skip to content

Commit

Permalink
feat: add default_subject_syntax_type
Browse files Browse the repository at this point in the history
  • Loading branch information
nanderstabel committed Apr 11, 2024
1 parent 3a9dc39 commit 986af6e
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 36 deletions.
2 changes: 1 addition & 1 deletion oid4vc-manager/src/managers/presentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use oid4vp::{
// TODO: make VP/VC fromat agnostic. In current form only jwt_vp_json + jwt_vc_json are supported.
pub fn create_presentation_submission(
presentation_definition: &PresentationDefinition,
credentials: Vec<serde_json::Value>,
credentials: &[serde_json::Value],
) -> Result<PresentationSubmission> {
let id = "Submission ID".to_string();
let definition_id = presentation_definition.id().clone();
Expand Down
9 changes: 6 additions & 3 deletions oid4vc-manager/src/managers/relying_party.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use oid4vc_core::{
authorization_request::{AuthorizationRequest, Object},
authorization_response::AuthorizationResponse,
openid4vc_extension::{Extension, ResponseHandle},
Subject,
Subject, SubjectSyntaxType,
};
use siopv2::RelyingParty;
use std::sync::Arc;
Expand All @@ -14,9 +14,12 @@ pub struct RelyingPartyManager {
}

impl RelyingPartyManager {
pub fn new(subject: Arc<dyn Subject>, subject_syntax_type: String) -> Result<Self> {
pub fn new(
subject: Arc<dyn Subject>,
default_subject_syntax_type: impl TryInto<SubjectSyntaxType>,
) -> Result<Self> {
Ok(Self {
relying_party: RelyingParty::new(subject, subject_syntax_type)?,
relying_party: RelyingParty::new(subject, default_subject_syntax_type)?,
})
}

Expand Down
3 changes: 1 addition & 2 deletions oid4vc-manager/src/methods/key_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,7 @@ mod tests {
.unwrap();

// Let the relying party validate the authorization_response.
let relying_party_manager =
RelyingPartyManager::new(Arc::new(KeySubject::new()), "did:key".to_string()).unwrap();
let relying_party_manager = RelyingPartyManager::new(Arc::new(KeySubject::new()), "did:key").unwrap();
assert!(relying_party_manager
.validate_response(&authorization_response)
.await
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 @@ -41,7 +41,7 @@ async fn test_authorization_code_flow() {
let subject_did = subject.identifier("did:key").unwrap();

// Create a new wallet.
let wallet = Wallet::new(Arc::new(subject), "did:key".to_string());
let wallet = Wallet::new(Arc::new(subject), "did:key").unwrap();

// Get the credential issuer url.
let credential_issuer_url = credential_issuer
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 @@ -45,7 +45,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference
let subject_did = subject.identifier("did:key").unwrap();

// Create a new wallet.
let wallet: Wallet = Wallet::new(Arc::new(subject), "did:key".to_string());
let wallet: Wallet = Wallet::new(Arc::new(subject), "did:key").unwrap();

// Get the credential offer url.
let credential_offer_query = credential_issuer
Expand Down
4 changes: 2 additions & 2 deletions oid4vc-manager/tests/oid4vp/implicit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async fn test_implicit_flow() {
// Create a new relying party.
let relying_party = Arc::new(KeySubject::new());
let relying_party_did = relying_party.identifier("did:key").unwrap();
let relying_party_manager = RelyingPartyManager::new(relying_party, "did:key".to_string()).unwrap();
let relying_party_manager = RelyingPartyManager::new(relying_party, "did:key").unwrap();

// Create authorization request with response_type `id_token vp_token`
let authorization_request = AuthorizationRequest::<Object<OID4VP>>::builder()
Expand Down Expand Up @@ -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();

Expand Down
33 changes: 23 additions & 10 deletions oid4vc-manager/tests/siopv2/implicit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use wiremock::{
Mock, MockServer, ResponseTemplate,
};

/// A Subject that can sign and verify messages with multiple different DID Methods.
pub struct MultiDidMethodSubject {
pub test_subject: TestSubject,
pub key_subject: KeySubject,
Expand All @@ -38,8 +39,12 @@ impl Sign for MultiDidMethodSubject {
}
}

fn sign(&self, message: &str, _subject_syntax_type: &str) -> anyhow::Result<Vec<u8>> {
self.test_subject.sign(message, _subject_syntax_type)
fn sign(&self, message: &str, subject_syntax_type: &str) -> anyhow::Result<Vec<u8>> {
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<Arc<dyn ExternalSign>> {
Expand Down Expand Up @@ -95,8 +100,11 @@ lazy_static! {
);
}

#[rstest::rstest]
#[case("did:key")]
#[case("did:test")]
#[tokio::test]
async fn test_implicit_flow() {
async fn test_implicit_flow(#[case] did_method: &str) {
// Create a new mock server and retreive it's url.
let mock_server = MockServer::start().await;
let server_url = mock_server.uri();
Expand All @@ -111,20 +119,22 @@ async fn test_implicit_flow() {
key_subject: KeySubject::from_keypair(generate::<Ed25519KeyPair>(None), None),
};

let client_id = subject.identifier(did_method).unwrap();

// Create a new relying party manager.
let relying_party_manager = RelyingPartyManager::new(Arc::new(subject), "did:test".to_string()).unwrap();
let relying_party_manager = RelyingPartyManager::new(Arc::new(subject), did_method).unwrap();

// Create a new RequestUrl with response mode `direct_post` for cross-device communication.
let authorization_request: AuthorizationRequest<Object<SIOPv2>> = AuthorizationRequest::<Object<SIOPv2>>::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::<url::Url>().unwrap())
.response_mode("direct_post".to_string())
.client_metadata(ClientMetadataResource::<ClientMetadataParameters>::ClientMetadata {
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(
Expand Down Expand Up @@ -162,17 +172,20 @@ async fn test_implicit_flow() {
// Create a new storage for the user's claims.
let storage = MemoryStorage::new(serde_json::from_value(USER_CLAIMS.clone()).unwrap());

// Create a new subject and validator.
let subject = TestSubject::new("did:test:subject".to_string(), "did:test:subject#key_id".to_string()).unwrap();
// Create a new subject.
let subject = MultiDidMethodSubject {
test_subject: TestSubject::new("did:test:subject".to_string(), "did:test:subject#key_id".to_string()).unwrap(),
key_subject: KeySubject::from_keypair(generate::<Ed25519KeyPair>(None), None),
};

// Create a new provider manager.
let provider_manager = ProviderManager::new(Arc::new(subject), "did:test").unwrap();
let provider_manager = ProviderManager::new(Arc::new(subject), did_method).unwrap();

// Create a new RequestUrl which includes a `request_uri` pointing to the mock server's `request_uri` endpoint.
let authorization_request = AuthorizationRequest::<ByReference> {
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::<url::Url>().unwrap(),
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CFC = CredentialFormats<WithParameters>>
where
CFC: CredentialFormatCollection,
Expand Down
26 changes: 16 additions & 10 deletions oid4vci/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,23 +24,28 @@ where
CFC: CredentialFormatCollection,
{
pub subject: SigningSubject,
pub default_subject_syntax_type: String,
pub default_subject_syntax_type: SubjectSyntaxType,
pub client: ClientWithMiddleware,
phantom: std::marker::PhantomData<CFC>,
}

impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
pub fn new(subject: SigningSubject, default_subject_syntax_type: String) -> Self {
pub fn new(
subject: SigningSubject,
default_subject_syntax_type: impl TryInto<SubjectSyntaxType>,
) -> anyhow::Result<Self> {
let retry_policy = ExponentialBackoff::builder().build_with_max_retries(5);
let client = ClientBuilder::new(reqwest::Client::new())
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
.build();
Self {
Ok(Self {
subject,
default_subject_syntax_type,
default_subject_syntax_type: default_subject_syntax_type
.try_into()
.map_err(|_| anyhow::anyhow!("Invalid did method"))?,
client,
phantom: std::marker::PhantomData,
}
})
}

pub async fn get_credential_offer(&self, credential_offer_uri: Url) -> Result<CredentialOfferParameters> {
Expand Down Expand Up @@ -107,7 +113,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
// TODO: must be `form`, but `AuthorizationRequest needs to be able to serilalize properly.
.json(&AuthorizationRequest {
response_type: "code".to_string(),
client_id: self.subject.identifier(&self.default_subject_syntax_type)?,
client_id: self.subject.identifier(&self.default_subject_syntax_type.to_string())?,
redirect_uri: None,
scope: None,
state: None,
Expand Down Expand Up @@ -143,7 +149,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
KeyProofType::builder()
.proof_type(ProofType::Jwt)
.signer(self.subject.clone())
.iss(self.subject.identifier(&self.default_subject_syntax_type)?)
.iss(self.subject.identifier(&self.default_subject_syntax_type.to_string())?)
.aud(credential_issuer_metadata.credential_issuer)
.iat(1571324800)
.exp(9999999999i64)
Expand All @@ -155,7 +161,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
.ok_or(anyhow::anyhow!("No c_nonce found."))?
.clone(),
)
.subject_syntax_type(&self.default_subject_syntax_type)
.subject_syntax_type(self.default_subject_syntax_type.to_string())
.build()?,
),
};
Expand All @@ -181,7 +187,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
KeyProofType::builder()
.proof_type(ProofType::Jwt)
.signer(self.subject.clone())
.iss(self.subject.identifier(&self.default_subject_syntax_type)?)
.iss(self.subject.identifier(&self.default_subject_syntax_type.to_string())?)
.aud(credential_issuer_metadata.credential_issuer)
.iat(1571324800)
.exp(9999999999i64)
Expand All @@ -193,7 +199,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Wallet<CFC> {
.ok_or(anyhow::anyhow!("No c_nonce found."))?
.clone(),
)
.subject_syntax_type(&self.default_subject_syntax_type)
.subject_syntax_type(self.default_subject_syntax_type.to_string())
.build()?,
);

Expand Down
12 changes: 7 additions & 5 deletions siopv2/src/relying_party.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@ use oid4vc_core::{
authorization_response::AuthorizationResponse,
jwt,
openid4vc_extension::{Extension, ResponseHandle},
Validator,
SubjectSyntaxType, Validator,
};
use std::collections::HashMap;

pub struct RelyingParty {
// TODO: Strictly speaking a relying party doesn't need to have a [`Subject`]. It just needs methods to
// sign and verify tokens. For simplicity we use a [`Subject`] here for now but we should consider a cleaner solution.
pub subject: SigningSubject,
pub default_subject_syntax_type: String,
pub default_subject_syntax_type: SubjectSyntaxType,
pub sessions: HashMap<(String, String), AuthorizationRequest<Object<SIOPv2>>>,
}

impl RelyingParty {
// TODO: Use RelyingPartyBuilder instead.
pub fn new(subject: SigningSubject, default_subject_syntax_type: String) -> Result<Self> {
pub fn new(subject: SigningSubject, default_subject_syntax_type: impl TryInto<SubjectSyntaxType>) -> Result<Self> {
Ok(RelyingParty {
subject,
default_subject_syntax_type,
default_subject_syntax_type: default_subject_syntax_type
.try_into()
.map_err(|_| anyhow::anyhow!("Invalid did method."))?,
sessions: HashMap::new(),
})
}
Expand All @@ -34,7 +36,7 @@ impl RelyingParty {
self.subject.clone(),
Header::new(Algorithm::EdDSA),
authorization_request,
&self.default_subject_syntax_type,
&self.default_subject_syntax_type.to_string(),
)
}

Expand Down

0 comments on commit 986af6e

Please sign in to comment.