Skip to content

Commit

Permalink
SD-JWT implementation improvements (#612)
Browse files Browse the repository at this point in the history
  • Loading branch information
timothee-haudebourg authored Sep 17, 2024
1 parent 099293a commit b830db0
Show file tree
Hide file tree
Showing 68 changed files with 4,104 additions and 1,807 deletions.
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,18 @@ with JSON Web Signatures (or Tokens) and Verifiable Credentials.

The simplest type of claim to load and verify is probably JSON Web
Signatures (JWSs), often use to encode JSON Web Tokens (JWTs). To represent
such claims SSI provides the `CompactJWSString` type representing a JWS
in compact textual form. One can load a JWS using `from_string` and verify
it using `verify`.
such claims SSI provides the `JwsBuf` type representing a JWS
in compact textual form. One can load a JWS using [`new`] and verify
it using [`verify`].

[`new`]: claims::JwsBuf::new
[`verify`]: claims::JwsSlice::verify

```rust
use ssi::prelude::*;

// Load a JWT from the file system.
let jwt = CompactJWSString::from_string(
let jwt = JwsBuf::new(
std::fs::read_to_string("examples/files/claims.jwt")
.expect("unable to load JWT")
).expect("invalid JWS");
Expand All @@ -78,10 +80,11 @@ Verifiable Credential are much more complex as they require interpreting
the input claims and proofs, such as Data-Integrity proofs as Linked-Data
using JSON-LD. This operation is highly configurable. SSI provide
functions exposing various levels of implementation details that you can
tweak as needed. The simplest of them is `any_credential_from_json_str`
tweak as needed. The simplest of them is [`any_credential_from_json_str`]
that will simply load a VC from a string, assuming it is signed using
any Data-Integrity proof supported by SSI.

[`any_credential_from_json_str`]: claims::vc::v1::data_integrity::any_credential_from_json_str

```rust
use ssi::prelude::*;
Expand Down Expand Up @@ -155,10 +158,11 @@ println!("{jwt}")
#### Verifiable Credential

We can use a similar technique to sign a VC with custom claims.
The `SpecializedJsonCredential` type provides a customizable
The [`SpecializedJsonCredential`] type provides a customizable
implementation of the VC data-model 1.1 where you can set the credential type
yourself.

[`SpecializedJsonCredential`]: claims::vc::v1::SpecializedJsonCredential

```rust
use static_iref::uri;
Expand Down Expand Up @@ -217,16 +221,19 @@ It is critical that custom claims can be interpreted as Linked-Data. In
the above example this is done by specifying a serialization URL for each
field of `MyCredentialSubject`. This can also be done by creating a custom
JSON-LD context and embed it to `credential` using either
`SpecializedJsonCredential`'s `context` field or leveraging its context type
[`SpecializedJsonCredential`]'s [`context`] field or leveraging its context type
parameter.

[`context`]: claims::vc::v1::SpecializedJsonCredential::context

## Data-Models

The examples above are using the VC data-model 1.1, but you ssi also has support for:
- `VC data-model 2.0`
- `A wrapper type to accept both`
- [`VC data-model 2.0`]
- [`A wrapper type to accept both`]

[`VC data-model 2.0`]: claims::vc::v2
[`A wrapper type to accept both`]: claims::vc::syntax::AnySpecializedJsonCredential

## Features

Expand Down
4 changes: 4 additions & 0 deletions crates/claims/core/src/verification/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ pub enum ProofValidationError {
}

impl ProofValidationError {
pub fn input_data(e: impl ToString) -> Self {
Self::InvalidInputData(e.to_string())
}

pub fn other(e: impl ToString) -> Self {
Self::Other(e.to_string())
}
Expand Down
71 changes: 42 additions & 29 deletions crates/claims/crates/data-integrity/core/src/signing/jws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{borrow::Cow, marker::PhantomData};
use ssi_claims_core::{ProofValidationError, SignatureError};
use ssi_crypto::algorithm::{SignatureAlgorithmInstance, SignatureAlgorithmType};
use ssi_jwk::{Algorithm, JWK};
use ssi_jws::{CompactJWSString, JWSSignature, JWS};
use ssi_jws::{DecodedJws, JwsSignature, JwsString};
use ssi_verification_methods::{MessageSigner, VerifyBytes, VerifyBytesWithRecoveryJwk};

use crate::{
Expand All @@ -24,13 +24,13 @@ use super::AlgorithmSelection;
linked_data::Deserialize,
)]
#[ld(prefix("sec" = "https://w3id.org/security#"))]
pub struct JwsSignature {
pub struct DetachedJwsSignature {
#[ld("sec:jws")]
pub jws: CompactJWSString,
pub jws: JwsString,
}

impl JwsSignature {
pub fn new(jws: CompactJWSString) -> Self {
impl DetachedJwsSignature {
pub fn new(jws: JwsString) -> Self {
Self { jws }
}

Expand All @@ -40,15 +40,20 @@ impl JwsSignature {
pub fn decode(
&self,
message: &[u8],
) -> Result<(Vec<u8>, JWSSignature, Algorithm), ProofValidationError> {
let JWS {
header, signature, ..
) -> Result<(Vec<u8>, JwsSignature, Algorithm), ProofValidationError> {
let DecodedJws {
signing_bytes: detached_signing_bytes,
signature,
} = self
.jws
.decode()
.map_err(|_| ProofValidationError::InvalidSignature)?;
let signing_bytes = header.encode_signing_bytes(message);
Ok((signing_bytes, signature, header.algorithm))
let signing_bytes = detached_signing_bytes.header.encode_signing_bytes(message);
Ok((
signing_bytes,
signature,
detached_signing_bytes.header.algorithm,
))
}

pub async fn sign_detached<A: SignatureAlgorithmType + Into<Algorithm>, S: MessageSigner<A>>(
Expand All @@ -60,27 +65,27 @@ impl JwsSignature {
let header = ssi_jws::Header::new_unencoded(algorithm_instance.algorithm().into(), key_id);
let signing_bytes = header.encode_signing_bytes(payload);
let signature = signer.sign(algorithm_instance, &signing_bytes).await?;
let jws = ssi_jws::CompactJWSString::encode_detached(header, &signature);
Ok(JwsSignature::new(jws))
let jws = ssi_jws::JwsString::encode_detached(header, &signature);
Ok(Self::new(jws))
}
}

impl AsRef<str> for JwsSignature {
impl AsRef<str> for DetachedJwsSignature {
fn as_ref(&self) -> &str {
self.jws.as_str()
}
}

impl super::AlterSignature for JwsSignature {
impl super::AlterSignature for DetachedJwsSignature {
fn alter(&mut self) {
self.jws = CompactJWSString::from_string(format!("ff{}", self.jws)).unwrap();
self.jws = JwsString::from_string(format!("ff{}", self.jws)).unwrap();
}
}

pub struct DetachedJwsSigning<A>(PhantomData<A>);

impl<A> SignatureAndVerificationAlgorithm for DetachedJwsSigning<A> {
type Signature = JwsSignature;
type Signature = DetachedJwsSignature;
}

impl<A, S, T> SignatureAlgorithm<S, T> for DetachedJwsSigning<A>
Expand All @@ -98,7 +103,7 @@ where
prepared_claims: S::PreparedClaims,
proof_configuration: ProofConfigurationRef<'_, S>,
) -> Result<Self::Signature, SignatureError> {
JwsSignature::sign_detached(
DetachedJwsSignature::sign_detached(
prepared_claims.as_ref(),
signer,
None,
Expand All @@ -110,7 +115,7 @@ where

impl<A, S> VerificationAlgorithm<S> for DetachedJwsSigning<A>
where
S: CryptographicSuite<Signature = JwsSignature>,
S: CryptographicSuite<Signature = DetachedJwsSignature>,
S::PreparedClaims: AsRef<[u8]>,
S::VerificationMethod: VerifyBytes<A>,
A: TryFrom<Algorithm>,
Expand All @@ -120,17 +125,21 @@ where
prepared_claims: S::PreparedClaims,
proof: ProofRef<S>,
) -> Result<ssi_claims_core::ProofValidity, ProofValidationError> {
let JWS {
header, signature, ..
let DecodedJws {
signing_bytes: detached_signing_bytes,
signature,
} = proof
.signature
.jws
.decode()
.map_err(|_| ProofValidationError::InvalidSignature)?;

let signing_bytes = header.encode_signing_bytes(prepared_claims.as_ref());
let signing_bytes = detached_signing_bytes
.header
.encode_signing_bytes(prepared_claims.as_ref());

let algorithm = header
let algorithm = detached_signing_bytes
.header
.algorithm
.try_into()
.map_err(|_| ProofValidationError::InvalidSignature)?;
Expand All @@ -142,7 +151,7 @@ where
pub struct DetachedJwsRecoverySigning<A>(PhantomData<A>);

impl<A> SignatureAndVerificationAlgorithm for DetachedJwsRecoverySigning<A> {
type Signature = JwsSignature;
type Signature = DetachedJwsSignature;
}

impl<A, S, T> SignatureAlgorithm<S, T> for DetachedJwsRecoverySigning<A>
Expand All @@ -159,7 +168,7 @@ where
prepared_claims: S::PreparedClaims,
proof_configuration: ProofConfigurationRef<'_, S>,
) -> Result<Self::Signature, SignatureError> {
JwsSignature::sign_detached(
DetachedJwsSignature::sign_detached(
prepared_claims.as_ref(),
signer,
proof_configuration.options.public_jwk().key_id.clone(),
Expand All @@ -171,7 +180,7 @@ where

impl<A, S> VerificationAlgorithm<S> for DetachedJwsRecoverySigning<A>
where
S: CryptographicSuite<Signature = JwsSignature>,
S: CryptographicSuite<Signature = DetachedJwsSignature>,
S::PreparedClaims: AsRef<[u8]>,
S::ProofOptions: RecoverPublicJwk,
S::VerificationMethod: VerifyBytesWithRecoveryJwk<A>,
Expand All @@ -182,17 +191,21 @@ where
prepared_claims: S::PreparedClaims,
proof: ProofRef<S>,
) -> Result<ssi_claims_core::ProofValidity, ProofValidationError> {
let JWS {
header, signature, ..
let DecodedJws {
signing_bytes: detached_signing_bytes,
signature,
} = proof
.signature
.jws
.decode()
.map_err(|_| ProofValidationError::InvalidSignature)?;

let signing_bytes = header.encode_signing_bytes(prepared_claims.as_ref());
let signing_bytes = detached_signing_bytes
.header
.encode_signing_bytes(prepared_claims.as_ref());

let found_algorithm = header
let found_algorithm = detached_signing_bytes
.header
.algorithm
.try_into()
.map_err(|_| ProofValidationError::InvalidSignature)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ repository = "https://github.com/spruceid/ssi/"
documentation = "https://docs.rs/ssi-di-sd-primitives/"

[dependencies]
ssi-core.workspace = true
ssi-rdf.workspace = true
ssi-json-ld.workspace = true
linked-data.workspace = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ use std::{

use linked_data::IntoQuadsError;
use rdf_types::{BlankIdBuf, LexicalQuad};
use ssi_core::JsonPointerBuf;
use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdObject};
use ssi_rdf::{urdna2015::NormalizingSubstitution, LexicalInterpretation};

use crate::{
canonicalize::label_replacement_canonicalize_nquads,
select::{select_canonical_nquads, SelectError},
skolemize::{expanded_to_deskolemized_nquads, SkolemError, Skolemize},
JsonPointerBuf,
};

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -126,10 +126,11 @@ mod tests {

use hmac::{Hmac, Mac};
use lazy_static::lazy_static;
use ssi_core::JsonPointerBuf;
use ssi_json_ld::CompactJsonLd;
use ssi_rdf::IntoNQuads;

use crate::{canonicalize::create_hmac_id_label_map_function, HmacShaAny, JsonPointerBuf};
use crate::{canonicalize::create_hmac_id_label_map_function, HmacShaAny};

use super::canonicalize_and_group;

Expand Down
5 changes: 2 additions & 3 deletions crates/claims/crates/data-integrity/sd-primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use hmac::Mac;
use serde::{Deserialize, Serialize};
use sha2::{Sha256, Sha384};

pub use ssi_core::{JsonPointer, JsonPointerBuf};

pub type HmacSha256 = Hmac<Sha256>;
pub type HmacSha384 = Hmac<Sha384>;

Expand Down Expand Up @@ -228,8 +230,5 @@ impl<'de> Deserialize<'de> for HmacShaAnyKey {

pub mod canonicalize;
pub mod group;
pub mod json_pointer;
pub mod select;
pub mod skolemize;

pub use json_pointer::{JsonPointer, JsonPointerBuf};
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::collections::{BTreeMap, HashMap};

use rdf_types::{BlankId, BlankIdBuf, LexicalQuad};
use ssi_core::{JsonPointer, JsonPointerBuf};
use ssi_json_ld::syntax::Value;

use crate::{
canonicalize::relabel_quads,
skolemize::{compact_to_deskolemized_nquads, SkolemError},
JsonPointer, JsonPointerBuf,
};

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -276,7 +276,7 @@ impl Select for ssi_json_ld::syntax::Object {
) -> Result<(), DanglingJsonPointer> {
match pointer.split_first() {
Some((token, rest)) => {
let key = token.to_str();
let key = token.to_decoded();
let a_item = self.get(key.as_ref()).next().ok_or(DanglingJsonPointer)?;
let b_item =
selection.get_mut_or_insert_with(&key, || create_initial_selection(a_item));
Expand Down
2 changes: 1 addition & 1 deletion crates/claims/crates/data-integrity/src/any/sd.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use serde::{Deserialize, Serialize};
use ssi_claims_core::ResolverProvider;
use ssi_core::JsonPointerBuf;
use ssi_data_integrity_core::{
suite::{CryptographicSuiteSelect, SelectionError, SelectiveCryptographicSuite},
DataIntegrity, ProofRef,
};
use ssi_di_sd_primitives::JsonPointerBuf;
use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdLoaderProvider, JsonLdNodeObject};
use ssi_rdf::LexicalInterpretation;
use ssi_verification_methods::{AnyMethod, VerificationMethodResolver};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use serde::Deserialize;
use ssi_di_sd_primitives::{HmacShaAnyKey, JsonPointerBuf};
use ssi_core::JsonPointerBuf;
use ssi_di_sd_primitives::HmacShaAnyKey;
use ssi_verification_methods::multikey::MultikeyPair;

#[derive(Debug, Default, Deserialize)]
Expand Down
3 changes: 1 addition & 2 deletions crates/claims/crates/data-integrity/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
pub use ssi_core::{JsonPointer, JsonPointerBuf};
pub use ssi_data_integrity_core::*;
pub use ssi_data_integrity_suites as suites;

pub use ssi_di_sd_primitives::{JsonPointer, JsonPointerBuf};

mod any;
pub use any::*;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use ssi_core::JsonPointerBuf;
use ssi_data_integrity_core::{
suite::{ConfigurationError, InputSignatureOptions, InputVerificationOptions},
ProofConfiguration, ProofOptions,
};
use ssi_di_sd_primitives::{HmacShaAnyKey, JsonPointerBuf};
use ssi_di_sd_primitives::HmacShaAnyKey;
use ssi_verification_methods::{multikey::MultikeyPair, Multikey};

use crate::EcdsaSd2023;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use std::{borrow::Cow, collections::HashMap};

use rdf_types::{BlankIdBuf, Quad};
use serde::Serialize;
use ssi_core::JsonPointerBuf;
use ssi_data_integrity_core::{DataIntegrity, Proof, ProofRef};
use ssi_di_sd_primitives::{
canonicalize::create_hmac_id_label_map_function,
group::{canonicalize_and_group, GroupError},
select::{select_json_ld, DanglingJsonPointer},
JsonPointerBuf,
};
use ssi_json_ld::{Expandable, ExpandedDocument, JsonLdNodeObject};
use ssi_multicodec::MultiEncodedBuf;
Expand Down
Loading

0 comments on commit b830db0

Please sign in to comment.