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 dedicated EdDSA verifier crate #1238

Merged
merged 11 commits into from
Sep 22, 2023
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"identity_resolver",
"identity_verification",
"identity_jose",
"identity_eddsa_verifier",
"examples",
]

Expand Down
3 changes: 2 additions & 1 deletion bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ crate-type = ["cdylib", "rlib"]
async-trait = { version = "0.1", default-features = false }
console_error_panic_hook = { version = "0.1" }
futures = { version = "0.3" }
identity_eddsa_verifier = { version = "0.7.0-alpha.7", path = "../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] }
js-sys = { version = "0.3.61" }
proc_typescript = { version = "0.1.0", path = "./proc_typescript" }
serde = { version = "1.0", features = ["derive"] }
Expand All @@ -33,7 +34,7 @@ wasm-bindgen-futures = { version = "0.4", default-features = false }
version = "0.7.0-alpha.7"
path = "../../identity_iota"
default-features = false
features = ["client", "revocation-bitmap", "resolver", "eddsa", "domain-linkage"]
features = ["client", "revocation-bitmap", "resolver", "domain-linkage"]

[dev-dependencies]
rand = "0.8.5"
Expand Down
3 changes: 2 additions & 1 deletion bindings/wasm/examples/src/0_basic/5_create_vc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import {
Credential,
EdDSAJwsVerifier,
FailFast,
JwkMemStore,
JwsSignatureOptions,
Expand Down Expand Up @@ -79,7 +80,7 @@ export async function createVC() {
// Validate the credential's signature, the credential's semantic structure,
// check that the issuance date is not in the future and that the expiration date is not in the past.
// Note that the validation returns an object containing the decoded credential.
const decoded_credential = new JwtCredentialValidator().validate(
const decoded_credential = new JwtCredentialValidator(new EdDSAJwsVerifier()).validate(
credentialJwt,
issuerDocument,
new JwtCredentialValidationOptions(),
Expand Down
7 changes: 4 additions & 3 deletions bindings/wasm/examples/src/0_basic/6_create_vp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
CoreDID,
Credential,
Duration,
EdDSAJwsVerifier,
FailFast,
IotaIdentityClient,
JwkMemStore,
Expand Down Expand Up @@ -97,7 +98,7 @@ export async function createVP() {
new JwsSignatureOptions(),
);

const res = new JwtCredentialValidator().validate(
const res = new JwtCredentialValidator(new EdDSAJwsVerifier()).validate(
credentialJwt,
issuerDocument,
new JwtCredentialValidationOptions(),
Expand Down Expand Up @@ -178,14 +179,14 @@ export async function createVP() {
);

// Validate presentation. Note that this doesn't validate the included credentials.
let decodedPresentation = new JwtPresentationValidator().validate(
let decodedPresentation = new JwtPresentationValidator(new EdDSAJwsVerifier()).validate(
presentationJwt,
resolvedHolder,
jwtPresentationValidationOptions,
);

// Validate the credentials in the presentation.
let credentialValidator = new JwtCredentialValidator();
let credentialValidator = new JwtCredentialValidator(new EdDSAJwsVerifier());
let validationOptions = new JwtCredentialValidationOptions({
subjectHolderRelationship: [
presentationHolderDID.toString(),
Expand Down
20 changes: 19 additions & 1 deletion bindings/wasm/examples/src/0_basic/7_revoke_vc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@

import {
Credential,
EdCurve,
FailFast,
IJwsVerifier,
IotaDocument,
IotaIdentityClient,
Jwk,
JwkMemStore,
JwsAlgorithm,
JwsSignatureOptions,
JwtCredentialValidationOptions,
JwtCredentialValidator,
Expand All @@ -16,6 +20,7 @@ import {
Service,
Storage,
VerificationMethod,
verifyEd25519,
} from "@iota/identity-wasm/node";
import { AliasOutput, Client, IRent, MnemonicSecretManager, Utils } from "@iota/sdk-wasm/node";
import { API_ENDPOINT, createDid } from "../util";
Expand Down Expand Up @@ -134,7 +139,7 @@ export async function revokeVC() {
console.log(`Credential JWT > ${credentialJwt.toString()}`);

// Validate the credential using the issuer's DID Document.
let jwtCredentialValidator = new JwtCredentialValidator();
let jwtCredentialValidator = new JwtCredentialValidator(new Ed25519JwsVerifier());
jwtCredentialValidator.validate(
credentialJwt,
issuerDocument,
Expand Down Expand Up @@ -225,3 +230,16 @@ export async function revokeVC() {
console.log(`Credential successfully revoked!`);
}
}

// A custom JWS Verifier capabale of verifying EdDSA signatures with curve Ed25519.
class Ed25519JwsVerifier implements IJwsVerifier {
verify(alg: JwsAlgorithm, signingInput: Uint8Array, decodedSignature: Uint8Array, publicKey: Jwk) {
switch (alg) {
case JwsAlgorithm.EdDSA:
// This verifies that the curve is Ed25519 so we don't need to check ourselves.
return verifyEd25519(alg, signingInput, decodedSignature, publicKey);
default:
throw new Error(`unsupported jws algorithm ${alg}`);
}
}
}
5 changes: 3 additions & 2 deletions bindings/wasm/examples/src/1_advanced/5_domain_linkage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DIDUrl,
DomainLinkageConfiguration,
Duration,
EdDSAJwsVerifier,
IotaDID,
IotaDocument,
IotaIdentityClient,
Expand Down Expand Up @@ -123,7 +124,7 @@ export async function domainLinkage() {

// Validate the linkage between the Domain Linkage Credential in the configuration and the provided issuer DID.
// Validation succeeds when no error is thrown.
new JwtDomainLinkageValidator().validateLinkage(
new JwtDomainLinkageValidator(new EdDSAJwsVerifier()).validateLinkage(
issuerDocument,
fetchedConfigurationResource,
domainFoo,
Expand Down Expand Up @@ -157,7 +158,7 @@ export async function domainLinkage() {

// Validate the linkage between the Domain Linkage Credential in the configuration and the provided issuer DID.
// Validation succeeds when no error is thrown.
new JwtDomainLinkageValidator().validateLinkage(
new JwtDomainLinkageValidator(new EdDSAJwsVerifier()).validateLinkage(
didDocument,
fetchedConfigurationResource,
domains[0],
Expand Down
2 changes: 1 addition & 1 deletion bindings/wasm/src/credential/domain_linkage_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl WasmJwtDomainLinkageValidator {
/// algorithm will be used.
#[wasm_bindgen(constructor)]
#[allow(non_snake_case)]
pub fn new(signatureVerifier: Option<IJwsVerifier>) -> WasmJwtDomainLinkageValidator {
pub fn new(signatureVerifier: IJwsVerifier) -> WasmJwtDomainLinkageValidator {
let signature_verifier = WasmJwsVerifier::new(signatureVerifier);
WasmJwtDomainLinkageValidator {
validator: JwtDomainLinkageValidator::with_signature_verifier(signature_verifier),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use identity_iota::core::Object;
use identity_iota::core::Url;
use identity_iota::credential::JwtCredentialValidator;
use identity_iota::credential::JwtCredentialValidatorUtils;
use identity_iota::credential::StatusCheck;
use identity_iota::did::CoreDID;

Expand Down Expand Up @@ -39,7 +40,7 @@ impl WasmJwtCredentialValidator {
/// algorithm will be used.
#[wasm_bindgen(constructor)]
#[allow(non_snake_case)]
pub fn new(signatureVerifier: Option<IJwsVerifier>) -> WasmJwtCredentialValidator {
pub fn new(signatureVerifier: IJwsVerifier) -> WasmJwtCredentialValidator {
let signature_verifier = WasmJwsVerifier::new(signatureVerifier);
WasmJwtCredentialValidator(JwtCredentialValidator::with_signature_verifier(signature_verifier))
}
Expand Down Expand Up @@ -123,13 +124,13 @@ impl WasmJwtCredentialValidator {
/// Validate that the credential expires on or after the specified timestamp.
#[wasm_bindgen(js_name = checkExpiresOnOrAfter)]
pub fn check_expires_on_or_after(credential: &WasmCredential, timestamp: &WasmTimestamp) -> Result<()> {
JwtCredentialValidator::check_expires_on_or_after(&credential.0, timestamp.0).wasm_result()
JwtCredentialValidatorUtils::check_expires_on_or_after(&credential.0, timestamp.0).wasm_result()
}

/// Validate that the credential is issued on or before the specified timestamp.
#[wasm_bindgen(js_name = checkIssuedOnOrBefore)]
pub fn check_issued_on_or_before(credential: &WasmCredential, timestamp: &WasmTimestamp) -> Result<()> {
JwtCredentialValidator::check_issued_on_or_before(&credential.0, timestamp.0).wasm_result()
JwtCredentialValidatorUtils::check_issued_on_or_before(&credential.0, timestamp.0).wasm_result()
}

/// Validate that the relationship between the `holder` and the credential subjects is in accordance with
Expand All @@ -141,7 +142,8 @@ impl WasmJwtCredentialValidator {
relationship: WasmSubjectHolderRelationship,
) -> Result<()> {
let holder: Url = Url::parse(holder).wasm_result()?;
JwtCredentialValidator::check_subject_holder_relationship(&credential.0, &holder, relationship.into()).wasm_result()
JwtCredentialValidatorUtils::check_subject_holder_relationship(&credential.0, &holder, relationship.into())
.wasm_result()
}

/// Checks whether the credential status has been revoked.
Expand All @@ -158,7 +160,7 @@ impl WasmJwtCredentialValidator {
let trusted_issuers: Vec<ImportedDocumentReadGuard<'_>> =
issuer_locks.iter().map(ImportedDocumentLock::blocking_read).collect();
let status_check: StatusCheck = statusCheck.into();
JwtCredentialValidator::check_status(&credential.0, &trusted_issuers, status_check).wasm_result()
JwtCredentialValidatorUtils::check_status(&credential.0, &trusted_issuers, status_check).wasm_result()
}

/// Utility for extracting the issuer field of a {@link Credential} as a DID.
Expand All @@ -168,7 +170,7 @@ impl WasmJwtCredentialValidator {
/// Fails if the issuer field is not a valid DID.
#[wasm_bindgen(js_name = extractIssuer)]
pub fn extract_issuer(credential: &WasmCredential) -> Result<WasmCoreDID> {
JwtCredentialValidator::extract_issuer::<CoreDID, Object>(&credential.0)
JwtCredentialValidatorUtils::extract_issuer::<CoreDID, Object>(&credential.0)
.map(WasmCoreDID::from)
.wasm_result()
}
Expand All @@ -180,7 +182,7 @@ impl WasmJwtCredentialValidator {
/// If the JWT decoding fails or the issuer field is not a valid DID.
#[wasm_bindgen(js_name = extractIssuerFromJwt)]
pub fn extract_issuer_from_jwt(credential: &WasmJwt) -> Result<WasmCoreDID> {
JwtCredentialValidator::extract_issuer_from_jwt::<CoreDID>(&credential.0)
JwtCredentialValidatorUtils::extract_issuer_from_jwt::<CoreDID>(&credential.0)
.map(WasmCoreDID::from)
.wasm_result()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::error::WasmResult;
use crate::verification::IJwsVerifier;
use crate::verification::WasmJwsVerifier;
use identity_iota::credential::JwtPresentationValidator;
use identity_iota::credential::JwtPresentationValidatorUtils;
use identity_iota::did::CoreDID;
use wasm_bindgen::prelude::*;

Expand All @@ -26,7 +27,7 @@ impl WasmJwtPresentationValidator {
/// algorithm will be used.
#[wasm_bindgen(constructor)]
#[allow(non_snake_case)]
pub fn new(signatureVerifier: Option<IJwsVerifier>) -> WasmJwtPresentationValidator {
pub fn new(signatureVerifier: IJwsVerifier) -> WasmJwtPresentationValidator {
let signature_verifier = WasmJwsVerifier::new(signatureVerifier);
WasmJwtPresentationValidator(JwtPresentationValidator::with_signature_verifier(signature_verifier))
}
Expand Down Expand Up @@ -76,7 +77,7 @@ impl WasmJwtPresentationValidator {
/// Validates the semantic structure of the {@link Presentation}.
#[wasm_bindgen(js_name = checkStructure)]
pub fn check_structure(presentation: &WasmPresentation) -> Result<()> {
JwtPresentationValidator::check_structure(&presentation.0).wasm_result()?;
JwtPresentationValidatorUtils::check_structure(&presentation.0).wasm_result()?;
Ok(())
}

Expand All @@ -87,7 +88,7 @@ impl WasmJwtPresentationValidator {
/// * If the holder can't be parsed as DIDs.
#[wasm_bindgen(js_name = extractHolder)]
pub fn extract_holder(presentation: &WasmJwt) -> Result<WasmCoreDID> {
let holder = JwtPresentationValidator::extract_holder::<CoreDID>(&presentation.0).wasm_result()?;
let holder = JwtPresentationValidatorUtils::extract_holder::<CoreDID>(&presentation.0).wasm_result()?;
Ok(WasmCoreDID(holder))
}
}
2 changes: 1 addition & 1 deletion bindings/wasm/src/did/wasm_core_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ impl WasmCoreDocument {
&self,
jws: &WasmJws,
options: &WasmJwsVerificationOptions,
signatureVerifier: Option<IJwsVerifier>,
signatureVerifier: IJwsVerifier,
detachedPayload: Option<String>,
) -> Result<WasmDecodedJws> {
let jws_verifier = WasmJwsVerifier::new(signatureVerifier);
Expand Down
2 changes: 1 addition & 1 deletion bindings/wasm/src/iota/iota_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ impl WasmIotaDocument {
&self,
jws: &WasmJws,
options: &WasmJwsVerificationOptions,
signatureVerifier: Option<IJwsVerifier>,
signatureVerifier: IJwsVerifier,
detachedPayload: Option<String>,
) -> Result<WasmDecodedJws> {
let jws_verifier = WasmJwsVerifier::new(signatureVerifier);
Expand Down
70 changes: 18 additions & 52 deletions bindings/wasm/src/verification/custom_verification.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use identity_iota::verification::jws::EdDSAJwsVerifier;
use identity_iota::verification::jws::JwsAlgorithm;
use identity_iota::verification::jws::JwsVerifier;
use identity_iota::verification::jws::SignatureVerificationError;
use identity_iota::verification::jws::SignatureVerificationErrorKind;
use identity_iota::verification::jws::VerificationInput;
use wasm_bindgen::prelude::*;

use crate::error::WasmResult;
use crate::jose::WasmJwk;
use crate::jose::WasmJwsAlgorithm;

/// Wrapper that enables custom TS JWS signature verification plugins to be used where the
/// JwsVerifier trait is required. Falls back to the default implementation if a custom
/// implementation was not passed.
pub(crate) struct WasmJwsVerifier(Option<IJwsVerifier>);
pub(crate) struct WasmJwsVerifier(IJwsVerifier);

impl WasmJwsVerifier {
pub(crate) fn new(verifier: Option<IJwsVerifier>) -> Self {
pub(crate) fn new(verifier: IJwsVerifier) -> Self {
Self(verifier)
}
}
Expand All @@ -30,26 +26,22 @@ impl JwsVerifier for WasmJwsVerifier {
input: identity_iota::verification::jws::VerificationInput,
public_key: &identity_iota::verification::jwk::Jwk,
) -> Result<(), identity_iota::verification::jws::SignatureVerificationError> {
match &self.0 {
None => EdDSAJwsVerifier::default().verify(input, public_key),
Some(custom) => {
let VerificationInput {
alg,
signing_input,
decoded_signature,
} = input;
let verification_result = custom.verify(
alg.name().to_owned(),
signing_input.into(),
decoded_signature.into(),
WasmJwk(public_key.to_owned()),
);
// Convert error
crate::error::stringify_js_error(verification_result).map_err(|error_string| {
SignatureVerificationError::new(SignatureVerificationErrorKind::Unspecified).with_custom_message(error_string)
})
}
}
let VerificationInput {
alg,
signing_input,
decoded_signature,
} = input;
let verification_result = IJwsVerifier::verify(
&self.0,
alg.name().to_owned(),
signing_input.into(),
decoded_signature.into(),
WasmJwk(public_key.to_owned()),
);
// Convert error
crate::error::stringify_js_error(verification_result).map_err(|error_string| {
SignatureVerificationError::new(SignatureVerificationErrorKind::Unspecified).with_custom_message(error_string)
})
}
}
#[wasm_bindgen(typescript_custom_section)]
Expand Down Expand Up @@ -86,29 +78,3 @@ extern "C" {
publicKey: WasmJwk,
) -> Result<(), JsValue>;
}

/// Verify a JWS signature secured with the `JwsAlgorithm::EdDSA` algorithm.
/// Only the `EdCurve::Ed25519` variant is supported for now.
///
/// This function is useful when one is building an `IJwsVerifier` that extends the default provided by
/// the IOTA Identity Framework.
///
/// # Warning
/// This function does not check whether `alg = EdDSA` in the protected header. Callers are expected to assert this
/// prior to calling the function.
#[wasm_bindgen(js_name = verifyEdDSA)]
#[allow(non_snake_case)]
pub fn verify_eddsa(
alg: WasmJwsAlgorithm,
signingInput: &[u8],
decodedSignature: &[u8],
publicKey: &WasmJwk,
) -> Result<(), JsValue> {
let alg: JwsAlgorithm = JwsAlgorithm::try_from(alg)?;
let input: VerificationInput = VerificationInput {
alg,
signing_input: Box::from(signingInput),
decoded_signature: Box::from(decodedSignature),
};
identity_iota::verification::jose::jws::EdDSAJwsVerifier::verify_eddsa(input, &publicKey.0).wasm_result()
}
Loading
Loading