Skip to content

Commit

Permalink
add 'SdJwtVcBuilder::from_credential' to easily convert into a SD-JWT VC
Browse files Browse the repository at this point in the history
  • Loading branch information
UMR1352 committed Dec 11, 2024
1 parent 03f1483 commit 51e1b28
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 2 deletions.
2 changes: 1 addition & 1 deletion identity_credential/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ anyhow = { version = "1" }
async-trait = { version = "0.1.64", default-features = false }
bls12_381_plus = { workspace = true, optional = true }
flate2 = { version = "1.0.28", default-features = false, features = ["rust_backend"], optional = true }
futures = { version = "0.3", default-features = false, optional = true }
futures = { version = "0.3", default-features = false, features = ["alloc"], optional = true }
identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false }
identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false }
identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion identity_credential/src/credential/jwt_serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl<'credential, T> CredentialJwtClaims<'credential, T>
where
T: ToOwned<Owned = T> + Serialize + DeserializeOwned,
{
pub(super) fn new(credential: &'credential Credential<T>, custom: Option<Object>) -> Result<Self> {
pub(crate) fn new(credential: &'credential Credential<T>, custom: Option<Object>) -> Result<Self> {
let Credential {
context,
id,
Expand Down
7 changes: 7 additions & 0 deletions identity_credential/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

//! Errors that may occur when working with Verifiable Credentials.
use crate::sd_jwt_vc;

/// Alias for a `Result` with the error type [`Error`].
pub type Result<T, E = Error> = ::core::result::Result<T, E>;

Expand Down Expand Up @@ -79,4 +81,9 @@ pub enum Error {
/// Cause by an invalid attribute path
#[error("Attribute Not found")]
SelectiveDisclosureError,

/// Failure of an SD-JWT VC operation.
#[cfg(feature = "sd-jwt-vc")]
#[error(transparent)]
SdJwtVc(#[from] sd_jwt_vc::Error),
}
56 changes: 56 additions & 0 deletions identity_credential/src/sd_jwt_vc/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::sync::LazyLock;
use identity_core::common::StringOrUrl;
use identity_core::common::Timestamp;
use identity_core::common::Url;
use identity_core::convert::ToJson;
use sd_jwt_payload_rework::Hasher;
use sd_jwt_payload_rework::JsonObject;
use sd_jwt_payload_rework::JwsSigner;
Expand All @@ -15,6 +16,10 @@ use sd_jwt_payload_rework::SdJwtBuilder;
use sd_jwt_payload_rework::Sha256Hasher;
use serde::Serialize;
use serde_json::json;
use serde_json::Value;

use crate::credential::Credential;
use crate::credential::CredentialJwtClaims;

use super::Error;
use super::Result;
Expand Down Expand Up @@ -101,6 +106,25 @@ impl<H: Hasher> SdJwtVcBuilder<H> {
})
}

/// Creates a new [`SdJwtVcBuilder`] starting from a [`Credential`] that is converted to a JWT claim set.
pub fn new_from_credential(credential: Credential, hasher: H) -> std::result::Result<Self, crate::Error> {
let mut vc_jwt_claims = CredentialJwtClaims::new(&credential, None)?
.to_json_value()
.map_err(|e| crate::Error::JwtClaimsSetSerializationError(Box::new(e)))?;
// When converting a VC to its JWT claims representation, some VC specific claims are putted into a `vc` object
// property. Flatten out `vc`, keeping the other JWT claims intact.
{
let claims = vc_jwt_claims.as_object_mut().expect("serialized VC is a JSON object");
let Value::Object(vc_properties) = claims.remove("vc").expect("serialized VC has `vc` property") else {
unreachable!("`vc` property's value is a JSON object");
};
for (key, value) in vc_properties {
claims.insert(key, value);
}
}
Ok(Self::new_with_hasher(vc_jwt_claims, hasher)?)
}

/// Substitutes a value with the digest of its disclosure.
///
/// ## Notes
Expand Down Expand Up @@ -244,7 +268,10 @@ impl<H: Hasher> SdJwtVcBuilder<H> {

#[cfg(test)]
mod tests {

use super::*;
use crate::credential::CredentialBuilder;
use crate::credential::Subject;
use crate::sd_jwt_vc::tests::TestSigner;

#[tokio::test]
Expand Down Expand Up @@ -327,4 +354,33 @@ mod tests {

Ok(())
}

#[tokio::test]
async fn building_sd_jwt_vc_from_credential_works() -> anyhow::Result<()> {
let credential = CredentialBuilder::default()
.id(Url::parse("https://example.com/credentials/42")?)
.issuance_date(Timestamp::now_utc())
.issuer(Url::parse("https://example.com/issuers/42")?)
.subject(Subject::with_id(Url::parse("https://example.com/subjects/42")?))
.build()?;

let sd_jwt_vc = SdJwtVcBuilder::new_from_credential(credential.clone(), Sha256Hasher::default())?

Check failure on line 367 in identity_credential/src/sd_jwt_vc/builder.rs

View workflow job for this annotation

GitHub Actions / clippy

use of `default` to create a unit struct

error: use of `default` to create a unit struct --> identity_credential/src/sd_jwt_vc/builder.rs:367:89 | 367 | let sd_jwt_vc = SdJwtVcBuilder::new_from_credential(credential.clone(), Sha256Hasher::default())? | ^^^^^^^^^^^ help: remove this call to `default` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#default_constructed_unit_structs = note: `-D clippy::default-constructed-unit-structs` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::default_constructed_unit_structs)]`
.vct(Url::parse("https://example.com/types/0")?)
.finish(&TestSigner, "HS256")
.await?;

assert_eq!(sd_jwt_vc.claims().nbf.as_ref().unwrap(), &credential.issuance_date);
assert_eq!(&sd_jwt_vc.claims().iss, credential.issuer.url());
assert_eq!(
sd_jwt_vc.claims().sub.as_ref().unwrap().as_url(),
credential.credential_subject.first().unwrap().id.as_ref()
);
assert_eq!(
sd_jwt_vc.claims().get("jti"),
Some(&json!(credential.id.as_ref().unwrap()))
);
assert_eq!(sd_jwt_vc.claims().get("type"), Some(&json!("VerifiableCredential")));

Ok(())
}
}
3 changes: 3 additions & 0 deletions identity_credential/src/sd_jwt_vc/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub enum StatusMechanism {
/// Reference to a status list containing this token's status.
#[serde(rename = "status_list")]
StatusList(StatusListRef),
/// A non-standard status mechanism.
#[serde(untagged)]
Custom(serde_json::Value),
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down

0 comments on commit 51e1b28

Please sign in to comment.