From 928d68da008db784c82a7fff704696c959a2937c Mon Sep 17 00:00:00 2001 From: Nander Stabel Date: Thu, 4 Apr 2024 18:15:51 +0200 Subject: [PATCH] feat: update OpenID4VCI to WG Draft 13 (#64) * fix: remove `iota_method` * test: add test-utils feature, bump ed25519-dalek dep * feat: add `JsonObject` * refactor: remove `serialize_unit_struct`, use `#[serde(tag = ...)]` instead * style: use `JsonObject` * feat: add `Extension` trait * feat: implement `Extension` trait for `siopv2` * feat: implement `Extension` trait for `oid4vp` * fix: update manager * fix: use `MustBe` macro to enforce `response_type` values * style: sort dependencies * fix: remove `siopv2_oid4vp` * feat: derive `Clone` trait for request handlers * feat: change `authorization_server` to `authoriation_servers` As described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-10.2.3-2.2 * feat: add `credential_response_encryption_alg_values_supported` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-10.2.3-2.6 * feat: add `credential_response_encryption_enc_values_supported` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-10.2.3-2.7 * feat: add `require_credential_response_encryption` as defined here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-10.2.3-2.8 * feat: add `credential_identifiers_supported` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-10.2.3-2.9 * tests: update example files for `CredentialIssuerMetadata` The example files are taken from here: https://github.com/openid/OpenID4VCI/tree/4ae490afd6955829b4769cb1b547e9e62c824659/examples `oid4vci/tests/examples/issuer_metadata.json` is removed as it does not comply anymore with the new `CredentialIssuerMetadata` struct. * feat: removed `id` field from `CredentialsSupportedObject` struct as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-10.2.3-2.11.1 Also changed `Option>` to `Vec` for simplicity since `#[serde(skip_serializing_if = "Vec::is_empty", default)]` ensures the field is not serialized if it's empty. * test: fix unit tests * feat: change `credentials_supported` to a hashmap as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-10.2.3-2.11.1 * test: update tests files test files are taken from here: https://github.com/openid/OpenID4VCI/tree/1b7cd7426c42dd8d78db3261ed2e85a520ec7041/examples * feat: change `credentials` to a vector of Strings as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-4.1.1-2.2 * test: update integration tests * feat: add `authorization_server` to `authorization_code` Grant Type as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-4.1.1-4.1.2.2 * feat: add `authorization_server` to `pre-authorized_code` Grant Type as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-4.1.1-4.2.2.4 * chore: add TODO's for adding `authorization_details` to `TokenResponse` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-6.2-4.3.1 and for `credential_identifier` here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-7.2-2.3 * chore: add TODO for adding fields to CredentialRequest related to JWE as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#section-7.2-2.4 * fix: add `type` field to `AuthorizationDetailsObject` struct In the previous solution using: `#[serde(tag = "type", rename = "openid_credential")]`. instances of this struct were correctly serialized, but deserialization was not working as expected. This was due to the fact that serde(tag) does not work when deserializing. * chore: update description links, rename types * chore: apply cargo clippy warnings * docs: README files with links to the OID4VCI WG Draft 12 * test: update example files for OID4VCI WG Draft 13 example files are taken from here: https://github.com/openid/OpenID4VCI/tree/ade0793a988dc1aafb3907643113ebf67396970b/examples * feat: rename `credentials` to `credential_configuration_ids` in `CredentialOfferParameters` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-4.1.1-2.2 * feat: add support for `TransactionCode` * `user_pin_required` is replaced for `tx_code` in the `pre-authorized_code` grant type. * Corresponding changes are made in the unit tests * feat: rename `user_pin` to `tx_code` in `TokenRequest` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-6.1-3.2 * chore: update TODO as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-7.2-2.4.1 * feat: remove `format` field from `CredentialResponse` struct the `format` field is not used anymore in the `CredentialResponse` struct, as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-credential-response * feat: add optional `notification_id` field to `CredentialResponse` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-7.3-6.5 * feat: add optional `notification_endpoint` field to `CredentialIssuerMetadata` struct as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-11.2.3-2.6 * feat: add `CredentialResponseEncryption` struct This replaces some encryption related fields in `CredentialIssuerMetadata` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-11.2.3-2.7.1 * feat: add optional `signed_metadata` field to `CredentialIssuerMetata` struct as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-11.2.3-2.9 * feat: rename `credentials_supported` to `credential_configurations_supported` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-11.2.3-2.11.1 * feat: rename `cryptographic_suites_supported` to `credential_signing_alg_values_supported` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-11.2.3-2.11.2.4 * feat: change `proof_types_supported` to a `HashMap` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-11.2.3-2.11.2.5.1 * feat: rename `CredentialsSupportedObject` to `CredentialConfigurationSupportedObject` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-11.2.3-2.11.1 * test: update testfiles to reflect the new `CredentialConfigurationsSupportedObject` struct * test: remove deleted testfiles and test that is not supported yet. * test: update tests in `credential_configurations_supported.rs` to match with testfiles * test: update test in `credential_issuer_metadata.rs` to match corresponding test file * feat: add optional `credential_configuration_id` field to `AuthorizationRequestDetails` * fix testfiles * as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-request-issuance-of-a-certa * chore: update links to OpenID4VCI WG Draft 13 * chore: update links to example files * chore: remove periods behind URLs in comments --- README.md | 2 +- .../src/managers/credential_issuer.rs | 28 +- oid4vc-manager/src/managers/relying_party.rs | 2 +- oid4vc-manager/src/methods/key_method.rs | 4 +- .../src/servers/credential_issuer.rs | 4 +- oid4vc-manager/src/storage.rs | 6 +- .../driver_license.json | 13 +- .../university_degree.json | 13 +- oid4vc-manager/tests/common/memory_storage.rs | 40 +- .../tests/oid4vci/authorization_code.rs | 13 +- .../tests/oid4vci/pre_authorized_code.rs | 85 ++--- oid4vci/Cargo.toml | 1 - oid4vci/README.md | 2 +- oid4vci/src/authorization_details.rs | 287 ++++++-------- oid4vci/src/authorization_request.rs | 2 +- oid4vci/src/authorization_response.rs | 2 +- .../iso_mdl/mso_mdoc.rs | 2 +- oid4vci/src/credential_format_profiles/mod.rs | 16 +- .../w3c_verifiable_credentials/jwt_vc_json.rs | 6 +- .../jwt_vc_json_ld.rs | 6 +- .../w3c_verifiable_credentials/ldp_vc.rs | 6 +- .../w3c_verifiable_credentials/mod.rs | 10 + .../credential_configurations_supported.rs | 291 ++++++++++++++ .../credential_issuer_metadata.rs | 355 ++++++------------ .../credentials_supported.rs | 244 ------------ oid4vci/src/credential_issuer/mod.rs | 10 +- oid4vci/src/credential_offer.rs | 219 ++++------- oid4vci/src/credential_request.rs | 103 ++--- oid4vci/src/credential_response.rs | 33 +- oid4vci/src/lib.rs | 2 +- oid4vci/src/proof.rs | 16 +- oid4vci/src/token_request.rs | 8 +- oid4vci/src/token_response.rs | 3 +- oid4vci/src/wallet/mod.rs | 12 +- .../tests/examples/authorization_details.json | 8 +- .../authorization_details_jwt_vc_json.json | 6 +- .../authorization_details_ldp_vc.json | 10 +- .../authorization_details_mso_mdoc.json | 5 +- ...rization_details_multiple_credentials.json | 19 +- .../authorization_details_with_as.json | 16 +- ...redential_issuer_metadata_jwt_vc_json.json | 89 +++++ .../credential_metadata_jwt_vc_json.json | 111 +++--- .../examples/credential_metadata_ldp_vc.json | 125 +++--- .../credential_metadata_mso_mdoc.json | 114 +++--- .../credential_offer_by_reference.json | 14 +- .../credential_offer_jwt_vc_json.json | 19 - .../examples/credential_offer_ldp_vc.json | 18 - .../examples/credential_offer_mso_mdoc.json | 15 - ...credential_offer_multiple_credentials.json | 32 +- .../credential_offer_pre-authz_code.json | 16 +- .../examples/credential_request_iso_mdl.json | 8 - .../credential_request_jwt_vc_json.json | 13 - oid4vci/tests/examples/issuer_metadata.json | 205 ---------- oid4vp/src/oid4vp.rs | 2 +- 54 files changed, 1136 insertions(+), 1555 deletions(-) rename oid4vc-manager/tests/common/{credentials_supported_objects => credential_configurations_supported_objects}/driver_license.json (76%) rename oid4vc-manager/tests/common/{credentials_supported_objects => credential_configurations_supported_objects}/university_degree.json (86%) create mode 100644 oid4vci/src/credential_issuer/credential_configurations_supported.rs delete mode 100644 oid4vci/src/credential_issuer/credentials_supported.rs create mode 100644 oid4vci/tests/examples/credential_issuer_metadata_jwt_vc_json.json delete mode 100644 oid4vci/tests/examples/credential_offer_jwt_vc_json.json delete mode 100644 oid4vci/tests/examples/credential_offer_ldp_vc.json delete mode 100644 oid4vci/tests/examples/credential_offer_mso_mdoc.json delete mode 100644 oid4vci/tests/examples/credential_request_iso_mdl.json delete mode 100644 oid4vci/tests/examples/credential_request_jwt_vc_json.json delete mode 100644 oid4vci/tests/examples/issuer_metadata.json diff --git a/README.md b/README.md index 37d046c8..9151fb2e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ An overview of all the specifications developed by the OpenID Digital Credential This workspace includes Rust implementations for the following DCP specifications: | Specification | Description | Version | -------------------| ------------------------------------------ | ------- -| [OID4VCI](oid4vci) | OpenID for Verifiable Credential Issuance | [Editor's Draft published: 30 August 2023](https://github.com/openid/OpenID4VCI/blob/111db260b1ad1915ca1462cc4904781beb179972/openid-4-verifiable-credential-issuance-1_0.md) +| [OID4VCI](oid4vci) | OpenID for Verifiable Credential Issuance | [Working Group Draft 13 published: 8 February 2024](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html) | [OID4VP](oid4vp) | OpenID for Verifiable Presentations | [Working Group Draft 20 published: 29 November 2023](https://openid.net/specs/openid-4-verifiable-presentations-1_0-20.html) | [SIOPv2](siopv2) | Self-Issued OpenID Provider v2 | [Working Group Draft 13 published: 28 November 2023](https://openid.net/specs/openid-connect-self-issued-v2-1_0-13.html) diff --git a/oid4vc-manager/src/managers/credential_issuer.rs b/oid4vc-manager/src/managers/credential_issuer.rs index e2662060..2a2f24a9 100644 --- a/oid4vc-manager/src/managers/credential_issuer.rs +++ b/oid4vc-manager/src/managers/credential_issuer.rs @@ -7,7 +7,7 @@ use oid4vci::{ authorization_server_metadata::AuthorizationServerMetadata, credential_issuer_metadata::CredentialIssuerMetadata, CredentialIssuer, }, - credential_offer::{CredentialOffer, CredentialOfferQuery, CredentialsObject, Grants}, + credential_offer::{CredentialOffer, CredentialOfferParameters, Grants}, }; use reqwest::Url; use std::{net::TcpListener, sync::Arc}; @@ -32,17 +32,21 @@ impl, CFC: CredentialFormatCollection> CredentialIssuerManager, CFC: CredentialFormatCollection> CredentialIssuerManager Result> { - let credentials: Vec> = self + pub fn credential_offer(&self) -> Result { + let credential_configuration_ids: Vec = self .credential_issuer .metadata - .credentials_supported + .credential_configurations_supported .iter() - .map(|credential| CredentialsObject::ByValue(credential.credential_format.clone())) + .map(|credential| credential.0.clone()) .collect(); - Ok(CredentialOffer { + Ok(CredentialOfferParameters { credential_issuer: self.credential_issuer.metadata.credential_issuer.clone(), - credentials, + credential_configuration_ids, grants: Some(Grants { authorization_code: self.storage.get_authorization_code(), pre_authorized_code: self.storage.get_pre_authorized_code(), @@ -87,9 +91,9 @@ impl, CFC: CredentialFormatCollection> CredentialIssuerManager Result { if by_reference { - Ok(CredentialOfferQuery::::CredentialOfferUri(self.credential_offer_uri()?).to_string()) + Ok(CredentialOffer::CredentialOfferUri(self.credential_offer_uri()?).to_string()) } else { - Ok(CredentialOfferQuery::CredentialOffer(self.credential_offer()?).to_string()) + Ok(CredentialOffer::CredentialOffer(Box::new(self.credential_offer()?)).to_string()) } } } diff --git a/oid4vc-manager/src/managers/relying_party.rs b/oid4vc-manager/src/managers/relying_party.rs index 8aedf8a9..221e1515 100644 --- a/oid4vc-manager/src/managers/relying_party.rs +++ b/oid4vc-manager/src/managers/relying_party.rs @@ -17,7 +17,7 @@ pub struct RelyingPartyManager { impl RelyingPartyManager { pub fn new(subjects: [Arc; N]) -> Result { Ok(Self { - relying_party: RelyingParty::new(subjects.get(0).ok_or_else(|| anyhow!("No subjects found."))?.clone())?, + relying_party: RelyingParty::new(subjects.first().ok_or_else(|| anyhow!("No subjects found."))?.clone())?, subjects: Subjects::try_from(subjects)?, }) } diff --git a/oid4vc-manager/src/methods/key_method.rs b/oid4vc-manager/src/methods/key_method.rs index ad0bc440..8530b910 100644 --- a/oid4vc-manager/src/methods/key_method.rs +++ b/oid4vc-manager/src/methods/key_method.rs @@ -47,7 +47,7 @@ impl Sign for KeySubject { self.document .authentication .as_ref() - .and_then(|authentication_methods| authentication_methods.get(0).cloned()) + .and_then(|authentication_methods| authentication_methods.first().cloned()) } fn sign(&self, message: &str) -> Result> { @@ -99,7 +99,7 @@ async fn resolve_public_key(kid: &str) -> Result> { let authentication_method = keypair .get_did_document(Config::default()) .authentication - .and_then(|authentication_methods| authentication_methods.get(0).cloned()) + .and_then(|authentication_methods| authentication_methods.first().cloned()) .ok_or(anyhow!("No public key found"))?; PatchedKeyPair::try_from(authentication_method.as_str()) .map(|keypair| keypair.public_key_bytes()) diff --git a/oid4vc-manager/src/servers/credential_issuer.rs b/oid4vc-manager/src/servers/credential_issuer.rs index 8a079919..d579707a 100644 --- a/oid4vc-manager/src/servers/credential_issuer.rs +++ b/oid4vc-manager/src/servers/credential_issuer.rs @@ -173,7 +173,7 @@ async fn credential, CFC: CredentialFormatCollection>( AuthBearer(access_token): AuthBearer, Json(credential_request): Json>, ) -> impl IntoResponse { - // TODO: The bunch of unwrap's here should be replaced with error responses as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-credential-error-response. + // TODO: The bunch of unwrap's here should be replaced with error responses as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-credential-error-response let proof = credential_issuer_manager .credential_issuer .validate_proof( @@ -211,7 +211,7 @@ async fn batch_credential, CFC: CredentialFormatCollection>( ) -> impl IntoResponse { let mut credential_responses = vec![]; for credential_request in batch_credential_request.credential_requests { - // TODO: The bunch of unwrap's here should be replaced with error responses as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-batch-credential-error-resp. + // TODO: The bunch of unwrap's here should be replaced with error responses as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-batch-credential-error-resp let proof = credential_issuer_manager .credential_issuer .validate_proof( diff --git a/oid4vc-manager/src/storage.rs b/oid4vc-manager/src/storage.rs index 2ace040c..c9230f53 100644 --- a/oid4vc-manager/src/storage.rs +++ b/oid4vc-manager/src/storage.rs @@ -1,8 +1,10 @@ +use std::collections::HashMap; + use oid4vc_core::authentication::subject::SigningSubject; use oid4vci::{ authorization_response::AuthorizationResponse, credential_format_profiles::CredentialFormatCollection, - credential_issuer::credentials_supported::CredentialsSupportedObject, + credential_issuer::credential_configurations_supported::CredentialConfigurationsSupportedObject, credential_offer::{AuthorizationCode, PreAuthorizedCode}, credential_response::CredentialResponse, token_request::TokenRequest, @@ -15,7 +17,7 @@ pub trait Storage: Send + Sync + 'static where CFC: CredentialFormatCollection, { - fn get_credentials_supported(&self) -> Vec>; + fn get_credential_configurations_supported(&self) -> HashMap>; fn get_authorization_response(&self) -> Option; fn get_authorization_code(&self) -> Option; fn get_pre_authorized_code(&self) -> Option; diff --git a/oid4vc-manager/tests/common/credentials_supported_objects/driver_license.json b/oid4vc-manager/tests/common/credential_configurations_supported_objects/driver_license.json similarity index 76% rename from oid4vc-manager/tests/common/credentials_supported_objects/driver_license.json rename to oid4vc-manager/tests/common/credential_configurations_supported_objects/driver_license.json index 7a71a798..bab08895 100644 --- a/oid4vc-manager/tests/common/credentials_supported_objects/driver_license.json +++ b/oid4vc-manager/tests/common/credential_configurations_supported_objects/driver_license.json @@ -1,11 +1,10 @@ { "format": "jwt_vc_json", - "id": "DriverLicense_JWT", "cryptographic_binding_methods_supported": [ "did:key", "did:iota" ], - "cryptographic_suites_supported": [ + "credential_signing_alg_values_supported": [ "EdDSA" ], "credential_definition":{ @@ -14,9 +13,13 @@ "DriverLicenseCredential" ] }, - "proof_types_supported": [ - "jwt" - ], + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": [ + "ES256" + ] + } + }, "display": [ { "name": "Driver License Credential", diff --git a/oid4vc-manager/tests/common/credentials_supported_objects/university_degree.json b/oid4vc-manager/tests/common/credential_configurations_supported_objects/university_degree.json similarity index 86% rename from oid4vc-manager/tests/common/credentials_supported_objects/university_degree.json rename to oid4vc-manager/tests/common/credential_configurations_supported_objects/university_degree.json index fd433a72..31a8c66c 100644 --- a/oid4vc-manager/tests/common/credentials_supported_objects/university_degree.json +++ b/oid4vc-manager/tests/common/credential_configurations_supported_objects/university_degree.json @@ -1,11 +1,10 @@ { "format": "jwt_vc_json", - "id": "UniversityDegree_JWT", "cryptographic_binding_methods_supported": [ "did:key", "did:iota" ], - "cryptographic_suites_supported": [ + "credential_signing_alg_values_supported": [ "EdDSA" ], "credential_definition":{ @@ -40,9 +39,13 @@ } } }, - "proof_types_supported": [ - "jwt" - ], + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": [ + "ES256" + ] + } + }, "display": [ { "name": "University Credential", diff --git a/oid4vc-manager/tests/common/memory_storage.rs b/oid4vc-manager/tests/common/memory_storage.rs index 8e6bcf29..12fba4ef 100644 --- a/oid4vc-manager/tests/common/memory_storage.rs +++ b/oid4vc-manager/tests/common/memory_storage.rs @@ -1,4 +1,4 @@ -use std::fs::File; +use std::{collections::HashMap, fs::File}; use jsonwebtoken::{Algorithm, Header}; use lazy_static::lazy_static; @@ -6,8 +6,8 @@ use oid4vc_core::{authentication::subject::SigningSubject, generate_authorizatio use oid4vc_manager::storage::Storage; use oid4vci::{ authorization_response::AuthorizationResponse, - credential_format_profiles::{Credential, CredentialFormatCollection, CredentialFormats, WithParameters}, - credential_issuer::credentials_supported::CredentialsSupportedObject, + credential_format_profiles::{CredentialFormatCollection, CredentialFormats, WithParameters}, + credential_issuer::credential_configurations_supported::CredentialConfigurationsSupportedObject, credential_offer::{AuthorizationCode, PreAuthorizedCode}, credential_response::{CredentialResponse, CredentialResponseType}, token_request::TokenRequest, @@ -24,7 +24,6 @@ lazy_static! { pre_authorized_code: generate_authorization_code(16), ..Default::default() }; - pub static ref USER_PIN: String = "493536".to_string(); pub static ref ACCESS_TOKEN: String = "czZCaGRSa3F0MzpnWDFmQmF0M2JW".to_string(); pub static ref C_NONCE: String = "tZignsnFbp".to_string(); } @@ -33,17 +32,27 @@ lazy_static! { pub struct MemoryStorage; impl Storage for MemoryStorage { - fn get_credentials_supported(&self) -> Vec> { + fn get_credential_configurations_supported(&self) -> HashMap> { vec![ - serde_json::from_reader( - File::open("./tests/common/credentials_supported_objects/university_degree.json").unwrap(), - ) - .unwrap(), - serde_json::from_reader( - File::open("./tests/common/credentials_supported_objects/driver_license.json").unwrap(), - ) - .unwrap(), + ( + "UniversityDegree_JWT".to_string(), + serde_json::from_reader( + File::open("./tests/common/credential_configurations_supported_objects/university_degree.json") + .unwrap(), + ) + .unwrap(), + ), + ( + "DriverLicense_JWT".to_string(), + serde_json::from_reader( + File::open("./tests/common/credential_configurations_supported_objects/driver_license.json") + .unwrap(), + ) + .unwrap(), + ), ] + .into_iter() + .collect() } fn get_authorization_code(&self) -> Option { @@ -112,7 +121,7 @@ impl Storage for Memory verifiable_credential["credentialSubject"]["id"] = json!(subject_did); (access_token == ACCESS_TOKEN.clone()).then_some(CredentialResponse { - credential: CredentialResponseType::Immediate(CredentialFormats::JwtVcJson(Credential { + credential: CredentialResponseType::Immediate { credential: serde_json::to_value( jwt::encode( signer.clone(), @@ -129,7 +138,8 @@ impl Storage for Memory .ok(), ) .unwrap(), - })), + notification_id: None, + }, c_nonce: Some(C_NONCE.clone()), c_nonce_expires_in: Some(86400), }) diff --git a/oid4vc-manager/tests/oid4vci/authorization_code.rs b/oid4vc-manager/tests/oid4vci/authorization_code.rs index a26962b8..62a8c0c1 100644 --- a/oid4vc-manager/tests/oid4vci/authorization_code.rs +++ b/oid4vc-manager/tests/oid4vci/authorization_code.rs @@ -6,7 +6,7 @@ use oid4vc_manager::{ servers::credential_issuer::Server, }; use oid4vci::{ - authorization_details::AuthorizationDetailsObject, + authorization_details::{AuthorizationDetailsObject, CredentialConfigurationOrFormat, OpenidCredential}, credential_format_profiles::{CredentialFormats, WithParameters}, credential_response::{CredentialResponse, CredentialResponseType}, token_request::TokenRequest, @@ -63,8 +63,8 @@ async fn test_authorization_code_flow() { // Get the credential format for a university degree. let university_degree_credential_format: CredentialFormats = credential_issuer_metadata - .credentials_supported - .get(0) + .credential_configurations_supported + .get("UniversityDegree_JWT") .unwrap() .clone() .credential_format; @@ -74,8 +74,11 @@ async fn test_authorization_code_flow() { .get_authorization_code( authorization_server_metadata.authorization_endpoint.unwrap(), vec![AuthorizationDetailsObject { + r#type: OpenidCredential::Type, locations: None, - credential_format: university_degree_credential_format.clone(), + credential_configuration_or_format: CredentialConfigurationOrFormat::CredentialFormat( + university_degree_credential_format.clone(), + ), } .into()], ) @@ -105,7 +108,7 @@ async fn test_authorization_code_flow() { .unwrap(); let credential = match credential_response.credential { - CredentialResponseType::Immediate(CredentialFormats::JwtVcJson(credential)) => credential.credential, + CredentialResponseType::Immediate { credential, .. } => credential, _ => panic!("Credential was not a JWT VC JSON."), }; diff --git a/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs b/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs index 830c5723..622038cf 100644 --- a/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs +++ b/oid4vc-manager/tests/oid4vci/pre_authorized_code.rs @@ -7,7 +7,7 @@ use oid4vc_manager::{ }; use oid4vci::{ credential_format_profiles::{CredentialFormats, WithParameters}, - credential_offer::{CredentialOffer, CredentialOfferQuery, CredentialsObject, Grants}, + credential_offer::{CredentialOffer, CredentialOfferParameters, Grants}, credential_response::{BatchCredentialResponse, CredentialResponse, CredentialResponseType}, token_request::TokenRequest, Wallet, @@ -45,7 +45,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference let subject_did = subject.identifier().unwrap(); // Create a new wallet. - let wallet = Wallet::new(Arc::new(subject)); + let wallet: Wallet> = Wallet::new(Arc::new(subject)); // Get the credential offer url. let credential_offer_query = credential_issuer @@ -54,9 +54,9 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference .unwrap(); // Parse the credential offer url. - let credential_offer: CredentialOffer = match credential_offer_query.parse().unwrap() { - CredentialOfferQuery::CredentialOffer(credential_offer) => credential_offer, - CredentialOfferQuery::CredentialOfferUri(credential_offer_uri) => { + let mut credential_offer: CredentialOfferParameters = match credential_offer_query.parse().unwrap() { + CredentialOffer::CredentialOffer(credential_offer) => *credential_offer, + CredentialOffer::CredentialOfferUri(credential_offer_uri) => { wallet.get_credential_offer(credential_offer_uri).await.unwrap() } }; @@ -86,7 +86,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference pre_authorized_code, .. }) => TokenRequest::PreAuthorizedCode { pre_authorized_code: pre_authorized_code.unwrap().pre_authorized_code, - user_pin: Some("493536".to_string()), + tx_code: Some("493536".to_string()), }, None => unreachable!(), }; @@ -97,12 +97,25 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference .await .unwrap(); + // Sort the credential_configuration_ids for predictable testing. + credential_offer.credential_configuration_ids.sort(); + + // The credential offer contains two credential_configuration_ids which are supported by the credential issuer. + let credentials: Vec<_> = credential_offer + .credential_configuration_ids + .iter() + .map(|credential| { + credential_issuer_metadata + .credential_configurations_supported + .get(credential) + .unwrap() + .credential_format + .clone() + }) + .collect(); + if !batch { - // The credential offer contains a credential format for a university degree. - let university_degree_credential_format = match credential_offer.credentials.get(0).unwrap().clone() { - CredentialsObject::ByValue(credential_format) => credential_format, - _ => unreachable!(), - }; + let university_degree_credential_format = credentials.last().unwrap().clone(); // Get the credential. let credential_response: CredentialResponse = wallet @@ -115,7 +128,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference .unwrap(); let credential = match credential_response.credential { - CredentialResponseType::Immediate(CredentialFormats::JwtVcJson(credential)) => credential.credential, + CredentialResponseType::Immediate { credential, .. } => credential, _ => panic!("Credential was not a JWT VC JSON."), }; @@ -147,17 +160,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference }) ) } else if batch { - // The credential offer contains two credentials - let credentials = credential_offer - .credentials - .into_iter() - .map(|credential| match credential { - CredentialsObject::ByValue(credential_format) => credential_format.clone(), - _ => unreachable!(), - }) - .collect::>(); - - // Get the credential. + // Get the credentials. let batch_credential_response: BatchCredentialResponse = wallet .get_batch_credential(credential_issuer_metadata, &token_response, credentials) .await @@ -168,9 +171,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference .into_iter() .map(|credential_response| { let credential = match credential_response { - CredentialResponseType::Immediate(CredentialFormats::JwtVcJson(credential)) => { - credential.credential - } + CredentialResponseType::Immediate { credential, .. } => credential, _ => panic!("Credential was not a JWT VC JSON."), }; @@ -180,7 +181,7 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference }) .collect(); - // Check the "UniversityDegree_JWT" credential. + // Check the "DriverLicense_JWT" credential. assert_eq!( credentials[0]["vc"], serde_json::json!({ @@ -188,24 +189,24 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], - "id": "UniversityDegree_JWT", + "id": "DriverLicense_JWT", "type": [ "VerifiableCredential", - "PersonalInformation" + "DriverLicenseCredential" ], - "issuanceDate": "2022-01-01T00:00:00Z", "issuer": credential_issuer_url, + "issuanceDate": "2022-08-15T09:30:00Z", + "expirationDate": "2027-08-15T23:59:59Z", "credentialSubject": { "id": subject_did, - "givenName": "Ferris", - "familyName": "Crabman", - "email": "ferris.crabman@crabmail.com", - "birthdate": "1985-05-21" + "licenseClass": "Class C", + "issuedBy": "California", + "validity": "Valid" } }) ); - // Check the "DriverLicense_JWT" credential. + // Check the "UniversityDegree_JWT" credential. assert_eq!( credentials[1]["vc"], serde_json::json!({ @@ -213,19 +214,19 @@ async fn test_pre_authorized_code_flow(#[case] batch: bool, #[case] by_reference "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], - "id": "DriverLicense_JWT", + "id": "UniversityDegree_JWT", "type": [ "VerifiableCredential", - "DriverLicenseCredential" + "PersonalInformation" ], + "issuanceDate": "2022-01-01T00:00:00Z", "issuer": credential_issuer_url, - "issuanceDate": "2022-08-15T09:30:00Z", - "expirationDate": "2027-08-15T23:59:59Z", "credentialSubject": { "id": subject_did, - "licenseClass": "Class C", - "issuedBy": "California", - "validity": "Valid" + "givenName": "Ferris", + "familyName": "Crabman", + "email": "ferris.crabman@crabmail.com", + "birthdate": "1985-05-21" } }) ); diff --git a/oid4vci/Cargo.toml b/oid4vci/Cargo.toml index 1585608f..66311114 100644 --- a/oid4vci/Cargo.toml +++ b/oid4vci/Cargo.toml @@ -27,6 +27,5 @@ serde_urlencoded.workspace = true serde_with.workspace = true tokio.workspace = true - [dev-dependencies] wiremock = "0.5" diff --git a/oid4vci/README.md b/oid4vci/README.md index f3f5e2f5..61910f6c 100644 --- a/oid4vci/README.md +++ b/oid4vci/README.md @@ -5,7 +5,7 @@ Working Group](https://openid.net/wg/digital-credentials-protocols/). | Specification | Description | Version | -------------------| ------------------------------------------ | ------- -| [OID4VCI](oid4vci) | OpenID for Verifiable Credential Issuance | [Working Group Draft 12 published: 26 November 2023](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html) +| [OID4VCI](oid4vci) | OpenID for Verifiable Credential Issuance | [Working Group Draft 13 published: 8 February 2024](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html) An overview of all the OpenID Digital Credentials Protocols implementation in Rust can be found [here](../README.md). diff --git a/oid4vci/src/authorization_details.rs b/oid4vci/src/authorization_details.rs index 369cb5a2..fd79cfa0 100644 --- a/oid4vci/src/authorization_details.rs +++ b/oid4vci/src/authorization_details.rs @@ -1,28 +1,53 @@ -use crate::credential_format_profiles::{CredentialFormatCollection, CredentialFormats, WithParameters}; +use crate::credential_format_profiles::{ + CredentialConfiguration, CredentialFormatCollection, CredentialFormats, WithParameters, +}; use reqwest::Url; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +/// Represents the `openid_credential` field of the `AuthorizationDetailsObject`. +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Default)] +pub enum OpenidCredential { + #[default] + #[serde(rename = "openid_credential")] + Type, +} + /// Represents an object of the `authorization_details` field of the `AuthorizationRequest` object in the Authorization Code Flow as -/// described in [OpenID4VCI](https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-request-issuance-of-a-certa) +/// described in [OpenID4VCI](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-request-issuance-of-a-certa) +// TODO: Add `credential_configuration_id` field. #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] -#[serde(tag = "type", rename = "openid_credential")] pub struct AuthorizationDetailsObject> where CFC: CredentialFormatCollection, { + pub r#type: OpenidCredential, pub locations: Option>, #[serde(flatten)] - pub credential_format: CFC, + pub credential_configuration_or_format: CredentialConfigurationOrFormat, +} + +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum CredentialConfigurationOrFormat +where + CFC: CredentialFormatCollection, +{ + CredentialConfigurationId { + credential_configuration_id: String, + #[serde(flatten)] + parameters: Option, + }, + CredentialFormat(CFC), } #[cfg(test)] mod tests { use super::*; use crate::credential_format_profiles::{ - w3c_verifiable_credentials::{jwt_vc_json, ldp_vc}, - CredentialFormats, Parameters, + w3c_verifiable_credentials::{jwt_vc_json, CredentialSubject}, + Parameters, }; use serde::de::DeserializeOwned; use serde_json::json; @@ -38,176 +63,97 @@ mod tests { } #[test] - fn test_authorization_details_serde_jwt_vc_json() { - let jwt_vc_json = json!({ + fn test_authorization_details_object_with_format() { + let json_value = json!({ "type": "openid_credential", "format": "jwt_vc_json", "credential_definition": { - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], - "credentialSubject": { - "given_name": {}, - "last_name": {}, - "degree": {} - } - } - }); - - let authorization_details_mso_mdoc: AuthorizationDetailsObject = - serde_json::from_value(jwt_vc_json.clone()).unwrap(); - - // Assert that the json Value is deserialized into the correct type. - assert_eq!( - authorization_details_mso_mdoc, - AuthorizationDetailsObject { - locations: None, - credential_format: CredentialFormats::JwtVcJson(Parameters { - parameters: ( - jwt_vc_json::CredentialDefinition { - type_: vec!["VerifiableCredential".into(), "UniversityDegreeCredential".into()], - credential_subject: Some(json!({ - "given_name": {}, - "last_name": {}, - "degree": {} - })), - }, - None - ) - .into() - }), - }, - ); - - // Assert that the `AuthorizationDetailsObject` can be serialized back into the original json Value. - assert_eq!( - serde_json::to_value(authorization_details_mso_mdoc).unwrap(), - jwt_vc_json - ); - } - - #[test] - fn test_authorization_details_serde_mso_mdoc() { - let mso_mdoc = json!({ - "type": "openid_credential", - "format": "mso_mdoc", - "doctype": "org.iso.18013.5.1.mDL", - "claims": { - "org.iso.18013.5.1": { - "given_name": {}, - "family_name": {}, - "birth_date": {} - }, - "org.iso.18013.5.1.aamva": { - "organ_donor": {} - } + "type": [ + "VerifiableCredential", + "UniversityDegreeCredential" + ] } }); - let authorization_details_mso_mdoc: AuthorizationDetailsObject = - serde_json::from_value(mso_mdoc.clone()).unwrap(); - - // Assert that the json Value is deserialized into the correct type. assert_eq!( - authorization_details_mso_mdoc, - AuthorizationDetailsObject { + AuthorizationDetailsObject::> { + r#type: OpenidCredential::Type, locations: None, - credential_format: CredentialFormats::MsoMdoc(Parameters { - parameters: ( - "org.iso.18013.5.1.mDL".to_string(), - Some(json!({ - "org.iso.18013.5.1": { - "given_name": {}, - "family_name": {}, - "birth_date": {} + credential_configuration_or_format: CredentialConfigurationOrFormat::CredentialFormat( + CredentialFormats::JwtVcJson(Parameters { + parameters: ( + jwt_vc_json::CredentialDefinition { + type_: vec!["VerifiableCredential".into(), "UniversityDegreeCredential".into()], + credential_subject: CredentialSubject::default(), }, - "org.iso.18013.5.1.aamva": { - "organ_donor": {} - } - })), - None - ) - .into() - }), + None, + ) + .into(), + }), + ), }, + serde_json::from_value(json_value).unwrap() ); - - // Assert that the `AuthorizationDetailsObject` can be serialized back into the original json Value. - assert_eq!(serde_json::to_value(authorization_details_mso_mdoc).unwrap(), mso_mdoc); } #[test] fn test_oid4vci_examples() { // Examples from - // https://bitbucket.org/openid/connect/src/master/openid-4-verifiable-credential-issuance/examples/. + // https://github.com/openid/OpenID4VCI/tree/80b2214814106e55e5fd09af3415ba4fc124b6be/examples assert_eq!( vec![AuthorizationDetailsObject { + r#type: OpenidCredential::Type, locations: None, - credential_format: CredentialFormats::JwtVcJson(Parameters { - parameters: ( - jwt_vc_json::CredentialDefinition { - type_: vec!["VerifiableCredential".into(), "UniversityDegreeCredential".into()], - credential_subject: Some(json!({ - "given_name": {}, - "family_name": {}, - "degree": {} - })), - }, - None - ) - .into() - }), + credential_configuration_or_format: CredentialConfigurationOrFormat::CredentialConfigurationId { + credential_configuration_id: "UniversityDegreeCredential".to_string(), + parameters: Some(CredentialConfiguration::W3cVerifiableCredential(CredentialSubject { + credential_subject: Some(json!({ + "given_name": {}, + "family_name": {}, + "degree": {} + })), + })), + } }], json_example::>("tests/examples/authorization_details_jwt_vc_json.json") ); assert_eq!( vec![AuthorizationDetailsObject { + r#type: OpenidCredential::Type, locations: None, - credential_format: CredentialFormats::LdpVc(Parameters { - parameters: ( - ldp_vc::CredentialDefinition { - context: vec![ - "https://www.w3.org/2018/credentials/v1".into(), - "https://www.w3.org/2018/credentials/examples/v1".into() - ], - type_: vec!["VerifiableCredential".into(), "UniversityDegreeCredential".into()], - credential_subject: Some(json!({ - "given_name": {}, - "family_name": {}, - "degree": {} - })), - }, - None - ) - .into() - }), + credential_configuration_or_format: CredentialConfigurationOrFormat::CredentialConfigurationId { + credential_configuration_id: "UniversityDegree_LDP_VC".to_string(), + parameters: Some(CredentialConfiguration::W3cVerifiableCredential(CredentialSubject { + credential_subject: Some(json!({ + "given_name": {}, + "family_name": {}, + "degree": {} + })), + })), + } }], json_example::>("tests/examples/authorization_details_ldp_vc.json") ); assert_eq!( vec![AuthorizationDetailsObject { + r#type: OpenidCredential::Type, locations: None, - credential_format: CredentialFormats::MsoMdoc(Parameters { - parameters: ( - "org.iso.18013.5.1.mDL".to_string(), - Some(json!({ - "org.iso.18013.5.1": { - "given_name": {}, - "family_name": {}, - "birth_date": {} - }, - "org.iso.18013.5.1.aamva": { - "organ_donor": {} - } - })), - None - ) - .into() - }), + credential_configuration_or_format: CredentialConfigurationOrFormat::CredentialConfigurationId { + credential_configuration_id: "org.iso.18013.5.1.mDL".to_string(), + parameters: Some(CredentialConfiguration::MsoMdoc(Some(json!({ + "org.iso.18013.5.1": { + "given_name": {}, + "family_name": {}, + "birth_date": {} + }, + "org.iso.18013.5.1.aamva": { + "organ_donor": {} + } + })))), + } }], json_example::>("tests/examples/authorization_details_mso_mdoc.json") ); @@ -215,27 +161,20 @@ mod tests { assert_eq!( vec![ AuthorizationDetailsObject { + r#type: OpenidCredential::Type, locations: None, - credential_format: CredentialFormats::LdpVc(Parameters { - parameters: ( - ldp_vc::CredentialDefinition { - context: vec![ - "https://www.w3.org/2018/credentials/v1".into(), - "https://www.w3.org/2018/credentials/examples/v1".into() - ], - type_: vec!["VerifiableCredential".into(), "UniversityDegreeCredential".into()], - credential_subject: None, - }, - None - ) - .into() - }), + credential_configuration_or_format: CredentialConfigurationOrFormat::CredentialConfigurationId { + credential_configuration_id: "UniversityDegreeCredential".to_string(), + parameters: None, + } }, AuthorizationDetailsObject { + r#type: OpenidCredential::Type, locations: None, - credential_format: CredentialFormats::MsoMdoc(Parameters { - parameters: ("org.iso.18013.5.1.mDL".to_string(), None, None).into() - }), + credential_configuration_or_format: CredentialConfigurationOrFormat::CredentialConfigurationId { + credential_configuration_id: "org.iso.18013.5.1.mDL".to_string(), + parameters: None, + } } ], json_example::>( @@ -245,34 +184,24 @@ mod tests { assert_eq!( vec![AuthorizationDetailsObject { + r#type: OpenidCredential::Type, locations: Some(vec!["https://credential-issuer.example.com".parse().unwrap()]), - credential_format: CredentialFormats::JwtVcJson(Parameters { - parameters: ( - jwt_vc_json::CredentialDefinition { - type_: vec!["VerifiableCredential".into(), "UniversityDegreeCredential".into()], - credential_subject: None, - }, - None - ) - .into() - }), + credential_configuration_or_format: CredentialConfigurationOrFormat::CredentialConfigurationId { + credential_configuration_id: "UniversityDegreeCredential".to_string(), + parameters: None, + } }], json_example::>("tests/examples/authorization_details_with_as.json") ); assert_eq!( vec![AuthorizationDetailsObject { + r#type: OpenidCredential::Type, locations: None, - credential_format: CredentialFormats::JwtVcJson(Parameters { - parameters: ( - jwt_vc_json::CredentialDefinition { - type_: vec!["VerifiableCredential".into(), "UniversityDegreeCredential".into()], - credential_subject: None, - }, - None - ) - .into() - }), + credential_configuration_or_format: CredentialConfigurationOrFormat::CredentialConfigurationId { + credential_configuration_id: "UniversityDegreeCredential".to_string(), + parameters: None, + }, }], json_example::>("tests/examples/authorization_details.json") ); diff --git a/oid4vci/src/authorization_request.rs b/oid4vci/src/authorization_request.rs index d4eecce7..aec40abf 100644 --- a/oid4vci/src/authorization_request.rs +++ b/oid4vci/src/authorization_request.rs @@ -4,7 +4,7 @@ use crate::{ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -/// The Authorization Request is used to request authorization as described here: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-request. +/// The Authorization Request is used to request authorization as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-authorization-request #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug)] pub struct AuthorizationRequest diff --git a/oid4vci/src/authorization_response.rs b/oid4vci/src/authorization_response.rs index 4a0255af..7d1ab2a0 100644 --- a/oid4vci/src/authorization_response.rs +++ b/oid4vci/src/authorization_response.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; // TODO: Temporary solution for the Authorization Code Flow. Eventually this should be implemented as described -// here: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-successful-authorization-re +// here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-successful-authorization-re #[derive(Serialize, Deserialize, Debug)] pub struct AuthorizationResponse { pub code: String, diff --git a/oid4vci/src/credential_format_profiles/iso_mdl/mso_mdoc.rs b/oid4vci/src/credential_format_profiles/iso_mdl/mso_mdoc.rs index 171cc857..095c6008 100644 --- a/oid4vci/src/credential_format_profiles/iso_mdl/mso_mdoc.rs +++ b/oid4vci/src/credential_format_profiles/iso_mdl/mso_mdoc.rs @@ -3,6 +3,6 @@ use crate::credential_format; credential_format!("mso_mdoc", MsoMdoc, { doctype: String, claims: Option, - // TODO: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#appendix-E.2.2-2.3 + // TODO: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#appendix-A.2.2-3.3 order: Option> }); diff --git a/oid4vci/src/credential_format_profiles/mod.rs b/oid4vci/src/credential_format_profiles/mod.rs index a01dbbfc..ea064f4b 100644 --- a/oid4vci/src/credential_format_profiles/mod.rs +++ b/oid4vci/src/credential_format_profiles/mod.rs @@ -4,9 +4,12 @@ pub mod w3c_verifiable_credentials; use self::{ iso_mdl::mso_mdoc::MsoMdoc, sealed::FormatExtension, - w3c_verifiable_credentials::{jwt_vc_json::JwtVcJson, jwt_vc_json_ld::JwtVcJsonLd, ldp_vc::LdpVc}, + w3c_verifiable_credentials::{ + jwt_vc_json::JwtVcJson, jwt_vc_json_ld::JwtVcJsonLd, ldp_vc::LdpVc, CredentialSubject, + }, }; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; #[macro_export] macro_rules! credential_format { @@ -108,6 +111,15 @@ where Unknown, } +#[skip_serializing_none] +#[derive(Debug, Serialize, Clone, Eq, PartialEq, Deserialize)] +pub enum CredentialConfiguration { + #[serde(rename = "credential_definition")] + W3cVerifiableCredential(CredentialSubject), + #[serde(rename = "claims")] + MsoMdoc(Option), +} + impl CredentialFormatCollection for CredentialFormats where C: FormatExtension {} impl CredentialFormats @@ -179,7 +191,7 @@ mod tests { "VerifiableCredential".to_string(), "DriverLicenseCredential".to_string(), ], - credential_subject: None, + credential_subject: Default::default(), }, order: None, }, diff --git a/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/jwt_vc_json.rs b/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/jwt_vc_json.rs index 4258f805..17680ec7 100644 --- a/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/jwt_vc_json.rs +++ b/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/jwt_vc_json.rs @@ -2,6 +2,8 @@ use crate::credential_format; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::CredentialSubject; + credential_format!("jwt_vc_json", JwtVcJson, { credential_definition: CredentialDefinition, order: Option @@ -12,6 +14,6 @@ credential_format!("jwt_vc_json", JwtVcJson, { pub struct CredentialDefinition { #[serde(rename = "type")] pub type_: Vec, - #[serde(rename = "credentialSubject")] - pub credential_subject: Option, + #[serde(flatten)] + pub credential_subject: CredentialSubject, } diff --git a/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/jwt_vc_json_ld.rs b/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/jwt_vc_json_ld.rs index 4f37f0b1..335f5f75 100644 --- a/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/jwt_vc_json_ld.rs +++ b/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/jwt_vc_json_ld.rs @@ -2,6 +2,8 @@ use crate::credential_format; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::CredentialSubject; + credential_format!("jwt_vc_json-ld", JwtVcJsonLd, { credential_definition: CredentialDefinition, order: Option @@ -14,6 +16,6 @@ pub struct CredentialDefinition { pub context: Vec, #[serde(rename = "type")] pub type_: Vec, - #[serde(rename = "credentialSubject")] - pub credential_subject: Option, + #[serde(flatten)] + pub credential_subject: CredentialSubject, } diff --git a/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/ldp_vc.rs b/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/ldp_vc.rs index a322968c..2ee94f5c 100644 --- a/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/ldp_vc.rs +++ b/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/ldp_vc.rs @@ -2,6 +2,8 @@ use crate::credential_format; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use super::CredentialSubject; + credential_format!("ldp_vc", LdpVc, { credential_definition: CredentialDefinition, order: Option @@ -14,6 +16,6 @@ pub struct CredentialDefinition { pub context: Vec, #[serde(rename = "type")] pub type_: Vec, - #[serde(rename = "credentialSubject")] - pub credential_subject: Option, + #[serde(flatten)] + pub credential_subject: CredentialSubject, } diff --git a/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/mod.rs b/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/mod.rs index 066bd49b..c5beb871 100644 --- a/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/mod.rs +++ b/oid4vci/src/credential_format_profiles/w3c_verifiable_credentials/mod.rs @@ -1,3 +1,13 @@ +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + pub mod jwt_vc_json; pub mod jwt_vc_json_ld; pub mod ldp_vc; + +#[skip_serializing_none] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)] +pub struct CredentialSubject { + #[serde(rename = "credentialSubject")] + pub credential_subject: Option, +} diff --git a/oid4vci/src/credential_issuer/credential_configurations_supported.rs b/oid4vci/src/credential_issuer/credential_configurations_supported.rs new file mode 100644 index 00000000..1e49106b --- /dev/null +++ b/oid4vci/src/credential_issuer/credential_configurations_supported.rs @@ -0,0 +1,291 @@ +use std::collections::HashMap; + +use crate::{ + credential_format_profiles::{CredentialFormatCollection, CredentialFormats, WithParameters}, + proof::KeyProofMetadata, + ProofType, +}; +use serde::{Deserialize, Serialize}; +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)] +pub struct CredentialConfigurationsSupportedObject> +where + CFC: CredentialFormatCollection, +{ + /// This field is flattened into a `format` field and optionally extra format-specific fields. + #[serde(flatten)] + pub credential_format: CFC, + // Use `Scope` from oid4vc-core/src/scope.rs. + pub scope: Option, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub cryptographic_binding_methods_supported: Vec, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub credential_signing_alg_values_supported: Vec, + #[serde(skip_serializing_if = "HashMap::is_empty", default)] + pub proof_types_supported: HashMap, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub display: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::credential_format_profiles::{ + w3c_verifiable_credentials::{jwt_vc_json, ldp_vc, CredentialSubject}, + CredentialFormats, Parameters, + }; + use serde::de::DeserializeOwned; + use serde_json::json; + use std::{collections::HashMap, fs::File, path::Path}; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct TestWrapper { + credential_configurations_supported: HashMap, + } + + fn json_example(path: &str) -> T + where + T: DeserializeOwned, + { + let file_path = Path::new(path); + let file = File::open(file_path).expect("file does not exist"); + serde_json::from_reader::<_, T>(file).expect("could not parse json") + } + + #[test] + fn test_oid4vci_examples() { + // Examples from + // https://github.com/openid/OpenID4VCI/tree/80b2214814106e55e5fd09af3415ba4fc124b6be/examples + + assert_eq!( + TestWrapper { + credential_configurations_supported: vec![( + "UniversityDegreeCredential".to_string(), + CredentialConfigurationsSupportedObject { + credential_format: CredentialFormats::JwtVcJson(Parameters { + parameters: ( + jwt_vc_json::CredentialDefinition { + type_: vec![ + "VerifiableCredential".to_string(), + "UniversityDegreeCredential".to_string() + ], + credential_subject: CredentialSubject { + credential_subject: Some(json!({ + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-US" + } + ] + }, + "family_name": { + "display": [ + { + "name": "Surname", + "locale": "en-US" + } + ] + }, + "degree": {}, + "gpa": { + "mandatory": true, + "display": [ + { + "name": "GPA" + } + ] + } + })) + }, + }, + None + ) + .into() + }), + scope: Some("UniversityDegree".to_string()), + cryptographic_binding_methods_supported: vec!["did:example".to_string()], + credential_signing_alg_values_supported: vec!["ES256".to_string()], + proof_types_supported: vec![( + ProofType::Jwt, + KeyProofMetadata { + proof_signing_alg_values_supported: vec!["ES256".to_string()] + } + )] + .into_iter() + .collect(), + display: vec![json!({ + "name": "University Credential", + "locale": "en-US", + "logo": { + "url": "https://university.example.edu/public/logo.png", + "alt_text": "a square logo of a university" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF" + })] + } + )] + .into_iter() + .collect() + }, + json_example::("tests/examples/credential_metadata_jwt_vc_json.json") + ); + + assert_eq!( + TestWrapper { + credential_configurations_supported: vec![( + "UniversityDegree_LDP_VC".to_string(), + CredentialConfigurationsSupportedObject { + credential_format: CredentialFormats::LdpVc(Parameters { + parameters: ( + ldp_vc::CredentialDefinition { + context: vec![ + "https://www.w3.org/2018/credentials/v1".to_string(), + "https://www.w3.org/2018/credentials/examples/v1".to_string() + ], + type_: vec![ + "VerifiableCredential".to_string(), + "UniversityDegreeCredential".to_string() + ], + credential_subject: CredentialSubject { + credential_subject: Some(json!({ + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-US" + } + ] + }, + "family_name": { + "display": [ + { + "name": "Surname", + "locale": "en-US" + } + ] + }, + "degree": {}, + "gpa": { + "mandatory": true, + "display": [ + { + "name": "GPA" + } + ] + } + })) + }, + }, + None + ) + .into() + }), + scope: None, + cryptographic_binding_methods_supported: vec!["did:example".to_string()], + credential_signing_alg_values_supported: vec!["Ed25519Signature2018".to_string()], + proof_types_supported: HashMap::new(), + display: vec![json!({ + "name": "University Credential", + "locale": "en-US", + "logo": { + "url": "https://university.example.edu/public/logo.png", + "alt_text": "a square logo of a university" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF" + } + )] + }, + )] + .into_iter() + .collect() + }, + json_example::("tests/examples/credential_metadata_ldp_vc.json") + ); + + assert_eq!( + TestWrapper { + credential_configurations_supported: vec![( + "org.iso.18013.5.1.mDL".to_string(), + CredentialConfigurationsSupportedObject { + credential_format: CredentialFormats::MsoMdoc(Parameters { + parameters: ( + "org.iso.18013.5.1.mDL".to_string(), + Some(json!({ + "org.iso.18013.5.1": { + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-US" + }, + { + "name": "名前", + "locale": "ja-JP" + } + ] + }, + "family_name": { + "display": [ + { + "name": "Surname", + "locale": "en-US" + } + ] + }, + "birth_date": { + "mandatory": true, + } + }, + "org.iso.18013.5.1.aamva": { + "organ_donor": {} + } + })), + None + ) + .into() + }), + scope: None, + cryptographic_binding_methods_supported: vec!["cose_key".to_string()], + credential_signing_alg_values_supported: vec![ + "ES256".to_string(), + "ES384".to_string(), + "ES512".to_string() + ], + proof_types_supported: HashMap::new(), + display: vec![ + json!({ + "name": "Mobile Driving License", + "locale": "en-US", + "logo": { + "url": "https://state.example.org/public/mdl.png", + "alt_text": "state mobile driving license" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF" + }), + json!({ + "name": "モバイル運転免許証", + "locale": "ja-JP", + "logo": { + "url": "https://state.example.org/public/mdl.png", + "alt_text": "米国州発行のモバイル運転免許証" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF" + }) + ] + } + )] + .into_iter() + .collect() + }, + json_example::("tests/examples/credential_metadata_mso_mdoc.json") + ); + } +} diff --git a/oid4vci/src/credential_issuer/credential_issuer_metadata.rs b/oid4vci/src/credential_issuer/credential_issuer_metadata.rs index 1cabd89e..09a12f87 100644 --- a/oid4vci/src/credential_issuer/credential_issuer_metadata.rs +++ b/oid4vci/src/credential_issuer/credential_issuer_metadata.rs @@ -1,13 +1,20 @@ -use super::credentials_supported::CredentialsSupportedObject; +use super::credential_configurations_supported::CredentialConfigurationsSupportedObject; use crate::credential_format_profiles::{CredentialFormatCollection, CredentialFormats, WithParameters}; use derivative::Derivative; -use oid4vc_core::JsonObject; use reqwest::Url; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use std::collections::HashMap; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct CredentialResponseEncryption { + pub alg_values_supported: Vec, + pub enc_values_supported: Vec, + pub encryption_required: bool, +} /// Credential Issuer Metadata as described here: -/// https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata. +/// https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-credential-issuer-metadata-p #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Derivative)] #[derivative(Default)] @@ -18,44 +25,31 @@ where // TODO: Temporary solution #[derivative(Default(value = "Url::parse(\"https://example.com\").unwrap()"))] pub credential_issuer: Url, - pub authorization_server: Option, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub authorization_servers: Vec, // TODO: Temporary solution #[derivative(Default(value = "Url::parse(\"https://example.com\").unwrap()"))] pub credential_endpoint: Url, pub batch_credential_endpoint: Option, pub deferred_credential_endpoint: Option, - pub credentials_supported: Vec>, + pub notification_endpoint: Option, + pub credential_response_encryption: Option, + pub credential_identifiers_supported: Option, + pub signed_metadata: Option, pub display: Option>, -} - -#[skip_serializing_none] -#[derive(Serialize, Deserialize, Debug)] -pub struct CredentialsSupportedDisplay { - name: String, - locale: Option, - logo: Option, - description: Option, - background_color: Option, - text_color: Option, - #[serde(flatten)] - other: Option, -} - -#[skip_serializing_none] -#[derive(Serialize, Deserialize, Debug)] -pub struct Logo { - url: Option, - alt_text: Option, - #[serde(flatten)] - other: Option, + pub credential_configurations_supported: HashMap>, } #[cfg(test)] mod tests { use super::*; - use crate::credential_format_profiles::{ - w3c_verifiable_credentials::{jwt_vc_json, ldp_vc}, - CredentialFormats, Parameters, WithParameters, + use crate::{ + credential_format_profiles::{ + w3c_verifiable_credentials::{jwt_vc_json, CredentialSubject}, + CredentialFormats, Parameters, WithParameters, + }, + proof::KeyProofMetadata, + ProofType, }; use serde::de::DeserializeOwned; use serde_json::json; @@ -73,234 +67,111 @@ mod tests { #[test] fn test_oid4vci_examples() { // Examples from - // https://bitbucket.org/openid/connect/src/master/openid-4-verifiable-credential-issuance/examples/. + // https://github.com/openid/OpenID4VCI/tree/80b2214814106e55e5fd09af3415ba4fc124b6be/examples assert_eq!( CredentialIssuerMetadata { - credential_endpoint: Url::parse("https://server.example.com/credential").unwrap(), - credentials_supported: vec![ - CredentialsSupportedObject { - id: Some("UniversityDegree_LDP".to_string()), - credential_format: CredentialFormats::::LdpVc(Parameters { - parameters: ( - ldp_vc::CredentialDefinition { - context: vec![ - "https://www.w3.org/2018/credentials/v1".to_string(), - "https://www.w3.org/2018/credentials/examples/v1".to_string(), - ], - type_: vec![ - "VerifiableCredential".to_string(), - "UniversityDegreeCredential".to_string() - ], - credential_subject: Some(json!({ - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" - }, - { - "name": "名前", - "locale": "ja-JP" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "degree": {}, - "gpa": { - "display": [ - { - "name": "GPA" - } - ] - } - })) - }, - None - ) - .into() - }), - scope: None, - cryptographic_binding_methods_supported: Some(vec!["did".to_string()]), - cryptographic_suites_supported: Some(vec!["Ed25519Signature2018".to_string()]), - proof_types_supported: None, - display: Some(vec![ - json!({ - "name": "University Credential", - "locale": "en-US", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alternative_text": "a square logo of a university" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }), - json!({ - "name": "在籍証明書", - "locale": "ja-JP", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alternative_text": "大学のロゴ" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }) - ]), - }, - CredentialsSupportedObject { - id: None, - credential_format: CredentialFormats::JwtVcJson(Parameters { + credential_issuer: "https://credential-issuer.example.com".parse().unwrap(), + authorization_servers: vec!["https://server.example.com".parse().unwrap()], + credential_endpoint: Url::parse("https://credential-issuer.example.com").unwrap(), + batch_credential_endpoint: Some( + "https://credential-issuer.example.com/batch_credential" + .parse() + .unwrap() + ), + deferred_credential_endpoint: Some( + "https://credential-issuer.example.com/deferred_credential" + .parse() + .unwrap() + ), + notification_endpoint: None, + credential_response_encryption: Some(CredentialResponseEncryption { + alg_values_supported: vec!["ECDH-ES".to_string()], + enc_values_supported: vec!["A128GCM".to_string()], + encryption_required: false + }), + credential_identifiers_supported: None, + signed_metadata: None, + display: Some(vec![ + json!({ + "name": "Example University", + "locale": "en-US" + }), + json!({ + "name": "Example Université", + "locale": "fr-FR" + }) + ]), + credential_configurations_supported: vec![( + "UniversityDegreeCredential".to_string(), + CredentialConfigurationsSupportedObject { + credential_format: CredentialFormats::::JwtVcJson(Parameters { parameters: ( jwt_vc_json::CredentialDefinition { type_: vec![ "VerifiableCredential".to_string(), "UniversityDegreeCredential".to_string() ], - credential_subject: Some(json!({ - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" - }, - { - "name": "名前", - "locale": "ja-JP" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "degree": {}, - "gpa": { - "display": [ - { - "name": "GPA" - } - ] - } - })) + credential_subject: CredentialSubject { + credential_subject: Some(json!({ + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-US" + }, + ] + }, + "family_name": { + "display": [ + { + "name": "Surname", + "locale": "en-US" + } + ] + }, + "degree": {}, + "gpa": { + "display": [ + { + "name": "GPA" + } + ] + } + })) + } }, None ) .into() }), - scope: None, - cryptographic_binding_methods_supported: Some(vec!["did".to_string()]), - cryptographic_suites_supported: Some(vec!["ES256K".to_string()]), - proof_types_supported: None, - display: Some(vec![ - json!({ - "name": "University Credential", - "locale": "en-US", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alternative_text": "a square logo of a university" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }), - json!({ - "name": "在籍証明書", - "locale": "ja-JP", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alternative_text": "大学のロゴ" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }) - ]), + scope: Some("UniversityDegree".to_string()), + cryptographic_binding_methods_supported: vec!["did:example".to_string()], + credential_signing_alg_values_supported: vec!["ES256".to_string()], + proof_types_supported: vec![( + ProofType::Jwt, + KeyProofMetadata { + proof_signing_alg_values_supported: vec!["ES256".to_string()] + } + )] + .into_iter() + .collect(), + display: vec![json!({ + "name": "University Credential", + "locale": "en-US", + "logo": { + "url": "https://university.example.edu/public/logo.png", + "alt_text": "a square logo of a university" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF" + })], }, - CredentialsSupportedObject { - id: None, - credential_format: CredentialFormats::MsoMdoc(Parameters { - parameters: ( - "org.iso.18013.5.1.mDL".to_string(), - Some(json!({ - "org.iso.18013.5.1": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" - }, - { - "name": "名前", - "locale": "ja-JP" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "birth_date": {} - }, - "org.iso.18013.5.1.aamva": { - "organ_donor": {} - } - })), - None - ) - .into() - }), - scope: None, - cryptographic_binding_methods_supported: Some(vec!["mso".to_string()]), - cryptographic_suites_supported: Some(vec![ - "ES256".to_string(), - "ES384".to_string(), - "ES512".to_string() - ]), - proof_types_supported: None, - display: Some(vec![ - json!({ - "name": "Mobile Driving License", - "locale": "en-US", - "logo": { - "url": "https://examplestate.com/public/mdl.png", - "alternative_text": "a square figure of a mobile driving license" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }), - json!({ - "name": "在籍証明書", - "locale": "ja-JP", - "logo": { - "url": "https://examplestate.com/public/mdl.png", - "alternative_text": "大学のロゴ" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }) - ]), - } - ], - credential_issuer: "https://server.example.com".parse().unwrap(), - authorization_server: None, - batch_credential_endpoint: None, - deferred_credential_endpoint: None, - display: None, + ),] + .into_iter() + .collect(), }, - json_example::("tests/examples/issuer_metadata.json") + json_example::("tests/examples/credential_issuer_metadata_jwt_vc_json.json") ); } } diff --git a/oid4vci/src/credential_issuer/credentials_supported.rs b/oid4vci/src/credential_issuer/credentials_supported.rs deleted file mode 100644 index 186ca148..00000000 --- a/oid4vci/src/credential_issuer/credentials_supported.rs +++ /dev/null @@ -1,244 +0,0 @@ -use crate::{ - credential_format_profiles::{CredentialFormatCollection, CredentialFormats, WithParameters}, - ProofType, -}; -use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; - -/// Credentials Supported object as described here: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-objects-comprising-credenti. -#[skip_serializing_none] -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct CredentialsSupportedObject> -where - CFC: CredentialFormatCollection, -{ - pub id: Option, - #[serde(flatten)] - pub credential_format: CFC, - pub scope: Option, - pub cryptographic_binding_methods_supported: Option>, - pub cryptographic_suites_supported: Option>, - pub proof_types_supported: Option>, - pub display: Option>, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::credential_format_profiles::{ - w3c_verifiable_credentials::{jwt_vc_json, ldp_vc}, - CredentialFormats, Parameters, - }; - use serde::de::DeserializeOwned; - use serde_json::json; - use std::{fs::File, path::Path}; - - fn json_example(path: &str) -> T - where - T: DeserializeOwned, - { - let file_path = Path::new(path); - let file = File::open(file_path).expect("file does not exist"); - serde_json::from_reader::<_, T>(file).expect("could not parse json") - } - - #[test] - fn test_oid4vci_examples() { - // Examples from - // https://bitbucket.org/openid/connect/src/master/openid-4-verifiable-credential-issuance/examples/. - - assert_eq!( - CredentialsSupportedObject { - id: Some("UniversityDegree_JWT".to_string()), - credential_format: CredentialFormats::JwtVcJson(Parameters { - parameters: ( - jwt_vc_json::CredentialDefinition { - type_: vec![ - "VerifiableCredential".to_string(), - "UniversityDegreeCredential".to_string() - ], - credential_subject: Some(json!({ - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "degree": {}, - "gpa": { - "display": [ - { - "name": "GPA" - } - ] - } - })), - }, - None - ) - .into() - }), - scope: None, - cryptographic_binding_methods_supported: Some(vec!["did:example".to_string()]), - cryptographic_suites_supported: Some(vec!["ES256K".to_string()]), - proof_types_supported: Some(vec![ProofType::Jwt]), - display: Some(vec![json!({ - "name": "University Credential", - "locale": "en-US", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alt_text": "a square logo of a university" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - })]) - }, - json_example::("tests/examples/credential_metadata_jwt_vc_json.json") - ); - - assert_eq!( - CredentialsSupportedObject { - id: None, - credential_format: CredentialFormats::LdpVc(Parameters { - parameters: ( - ldp_vc::CredentialDefinition { - context: vec![ - "https://www.w3.org/2018/credentials/v1".to_string(), - "https://www.w3.org/2018/credentials/examples/v1".to_string() - ], - type_: vec![ - "VerifiableCredential".to_string(), - "UniversityDegreeCredential".to_string() - ], - credential_subject: Some(json!({ - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "degree": {}, - "gpa": { - "display": [ - { - "name": "GPA" - } - ] - } - })), - }, - None - ) - .into() - }), - scope: None, - cryptographic_binding_methods_supported: Some(vec!["did:example".to_string()]), - cryptographic_suites_supported: Some(vec!["Ed25519Signature2018".to_string()]), - proof_types_supported: None, - display: Some(vec![json!({ - "name": "University Credential", - "locale": "en-US", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alt_text": "a square logo of a university" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - })]) - }, - json_example::("tests/examples/credential_metadata_ldp_vc.json") - ); - - assert_eq!( - CredentialsSupportedObject { - id: None, - credential_format: CredentialFormats::MsoMdoc(Parameters { - parameters: ( - "org.iso.18013.5.1.mDL".to_string(), - Some(json!({ - "org.iso.18013.5.1": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" - }, - { - "name": "名前", - "locale": "ja-JP" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "birth_date": {} - }, - "org.iso.18013.5.1.aamva": { - "organ_donor": {} - } - })), - None - ) - .into() - }), - scope: None, - cryptographic_binding_methods_supported: Some(vec!["mso".to_string()]), - cryptographic_suites_supported: Some(vec![ - "ES256".to_string(), - "ES384".to_string(), - "ES512".to_string() - ]), - proof_types_supported: None, - display: Some(vec![ - json!({ - "name": "Mobile Driving License", - "locale": "en-US", - "logo": { - "url": "https://examplestate.com/public/mdl.png", - "alt_text": "a square figure of a mobile driving license" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }), - json!({ - "name": "在籍証明書", - "locale": "ja-JP", - "logo": { - "url": "https://examplestate.com/public/mdl.png", - "alt_text": "大学のロゴ" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }) - ]) - }, - json_example::("tests/examples/credential_metadata_mso_mdoc.json") - ); - } -} diff --git a/oid4vci/src/credential_issuer/mod.rs b/oid4vci/src/credential_issuer/mod.rs index ac6facfa..9ce0d89e 100644 --- a/oid4vci/src/credential_issuer/mod.rs +++ b/oid4vci/src/credential_issuer/mod.rs @@ -1,11 +1,11 @@ pub mod authorization_server_metadata; +pub mod credential_configurations_supported; pub mod credential_issuer_metadata; -pub mod credentials_supported; use self::{ authorization_server_metadata::AuthorizationServerMetadata, credential_issuer_metadata::CredentialIssuerMetadata, }; -use crate::{credential_format_profiles::CredentialFormatCollection, proof::ProofOfPossession, Proof}; +use crate::{credential_format_profiles::CredentialFormatCollection, proof::ProofOfPossession, KeyProofType}; use oid4vc_core::{authentication::subject::SigningSubject, Decoder}; #[derive(Clone)] @@ -19,10 +19,10 @@ where } impl CredentialIssuer { - pub async fn validate_proof(&self, proof: Proof, decoder: Decoder) -> anyhow::Result { + pub async fn validate_proof(&self, proof: KeyProofType, decoder: Decoder) -> anyhow::Result { match proof { - Proof::Jwt { jwt, .. } => decoder.decode(jwt).await, - Proof::Cwt { .. } => unimplemented!("CWT is not supported yet"), + KeyProofType::Jwt { jwt, .. } => decoder.decode(jwt).await, + KeyProofType::Cwt { .. } => unimplemented!("CWT is not supported yet"), } } } diff --git a/oid4vci/src/credential_offer.rs b/oid4vci/src/credential_offer.rs index 5a7d466a..1966655c 100644 --- a/oid4vci/src/credential_offer.rs +++ b/oid4vci/src/credential_offer.rs @@ -1,4 +1,3 @@ -use crate::credential_format_profiles::{CredentialFormatCollection, CredentialFormats, WithParameters}; use anyhow::Result; use oid4vc_core::{to_query_value, JsonObject}; use reqwest::Url; @@ -6,44 +5,59 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use serde_with::skip_serializing_none; +/// Grant Type `authorization_code` as described in https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-4.1.1-4.1.1 +#[skip_serializing_none] #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] pub struct AuthorizationCode { pub issuer_state: Option, + pub authorization_server: Option, } +/// Grant Type `pre-authorized_code` as described in https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-4.1.1-4.2.1 #[skip_serializing_none] #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Default)] pub struct PreAuthorizedCode { #[serde(rename = "pre-authorized_code")] pub pre_authorized_code: String, - #[serde(default)] - pub user_pin_required: bool, + pub tx_code: Option, pub interval: Option, + pub authorization_server: Option, } -/// Credential Offer as described in https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer. +#[skip_serializing_none] +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Default)] +pub struct TransactionCode { + pub input_mode: Option, + pub length: Option, + pub description: Option, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Default)] +#[serde(rename_all = "lowercase")] +pub enum InputMode { + #[default] + Numeric, + Text, +} + +/// Credential Offer Parameters as described in https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-credential-offer-parameters #[skip_serializing_none] #[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Clone)] -pub struct CredentialOffer> -where - CFC: CredentialFormatCollection, -{ +pub struct CredentialOfferParameters { pub credential_issuer: Url, - pub credentials: Vec>, + pub credential_configuration_ids: Vec, pub grants: Option, } +/// Credential Offer as described in https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-credential-offer #[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Clone)] #[serde(rename_all = "snake_case")] -pub enum CredentialOfferQuery> -where - CFC: CredentialFormatCollection, -{ +pub enum CredentialOffer { CredentialOfferUri(Url), - CredentialOffer(CredentialOffer), + CredentialOffer(Box), } -impl std::str::FromStr for CredentialOfferQuery { +impl std::str::FromStr for CredentialOffer { type Err = anyhow::Error; fn from_str(s: &str) -> Result { @@ -59,10 +73,10 @@ impl std::str::FromStr for CredentialOfferQuery { } } -impl std::fmt::Display for CredentialOfferQuery { +impl std::fmt::Display for CredentialOffer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - CredentialOfferQuery::CredentialOfferUri(uri) => { + CredentialOffer::CredentialOfferUri(uri) => { let mut url = Url::parse("openid-credential-offer://").map_err(|_| std::fmt::Error)?; url.query_pairs_mut().append_pair( "credential_offer_uri", @@ -70,7 +84,7 @@ impl std::fmt::Display for CredentialOfferQuery ); write!(f, "{}", url) } - CredentialOfferQuery::CredentialOffer(offer) => { + CredentialOffer::CredentialOffer(offer) => { let mut url = Url::parse("openid-credential-offer://").map_err(|_| std::fmt::Error)?; url.query_pairs_mut() .append_pair("credential_offer", &to_query_value(offer).map_err(|_| std::fmt::Error)?); @@ -80,17 +94,7 @@ impl std::fmt::Display for CredentialOfferQuery } } -#[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Clone)] -#[serde(untagged)] -pub enum CredentialsObject> -where - CFC: CredentialFormatCollection, -{ - ByReference(String), - ByValue(CFC), -} - -/// Grants as described in https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer-parameters. +/// Grants as described in https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-4.1.1-2.3 #[skip_serializing_none] #[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Clone, Default)] pub struct Grants { @@ -104,10 +108,6 @@ mod tests { use std::{fs::File, path::Path}; use super::*; - use crate::credential_format_profiles::{ - w3c_verifiable_credentials::{jwt_vc_json, ldp_vc}, - CredentialFormats, Parameters, - }; use serde::de::DeserializeOwned; use serde_json::json; @@ -124,45 +124,35 @@ mod tests { fn test_credential_offer_serde() { let json = json!({ "credential_issuer": "https://credential-issuer.example.com/", - "credentials": [ + "credential_configuration_ids": [ "UniversityDegree_JWT", - { - "format": "mso_mdoc", - "doctype": "org.iso.18013.5.1.mDL" - } ], "grants": { "authorization_code": { "issuer_state": "eyJhbGciOiJSU0Et...FYUaBy" }, "urn:ietf:params:oauth:grant-type:pre-authorized_code": { - "pre-authorized_code": "adhjhdjajkdkhjhdj", - "user_pin_required": true + "pre-authorized_code": "adhjhdjajkdkhjhdj" } } }); - let credential_offer: CredentialOffer = serde_json::from_value(json.clone()).unwrap(); + let credential_offer: CredentialOfferParameters = serde_json::from_value(json.clone()).unwrap(); // Assert that the json Value is deserialized into the correct type. assert_eq!( credential_offer, - CredentialOffer { + CredentialOfferParameters { credential_issuer: "https://credential-issuer.example.com".parse().unwrap(), - credentials: vec![ - CredentialsObject::ByReference("UniversityDegree_JWT".to_string()), - CredentialsObject::ByValue(CredentialFormats::MsoMdoc(Parameters { - parameters: ("org.iso.18013.5.1.mDL".to_string(), None, None).into() - })) - ], + credential_configuration_ids: vec!["UniversityDegree_JWT".to_string(),], grants: Some(Grants { pre_authorized_code: Some(PreAuthorizedCode { pre_authorized_code: "adhjhdjajkdkhjhdj".to_string(), - user_pin_required: true, ..Default::default() }), authorization_code: Some(AuthorizationCode { - issuer_state: Some("eyJhbGciOiJSU0Et...FYUaBy".to_string()) + issuer_state: Some("eyJhbGciOiJSU0Et...FYUaBy".to_string()), + authorization_server: None }) }) } @@ -175,142 +165,67 @@ mod tests { #[test] fn test_oid4vci_examples() { // Examples from - // https://bitbucket.org/openid/connect/src/master/openid-4-verifiable-credential-issuance/examples/. + // https://github.com/openid/OpenID4VCI/tree/80b2214814106e55e5fd09af3415ba4fc124b6be/examples assert_eq!( - CredentialOffer { + CredentialOfferParameters { credential_issuer: "https://credential-issuer.example.com".parse().unwrap(), - credentials: vec![CredentialsObject::ByReference("UniversityDegree_LDP".to_string()),], + credential_configuration_ids: vec!["UniversityDegree_LDP_VC".to_string(),], grants: Some(Grants { authorization_code: None, pre_authorized_code: Some(PreAuthorizedCode { pre_authorized_code: "adhjhdjajkdkhjhdj".to_string(), - user_pin_required: true, + tx_code: Some(TransactionCode::default()), ..Default::default() }) }) }, - json_example::("tests/examples/credential_offer_by_reference.json") - ); - - assert_eq!( - CredentialOffer { - credential_issuer: "https://credential-issuer.example.com".parse().unwrap(), - credentials: vec![CredentialsObject::ByValue(CredentialFormats::JwtVcJson(Parameters { - parameters: ( - jwt_vc_json::CredentialDefinition { - type_: vec![ - "VerifiableCredential".to_string(), - "UniversityDegreeCredential".to_string() - ], - credential_subject: None - }, - None - ) - .into() - })),], - grants: Some(Grants { - authorization_code: Some(AuthorizationCode { - issuer_state: Some("eyJhbGciOiJSU0Et...FYUaBy".to_string()) - }), - pre_authorized_code: None - }) - }, - json_example::("tests/examples/credential_offer_jwt_vc_json.json") + json_example::("tests/examples/credential_offer_by_reference.json") ); assert_eq!( - CredentialOffer { + CredentialOfferParameters { credential_issuer: "https://credential-issuer.example.com".parse().unwrap(), - credentials: vec![CredentialsObject::ByValue(CredentialFormats::LdpVc(Parameters { - parameters: ( - ldp_vc::CredentialDefinition { - context: vec![ - "https://www.w3.org/2018/credentials/v1".to_string(), - "https://www.w3.org/2018/credentials/examples/v1".to_string() - ], - type_: vec![ - "VerifiableCredential".to_string(), - "UniversityDegreeCredential".to_string() - ], - credential_subject: None - }, - None - ) - .into() - })),], - grants: None - }, - json_example::("tests/examples/credential_offer_ldp_vc.json") - ); - - assert_eq!( - CredentialOffer { - credential_issuer: "https://credential-issuer.example.com".parse().unwrap(), - credentials: vec![CredentialsObject::ByValue(CredentialFormats::MsoMdoc(Parameters { - parameters: ("org.iso.18013.5.1.mDL".to_string(), None, None).into() - })),], - grants: Some(Grants { - authorization_code: None, - pre_authorized_code: Some(PreAuthorizedCode { - pre_authorized_code: "adhjhdjajkdkhjhdj".to_string(), - user_pin_required: true, - ..Default::default() - }) - }) - }, - json_example::("tests/examples/credential_offer_mso_mdoc.json") - ); - - assert_eq!( - CredentialOffer { - credential_issuer: "https://credential-issuer.example.com".parse().unwrap(), - credentials: vec![ - CredentialsObject::ByReference("UniversityDegree_JWT".to_string()), - CredentialsObject::ByValue(CredentialFormats::MsoMdoc(Parameters { - parameters: ("org.iso.18013.5.1.mDL".to_string(), None, None).into() - })), + credential_configuration_ids: vec![ + "UniversityDegreeCredential".to_string(), + "org.iso.18013.5.1.mDL".to_string(), ], grants: Some(Grants { - authorization_code: Some(AuthorizationCode { - issuer_state: Some("eyJhbGciOiJSU0Et...FYUaBy".to_string()) - }), + authorization_code: None, pre_authorized_code: Some(PreAuthorizedCode { - pre_authorized_code: "adhjhdjajkdkhjhdj".to_string(), - user_pin_required: true, + pre_authorized_code: "oaKazRN8I0IbtZ0C7JuMn5".to_string(), + tx_code: Some(TransactionCode { + length: Some(4), + input_mode: Some(InputMode::Numeric), + description: Some("Please provide the one-time code that was sent via e-mail".to_string()), + }), ..Default::default() }) }) }, - json_example::("tests/examples/credential_offer_multiple_credentials.json") + json_example::("tests/examples/credential_offer_multiple_credentials.json") ); assert_eq!( - CredentialOffer { + CredentialOfferParameters { credential_issuer: "https://credential-issuer.example.com".parse().unwrap(), - credentials: vec![CredentialsObject::ByValue(CredentialFormats::JwtVcJson(Parameters { - parameters: ( - jwt_vc_json::CredentialDefinition { - type_: vec![ - "VerifiableCredential".to_string(), - "UniversityDegreeCredential".to_string() - ], - credential_subject: None - }, - None - ) - .into() - })),], + credential_configuration_ids: vec!["UniversityDegreeCredential".to_string()], grants: Some(Grants { authorization_code: None, pre_authorized_code: Some(PreAuthorizedCode { pre_authorized_code: "adhjhdjajkdkhjhdj".to_string(), - user_pin_required: true, + tx_code: Some(TransactionCode { + description: Some( + "Please provide the one-time code which was sent to your mobile phone via SMS" + .to_string() + ), + ..Default::default() + }), ..Default::default() }) }) }, - json_example::("tests/examples/credential_offer_pre-authz_code.json") + json_example::("tests/examples/credential_offer_pre-authz_code.json") ); } } diff --git a/oid4vci/src/credential_request.rs b/oid4vci/src/credential_request.rs index bf9dfb68..37056db8 100644 --- a/oid4vci/src/credential_request.rs +++ b/oid4vci/src/credential_request.rs @@ -1,11 +1,11 @@ use crate::{ credential_format_profiles::{CredentialFormatCollection, CredentialFormats, WithParameters}, - proof::Proof, + proof::KeyProofType, }; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -/// Credential Request as described here: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-credential-request +/// Credential Request as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-credential-request #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct CredentialRequest> @@ -14,9 +14,12 @@ where { #[serde(flatten)] pub credential_format: CFC, - pub proof: Option, + pub proof: Option, + // TODO: add `credential_identifier` field when support for Authorization Code Flow is added. + // TODO: add `credential_response_encryption` field when support for JWE is added. } +/// Batch Credential Request as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-batch-credential-request #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct BatchCredentialRequest where @@ -31,7 +34,7 @@ mod tests { use crate::credential_format_profiles::{ w3c_verifiable_credentials::{ jwt_vc_json::{self, CredentialDefinition}, - jwt_vc_json_ld, ldp_vc, + jwt_vc_json_ld, ldp_vc, CredentialSubject, }, CredentialFormats, Parameters, }; @@ -82,17 +85,19 @@ mod tests { "VerifiableCredential".to_string(), "UniversityDegreeCredential".to_string() ], - credential_subject: Some(json!({ - "given_name": {}, - "family_name": {}, - "degree": {} - })), + credential_subject: CredentialSubject { + credential_subject: Some(json!({ + "given_name": {}, + "family_name": {}, + "degree": {} + })) + }, }, None ) .into() }), - proof: Some(Proof::Jwt { + proof: Some(KeyProofType::Jwt { jwt: "eyJraWQiOiJkaWQ6ZXhhbXBsZ...KPxgihac0aW9EkL1nOzM".to_string() }) }, @@ -149,7 +154,7 @@ mod tests { ) .into() }), - proof: Some(Proof::Jwt { + proof: Some(KeyProofType::Jwt { jwt: "eyJraWQiOiJkaWQ6ZXhhbXBsZ...KPxgihac0aW9EkL1nOzM".to_string() }) }, @@ -162,19 +167,7 @@ mod tests { #[test] fn test_oid4vci_examples() { // Examples from - // https://bitbucket.org/openid/connect/src/master/openid-4-verifiable-credential-issuance/examples/. - - assert_eq!( - CredentialRequest { - credential_format: CredentialFormats::MsoMdoc(Parameters { - parameters: ("org.iso.18013.5.1.mDL".to_string(), None, None).into() - }), - proof: Some(Proof::Jwt { - jwt: "eyJraWQiOiJkaWQ6ZXhhbXBsZ...KPxgihac0aW9EkL1nOzM".to_string() - }) - }, - json_example::("tests/examples/credential_request_iso_mdl.json") - ); + // https://github.com/openid/OpenID4VCI/tree/80b2214814106e55e5fd09af3415ba4fc124b6be/examples assert_eq!( CredentialRequest { @@ -195,7 +188,7 @@ mod tests { ) .into() }), - proof: Some(Proof::Jwt { + proof: Some(KeyProofType::Jwt { jwt: "eyJraWQiOiJkaWQ6ZXhhbXBsZ...KPxgihac0aW9EkL1nOzM".to_string() }) }, @@ -215,17 +208,19 @@ mod tests { "VerifiableCredential".to_string(), "UniversityDegreeCredential".to_string() ], - credential_subject: Some(json!({ - "degree": { - "type":{} - } - })), + credential_subject: CredentialSubject { + credential_subject: Some(json!({ + "degree": { + "type":{} + } + })) + }, }, None ) .into() }), - proof: Some(Proof::Jwt { + proof: Some(KeyProofType::Jwt { jwt: "eyJraWQiOiJkaWQ6ZXhhbXBsZ...KPxgihac0aW9EkL1nOzM".to_string() }) }, @@ -241,41 +236,19 @@ mod tests { "VerifiableCredential".to_string(), "UniversityDegreeCredential".to_string() ], - credential_subject: None - }, - None - ) - .into() - }), - proof: Some(Proof::Jwt { - jwt: "eyJraWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEva2V5cy8xIiwiYWxnIjoiRVMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJzNkJoZFJrcXQzIiwiYXVkIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJpYXQiOiIyMDE4LTA5LTE0VDIxOjE5OjEwWiIsIm5vbmNlIjoidFppZ25zbkZicCJ9.ewdkIkPV50iOeBUqMXCC_aZKPxgihac0aW9EkL1nOzM".to_string() - }) - }, - json_example::( - "tests/examples/credential_request_jwt_vc_json.json" - ) - ); - - assert_eq!( - CredentialRequest { - credential_format: CredentialFormats::JwtVcJson(Parameters { - parameters: ( - jwt_vc_json::CredentialDefinition { - type_: vec![ - "VerifiableCredential".to_string(), - "UniversityDegreeCredential".to_string() - ], - credential_subject: Some(json!({ + credential_subject: CredentialSubject { + credential_subject: Some(json!({ "given_name": {}, "family_name": {}, "degree": {} - })) + })) + }, }, None ) .into() }), - proof: Some(Proof::Jwt { + proof: Some(KeyProofType::Jwt { jwt: "eyJraWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEva2V5cy8xIiwiYWxnIjoiRVMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJzNkJoZFJrcXQzIiwiYXVkIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJpYXQiOiIyMDE4LTA5LTE0VDIxOjE5OjEwWiIsIm5vbmNlIjoidFppZ25zbkZicCJ9.ewdkIkPV50iOeBUqMXCC_aZKPxgihac0aW9EkL1nOzM".to_string() }) }, @@ -297,17 +270,19 @@ mod tests { "VerifiableCredential".to_string(), "UniversityDegreeCredential".to_string() ], - credential_subject: Some(json!({ - "degree": { - "type": {} - } - })) + credential_subject: CredentialSubject { + credential_subject: Some(json!({ + "degree": { + "type": {} + } + })) + } }, None ) .into() }), - proof: Some(Proof::Jwt { + proof: Some(KeyProofType::Jwt { jwt: "eyJraWQiOiJkaWQ6ZXhhbXBsZ...KPxgihac0aW9EkL1nOzM".to_string() }) }, diff --git a/oid4vci/src/credential_response.rs b/oid4vci/src/credential_response.rs index 2a644ae0..a9c759bb 100644 --- a/oid4vci/src/credential_response.rs +++ b/oid4vci/src/credential_response.rs @@ -1,21 +1,17 @@ -use crate::credential_format_profiles::{CredentialFormatCollection, CredentialFormats, WithCredential}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -/// Credential Response as described here: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-credential-response. +/// Credential Response as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-credential-response #[skip_serializing_none] #[derive(Serialize, Debug, PartialEq, Deserialize, Clone)] -pub struct CredentialResponse> -where - CFC: CredentialFormatCollection, -{ +pub struct CredentialResponse { #[serde(flatten)] - pub credential: CredentialResponseType, + pub credential: CredentialResponseType, pub c_nonce: Option, pub c_nonce_expires_in: Option, } -/// Batch Credential Response as described here: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-batch-credential-response. +/// Batch Credential Response as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-batch-credential-response #[skip_serializing_none] #[derive(Serialize, Debug, PartialEq, Deserialize)] pub struct BatchCredentialResponse { @@ -27,18 +23,19 @@ pub struct BatchCredentialResponse { #[skip_serializing_none] #[derive(Serialize, Debug, PartialEq, Deserialize, Clone)] #[serde(untagged)] -pub enum CredentialResponseType> -where - CFC: CredentialFormatCollection, -{ - Deferred { transaction_id: String }, - Immediate(CFC), +pub enum CredentialResponseType { + Deferred { + transaction_id: String, + }, + Immediate { + credential: serde_json::Value, + notification_id: Option, + }, } #[cfg(test)] mod tests { use super::*; - use crate::credential_format_profiles::Credential; use serde_json::json; #[test] @@ -70,7 +67,7 @@ mod tests { CredentialResponseType::Deferred { transaction_id: "123".to_string(), }, - CredentialResponseType::Immediate(CredentialFormats::::JwtVcJson(Credential { + CredentialResponseType::Immediate { credential: json!({ "id": "http://example.edu/credentials/3732", "type": ["VerifiableCredential", "UniversityDegreeCredential"], @@ -84,7 +81,8 @@ mod tests { } } }), - })), + notification_id: None, + }, ], c_nonce: Some("456".to_string()), c_nonce_expires_in: Some(789), @@ -98,7 +96,6 @@ mod tests { "transaction_id": "123" }, { - "format": "jwt_vc_json", "credential": { "id": "http://example.edu/credentials/3732", "type": ["VerifiableCredential", "UniversityDegreeCredential"], diff --git a/oid4vci/src/lib.rs b/oid4vci/src/lib.rs index 22021ed9..2e0ce9e9 100644 --- a/oid4vci/src/lib.rs +++ b/oid4vci/src/lib.rs @@ -13,5 +13,5 @@ pub mod token_response; pub mod wallet; pub use credential::{VerifiableCredentialJwt, VerifiableCredentialJwtBuilder}; -pub use proof::{Proof, ProofType}; +pub use proof::{KeyProofType, ProofType}; pub use wallet::Wallet; diff --git a/oid4vci/src/proof.rs b/oid4vci/src/proof.rs index 353ead1d..8cd0cd82 100644 --- a/oid4vci/src/proof.rs +++ b/oid4vci/src/proof.rs @@ -3,27 +3,33 @@ use oid4vc_core::{builder_fn, jwt, RFC7519Claims, Subject}; use serde::{Deserialize, Serialize}; use std::sync::Arc; -/// Key Proof Type (JWT or CWT) and the proof itself, as described here: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-key-proof-types. +/// Key Proof Type (JWT or CWT) and the proof itself, as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#proof-types #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(tag = "proof_type")] -pub enum Proof { +pub enum KeyProofType { #[serde(rename = "jwt")] Jwt { jwt: String }, #[serde(rename = "cwt")] Cwt { cwt: String }, } -impl Proof { +impl KeyProofType { pub fn builder() -> ProofBuilder { ProofBuilder::default() } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct KeyProofMetadata { + pub proof_signing_alg_values_supported: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] #[serde(rename_all = "lowercase")] pub enum ProofType { Jwt, Cwt, + // TODO: add support for `LdpVp` as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#section-7.2.1-2.3 } #[derive(Default)] @@ -42,13 +48,13 @@ pub struct ProofOfPossession { } impl ProofBuilder { - pub fn build(self) -> anyhow::Result { + pub fn build(self) -> anyhow::Result { anyhow::ensure!(self.rfc7519_claims.aud.is_some(), "aud claim is required"); anyhow::ensure!(self.rfc7519_claims.iat.is_some(), "iat claim is required"); anyhow::ensure!(self.nonce.is_some(), "nonce claim is required"); match self.proof_type { - Some(ProofType::Jwt) => Ok(Proof::Jwt { + Some(ProofType::Jwt) => Ok(KeyProofType::Jwt { jwt: jwt::encode( self.signer.ok_or(anyhow::anyhow!("No subject found"))?.clone(), Header { diff --git a/oid4vci/src/token_request.rs b/oid4vci/src/token_request.rs index 730c0855..b784fb9e 100644 --- a/oid4vci/src/token_request.rs +++ b/oid4vci/src/token_request.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -/// Token Request as described here: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-token-request. +/// Token Request as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-token-request #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(tag = "grant_type")] @@ -16,7 +16,7 @@ pub enum TokenRequest { PreAuthorizedCode { #[serde(rename = "pre-authorized_code")] pre_authorized_code: String, - user_pin: Option, + tx_code: Option, }, } @@ -45,12 +45,12 @@ mod tests { serde_urlencoded::from_str::( "grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code\ &pre-authorized_code=SplxlOBeZQQYbYS6WxSbIA\ - &user_pin=493536" + &tx_code=493536" ) .unwrap(), TokenRequest::PreAuthorizedCode { pre_authorized_code: "SplxlOBeZQQYbYS6WxSbIA".to_string(), - user_pin: Some("493536".to_string()) + tx_code: Some("493536".to_string()) } ); } diff --git a/oid4vci/src/token_response.rs b/oid4vci/src/token_response.rs index 2832af48..12eaaf85 100644 --- a/oid4vci/src/token_response.rs +++ b/oid4vci/src/token_response.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -/// Token Response as described here: https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-successful-token-response. +/// Token Response as described here: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-13.html#name-successful-token-response #[skip_serializing_none] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct TokenResponse { @@ -12,4 +12,5 @@ pub struct TokenResponse { pub scope: Option, pub c_nonce: Option, pub c_nonce_expires_in: Option, + // TODO: add `authorization_details` field when support for Authorization Code Flow is added. } diff --git a/oid4vci/src/wallet/mod.rs b/oid4vci/src/wallet/mod.rs index d95faf3a..6fc7df9f 100644 --- a/oid4vci/src/wallet/mod.rs +++ b/oid4vci/src/wallet/mod.rs @@ -5,10 +5,10 @@ use crate::credential_format_profiles::{CredentialFormatCollection, CredentialFo use crate::credential_issuer::{ authorization_server_metadata::AuthorizationServerMetadata, credential_issuer_metadata::CredentialIssuerMetadata, }; -use crate::credential_offer::CredentialOffer; +use crate::credential_offer::CredentialOfferParameters; use crate::credential_request::{BatchCredentialRequest, CredentialRequest}; use crate::credential_response::BatchCredentialResponse; -use crate::proof::{Proof, ProofType}; +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; @@ -40,12 +40,12 @@ impl Wallet { } } - pub async fn get_credential_offer(&self, credential_offer_uri: Url) -> Result { + pub async fn get_credential_offer(&self, credential_offer_uri: Url) -> Result { self.client .get(credential_offer_uri) .send() .await? - .json::() + .json::() .await .map_err(|_| anyhow::anyhow!("Failed to get credential offer")) } @@ -138,7 +138,7 @@ impl Wallet { let credential_request = CredentialRequest { credential_format, proof: Some( - Proof::builder() + KeyProofType::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) .iss(self.subject.identifier()?) @@ -175,7 +175,7 @@ impl Wallet { credential_formats: Vec, ) -> Result { let proof = Some( - Proof::builder() + KeyProofType::builder() .proof_type(ProofType::Jwt) .signer(self.subject.clone()) .iss(self.subject.identifier()?) diff --git a/oid4vci/tests/examples/authorization_details.json b/oid4vci/tests/examples/authorization_details.json index e6b47cef..f27ca3a9 100644 --- a/oid4vci/tests/examples/authorization_details.json +++ b/oid4vci/tests/examples/authorization_details.json @@ -1,12 +1,6 @@ [ { "type": "openid_credential", - "format": "jwt_vc_json", - "credential_definition": { - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ] - } + "credential_configuration_id": "UniversityDegreeCredential" } ] diff --git a/oid4vci/tests/examples/authorization_details_jwt_vc_json.json b/oid4vci/tests/examples/authorization_details_jwt_vc_json.json index 54b0975e..2d5f262f 100644 --- a/oid4vci/tests/examples/authorization_details_jwt_vc_json.json +++ b/oid4vci/tests/examples/authorization_details_jwt_vc_json.json @@ -1,12 +1,8 @@ [ { "type": "openid_credential", - "format": "jwt_vc_json", + "credential_configuration_id": "UniversityDegreeCredential", "credential_definition": { - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], "credentialSubject": { "given_name": {}, "family_name": {}, diff --git a/oid4vci/tests/examples/authorization_details_ldp_vc.json b/oid4vci/tests/examples/authorization_details_ldp_vc.json index af20a402..5f85ab69 100644 --- a/oid4vci/tests/examples/authorization_details_ldp_vc.json +++ b/oid4vci/tests/examples/authorization_details_ldp_vc.json @@ -1,16 +1,8 @@ [ { "type": "openid_credential", - "format": "ldp_vc", + "credential_configuration_id": "UniversityDegree_LDP_VC", "credential_definition": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], "credentialSubject": { "given_name": {}, "family_name": {}, diff --git a/oid4vci/tests/examples/authorization_details_mso_mdoc.json b/oid4vci/tests/examples/authorization_details_mso_mdoc.json index ea4a24fb..37eda279 100644 --- a/oid4vci/tests/examples/authorization_details_mso_mdoc.json +++ b/oid4vci/tests/examples/authorization_details_mso_mdoc.json @@ -1,8 +1,7 @@ [ { - "type": "openid_credential", - "format": "mso_mdoc", - "doctype": "org.iso.18013.5.1.mDL", + "type":"openid_credential", + "credential_configuration_id": "org.iso.18013.5.1.mDL", "claims": { "org.iso.18013.5.1": { "given_name": {}, diff --git a/oid4vci/tests/examples/authorization_details_multiple_credentials.json b/oid4vci/tests/examples/authorization_details_multiple_credentials.json index 0f259b5b..21c5aef7 100644 --- a/oid4vci/tests/examples/authorization_details_multiple_credentials.json +++ b/oid4vci/tests/examples/authorization_details_multiple_credentials.json @@ -1,21 +1,10 @@ [ { - "type":"openid_credential", - "format": "ldp_vc", - "credential_definition": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ] - } + "type":"openid_credential", + "credential_configuration_id": "UniversityDegreeCredential" }, { - "type":"openid_credential", - "format": "mso_mdoc", - "doctype":"org.iso.18013.5.1.mDL" + "type":"openid_credential", + "credential_configuration_id": "org.iso.18013.5.1.mDL" } ] diff --git a/oid4vci/tests/examples/authorization_details_with_as.json b/oid4vci/tests/examples/authorization_details_with_as.json index d31083df..1cdb2296 100644 --- a/oid4vci/tests/examples/authorization_details_with_as.json +++ b/oid4vci/tests/examples/authorization_details_with_as.json @@ -1,15 +1,9 @@ [ { - "type": "openid_credential", - "locations": [ - "https://credential-issuer.example.com" - ], - "format": "jwt_vc_json", - "credential_definition": { - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ] - } + "type": "openid_credential", + "locations": [ + "https://credential-issuer.example.com" + ], + "credential_configuration_id": "UniversityDegreeCredential" } ] diff --git a/oid4vci/tests/examples/credential_issuer_metadata_jwt_vc_json.json b/oid4vci/tests/examples/credential_issuer_metadata_jwt_vc_json.json new file mode 100644 index 00000000..4daf6053 --- /dev/null +++ b/oid4vci/tests/examples/credential_issuer_metadata_jwt_vc_json.json @@ -0,0 +1,89 @@ +{ + "credential_issuer": "https://credential-issuer.example.com", + "authorization_servers": [ "https://server.example.com" ], + "credential_endpoint": "https://credential-issuer.example.com", + "batch_credential_endpoint": "https://credential-issuer.example.com/batch_credential", + "deferred_credential_endpoint": "https://credential-issuer.example.com/deferred_credential", + "credential_response_encryption": { + "alg_values_supported" : [ + "ECDH-ES" + ], + "enc_values_supported" : [ + "A128GCM" + ], + "encryption_required": false + }, + "display": [ + { + "name": "Example University", + "locale": "en-US" + }, + { + "name": "Example Université", + "locale": "fr-FR" + } + ], + "credential_configurations_supported": { + "UniversityDegreeCredential": { + "format": "jwt_vc_json", + "scope": "UniversityDegree", + "cryptographic_binding_methods_supported": [ + "did:example" + ], + "credential_signing_alg_values_supported": [ + "ES256" + ], + "credential_definition":{ + "type": [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + "credentialSubject": { + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-US" + } + ] + }, + "family_name": { + "display": [ + { + "name": "Surname", + "locale": "en-US" + } + ] + }, + "degree": {}, + "gpa": { + "display": [ + { + "name": "GPA" + } + ] + } + } + }, + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": [ + "ES256" + ] + } + }, + "display": [ + { + "name": "University Credential", + "locale": "en-US", + "logo": { + "url": "https://university.example.edu/public/logo.png", + "alt_text": "a square logo of a university" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF" + } + ] + } + } +} diff --git a/oid4vci/tests/examples/credential_metadata_jwt_vc_json.json b/oid4vci/tests/examples/credential_metadata_jwt_vc_json.json index 56b8638b..c72b69e5 100644 --- a/oid4vci/tests/examples/credential_metadata_jwt_vc_json.json +++ b/oid4vci/tests/examples/credential_metadata_jwt_vc_json.json @@ -1,57 +1,66 @@ { - "format": "jwt_vc_json", - "id": "UniversityDegree_JWT", - "cryptographic_binding_methods_supported": [ - "did:example" - ], - "cryptographic_suites_supported": [ - "ES256K" - ], - "credential_definition":{ - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], - "credentialSubject": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" + "credential_configurations_supported": { + "UniversityDegreeCredential": { + "format": "jwt_vc_json", + "scope": "UniversityDegree", + "cryptographic_binding_methods_supported": [ + "did:example" + ], + "credential_signing_alg_values_supported": [ + "ES256" + ], + "credential_definition":{ + "type": [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + "credentialSubject": { + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-US" + } + ] + }, + "family_name": { + "display": [ + { + "name": "Surname", + "locale": "en-US" + } + ] + }, + "degree": {}, + "gpa": { + "mandatory": true, + "display": [ + { + "name": "GPA" + } + ] } - ] + } }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "degree": {}, - "gpa": { - "display": [ - { - "name": "GPA" - } - ] - } - } - }, - "proof_types_supported": [ - "jwt" - ], - "display": [ - { - "name": "University Credential", - "locale": "en-US", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alt_text": "a square logo of a university" + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": [ + "ES256" + ] + } }, - "background_color": "#12107c", - "text_color": "#FFFFFF" + "display": [ + { + "name": "University Credential", + "locale": "en-US", + "logo": { + "url": "https://university.example.edu/public/logo.png", + "alt_text": "a square logo of a university" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF" + } + ] } - ] + } } diff --git a/oid4vci/tests/examples/credential_metadata_ldp_vc.json b/oid4vci/tests/examples/credential_metadata_ldp_vc.json index 9bdd0159..afddcd63 100644 --- a/oid4vci/tests/examples/credential_metadata_ldp_vc.json +++ b/oid4vci/tests/examples/credential_metadata_ldp_vc.json @@ -1,65 +1,70 @@ { - "format": "ldp_vc", - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], - "cryptographic_binding_methods_supported": [ - "did:example" - ], - "cryptographic_suites_supported": [ - "Ed25519Signature2018" - ], - "credential_definition": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], - "credentialSubject": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" + "credential_configurations_supported": { + "UniversityDegree_LDP_VC": { + "format": "ldp_vc", + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "type": [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + "cryptographic_binding_methods_supported": [ + "did:example" + ], + "credential_signing_alg_values_supported": [ + "Ed25519Signature2018" + ], + "credential_definition": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "type": [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + "credentialSubject": { + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-US" + } + ] + }, + "family_name": { + "display": [ + { + "name": "Surname", + "locale": "en-US" + } + ] + }, + "degree": {}, + "gpa": { + "mandatory": true, + "display": [ + { + "name": "GPA" + } + ] } - ] + } }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "degree": {}, - "gpa": { - "display": [ - { - "name": "GPA" - } - ] - } - } - }, - "display": [ - { - "name": "University Credential", - "locale": "en-US", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alt_text": "a square logo of a university" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" + "display": [ + { + "name": "University Credential", + "locale": "en-US", + "logo": { + "url": "https://university.example.edu/public/logo.png", + "alt_text": "a square logo of a university" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF" + } + ] } - ] + } } diff --git a/oid4vci/tests/examples/credential_metadata_mso_mdoc.json b/oid4vci/tests/examples/credential_metadata_mso_mdoc.json index 732a4a49..2d71055b 100644 --- a/oid4vci/tests/examples/credential_metadata_mso_mdoc.json +++ b/oid4vci/tests/examples/credential_metadata_mso_mdoc.json @@ -1,60 +1,66 @@ { - "format": "mso_mdoc", - "doctype": "org.iso.18013.5.1.mDL", - "cryptographic_binding_methods_supported": [ - "mso" - ], - "cryptographic_suites_supported": [ - "ES256", "ES384", "ES512" - ], - "display": [ - { - "name": "Mobile Driving License", - "locale": "en-US", - "logo": { - "url": "https://examplestate.com/public/mdl.png", - "alt_text": "a square figure of a mobile driving license" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }, - { - "name": "在籍証明書", - "locale": "ja-JP", - "logo": { - "url": "https://examplestate.com/public/mdl.png", - "alt_text": "大学のロゴ" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - } - ], - "claims": { - "org.iso.18013.5.1": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" + "credential_configurations_supported": { + "org.iso.18013.5.1.mDL": { + "format": "mso_mdoc", + "doctype": "org.iso.18013.5.1.mDL", + "cryptographic_binding_methods_supported": [ + "cose_key" + ], + "credential_signing_alg_values_supported": [ + "ES256", "ES384", "ES512" + ], + "display": [ + { + "name": "Mobile Driving License", + "locale": "en-US", + "logo": { + "url": "https://state.example.org/public/mdl.png", + "alt_text": "state mobile driving license" }, - { - "name": "名前", - "locale": "ja-JP" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" + "background_color": "#12107c", + "text_color": "#FFFFFF" + }, + { + "name": "モバイル運転免許証", + "locale": "ja-JP", + "logo": { + "url": "https://state.example.org/public/mdl.png", + "alt_text": "米国州発行のモバイル運転免許証" + }, + "background_color": "#12107c", + "text_color": "#FFFFFF" + } + ], + "claims": { + "org.iso.18013.5.1": { + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-US" + }, + { + "name": "名前", + "locale": "ja-JP" + } + ] + }, + "family_name": { + "display": [ + { + "name": "Surname", + "locale": "en-US" + } + ] + }, + "birth_date": { + "mandatory": true } - ] - }, - "birth_date": {} - }, - "org.iso.18013.5.1.aamva": { - "organ_donor": {} + }, + "org.iso.18013.5.1.aamva": { + "organ_donor": {} + } + } } } } diff --git a/oid4vci/tests/examples/credential_offer_by_reference.json b/oid4vci/tests/examples/credential_offer_by_reference.json index 09f6f16d..80763be9 100644 --- a/oid4vci/tests/examples/credential_offer_by_reference.json +++ b/oid4vci/tests/examples/credential_offer_by_reference.json @@ -1,12 +1,12 @@ { "credential_issuer": "https://credential-issuer.example.com", - "credentials": [ - "UniversityDegree_LDP" + "credential_configuration_ids": [ + "UniversityDegree_LDP_VC" ], "grants": { - "urn:ietf:params:oauth:grant-type:pre-authorized_code": { - "pre-authorized_code": "adhjhdjajkdkhjhdj", - "user_pin_required": true - } - } + "urn:ietf:params:oauth:grant-type:pre-authorized_code": { + "pre-authorized_code": "adhjhdjajkdkhjhdj", + "tx_code": {} + } + } } diff --git a/oid4vci/tests/examples/credential_offer_jwt_vc_json.json b/oid4vci/tests/examples/credential_offer_jwt_vc_json.json deleted file mode 100644 index 57050ef4..00000000 --- a/oid4vci/tests/examples/credential_offer_jwt_vc_json.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "credential_issuer": "https://credential-issuer.example.com", - "credentials": [ - { - "format": "jwt_vc_json", - "credential_definition": { - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ] - } - } - ], - "grants": { - "authorization_code": { - "issuer_state": "eyJhbGciOiJSU0Et...FYUaBy" - } - } -} diff --git a/oid4vci/tests/examples/credential_offer_ldp_vc.json b/oid4vci/tests/examples/credential_offer_ldp_vc.json deleted file mode 100644 index 01b154ee..00000000 --- a/oid4vci/tests/examples/credential_offer_ldp_vc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "credential_issuer": "https://credential-issuer.example.com", - "credentials": [ - { - "format": "ldp_vc", - "credential_definition": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ] - } - } - ] -} diff --git a/oid4vci/tests/examples/credential_offer_mso_mdoc.json b/oid4vci/tests/examples/credential_offer_mso_mdoc.json deleted file mode 100644 index fc75a197..00000000 --- a/oid4vci/tests/examples/credential_offer_mso_mdoc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "credential_issuer": "https://credential-issuer.example.com", - "credentials": [ - { - "format": "mso_mdoc", - "doctype": "org.iso.18013.5.1.mDL" - } - ], - "grants": { - "urn:ietf:params:oauth:grant-type:pre-authorized_code": { - "pre-authorized_code": "adhjhdjajkdkhjhdj", - "user_pin_required": true - } - } -} diff --git a/oid4vci/tests/examples/credential_offer_multiple_credentials.json b/oid4vci/tests/examples/credential_offer_multiple_credentials.json index cc9d0ac0..ad6df1eb 100644 --- a/oid4vci/tests/examples/credential_offer_multiple_credentials.json +++ b/oid4vci/tests/examples/credential_offer_multiple_credentials.json @@ -1,19 +1,17 @@ { - "credential_issuer": "https://credential-issuer.example.com", - "credentials": [ - "UniversityDegree_JWT", - { - "format": "mso_mdoc", - "doctype": "org.iso.18013.5.1.mDL" - } - ], - "grants": { - "authorization_code": { - "issuer_state": "eyJhbGciOiJSU0Et...FYUaBy" - }, - "urn:ietf:params:oauth:grant-type:pre-authorized_code": { - "pre-authorized_code": "adhjhdjajkdkhjhdj", - "user_pin_required": true - } - } + "credential_issuer": "https://credential-issuer.example.com", + "credential_configuration_ids": [ + "UniversityDegreeCredential", + "org.iso.18013.5.1.mDL" + ], + "grants": { + "urn:ietf:params:oauth:grant-type:pre-authorized_code": { + "pre-authorized_code": "oaKazRN8I0IbtZ0C7JuMn5", + "tx_code": { + "length": 4, + "input_mode": "numeric", + "description": "Please provide the one-time code that was sent via e-mail" + } + } + } } diff --git a/oid4vci/tests/examples/credential_offer_pre-authz_code.json b/oid4vci/tests/examples/credential_offer_pre-authz_code.json index d8564a92..f754bc45 100644 --- a/oid4vci/tests/examples/credential_offer_pre-authz_code.json +++ b/oid4vci/tests/examples/credential_offer_pre-authz_code.json @@ -1,20 +1,14 @@ { "credential_issuer": "https://credential-issuer.example.com", - "credentials": [ - { - "format": "jwt_vc_json", - "credential_definition": { - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ] - } - } + "credential_configuration_ids": [ + "UniversityDegreeCredential" ], "grants": { "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "adhjhdjajkdkhjhdj", - "user_pin_required": true + "tx_code" : { + "description" : "Please provide the one-time code which was sent to your mobile phone via SMS" + } } } } diff --git a/oid4vci/tests/examples/credential_request_iso_mdl.json b/oid4vci/tests/examples/credential_request_iso_mdl.json deleted file mode 100644 index 25bf45cd..00000000 --- a/oid4vci/tests/examples/credential_request_iso_mdl.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "format": "mso_mdoc", - "doctype": "org.iso.18013.5.1.mDL", - "proof": { - "proof_type": "jwt", - "jwt": "eyJraWQiOiJkaWQ6ZXhhbXBsZ...KPxgihac0aW9EkL1nOzM" - } -} diff --git a/oid4vci/tests/examples/credential_request_jwt_vc_json.json b/oid4vci/tests/examples/credential_request_jwt_vc_json.json deleted file mode 100644 index 3811f6e3..00000000 --- a/oid4vci/tests/examples/credential_request_jwt_vc_json.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "format": "jwt_vc_json", - "credential_definition": { - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ] - }, - "proof": { - "proof_type": "jwt", - "jwt":"eyJraWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEva2V5cy8xIiwiYWxnIjoiRVMyNTYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJzNkJoZFJrcXQzIiwiYXVkIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJpYXQiOiIyMDE4LTA5LTE0VDIxOjE5OjEwWiIsIm5vbmNlIjoidFppZ25zbkZicCJ9.ewdkIkPV50iOeBUqMXCC_aZKPxgihac0aW9EkL1nOzM" - } -} diff --git a/oid4vci/tests/examples/issuer_metadata.json b/oid4vci/tests/examples/issuer_metadata.json deleted file mode 100644 index cb1b157c..00000000 --- a/oid4vci/tests/examples/issuer_metadata.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "credential_endpoint": "https://server.example.com/credential", - "credentials_supported": [ - { - "id":"UniversityDegree_LDP", - "format": "ldp_vc", - "cryptographic_binding_methods_supported": [ - "did" - ], - "cryptographic_suites_supported": [ - "Ed25519Signature2018" - ], - "credential_definition": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1" - ], - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], - "credentialSubject": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" - }, - { - "name": "名前", - "locale": "ja-JP" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "degree": {}, - "gpa": { - "display": [ - { - "name": "GPA" - } - ] - } - } - }, - "display": [ - { - "name": "University Credential", - "locale": "en-US", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alternative_text": "a square logo of a university" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }, - { - "name": "在籍証明書", - "locale": "ja-JP", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alternative_text": "大学のロゴ" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - } - ] - }, - { - "format": "jwt_vc_json", - "cryptographic_binding_methods_supported": [ - "did" - ], - "cryptographic_suites_supported": [ - "ES256K" - ], - "credential_definition": { - "type": [ - "VerifiableCredential", - "UniversityDegreeCredential" - ], - "credentialSubject": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" - }, - { - "name": "名前", - "locale": "ja-JP" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "degree": {}, - "gpa": { - "display": [ - { - "name": "GPA" - } - ] - } - } - }, - "display": [ - { - "name": "University Credential", - "locale": "en-US", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alternative_text": "a square logo of a university" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }, - { - "name": "在籍証明書", - "locale": "ja-JP", - "logo": { - "url": "https://exampleuniversity.com/public/logo.png", - "alternative_text": "大学のロゴ" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - } - ] - }, - { - "format": "mso_mdoc", - "doctype": "org.iso.18013.5.1.mDL", - "cryptographic_binding_methods_supported": [ - "mso" - ], - "cryptographic_suites_supported": [ - "ES256", "ES384", "ES512" - ], - "display": [ - { - "name": "Mobile Driving License", - "locale": "en-US", - "logo": { - "url": "https://examplestate.com/public/mdl.png", - "alternative_text": "a square figure of a mobile driving license" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - }, - { - "name": "在籍証明書", - "locale": "ja-JP", - "logo": { - "url": "https://examplestate.com/public/mdl.png", - "alternative_text": "大学のロゴ" - }, - "background_color": "#12107c", - "text_color": "#FFFFFF" - } - ], - "claims": { - "org.iso.18013.5.1": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-US" - }, - { - "name": "名前", - "locale": "ja-JP" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-US" - } - ] - }, - "birth_date": {} - }, - "org.iso.18013.5.1.aamva": { - "organ_donor": {} - } - } - } - ], - "credential_issuer": "https://server.example.com" -} diff --git a/oid4vp/src/oid4vp.rs b/oid4vp/src/oid4vp.rs index 53682966..2c1524e6 100644 --- a/oid4vp/src/oid4vp.rs +++ b/oid4vp/src/oid4vp.rs @@ -76,7 +76,7 @@ impl Extension for OID4VP { state, extension: AuthorizationResponseParameters { oid4vp_parameters: Oid4vpParams::Params { - vp_token: jwts.get(0).unwrap().to_owned(), + vp_token: jwts.first().unwrap().to_owned(), presentation_submission: user_input.presentation_submission, }, },