Skip to content

Commit

Permalink
wasm bindings for validator
Browse files Browse the repository at this point in the history
  • Loading branch information
abdulmth committed Jan 15, 2024
1 parent 68622e7 commit 1106490
Show file tree
Hide file tree
Showing 17 changed files with 410 additions and 35 deletions.
200 changes: 200 additions & 0 deletions bindings/wasm/examples/src/1_advanced/6_sd_jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import {
Credential,
DecodedJwtCredential,
EdDSAJwsVerifier,
FailFast,
JwkMemStore,
JwsSignatureOptions,
JwsVerificationOptions,
JwtCredentialValidationOptions,
KeyBindingJwtClaims,
KeyBindingJWTValidationOptions,
KeyIdMemStore,
SdJwt,
SdJwtCredentialValidator,
SdObjectEncoder,
Storage,
Timestamp,
} from "@iota/identity-wasm/node";
import { Client, MnemonicSecretManager, Utils } from "@iota/sdk-wasm/node";
import { API_ENDPOINT, createDid } from "../util";

/**
* Demonstrates how to create a selective disclosure verifiable credential and validate it
* using the standard [Selective Disclosure for JWTs (SD-JWT)](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html).
*/
export async function sdJwt() {
// ===========================================================================
// Step 1: Create identities for the issuer and the holder.
// ===========================================================================

const client = new Client({
primaryNode: API_ENDPOINT,
localPow: true,
});

// Creates a new wallet and identity (see "0_create_did" example).
const issuerSecretManager: MnemonicSecretManager = {
mnemonic: Utils.generateMnemonic(),
};
const issuerStorage: Storage = new Storage(
new JwkMemStore(),
new KeyIdMemStore(),
);
let { document: issuerDocument, fragment: issuerFragment } = await createDid(
client,
issuerSecretManager,
issuerStorage,
);

// Create an identity for the holder, in this case also the subject.
const aliceSecretManager: MnemonicSecretManager = {
mnemonic: Utils.generateMnemonic(),
};
const aliceStorage: Storage = new Storage(
new JwkMemStore(),
new KeyIdMemStore(),
);
let { document: aliceDocument, fragment: aliceFragment } = await createDid(
client,
aliceSecretManager,
aliceStorage,
);

// ===========================================================================
// Step 2: Issuer creates and signs a selectively disclosable JWT verifiable credential.
// ===========================================================================

// Create an address credential subject.
const subject = {
id: aliceDocument.id(),
name: "Alice",
address: {
locality: "Maxstadt",
postal_code: "12344",
country: "DE",
street_address: "Weidenstraße 22"
}
};

// Build credential using subject above and issuer.
const credential = new Credential({
id: "https://example.com/credentials/3732",
type: "AddressCredential",
issuer: issuerDocument.id(),
credentialSubject: subject,
});


// In Order to create an selective disclosure JWT, the plain text JWT
// claims set must be created first.
let payload = credential.toJwtClaims();

// Using the crate `sd-jwt` properties of the claims can be made selectively disclosable.
// The default sha-256 hasher will be used to create the digests.
// Read more in https://github.com/iotaledger/sd-jwt-payload .
let encoder = new SdObjectEncoder(payload);

// Make "locality", "postal_code" and "street_address" selectively disclosable while keeping
// other properties in plain text.
let disclosures = [
encoder.conceal(["vc", "credentialSubject", "address", "locality"]),
encoder.conceal(["vc", "credentialSubject", "address", "postal_code"]),
encoder.conceal(["vc", "credentialSubject", "address", "street_address"]),
];
encoder.addSdAlgProperty();
const encodedPayload = encoder.encodeToString();

// Create the signed JWT.
let jws = await issuerDocument.createJws(issuerStorage, issuerFragment, encodedPayload, new JwsSignatureOptions());

// ===========================================================================
// Step 3: Issuer sends the JWT and the disclosures to the holder.
// ===========================================================================

// One way to send the JWT and the disclosures, is by creating an SD-JWT with all the
// disclosures.
const strDisclosures = disclosures.map(disclosure => disclosure.toEncodedString());

let sdJwt = new SdJwt(jws.toString(), strDisclosures, undefined).presentation();

console.log(sdJwt)

// ===========================================================================
// Step 4: Verifier sends the holder a challenge and requests a signed Verifiable Presentation.
// ===========================================================================
const VERIFIER_DID = "did:example:verifier";
// A unique random challenge generated by the requester per presentation can mitigate replay attacks.
let nonce = "475a7984-1bb5-4c4c-a56f-822bccd46440";

// ===========================================================================
// Step 5: Holder creates an SD-JWT to be presented to a verifier.
// ===========================================================================

const sdJwtReceived = SdJwt.parse(sdJwt);

// The holder only wants to present "locality" and "postal_code" but not "street_address".
const toBeDisclosed = [
strDisclosures[0],
strDisclosures[1]
]

// Optionally, the holder can add a Key Binding JWT (KB-JWT). This is dependent on the verifier's policy.
// Issuing the KB-JWT is done by creating the claims set and setting the header `typ` value
// with the help of `KeyBindingJwtClaims`.
const bindingClaims = new KeyBindingJwtClaims(
sdJwtReceived.jwt(),
toBeDisclosed,
nonce,
VERIFIER_DID,
Timestamp.nowUTC(),
);

// Setting the `typ` in the header is required.
const options = new JwsSignatureOptions({
typ: KeyBindingJwtClaims.keyBindingJwtHeaderTyp()
})
const KbJwt = await aliceDocument.createJws(aliceStorage, aliceFragment, bindingClaims.toString(), options);

// Create the final SD-JWT.
let finalSdJwt = new SdJwt(sdJwtReceived.jwt().toString(), toBeDisclosed, KbJwt.toString());

// ===========================================================================
// Step 6: Holder presents the SD-JWT to the verifier.
// ===========================================================================

let sdJwtPresentation = finalSdJwt.presentation();

// ===========================================================================
// Step 7: Verifier receives the SD-JWT and verifies it.
// ===========================================================================

const sdJwtObj = SdJwt.parse(sdJwtPresentation);

// Verify the JWT.
let validator = new SdJwtCredentialValidator(new EdDSAJwsVerifier());
let decodedCredential: DecodedJwtCredential = validator.validateCredential(
sdJwtObj,
issuerDocument,
new JwtCredentialValidationOptions(),
FailFast.FirstError
);

console.log("JWT successfully validated")
console.log("credentialjwt validation", decodedCredential.credential());

// Verify the Key Binding JWT.
let kbValidationOptions = new KeyBindingJWTValidationOptions(
{
aud: VERIFIER_DID,
nonce: nonce,
jwsOptions: new JwsVerificationOptions()

});
validator.validateKeyBindingJwt(sdJwtObj, aliceDocument, kbValidationOptions);

console.log("Key Binding JWT successfully validated");
}
3 changes: 3 additions & 0 deletions bindings/wasm/examples/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { nftOwnsDid } from "./1_advanced/2_nft_owns_did";
import { didIssuesTokens } from "./1_advanced/3_did_issues_tokens";
import { customResolution } from "./1_advanced/4_custom_resolution";
import { domainLinkage } from "./1_advanced/5_domain_linkage";
import { sdJwt } from "./1_advanced/6_sd_jwt";

async function main() {
// Extract example name.
Expand Down Expand Up @@ -52,6 +53,8 @@ async function main() {
return await customResolution();
case "5_domain_linkage":
return await domainLinkage();
case "6_sd_jwt":
return await sdJwt();
default:
throw "Unknown example name: '" + argument + "'";
}
Expand Down
23 changes: 23 additions & 0 deletions bindings/wasm/src/credential/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ use identity_iota::core::Object;
use identity_iota::credential::Credential;
use identity_iota::credential::CredentialBuilder;
use identity_iota::credential::DomainLinkageCredentialBuilder;
use serde_json::Value;
use std::collections::BTreeMap;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

use crate::common::ArrayString;
use crate::common::MapStringAny;
use crate::common::RecordStringAny;
use crate::common::WasmTimestamp;
use crate::credential::domain_linkage_credential_builder::IDomainLinkageCredential;
use crate::credential::ArrayContext;
Expand Down Expand Up @@ -215,6 +218,26 @@ impl WasmCredential {
pub fn set_proof(&mut self, proof: Option<WasmProof>) {
self.0.set_proof(proof.map(|wasm_proof| wasm_proof.0))
}

/// Serializes the `Credential` as a JWT claims set
/// in accordance with [VC Data Model v1.1](https://www.w3.org/TR/vc-data-model/#json-web-token).
///
/// The resulting object can be used as the payload of a JWS when issuing the credential.
#[wasm_bindgen(js_name = "toJwtClaims")]
pub fn to_jwt_claims(&self, custom_claims: Option<RecordStringAny>) -> Result<RecordStringAny> {
let serialized: String = if let Some(object) = custom_claims {
let object: BTreeMap<String, Value> = object.into_serde().wasm_result()?;
self.0.serialize_jwt(Some(object)).wasm_result()?
} else {
self.0.serialize_jwt(None).wasm_result()?
};
let serialized: BTreeMap<String, Value> = serde_json::from_str(&serialized).wasm_result()?;
Ok(
JsValue::from_serde(&serialized)
.wasm_result()?
.unchecked_into::<RecordStringAny>(),
)
}
}

impl_wasm_json!(WasmCredential, Credential);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use crate::error::Result;
use crate::error::WasmResult;
use identity_iota::credential::KeyBindingJWTValidationOptions;
use wasm_bindgen::prelude::*;

/// Options to declare validation criteria when validating credentials.
#[wasm_bindgen(js_name = KeyBindingJWTValidationOptions)]
pub struct WasmKeyBindingJWTValidationOptions(pub(crate) KeyBindingJWTValidationOptions);

#[wasm_bindgen(js_class = KeyBindingJWTValidationOptions)]
impl WasmKeyBindingJWTValidationOptions {
#[wasm_bindgen(constructor)]
pub fn new(options: Option<IKeyBindingJWTValidationOptions>) -> Result<WasmKeyBindingJWTValidationOptions> {
if let Some(opts) = options {
let options: KeyBindingJWTValidationOptions = opts.into_serde().wasm_result()?;
Ok(WasmKeyBindingJWTValidationOptions::from(options))
} else {
Ok(WasmKeyBindingJWTValidationOptions::from(
KeyBindingJWTValidationOptions::default(),
))
}
}
}

impl_wasm_json!(WasmKeyBindingJWTValidationOptions, KeyBindingJWTValidationOptions);
impl_wasm_clone!(WasmKeyBindingJWTValidationOptions, KeyBindingJWTValidationOptions);

impl From<KeyBindingJWTValidationOptions> for WasmKeyBindingJWTValidationOptions {
fn from(options: KeyBindingJWTValidationOptions) -> Self {
Self(options)
}
}

impl From<WasmKeyBindingJWTValidationOptions> for KeyBindingJWTValidationOptions {
fn from(options: WasmKeyBindingJWTValidationOptions) -> Self {
options.0
}
}

// Interface to allow creating `KeyBindingJWTValidationOptions` easily.
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "IKeyBindingJWTValidationOptions")]
pub type IKeyBindingJWTValidationOptions;
}

#[wasm_bindgen(typescript_custom_section)]
const I_KEY_BINDING_JWT_VALIDATION_OPTIONS: &'static str = r#"
/** Holds options to create a new `KeyBindingJWTValidationOptions`. */
interface IKeyBindingJWTValidationOptions {
/**
* Validates the nonce value of the KB-JWT claims.
*/
readonly nonce?: string;
/**
* Validates the `aud` properties in the KB-JWT claims.
*/
readonly aud?: string;
/**
* Options which affect the verification of the signature on the KB-JWT.
*/
readonly jwsOptions: JwsVerificationOptions;
/**
* Declares that the KB-JWT is considered invalid if the `iat` value in the claims
* is earlier than this timestamp.
*/
readonly earliestIssuanceDate?: Timestamp;
/**
* Declares that the KB-JWT is considered invalid if the `iat` value in the claims is
* later than this timestamp.
*
* Uses the current timestamp during validation if not set.
*/
readonly latestIssuanceDate?: Timestamp;
}"#;
2 changes: 2 additions & 0 deletions bindings/wasm/src/credential/jwt_credential_validation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

mod decoded_jwt_credential;
mod jwt_credential_validator;
mod kb_validation_options;
mod options;
mod sd_jwt_validator;
mod unknown_credential;

pub use self::decoded_jwt_credential::*;
pub use self::jwt_credential_validator::*;
pub use self::kb_validation_options::*;
pub use self::options::*;
pub use self::sd_jwt_validator::*;
pub use self::unknown_credential::*;
Loading

0 comments on commit 1106490

Please sign in to comment.