Skip to content

Commit

Permalink
Add VCDM 2.0 SD-JWT profile (#24)
Browse files Browse the repository at this point in the history
* Add VCDM SD-JWT profile
  • Loading branch information
rschulman authored Nov 21, 2024
1 parent d95fe3a commit a802dfe
Show file tree
Hide file tree
Showing 10 changed files with 586 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,9 @@ where
{
#[serde(flatten, bound = "CR: CredentialResponseProfile")]
response_kind: ResponseEnum<CR>,
#[serde(skip_serializing_if = "Option::is_none")]
c_nonce: Option<Nonce>,
#[serde(skip_serializing_if = "Option::is_none")]
c_nonce_expires_in: Option<i64>,
}

Expand Down Expand Up @@ -416,7 +418,9 @@ where
{
#[serde(bound = "CR: CredentialResponseProfile")]
credential_responses: Vec<ResponseEnum<CR>>,
#[serde(skip_serializing_if = "Option::is_none")]
c_nonce: Option<Nonce>,
#[serde(skip_serializing_if = "Option::is_none")]
c_nonce_expires_in: Option<i64>,
}

Expand Down
37 changes: 37 additions & 0 deletions src/custom/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
pub mod profiles;

pub mod metadata {
use crate::metadata;

use super::profiles::CustomProfilesCredentialConfiguration;

pub type CredentialIssuerMetadata =
metadata::CredentialIssuerMetadata<CustomProfilesCredentialConfiguration>;
}

pub mod credential {
use crate::credential;

use super::profiles::CustomProfilesCredentialRequest;

pub type Request = credential::Request<CustomProfilesCredentialRequest>;
pub type BatchRequest = credential::BatchRequest<CustomProfilesCredentialRequest>;
}

pub mod authorization {
use crate::authorization;

use super::profiles::CustomProfilesAuthorizationDetailsObject;

pub type AuthorizationDetailsObject =
authorization::AuthorizationDetailsObject<CustomProfilesAuthorizationDetailsObject>;
}

pub mod client {

use crate::client;

use super::profiles::CustomProfiles;

pub type Client = client::Client<CustomProfiles>;
}
185 changes: 185 additions & 0 deletions src/custom/profiles/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use std::{collections::HashMap, fmt::Debug};

use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{
profiles::{
AuthorizationDetailsObjectProfile, CredentialConfigurationProfile,
CredentialRequestProfile, CredentialResponseProfile, Profile,
},
types::{ClaimValueType, CredentialConfigurationId, LanguageTag},
};

pub mod vc_sd_jwt;

pub struct CustomProfiles;
impl Profile for CustomProfiles {
type CredentialConfiguration = CustomProfilesCredentialConfiguration;
type AuthorizationDetailsObject = CustomProfilesAuthorizationDetailsObject;
type CredentialRequest = CustomProfilesCredentialRequest;
type CredentialResponse = CustomProfilesCredentialResponse;
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum CustomProfilesCredentialConfiguration {
VcSdJwt(vc_sd_jwt::CredentialConfiguration),
}

impl CredentialConfigurationProfile for CustomProfilesCredentialConfiguration {}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum CustomProfilesAuthorizationDetailsObject {
WithFormat {
#[serde(flatten)]
inner: AuthorizationDetailsObjectWithFormat,
#[serde(
default,
skip_serializing,
deserialize_with = "crate::deny_field::deny_field",
rename = "credential_identifier"
)]
_credential_identifier: (),
},
WithIdAndUnresolvedProfile {
credential_configuration_id: CredentialConfigurationId,
#[serde(flatten)]
inner: HashMap<String, Value>,
#[serde(
default,
skip_serializing,
deserialize_with = "crate::deny_field::deny_field",
rename = "format"
)]
_format: (),
},
#[serde(skip_deserializing)]
WithId {
credential_configuration_id: CredentialConfigurationId,
#[serde(flatten)]
inner: AuthorizationDetailsObjectWithCredentialConfigurationId,
#[serde(
default,
skip_serializing,
deserialize_with = "crate::deny_field::deny_field",
rename = "format"
)]
_format: (),
},
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum AuthorizationDetailsObjectWithFormat {
VcSdJwt(vc_sd_jwt::AuthorizationDetailsObjectWithFormat),
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum AuthorizationDetailsObjectWithCredentialConfigurationId {
VcSdJwt(vc_sd_jwt::AuthorizationDetailsObject),
}

impl AuthorizationDetailsObjectProfile for CustomProfilesAuthorizationDetailsObject {}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum CustomProfilesCredentialRequest {
WithFormat {
#[serde(flatten)]
inner: CredentialRequestWithFormat,
#[serde(
default,
skip_serializing,
deserialize_with = "crate::deny_field::deny_field",
rename = "credential_identifier"
)]
_credential_identifier: (),
},
WithIdAndUnresolvedProfile {
credential_identifier: CredentialConfigurationId,
#[serde(flatten)]
inner: HashMap<String, Value>,
#[serde(
default,
skip_serializing,
deserialize_with = "crate::deny_field::deny_field",
rename = "format"
)]
_format: (),
},
#[serde(skip_deserializing)]
WithId {
credential_identifier: CredentialConfigurationId,
#[serde(flatten)]
inner: CredentialRequestWithCredentialIdentifier,
#[serde(
default,
skip_serializing,
deserialize_with = "crate::deny_field::deny_field",
rename = "format"
)]
_format: (),
},
}

impl CredentialRequestProfile for CustomProfilesCredentialRequest {
type Response = CustomProfilesCredentialResponse;
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum CredentialRequestWithFormat {
VcSdJwt(vc_sd_jwt::CredentialRequestWithFormat),
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum CredentialRequestWithCredentialIdentifier {
VcSdJwt(vc_sd_jwt::CredentialRequest),
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CustomProfilesCredentialResponse;

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum CustomProfilesCredentialResponseType {
VcSdJwt(<vc_sd_jwt::CredentialResponse as CredentialResponseProfile>::Type),
}

impl CredentialResponseProfile for CustomProfilesCredentialResponse {
type Type = CustomProfilesCredentialResponseType;
}

#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct AuthorizationDetailsObjectClaim {
#[serde(default, skip_serializing_if = "is_false")]
mandatory: bool,
}

#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct CredentialConfigurationClaim {
#[serde(default, skip_serializing_if = "is_false")]
mandatory: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
value_type: Option<ClaimValueType>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
display: Vec<ClaimDisplay>,
}

fn is_false(b: &bool) -> bool {
!b
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ClaimDisplay {
#[serde(default, skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
locale: Option<LanguageTag>,
#[serde(flatten)]
additional_fields: HashMap<String, Value>,
}
71 changes: 71 additions & 0 deletions src/custom/profiles/vc_sd_jwt/authorization_detail.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use serde::{Deserialize, Serialize};

use crate::{
core::profiles::CredentialConfigurationClaim, profiles::AuthorizationDetailsObjectProfile,
};

use super::{Claims, Format};

#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize)]
pub struct AuthorizationDetailsObjectWithFormat {
format: Format,
vct: String,
#[serde(skip_serializing_if = "Option::is_none")]
claims: Option<Claims<CredentialConfigurationClaim>>,
}

impl AuthorizationDetailsObjectWithFormat {
field_getters_setters![
pub self [self] ["VC SD-JWT authorization detail value"] {
set_vct -> vct[String],
set_claims -> claims[Option<Claims<CredentialConfigurationClaim>>],
}
];
}

impl AuthorizationDetailsObjectProfile for AuthorizationDetailsObjectWithFormat {}

#[derive(Clone, Debug, Deserialize, Default, PartialEq, Serialize)]
pub struct AuthorizationDetailsObject {
vct: String,
claims: Option<Claims<CredentialConfigurationClaim>>,
}

impl AuthorizationDetailsObject {
field_getters_setters![
pub self [self] ["VC SD-JWT authorization detail value"] {
set_vct -> vct[String],
set_claims -> claims[Option<Claims<CredentialConfigurationClaim>>],
}
];
}

impl AuthorizationDetailsObjectProfile for AuthorizationDetailsObject {}

#[cfg(test)]
mod test {
use serde_json::json;

use crate::authorization::AuthorizationDetailsObject;

#[test]
fn roundtrip_with_format() {
let expected_json = json!(
{
"type": "openid_credential",
"format": "spruce-vc+sd-jwt",
"vct": "SD_JWT_VC_example_in_OpenID4VCI"
}
);

let authorization_detail: AuthorizationDetailsObject<
super::AuthorizationDetailsObjectWithFormat,
> = serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_str(
&serde_json::to_string(&expected_json).unwrap(),
))
.unwrap();

let roundtripped = serde_json::to_value(authorization_detail).unwrap();
assert_json_diff::assert_json_eq!(expected_json, roundtripped)
}
}
Loading

0 comments on commit a802dfe

Please sign in to comment.