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 support for StatusList2021 #1273

Merged
merged 40 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
97ab614
StatusList2021
UMR1352 Dec 14, 2023
1ff7b62
StatusList2021Credential
UMR1352 Dec 18, 2023
5b56932
StatusList2021Entry
UMR1352 Dec 19, 2023
05107b9
Multiple CredentialStatuses
UMR1352 Dec 19, 2023
d9d07e9
conversions between statuses, fix test
UMR1352 Dec 20, 2023
3800ab4
StatusList2021Credential deserializable
UMR1352 Dec 20, 2023
a97bf6f
test revocation checking with statuslist2021
UMR1352 Dec 21, 2023
1282cb7
WASM bindings
UMR1352 Jan 8, 2024
71ca5fc
example
UMR1352 Jan 9, 2024
9c7b91b
example enhancement
UMR1352 Jan 11, 2024
57dd0d1
wasm bindings example
UMR1352 Jan 12, 2024
4dbfc67
License
UMR1352 Jan 16, 2024
914cf9f
docs
UMR1352 Jan 16, 2024
b5d5d6f
make clippy happy
UMR1352 Jan 16, 2024
a5b054e
clippy again
UMR1352 Jan 16, 2024
425774a
fmt
UMR1352 Jan 16, 2024
4b3ed99
fmt & clippy
UMR1352 Jan 16, 2024
19d4aad
license template
UMR1352 Jan 16, 2024
311bbb5
revocation feature gate
UMR1352 Jan 16, 2024
77f56fe
review comments
UMR1352 Jan 17, 2024
d961fef
fix bindings
UMR1352 Jan 17, 2024
7993eb3
dprint fmt
UMR1352 Jan 17, 2024
bf8b302
credential status refactor
UMR1352 Jan 18, 2024
7c67b67
clippy + fmt
UMR1352 Jan 18, 2024
676b3d0
better example
UMR1352 Jan 18, 2024
029d0f1
status list review comment
UMR1352 Jan 19, 2024
a3b5161
status list credential PR comments
UMR1352 Jan 19, 2024
467be81
Review comments
UMR1352 Jan 22, 2024
b8083b1
Fix WASM bindings
UMR1352 Jan 22, 2024
5d5635b
Merge branch 'main' into feat/status-list-2021
UMR1352 Jan 22, 2024
f28996b
Fix merge issue
UMR1352 Jan 22, 2024
292af3c
dprint fmt
UMR1352 Jan 22, 2024
2adef48
cargo clippy
UMR1352 Jan 22, 2024
18e2002
bindings tests
UMR1352 Jan 22, 2024
f076118
review comments
UMR1352 Jan 22, 2024
8b53ddb
wasm example
UMR1352 Jan 23, 2024
23283a8
Update bindings/wasm/examples/src/1_advanced/7_status_list_2021.ts
UMR1352 Jan 23, 2024
fd8f1c2
review comments
UMR1352 Jan 24, 2024
2852a53
clippy
UMR1352 Jan 24, 2024
f1e29b7
review comments
UMR1352 Jan 24, 2024
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
2 changes: 1 addition & 1 deletion bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ wasm-bindgen-futures = { version = "0.4", default-features = false }
version = "1.0.0"
path = "../../identity_iota"
default-features = false
features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt"]
features = ["client", "revocation-bitmap", "resolver", "domain-linkage", "sd-jwt", "status-list-2021"]

[dev-dependencies]
rand = "0.8.5"
Expand Down
11 changes: 11 additions & 0 deletions bindings/wasm/cypress/e2e/1_advanced/7_status_list_2021.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { statusList2021 } from "../../../examples/dist/web/1_advanced/7_status_list_2021";
import { setup } from "../../support/setup";

describe(
"statusList2021",
() => {
it("Status List 2021", async () => {
await setup(statusList2021);
});
},
);
690 changes: 545 additions & 145 deletions bindings/wasm/docs/api-reference.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions bindings/wasm/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ The following advanced examples are available:
| [4_custom_resolution](src/1_advanced/4_custom_resolution.ts) | Demonstrates how to set up a resolver using custom handlers. |
| [5_domain_linkage](src/1_advanced/5_domain_linkage.ts) | Demonstrates how to link a domain and a DID and verify the linkage. |
| [6_sd_jwt](src/1_advanced/6_sd_jwt.ts) | Demonstrates how to create a selective disclosure verifiable credential |
| [7_domain_linkage](src/1_advanced/7_status_list_2021.ts) | Demonstrates how to revoke a credential using `StatusList2021`. |

## Browser

Expand Down
169 changes: 169 additions & 0 deletions bindings/wasm/examples/src/1_advanced/7_status_list_2021.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import {
Credential,
EdDSAJwsVerifier,
FailFast,
JwkMemStore,
JwsSignatureOptions,
JwtCredentialValidationOptions,
JwtCredentialValidator,
KeyIdMemStore,
StatusCheck,
StatusList2021,
StatusList2021Credential,
StatusList2021CredentialBuilder,
StatusList2021Entry,
StatusPurpose,
Storage,
} from "@iota/identity-wasm/node";
import { Client, MnemonicSecretManager, Utils } from "@iota/sdk-wasm/node";
import { API_ENDPOINT, createDid } from "../util";

export async function statusList2021() {
UMR1352 marked this conversation as resolved.
Show resolved Hide resolved
// ===========================================================================
// Create a Verifiable Credential.
// ===========================================================================

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

// Generate a random mnemonic for the issuer.
const issuerSecretManager: MnemonicSecretManager = {
mnemonic: Utils.generateMnemonic(),
};

// Create an identity for the issuer with one verification method `key-1`.
const issuerStorage: Storage = new Storage(
new JwkMemStore(),
new KeyIdMemStore(),
);
let { document: issuerDocument, fragment: issuerFragment } = await createDid(
client,
issuerSecretManager,
issuerStorage,
);

// Generate a random mnemonic for Alice.
const aliceSecretManager: MnemonicSecretManager = {
mnemonic: Utils.generateMnemonic(),
};

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

// Create a new empty status list. No credentials have been revoked yet.
const statusList = new StatusList2021();

// Create a status list credential so that the status list can be stored anywhere.
// The issuer makes this credential available on `http://example.com/credential/status`.
// For the purposes of this example, the credential will be used directly without fetching.
const statusListCredential = new StatusList2021CredentialBuilder(statusList)
.purpose(StatusPurpose.Revocation)
.subjectId("http://example.com/credential/status")
.issuer(issuerDocument.id().toString())
.build();
const statusListCredentialJSON = statusListCredential.toJSON();
console.log("Status list credential > " + statusListCredential);

// Create a credential subject indicating the degree earned by Alice, linked to their DID.
const subject = {
id: aliceDocument.id(),
name: "Alice",
degreeName: "Bachelor of Science and Arts",
degreeType: "BachelorDegree",
GPA: "4.0",
};

// Create an unsigned `UniversityDegree` credential for Alice.
// The issuer also chooses a unique `StatusList2021` index to be able to revoke it later.
const CREDENTIAL_INDEX = 5;
const status = new StatusList2021Entry(statusListCredential.id(), statusListCredential.purpose(), CREDENTIAL_INDEX)
.toStatus();
const credential = new Credential({
id: "https://example.edu/credentials/3732",
type: "UniversityDegreeCredential",
credentialStatus: status,
issuer: issuerDocument.id(),
credentialSubject: subject,
});

// Create signed JWT credential.
const credentialJwt = await issuerDocument.createCredentialJwt(
issuerStorage,
issuerFragment,
credential,
new JwsSignatureOptions(),
);
console.log(`Credential JWT > ${credentialJwt.toString()}`);

// Validate the credential using the issuer's DID Document.
eike-hass marked this conversation as resolved.
Show resolved Hide resolved
const validationOptions = new JwtCredentialValidationOptions({ status: StatusCheck.SkipUnsupported });
// The validator has no way of retrieving the status list to check for the
// revocation of the credential. Let's skip that pass and perform the operation manually.
let jwtCredentialValidator = new JwtCredentialValidator(new EdDSAJwsVerifier());

try {
jwtCredentialValidator.validate(
credentialJwt,
issuerDocument,
validationOptions,
FailFast.FirstError,
);
// Check manually for revocation
JwtCredentialValidator.checkStatusWithStatusList2021(
credential,
statusListCredential,
StatusCheck.Strict,
);
} catch (e) {
// This line shouldn't be called as the credential is valid and unrevoked
console.log("Something went wrong: " + e);
}

// ===========================================================================
// Revocation of the Verifiable Credential.
// ===========================================================================

// At a later time, the issuer university found out that Alice cheated in her final exam.
// The issuer will revoke Alice's credential.

// The issuer retrieves the status list credential.
const refetchedStatusListCredential = new StatusList2021Credential(new Credential(statusListCredentialJSON as any));

// Update the status list credential.
// This revokes the credential's unique index.
refetchedStatusListCredential.setCredentialStatus(credential, CREDENTIAL_INDEX, true);

// Credential verification now fails.
try {
jwtCredentialValidator.validate(
credentialJwt,
issuerDocument,
validationOptions,
FailFast.FirstError,
);
/// Since the credential has been revoked, this validation step will throw an error.
JwtCredentialValidator.checkStatusWithStatusList2021(
credential,
refetchedStatusListCredential,
StatusCheck.Strict,
);
// In case the revocation failed for some reason we will hit this point
console.log("Revocation Failed!");
UMR1352 marked this conversation as resolved.
Show resolved Hide resolved
UMR1352 marked this conversation as resolved.
Show resolved Hide resolved
} catch (e) {
/// The credential has been revoked.
console.log("The credential has been successfully revoked.");
}
}
3 changes: 3 additions & 0 deletions bindings/wasm/examples/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ 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";
import { statusList2021 } from "./1_advanced/7_status_list_2021";

async function main() {
// Extract example name.
Expand Down Expand Up @@ -55,6 +56,8 @@ async function main() {
return await domainLinkage();
case "6_sd_jwt":
return await sdJwt();
case "7_status_list_2021":
return await statusList2021();
default:
throw "Unknown example name: '" + argument + "'";
}
Expand Down
8 changes: 8 additions & 0 deletions bindings/wasm/examples/src/tests/7_status_list_2021.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { statusList2021 } from "../1_advanced/7_status_list_2021";

// Only verifies that no uncaught exceptions are thrown, including syntax errors etc.
describe("Test node examples", function() {
it("StatusList2021", async () => {
await statusList2021();
});
});
1 change: 1 addition & 0 deletions bindings/wasm/src/credential/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::error::Result;
use crate::error::WasmResult;

#[wasm_bindgen(js_name = Credential, inspectable)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WasmCredential(pub(crate) Credential);

#[wasm_bindgen(js_class = Credential)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020-2023 IOTA Stiftung
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use identity_iota::core::Object;
Expand All @@ -13,6 +13,7 @@ use crate::common::ImportedDocumentLock;
use crate::common::ImportedDocumentReadGuard;
use crate::common::WasmTimestamp;
use crate::credential::options::WasmStatusCheck;
use crate::credential::revocation::status_list_2021::WasmStatusList2021Credential;
use crate::credential::WasmCredential;
use crate::credential::WasmDecodedJwtCredential;
use crate::credential::WasmFailFast;
Expand Down Expand Up @@ -170,6 +171,21 @@ impl WasmJwtCredentialValidator {
JwtCredentialValidatorUtils::check_status(&credential.0, &trusted_issuers, status_check).wasm_result()
}

/// Checks wheter the credential status has been revoked using `StatusList2021`.
#[wasm_bindgen(js_name = checkStatusWithStatusList2021)]
pub fn check_status_with_status_list_2021(
credential: &WasmCredential,
status_list: &WasmStatusList2021Credential,
status_check: WasmStatusCheck,
) -> Result<()> {
JwtCredentialValidatorUtils::check_status_with_status_list_2021(
&credential.0,
&status_list.inner,
status_check.into(),
)
.wasm_result()
}

/// Utility for extracting the issuer field of a {@link Credential} as a DID.
///
/// ### Errors
Expand Down
2 changes: 2 additions & 0 deletions bindings/wasm/src/credential/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub use self::options::WasmFailFast;
pub use self::options::WasmSubjectHolderRelationship;
pub use self::presentation::*;
pub use self::proof::WasmProof;
pub use self::revocation::*;
pub use self::types::*;

mod credential;
Expand All @@ -29,4 +30,5 @@ mod linked_domain_service;
mod options;
mod presentation;
mod proof;
mod revocation;
mod types;
4 changes: 4 additions & 0 deletions bindings/wasm/src/credential/revocation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

pub mod status_list_2021;
Loading
Loading