-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
410 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
bindings/wasm/src/credential/jwt_credential_validation/kb_validation_options.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}"#; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.