Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add VCDM 2.0 SD-JWT profile #24

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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