Skip to content

Commit

Permalink
feat: update OpenID4VCI to WG Draft 13 (#64)
Browse files Browse the repository at this point in the history
* 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<Vec<T>>` to `Vec<T>` 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<ProofType, KeyProofMetadata>`

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
  • Loading branch information
nanderstabel authored Apr 4, 2024
1 parent 176a3f3 commit 928d68d
Show file tree
Hide file tree
Showing 54 changed files with 1,136 additions and 1,555 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
28 changes: 16 additions & 12 deletions oid4vc-manager/src/managers/credential_issuer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -32,17 +32,21 @@ impl<S: Storage<CFC>, CFC: CredentialFormatCollection> CredentialIssuerManager<S
Ok(Self {
credential_issuer: CredentialIssuer {
subject: subjects
.get(0)
.first()
.ok_or_else(|| anyhow::anyhow!("No subjects found."))?
.clone(),
metadata: CredentialIssuerMetadata {
credential_issuer: issuer_url.clone(),
authorization_server: None,
authorization_servers: vec![],
credential_endpoint: issuer_url.join("/credential")?,
batch_credential_endpoint: Some(issuer_url.join("/batch_credential")?),
deferred_credential_endpoint: None,
credentials_supported: storage.get_credentials_supported(),
notification_endpoint: None,
credential_response_encryption: None,
credential_identifiers_supported: None,
signed_metadata: None,
display: None,
credential_configurations_supported: storage.get_credential_configurations_supported(),
},
authorization_server_metadata: AuthorizationServerMetadata {
issuer: issuer_url.clone(),
Expand All @@ -62,17 +66,17 @@ impl<S: Storage<CFC>, CFC: CredentialFormatCollection> CredentialIssuerManager<S
Ok(self.credential_issuer.metadata.credential_issuer.clone())
}

pub fn credential_offer(&self) -> Result<CredentialOffer<CFC>> {
let credentials: Vec<CredentialsObject<CFC>> = self
pub fn credential_offer(&self) -> Result<CredentialOfferParameters> {
let credential_configuration_ids: Vec<String> = 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(),
Expand All @@ -87,9 +91,9 @@ impl<S: Storage<CFC>, CFC: CredentialFormatCollection> CredentialIssuerManager<S

pub fn credential_offer_query(&self, by_reference: bool) -> Result<String> {
if by_reference {
Ok(CredentialOfferQuery::<CFC>::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())
}
}
}
2 changes: 1 addition & 1 deletion oid4vc-manager/src/managers/relying_party.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub struct RelyingPartyManager {
impl RelyingPartyManager {
pub fn new<const N: usize>(subjects: [Arc<dyn Subject>; N]) -> Result<Self> {
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)?,
})
}
Expand Down
4 changes: 2 additions & 2 deletions oid4vc-manager/src/methods/key_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>> {
Expand Down Expand Up @@ -99,7 +99,7 @@ async fn resolve_public_key(kid: &str) -> Result<Vec<u8>> {
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())
Expand Down
4 changes: 2 additions & 2 deletions oid4vc-manager/src/servers/credential_issuer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ async fn credential<S: Storage<CFC>, CFC: CredentialFormatCollection>(
AuthBearer(access_token): AuthBearer,
Json(credential_request): Json<CredentialRequest<CFC>>,
) -> 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(
Expand Down Expand Up @@ -211,7 +211,7 @@ async fn batch_credential<S: Storage<CFC>, 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(
Expand Down
6 changes: 4 additions & 2 deletions oid4vc-manager/src/storage.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -15,7 +17,7 @@ pub trait Storage<CFC>: Send + Sync + 'static
where
CFC: CredentialFormatCollection,
{
fn get_credentials_supported(&self) -> Vec<CredentialsSupportedObject<CFC>>;
fn get_credential_configurations_supported(&self) -> HashMap<String, CredentialConfigurationsSupportedObject<CFC>>;
fn get_authorization_response(&self) -> Option<AuthorizationResponse>;
fn get_authorization_code(&self) -> Option<AuthorizationCode>;
fn get_pre_authorized_code(&self) -> Option<PreAuthorizedCode>;
Expand Down
Original file line number Diff line number Diff line change
@@ -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":{
Expand All @@ -14,9 +13,13 @@
"DriverLicenseCredential"
]
},
"proof_types_supported": [
"jwt"
],
"proof_types_supported": {
"jwt": {
"proof_signing_alg_values_supported": [
"ES256"
]
}
},
"display": [
{
"name": "Driver License Credential",
Expand Down
Original file line number Diff line number Diff line change
@@ -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":{
Expand Down Expand Up @@ -40,9 +39,13 @@
}
}
},
"proof_types_supported": [
"jwt"
],
"proof_types_supported": {
"jwt": {
"proof_signing_alg_values_supported": [
"ES256"
]
}
},
"display": [
{
"name": "University Credential",
Expand Down
40 changes: 25 additions & 15 deletions oid4vc-manager/tests/common/memory_storage.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::fs::File;
use std::{collections::HashMap, fs::File};

use jsonwebtoken::{Algorithm, Header};
use lazy_static::lazy_static;
use oid4vc_core::{authentication::subject::SigningSubject, generate_authorization_code, jwt};
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,
Expand All @@ -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();
}
Expand All @@ -33,17 +32,27 @@ lazy_static! {
pub struct MemoryStorage;

impl<CFC: CredentialFormatCollection + DeserializeOwned> Storage<CFC> for MemoryStorage {
fn get_credentials_supported(&self) -> Vec<CredentialsSupportedObject<CFC>> {
fn get_credential_configurations_supported(&self) -> HashMap<String, CredentialConfigurationsSupportedObject<CFC>> {
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<AuthorizationCode> {
Expand Down Expand Up @@ -112,7 +121,7 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Storage<CFC> 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(),
Expand All @@ -129,7 +138,8 @@ impl<CFC: CredentialFormatCollection + DeserializeOwned> Storage<CFC> for Memory
.ok(),
)
.unwrap(),
})),
notification_id: None,
},
c_nonce: Some(C_NONCE.clone()),
c_nonce_expires_in: Some(86400),
})
Expand Down
13 changes: 8 additions & 5 deletions oid4vc-manager/tests/oid4vci/authorization_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -63,8 +63,8 @@ async fn test_authorization_code_flow() {

// Get the credential format for a university degree.
let university_degree_credential_format: CredentialFormats<WithParameters> = credential_issuer_metadata
.credentials_supported
.get(0)
.credential_configurations_supported
.get("UniversityDegree_JWT")
.unwrap()
.clone()
.credential_format;
Expand All @@ -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()],
)
Expand Down Expand Up @@ -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."),
};

Expand Down
Loading

0 comments on commit 928d68d

Please sign in to comment.