Skip to content

Commit

Permalink
Add WASM bindings for EcDSA JWS Verifier (#1396)
Browse files Browse the repository at this point in the history
* wasm bindings for ecdsa verifier

* make signature verifier optional, defaulting to a compound verifier

* update comments on new default wasm jws verifier

* cargo fmt

* dprint fmt
  • Loading branch information
UMR1352 authored Sep 5, 2024
1 parent deecc7e commit ba36609
Showing 10 changed files with 260 additions and 159 deletions.
1 change: 1 addition & 0 deletions bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ async-trait = { version = "0.1", default-features = false }
bls12_381_plus = "0.8.17"
console_error_panic_hook = { version = "0.1" }
futures = { version = "0.3" }
identity_ecdsa_verifier = { path = "../../identity_ecdsa_verifier", default-features = false, features = ["es256", "es256k"] }
identity_eddsa_verifier = { path = "../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] }
js-sys = { version = "0.3.61" }
json-proof-token = "0.3.4"
288 changes: 167 additions & 121 deletions bindings/wasm/docs/api-reference.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions bindings/wasm/src/credential/domain_linkage_validator.rs
Original file line number Diff line number Diff line change
@@ -24,11 +24,11 @@ pub struct WasmJwtDomainLinkageValidator {
#[wasm_bindgen(js_class = JwtDomainLinkageValidator)]
impl WasmJwtDomainLinkageValidator {
/// Creates a new {@link JwtDomainLinkageValidator}. If a `signatureVerifier` is provided it will be used when
/// verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA`
/// algorithm will be used.
/// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K`
/// algorithms will be used.
#[wasm_bindgen(constructor)]
#[allow(non_snake_case)]
pub fn new(signatureVerifier: IJwsVerifier) -> WasmJwtDomainLinkageValidator {
pub fn new(signatureVerifier: Option<IJwsVerifier>) -> WasmJwtDomainLinkageValidator {
let signature_verifier = WasmJwsVerifier::new(signatureVerifier);
WasmJwtDomainLinkageValidator {
validator: JwtDomainLinkageValidator::with_signature_verifier(signature_verifier),
Original file line number Diff line number Diff line change
@@ -37,11 +37,11 @@ pub struct WasmJwtCredentialValidator(JwtCredentialValidator<WasmJwsVerifier>);
#[wasm_bindgen(js_class = JwtCredentialValidator)]
impl WasmJwtCredentialValidator {
/// Creates a new {@link JwtCredentialValidator}. If a `signatureVerifier` is provided it will be used when
/// verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA`
/// algorithm will be used.
/// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K`
/// algorithms will be used.
#[wasm_bindgen(constructor)]
#[allow(non_snake_case)]
pub fn new(signatureVerifier: IJwsVerifier) -> WasmJwtCredentialValidator {
pub fn new(signatureVerifier: Option<IJwsVerifier>) -> WasmJwtCredentialValidator {
let signature_verifier = WasmJwsVerifier::new(signatureVerifier);
WasmJwtCredentialValidator(JwtCredentialValidator::with_signature_verifier(signature_verifier))
}
Original file line number Diff line number Diff line change
@@ -29,11 +29,11 @@ pub struct WasmSdJwtCredentialValidator(SdJwtCredentialValidator<WasmJwsVerifier
#[wasm_bindgen(js_class = SdJwtCredentialValidator)]
impl WasmSdJwtCredentialValidator {
/// Creates a new `SdJwtCredentialValidator`. If a `signatureVerifier` is provided it will be used when
/// verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA`
/// algorithm will be used.
/// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K`
/// algorithms will be used.
#[wasm_bindgen(constructor)]
#[allow(non_snake_case)]
pub fn new(signatureVerifier: IJwsVerifier) -> WasmSdJwtCredentialValidator {
pub fn new(signatureVerifier: Option<IJwsVerifier>) -> WasmSdJwtCredentialValidator {
let signature_verifier = WasmJwsVerifier::new(signatureVerifier);
WasmSdJwtCredentialValidator(SdJwtCredentialValidator::with_signature_verifier(
signature_verifier,
Original file line number Diff line number Diff line change
@@ -23,11 +23,11 @@ pub struct WasmJwtPresentationValidator(JwtPresentationValidator<WasmJwsVerifier
#[wasm_bindgen(js_class = JwtPresentationValidator)]
impl WasmJwtPresentationValidator {
/// Creates a new {@link JwtPresentationValidator}. If a `signatureVerifier` is provided it will be used when
/// verifying decoded JWS signatures, otherwise the default which is only capable of handling the `EdDSA`
/// algorithm will be used.
/// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K`
/// algorithms will be used.
#[wasm_bindgen(constructor)]
#[allow(non_snake_case)]
pub fn new(signatureVerifier: IJwsVerifier) -> WasmJwtPresentationValidator {
pub fn new(signatureVerifier: Option<IJwsVerifier>) -> WasmJwtPresentationValidator {
let signature_verifier = WasmJwsVerifier::new(signatureVerifier);
WasmJwtPresentationValidator(JwtPresentationValidator::with_signature_verifier(signature_verifier))
}
7 changes: 4 additions & 3 deletions bindings/wasm/src/did/wasm_core_document.rs
Original file line number Diff line number Diff line change
@@ -495,8 +495,9 @@ impl WasmCoreDocument {
// ===========================================================================

/// Decodes and verifies the provided JWS according to the passed `options` and `signatureVerifier`.
/// If no `signatureVerifier` argument is provided a default verifier will be used that is (only) capable of
/// verifying EdDSA signatures.
/// If a `signatureVerifier` is provided it will be used when
/// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K`
/// algorithms will be used.
///
/// Regardless of which options are passed the following conditions must be met in order for a verification attempt to
/// take place.
@@ -509,7 +510,7 @@ impl WasmCoreDocument {
&self,
jws: &WasmJws,
options: &WasmJwsVerificationOptions,
signatureVerifier: IJwsVerifier,
signatureVerifier: Option<IJwsVerifier>,
detachedPayload: Option<String>,
) -> Result<WasmDecodedJws> {
let jws_verifier = WasmJwsVerifier::new(signatureVerifier);
7 changes: 4 additions & 3 deletions bindings/wasm/src/iota/iota_document.rs
Original file line number Diff line number Diff line change
@@ -384,8 +384,9 @@ impl WasmIotaDocument {
// ===========================================================================

/// Decodes and verifies the provided JWS according to the passed `options` and `signatureVerifier`.
/// If no `signatureVerifier` argument is provided a default verifier will be used that is (only) capable of
/// verifying EdDSA signatures.
/// If a `signatureVerifier` is provided it will be used when
/// verifying decoded JWS signatures, otherwise a default verifier capable of handling the `EdDSA`, `ES256`, `ES256K`
/// algorithms will be used.
///
/// Regardless of which options are passed the following conditions must be met in order for a verification attempt to
/// take place.
@@ -397,7 +398,7 @@ impl WasmIotaDocument {
&self,
jws: &WasmJws,
options: &WasmJwsVerificationOptions,
signatureVerifier: IJwsVerifier,
signatureVerifier: Option<IJwsVerifier>,
detachedPayload: Option<String>,
) -> Result<WasmDecodedJws> {
let jws_verifier = WasmJwsVerifier::new(signatureVerifier);
51 changes: 31 additions & 20 deletions bindings/wasm/src/verification/custom_verification.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use identity_ecdsa_verifier::EcDSAJwsVerifier;
use identity_eddsa_verifier::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;
@@ -10,12 +13,12 @@ use wasm_bindgen::prelude::*;
use crate::jose::WasmJwk;

/// 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(IJwsVerifier);
/// JwsVerifier trait is required. Falls back to the default implementation capable of handling
/// EdDSA (ED25519), ES256, ES256K if a custom implementation is not passed.
pub(crate) struct WasmJwsVerifier(Option<IJwsVerifier>);

impl WasmJwsVerifier {
pub(crate) fn new(verifier: IJwsVerifier) -> Self {
pub(crate) fn new(verifier: Option<IJwsVerifier>) -> Self {
Self(verifier)
}
}
@@ -26,22 +29,30 @@ impl JwsVerifier for WasmJwsVerifier {
input: identity_iota::verification::jws::VerificationInput,
public_key: &identity_iota::verification::jwk::Jwk,
) -> Result<(), identity_iota::verification::jws::SignatureVerificationError> {
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)
})
if let Some(verifier) = &self.0 {
let VerificationInput {
alg,
signing_input,
decoded_signature,
} = input;
let verification_result = IJwsVerifier::verify(
verifier,
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)
})
} else {
match input.alg {
JwsAlgorithm::EdDSA => EdDSAJwsVerifier::default().verify(input, public_key),
JwsAlgorithm::ES256 | JwsAlgorithm::ES256K => EcDSAJwsVerifier::default().verify(input, public_key),
_ => Err(identity_iota::verification::jws::SignatureVerificationErrorKind::UnsupportedAlg.into()),
}
}
}
}
#[wasm_bindgen(typescript_custom_section)]
41 changes: 41 additions & 0 deletions bindings/wasm/src/verification/jws_verifier.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use identity_ecdsa_verifier::EcDSAJwsVerifier;
use identity_eddsa_verifier::Ed25519Verifier;
use identity_eddsa_verifier::EdDSAJwsVerifier;
use identity_iota::verification::jws::JwsAlgorithm;
@@ -80,3 +81,43 @@ impl WasmEdDSAJwsVerifier {
EdDSAJwsVerifier::default().verify(input, &publicKey.0).wasm_result()
}
}

/// An implementor of `IJwsVerifier` that can handle the
/// `EcDSA` algorithm.
#[wasm_bindgen(js_name = EcDSAJwsVerifier)]
pub struct WasmEcDSAJwsVerifier();

#[wasm_bindgen(js_class = EcDSAJwsVerifier)]
#[allow(clippy::new_without_default)]
impl WasmEcDSAJwsVerifier {
/// Constructs an EcDSAJwsVerifier.
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self()
}

/// Verify a JWS signature secured with the `EcDSA` algorithm.
/// Only the `ES256` and `ES256K` curves are supported for now.
///
/// # Warning
///
/// This function does not check the `alg` property in the protected header. Callers are expected to assert this
/// prior to calling the function.
#[wasm_bindgen]
#[allow(non_snake_case)]
pub fn verify(
&self,
alg: WasmJwsAlgorithm,
signingInput: &[u8],
decodedSignature: &[u8],
publicKey: &WasmJwk,
) -> Result<(), JsValue> {
let alg: JwsAlgorithm = JwsAlgorithm::try_from(alg)?;
let input: VerificationInput = VerificationInput {
alg,
signing_input: signingInput.into(),
decoded_signature: decodedSignature.into(),
};
EcDSAJwsVerifier::default().verify(input, &publicKey.0).wasm_result()
}
}

0 comments on commit ba36609

Please sign in to comment.