diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d3fa510e..ab1b36f03b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [v1.4.0](https://github.com/iotaledger/identity.rs/tree/v1.4.0) (2024-09-23) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.3.1...v1.4.0) + +### Added + +- Add feature to support custom `now_utc` implementations [\#1397](https://github.com/iotaledger/identity.rs/pull/1397) +- Add support for `did:jwk` resolution [\#1404](https://github.com/iotaledger/identity.rs/pull/1404) +- Linked Verifiable Presentations [\#1398](https://github.com/iotaledger/identity.rs/pull/1398) +- Add support for custom JWS algorithms [\#1410](https://github.com/iotaledger/identity.rs/pull/1410) + +### Patch + +- Make `bls12_381_plus` dependency more flexible again [\#1393](https://github.com/iotaledger/identity.rs/pull/1393) +- Mark `js-sys` as optional for identity_core [\#1405](https://github.com/iotaledger/identity.rs/pull/1405) +- Remove dependency on `identity_core` default features [\#1408](https://github.com/iotaledger/identity.rs/pull/1408) + ## [v1.3.1](https://github.com/iotaledger/identity.rs/tree/v1.3.1) (2024-06-12) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.3.0...v1.3.1) @@ -8,8 +25,6 @@ - Pin and bump `bls12_381_plus` dependency [\#1378](https://github.com/iotaledger/identity.rs/pull/1378) -# Changelog - ## [v1.3.0](https://github.com/iotaledger/identity.rs/tree/v1.3.0) (2024-05-28) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/v1.2.0...v1.3.0) diff --git a/README.md b/README.md index e8001e6788..773cae64f9 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,9 @@ --- +> [!NOTE] +> This version of the library is compatible with IOTA Stardust networks, for a version of the library compatible with IOTA Rebased networks check [here](https://github.com/iotaledger/identity.rs/tree/feat/identity-rebased-alpha/) + ## Introduction IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. @@ -54,7 +57,7 @@ If you want to include IOTA Identity in your project, simply add it as a depende ```toml [dependencies] -identity_iota = { version = "1.3.1" } +identity_iota = { version = "1.4.0" } ``` To try out the [examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples), you can also do this: @@ -88,7 +91,7 @@ version = "1.0.0" edition = "2021" [dependencies] -identity_iota = { version = "1.3.1", features = ["memstore"] } +identity_iota = { version = "1.4.0", features = ["memstore"] } iota-sdk = { version = "1.0.2", default-features = true, features = ["tls", "client", "stronghold"] } tokio = { version = "1", features = ["full"] } anyhow = "1.0.62" diff --git a/bindings/wasm/CHANGELOG.md b/bindings/wasm/CHANGELOG.md index cd49c3874f..55ab6c5e4e 100644 --- a/bindings/wasm/CHANGELOG.md +++ b/bindings/wasm/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog -## [wasm-v1.3.1](https://github.com/iotaledger/identity.rs/tree/wasm-v1.3.1) (2024-06-27) +## [wasm-v1.4.0](https://github.com/iotaledger/identity.rs/tree/wasm-v1.4.0) (2024-09-23) + +[Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.3.1...wasm-v1.4.0) + +### Added + +- Add support for `did:jwk` resolution [\#1404](https://github.com/iotaledger/identity.rs/pull/1404) +- Linked Verifiable Presentations [\#1398](https://github.com/iotaledger/identity.rs/pull/1398) +- Add WASM bindings for EcDSA JWS Verifier [\#1396](https://github.com/iotaledger/identity.rs/pull/1396) + +## [wasm-v1.3.1](https://github.com/iotaledger/identity.rs/tree/wasm-v1.3.1) (2024-06-28) [Full Changelog](https://github.com/iotaledger/identity.rs/compare/wasm-v1.3.0...wasm-v1.3.1) diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index b3017d8dc8..8406b386b2 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_wasm" -version = "1.3.1" +version = "1.4.0" authors = ["IOTA Stiftung"] edition = "2021" homepage = "https://www.iota.org" diff --git a/bindings/wasm/package-lock.json b/bindings/wasm/package-lock.json index f70949c608..c6afb8ec91 100644 --- a/bindings/wasm/package-lock.json +++ b/bindings/wasm/package-lock.json @@ -1,12 +1,12 @@ { "name": "@iota/identity-wasm", - "version": "1.3.1", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@iota/identity-wasm", - "version": "1.3.1", + "version": "1.4.0", "license": "Apache-2.0", "dependencies": { "@noble/ed25519": "^1.7.3", diff --git a/bindings/wasm/package.json b/bindings/wasm/package.json index 085abe22a7..1673750e23 100644 --- a/bindings/wasm/package.json +++ b/bindings/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@iota/identity-wasm", - "version": "1.3.1", + "version": "1.4.0", "description": "WASM bindings for IOTA Identity - A Self Sovereign Identity Framework implementing the DID and VC standards from W3C. To be used in Javascript/Typescript", "repository": { "type": "git", diff --git a/bindings/wasm/rust-toolchain.toml b/bindings/wasm/rust-toolchain.toml index 825d39b571..eb46cc977d 100644 --- a/bindings/wasm/rust-toolchain.toml +++ b/bindings/wasm/rust-toolchain.toml @@ -1,5 +1,6 @@ [toolchain] -channel = "stable" +# @itsyaasir - Update to latest stable version when wasm-bindgen is updated +channel = "1.81" components = ["rustfmt"] targets = ["wasm32-unknown-unknown"] profile = "minimal" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 9866115ad3..1a3d313705 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "examples" -version = "1.3.1" +version = "1.4.0" authors = ["IOTA Stiftung"] edition = "2021" publish = false diff --git a/identity_core/Cargo.toml b/identity_core/Cargo.toml index 239383c2f6..fcdd263cc7 100644 --- a/identity_core/Cargo.toml +++ b/identity_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_core" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index ab77b10d36..91ff18c974 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_credential" -version = "1.3.1" +version = "1.4.0" authors = ["IOTA Stiftung"] edition = "2021" homepage.workspace = true @@ -16,11 +16,11 @@ anyhow = { version = "1" } async-trait = { version = "0.1.64", default-features = false } bls12_381_plus = { workspace = true, optional = true } flate2 = { version = "1.0.28", default-features = false, features = ["rust_backend"], optional = true } -futures = { version = "0.3", default-features = false, optional = true, features = ["alloc"] } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +futures = { version = "0.3", default-features = false, optional = true } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] } itertools = { version = "0.11", default-features = false, features = ["use_std"], optional = true } json-proof-token = { workspace = true, optional = true } diff --git a/identity_credential/src/revocation/status_list_2021/entry.rs b/identity_credential/src/revocation/status_list_2021/entry.rs index 7eecf2f28e..92415d06b7 100644 --- a/identity_credential/src/revocation/status_list_2021/entry.rs +++ b/identity_credential/src/revocation/status_list_2021/entry.rs @@ -37,6 +37,14 @@ where .map(ToOwned::to_owned) } +/// Serialize usize as string. +fn serialize_number_as_string(value: &usize, serializer: S) -> Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&value.to_string()) +} + /// [StatusList2021Entry](https://www.w3.org/TR/2023/WD-vc-status-list-20230427/#statuslist2021entry) implementation. #[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] #[serde(rename_all = "camelCase")] @@ -45,7 +53,10 @@ pub struct StatusList2021Entry { #[serde(rename = "type", deserialize_with = "deserialize_status_entry_type")] type_: String, status_purpose: StatusPurpose, - #[serde(deserialize_with = "serde_aux::prelude::deserialize_number_from_string")] + #[serde( + deserialize_with = "serde_aux::prelude::deserialize_number_from_string", + serialize_with = "serialize_number_as_string" + )] status_list_index: usize, status_list_credential: Url, } @@ -142,4 +153,13 @@ mod tests { }); serde_json::from_value::(status).expect("wrong type"); } + + #[test] + fn test_status_list_index_serialization() { + let base_url = Url::parse("https://example.com/credentials/status/3").unwrap(); + + let entry1 = StatusList2021Entry::new(base_url.clone(), StatusPurpose::Revocation, 94567, None); + let json1 = serde_json::to_value(&entry1).unwrap(); + assert_eq!(json1["statusListIndex"], "94567"); + } } diff --git a/identity_did/Cargo.toml b/identity_did/Cargo.toml index e1025a9fcd..473ffc8860 100644 --- a/identity_did/Cargo.toml +++ b/identity_did/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_did" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition = "2021" homepage.workspace = true @@ -13,8 +13,8 @@ description = "Agnostic implementation of the Decentralized Identifiers (DID) st [dependencies] did_url_parser = { version = "0.2.0", features = ["std", "serde"] } form_urlencoded = { version = "1.2.0", default-features = false, features = ["alloc"] } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_jose = { version = "=1.3.1", path = "../identity_jose" } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_jose = { version = "=1.4.0", path = "../identity_jose" } serde.workspace = true strum.workspace = true thiserror.workspace = true diff --git a/identity_document/Cargo.toml b/identity_document/Cargo.toml index f87fc86c33..4bb50dd09d 100644 --- a/identity_document/Cargo.toml +++ b/identity_document/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_document" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -13,9 +13,9 @@ description = "Method-agnostic implementation of the Decentralized Identifiers ( [dependencies] did_url_parser = { version = "0.2.0", features = ["std", "serde"] } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_did = { version = "=1.3.1", path = "../identity_did" } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_did = { version = "=1.4.0", path = "../identity_did" } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } indexmap = { version = "2.0", default-features = false, features = ["std", "serde"] } serde.workspace = true strum.workspace = true diff --git a/identity_ecdsa_verifier/Cargo.toml b/identity_ecdsa_verifier/Cargo.toml index 654b8aebe3..6829d41ae0 100644 --- a/identity_ecdsa_verifier/Cargo.toml +++ b/identity_ecdsa_verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_ecdsa_verifier" -version = "1.3.1" +version = "1.4.0" authors = ["IOTA Stiftung", "Filancore GmbH"] edition.workspace = true homepage.workspace = true @@ -15,7 +15,7 @@ description = "JWS ECDSA signature verification for IOTA Identity" workspace = true [dependencies] -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } k256 = { version = "0.13.3", default-features = false, features = ["std", "ecdsa", "ecdsa-core"], optional = true } p256 = { version = "0.13.2", default-features = false, features = ["std", "ecdsa", "ecdsa-core"], optional = true } signature = { version = "2", default-features = false } diff --git a/identity_eddsa_verifier/Cargo.toml b/identity_eddsa_verifier/Cargo.toml index 97308beebf..b7da49295a 100644 --- a/identity_eddsa_verifier/Cargo.toml +++ b/identity_eddsa_verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_eddsa_verifier" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,7 +12,7 @@ rust-version.workspace = true description = "JWS EdDSA signature verification for IOTA Identity" [dependencies] -identity_jose = { version = "=1.3.1", path = "../identity_jose", default-features = false } +identity_jose = { version = "=1.4.0", path = "../identity_jose", default-features = false } iota-crypto = { version = "0.23.2", default-features = false, features = ["std"] } [features] diff --git a/identity_iota/Cargo.toml b/identity_iota/Cargo.toml index 6ba8f5d7aa..6d1bf0a9a5 100644 --- a/identity_iota/Cargo.toml +++ b/identity_iota/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_iota" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -12,14 +12,14 @@ rust-version.workspace = true description = "Framework for Self-Sovereign Identity with IOTA DID." [dependencies] -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.3.1", path = "../identity_credential", features = ["validator"], default-features = false } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } -identity_iota_core = { version = "=1.3.1", path = "../identity_iota_core", default-features = false } -identity_resolver = { version = "=1.3.1", path = "../identity_resolver", default-features = false, optional = true } -identity_storage = { version = "=1.3.1", path = "../identity_storage", default-features = false, features = ["iota-document"] } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.4.0", path = "../identity_credential", features = ["validator"], default-features = false } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } +identity_iota_core = { version = "=1.4.0", path = "../identity_iota_core", default-features = false } +identity_resolver = { version = "=1.4.0", path = "../identity_resolver", default-features = false, optional = true } +identity_storage = { version = "=1.4.0", path = "../identity_storage", default-features = false, features = ["iota-document"] } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } [dev-dependencies] anyhow = "1.0.64" diff --git a/identity_iota/README.md b/identity_iota/README.md index e8001e6788..773cae64f9 100644 --- a/identity_iota/README.md +++ b/identity_iota/README.md @@ -22,6 +22,9 @@ --- +> [!NOTE] +> This version of the library is compatible with IOTA Stardust networks, for a version of the library compatible with IOTA Rebased networks check [here](https://github.com/iotaledger/identity.rs/tree/feat/identity-rebased-alpha/) + ## Introduction IOTA Identity is a [Rust](https://www.rust-lang.org/) implementation of decentralized digital identity, also known as Self-Sovereign Identity (SSI). It implements the W3C [Decentralized Identifiers (DID)](https://www.w3.org/TR/did-core/) and [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) specifications. This library can be used to create, resolve and authenticate digital identities and to create verifiable credentials and presentations in order to share information in a verifiable manner and establish trust in the digital world. It does so while supporting secure storage of cryptographic keys, which can be implemented for your preferred key management system. Many of the individual libraries (Rust crates) are agnostic over the concrete DID method, with the exception of some libraries dedicated to implement the [IOTA DID method](https://wiki.iota.org/identity.rs/specs/did/iota_did_method_spec/), which is an implementation of decentralized digital identity on the IOTA and Shimmer networks. Written in stable Rust, IOTA Identity has strong guarantees of memory safety and process integrity while maintaining exceptional performance. @@ -54,7 +57,7 @@ If you want to include IOTA Identity in your project, simply add it as a depende ```toml [dependencies] -identity_iota = { version = "1.3.1" } +identity_iota = { version = "1.4.0" } ``` To try out the [examples](https://github.com/iotaledger/identity.rs/blob/HEAD/examples), you can also do this: @@ -88,7 +91,7 @@ version = "1.0.0" edition = "2021" [dependencies] -identity_iota = { version = "1.3.1", features = ["memstore"] } +identity_iota = { version = "1.4.0", features = ["memstore"] } iota-sdk = { version = "1.0.2", default-features = true, features = ["tls", "client", "stronghold"] } tokio = { version = "1", features = ["full"] } anyhow = "1.0.62" diff --git a/identity_iota_core/Cargo.toml b/identity_iota_core/Cargo.toml index f44a3ca27c..73dcc4190e 100644 --- a/identity_iota_core/Cargo.toml +++ b/identity_iota_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_iota_core" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -14,11 +14,11 @@ description = "An IOTA Ledger integration for the IOTA DID Method." [dependencies] async-trait = { version = "0.1.56", default-features = false, optional = true } futures = { version = "0.3", default-features = false } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.3.1", path = "../identity_credential", default-features = false, features = ["validator"] } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.4.0", path = "../identity_credential", default-features = false, features = ["validator"] } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } iota-sdk = { version = "1.1.5", default-features = false, features = ["serde", "std"], optional = true } num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["std"] } diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 1dbcb2781f..73a7fa3cdb 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_jose" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -13,7 +13,7 @@ description = "A library for JOSE (JSON Object Signing and Encryption)" [dependencies] bls12_381_plus.workspace = true -identity_core = { version = "=1.3.1", path = "../identity_core" } +identity_core = { version = "=1.4.0", path = "../identity_core" } iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha"] } json-proof-token.workspace = true serde.workspace = true @@ -34,3 +34,10 @@ test = true [lints] workspace = true + +[features] +custom_alg = [] + +[[test]] +name = "custom_alg" +required-features = ["custom_alg"] diff --git a/identity_jose/src/jwk/key.rs b/identity_jose/src/jwk/key.rs index be1db84c35..e2cb05d62d 100644 --- a/identity_jose/src/jwk/key.rs +++ b/identity_jose/src/jwk/key.rs @@ -395,9 +395,9 @@ impl Jwk { // =========================================================================== /// Checks if the `alg` claim of the JWK is equal to `expected`. - pub fn check_alg(&self, expected: &str) -> Result<()> { + pub fn check_alg(&self, expected: impl AsRef) -> Result<()> { match self.alg() { - Some(value) if value == expected => Ok(()), + Some(value) if value == expected.as_ref() => Ok(()), Some(_) => Err(Error::InvalidClaim("alg")), None => Ok(()), } diff --git a/identity_jose/src/jws/algorithm.rs b/identity_jose/src/jws/algorithm.rs index a60dc84050..1d6b1c319c 100644 --- a/identity_jose/src/jws/algorithm.rs +++ b/identity_jose/src/jws/algorithm.rs @@ -6,12 +6,11 @@ use core::fmt::Formatter; use core::fmt::Result; use std::str::FromStr; -use crate::error::Error; - /// Supported algorithms for the JSON Web Signatures `alg` claim. /// /// [More Info](https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms) -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize, serde::Serialize)] +#[cfg_attr(not(feature = "custom_alg"), derive(Copy))] #[allow(non_camel_case_types)] pub enum JwsAlgorithm { /// HMAC using SHA-256 @@ -45,10 +44,19 @@ pub enum JwsAlgorithm { NONE, /// EdDSA signature algorithms EdDSA, + /// Custom algorithm + #[cfg(feature = "custom_alg")] + #[serde(untagged)] + Custom(String), } impl JwsAlgorithm { /// A slice of all supported [`JwsAlgorithm`]s. + /// + /// Not available when feature `custom_alg` is enabled + /// as it is not possible to enumerate all variants when + /// supporting arbitrary `alg` values. + #[cfg(not(feature = "custom_alg"))] pub const ALL: &'static [Self] = &[ Self::HS256, Self::HS384, @@ -68,6 +76,7 @@ impl JwsAlgorithm { ]; /// Returns the JWS algorithm as a `str` slice. + #[cfg(not(feature = "custom_alg"))] pub const fn name(self) -> &'static str { match self { Self::HS256 => "HS256", @@ -87,6 +96,29 @@ impl JwsAlgorithm { Self::EdDSA => "EdDSA", } } + + /// Returns the JWS algorithm as a `str` slice. + #[cfg(feature = "custom_alg")] + pub fn name(&self) -> String { + match self { + Self::HS256 => "HS256".to_string(), + Self::HS384 => "HS384".to_string(), + Self::HS512 => "HS512".to_string(), + Self::RS256 => "RS256".to_string(), + Self::RS384 => "RS384".to_string(), + Self::RS512 => "RS512".to_string(), + Self::PS256 => "PS256".to_string(), + Self::PS384 => "PS384".to_string(), + Self::PS512 => "PS512".to_string(), + Self::ES256 => "ES256".to_string(), + Self::ES384 => "ES384".to_string(), + Self::ES512 => "ES512".to_string(), + Self::ES256K => "ES256K".to_string(), + Self::NONE => "none".to_string(), + Self::EdDSA => "EdDSA".to_string(), + Self::Custom(name) => name.clone(), + } + } } impl FromStr for JwsAlgorithm { @@ -109,13 +141,24 @@ impl FromStr for JwsAlgorithm { "ES256K" => Ok(Self::ES256K), "none" => Ok(Self::NONE), "EdDSA" => Ok(Self::EdDSA), - _ => Err(Error::JwsAlgorithmParsingError), + #[cfg(feature = "custom_alg")] + value => Ok(Self::Custom(value.to_string())), + #[cfg(not(feature = "custom_alg"))] + _ => Err(crate::error::Error::JwsAlgorithmParsingError), } } } +#[cfg(not(feature = "custom_alg"))] impl Display for JwsAlgorithm { fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.write_str(self.name()) } } + +#[cfg(feature = "custom_alg")] +impl Display for JwsAlgorithm { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_str(&(*self).name()) + } +} diff --git a/identity_jose/src/jws/header.rs b/identity_jose/src/jws/header.rs index b5749608a1..30b4f5fe82 100644 --- a/identity_jose/src/jws/header.rs +++ b/identity_jose/src/jws/header.rs @@ -67,7 +67,7 @@ impl JwsHeader { /// Returns the value for the algorithm claim (alg). pub fn alg(&self) -> Option { - self.alg.as_ref().copied() + self.alg.as_ref().cloned() } /// Sets a value for the algorithm claim (alg). diff --git a/identity_jose/tests/custom_alg.rs b/identity_jose/tests/custom_alg.rs new file mode 100644 index 0000000000..3297d6258b --- /dev/null +++ b/identity_jose/tests/custom_alg.rs @@ -0,0 +1,110 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::Deref; +use std::time::SystemTime; + +use crypto::signatures::ed25519::PublicKey; +use crypto::signatures::ed25519::SecretKey; +use crypto::signatures::ed25519::Signature; +use identity_jose::jwk::EdCurve; +use identity_jose::jwk::Jwk; +use identity_jose::jwk::JwkParamsOkp; +use identity_jose::jwk::JwkType; +use identity_jose::jws::CompactJwsEncoder; +use identity_jose::jws::Decoder; +use identity_jose::jws::JwsAlgorithm; +use identity_jose::jws::JwsHeader; +use identity_jose::jws::JwsVerifierFn; +use identity_jose::jws::SignatureVerificationError; +use identity_jose::jws::SignatureVerificationErrorKind; +use identity_jose::jws::VerificationInput; +use identity_jose::jwt::JwtClaims; +use identity_jose::jwu; +use jsonprooftoken::encoding::base64url_decode; + +#[test] +fn custom_alg_roundtrip() { + let secret_key = SecretKey::generate().unwrap(); + let public_key = secret_key.public_key(); + + let mut header: JwsHeader = JwsHeader::new(); + header.set_alg(JwsAlgorithm::Custom("test".to_string())); + let kid = "did:iota:0x123#signing-key"; + header.set_kid(kid); + + let mut claims: JwtClaims = JwtClaims::new(); + claims.set_iss("issuer"); + claims.set_iat( + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() as i64, + ); + claims.set_custom(serde_json::json!({"num": 42u64})); + + let claims_bytes: Vec = serde_json::to_vec(&claims).unwrap(); + + let encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new(&claims_bytes, &header).unwrap(); + let signing_input: &[u8] = encoder.signing_input(); + let signature = secret_key.sign(signing_input).to_bytes(); + let jws = encoder.into_jws(&signature); + + let header = jws.split(".").next().unwrap(); + let header_json = String::from_utf8(base64url_decode(header.as_bytes())).expect("failed to decode header"); + assert_eq!(header_json, r#"{"kid":"did:iota:0x123#signing-key","alg":"test"}"#); + + let verifier = JwsVerifierFn::from(|input: VerificationInput, key: &Jwk| { + if input.alg != JwsAlgorithm::Custom("test".to_string()) { + panic!("invalid algorithm"); + } + verify(input, key) + }); + let decoder = Decoder::new(); + let mut public_key_jwk = Jwk::new(JwkType::Okp); + public_key_jwk.set_kid(kid); + public_key_jwk + .set_params(JwkParamsOkp { + crv: "Ed25519".into(), + x: jwu::encode_b64(public_key.as_slice()), + d: None, + }) + .unwrap(); + + let token = decoder + .decode_compact_serialization(jws.as_bytes(), None) + .and_then(|decoded| decoded.verify(&verifier, &public_key_jwk)) + .unwrap(); + + let recovered_claims: JwtClaims = serde_json::from_slice(&token.claims).unwrap(); + + assert_eq!(token.protected.alg(), Some(JwsAlgorithm::Custom("test".to_string()))); + assert_eq!(claims, recovered_claims); +} + +fn verify(verification_input: VerificationInput, jwk: &Jwk) -> Result<(), SignatureVerificationError> { + let public_key = expand_public_jwk(jwk); + + let signature_arr = <[u8; Signature::LENGTH]>::try_from(verification_input.decoded_signature.deref()) + .map_err(|err| err.to_string()) + .unwrap(); + + let signature = Signature::from_bytes(signature_arr); + if public_key.verify(&signature, &verification_input.signing_input) { + Ok(()) + } else { + Err(SignatureVerificationErrorKind::InvalidSignature.into()) + } +} + +fn expand_public_jwk(jwk: &Jwk) -> PublicKey { + let params: &JwkParamsOkp = jwk.try_okp_params().unwrap(); + + if params.try_ed_curve().unwrap() != EdCurve::Ed25519 { + panic!("expected an ed25519 jwk"); + } + + let pk: [u8; PublicKey::LENGTH] = jwu::decode_b64(params.x.as_str()).unwrap().try_into().unwrap(); + + PublicKey::try_from(pk).unwrap() +} diff --git a/identity_resolver/Cargo.toml b/identity_resolver/Cargo.toml index a85969c286..d99158835d 100644 --- a/identity_resolver/Cargo.toml +++ b/identity_resolver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_resolver" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -15,16 +15,16 @@ description = "DID Resolution utilities for the identity.rs library." # This is currently necessary for the ResolutionHandler trait. This can be made an optional dependency if alternative ways of attaching handlers are introduced. async-trait = { version = "0.1", default-features = false } futures = { version = "0.3" } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.3.1", path = "../identity_credential", default-features = false, features = ["validator"] } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.4.0", path = "../identity_credential", default-features = false, features = ["validator"] } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } serde = { version = "1.0", default-features = false, features = ["std", "derive"] } strum.workspace = true thiserror = { version = "1.0", default-features = false } [dependencies.identity_iota_core] -version = "=1.3.1" +version = "=1.4.0" path = "../identity_iota_core" default-features = false features = ["send-sync-client-ext", "iota-client"] diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index fbbe93b346..5331dc725f 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_storage" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -16,12 +16,12 @@ anyhow = "1.0.82" async-trait = { version = "0.1.64", default-features = false } bls12_381_plus = { workspace = true, optional = true } futures = { version = "0.3.27", default-features = false, features = ["async-await"] } -identity_core = { version = "=1.3.1", path = "../identity_core", default-features = false } -identity_credential = { version = "=1.3.1", path = "../identity_credential", default-features = false, features = ["credential", "presentation", "revocation-bitmap"] } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_document = { version = "=1.3.1", path = "../identity_document", default-features = false } -identity_iota_core = { version = "=1.3.1", path = "../identity_iota_core", default-features = false, optional = true } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_core = { version = "=1.4.0", path = "../identity_core", default-features = false } +identity_credential = { version = "=1.4.0", path = "../identity_credential", default-features = false, features = ["credential", "presentation", "revocation-bitmap"] } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_document = { version = "=1.4.0", path = "../identity_document", default-features = false } +identity_iota_core = { version = "=1.4.0", path = "../identity_iota_core", default-features = false, optional = true } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519", "random"], optional = true } json-proof-token = { workspace = true, optional = true } rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"], optional = true } @@ -33,8 +33,8 @@ tokio = { version = "1.29.0", default-features = false, features = ["macros", "s zkryptium = { workspace = true, optional = true } [dev-dependencies] -identity_credential = { version = "=1.3.1", path = "../identity_credential", features = ["revocation-bitmap"] } -identity_eddsa_verifier = { version = "=1.3.1", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } +identity_credential = { version = "=1.4.0", path = "../identity_credential", features = ["revocation-bitmap"] } +identity_eddsa_verifier = { version = "=1.4.0", path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } once_cell = { version = "1.18", default-features = false } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 9bf4e6ea9a..2f51be97ce 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -58,7 +58,7 @@ impl JwkStorage for JwkMemStore { async fn generate(&self, key_type: KeyType, alg: JwsAlgorithm) -> KeyStorageResult { let key_type: MemStoreKeyType = MemStoreKeyType::try_from(&key_type)?; - check_key_alg_compatibility(key_type, alg)?; + check_key_alg_compatibility(key_type, &alg)?; let (private_key, public_key) = match key_type { MemStoreKeyType::Ed25519 => { @@ -102,7 +102,7 @@ impl JwkStorage for JwkMemStore { Some(alg) => { let alg: JwsAlgorithm = JwsAlgorithm::from_str(alg) .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm).with_source(err))?; - check_key_alg_compatibility(key_type, alg)?; + check_key_alg_compatibility(key_type, &alg)?; } None => { return Err( @@ -291,7 +291,7 @@ fn random_key_id() -> KeyId { } /// Check that the key type can be used with the algorithm. -fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: JwsAlgorithm) -> KeyStorageResult<()> { +fn check_key_alg_compatibility(key_type: MemStoreKeyType, alg: &JwsAlgorithm) -> KeyStorageResult<()> { match (key_type, alg) { (MemStoreKeyType::Ed25519, JwsAlgorithm::EdDSA) => Ok(()), (key_type, alg) => Err( diff --git a/identity_stronghold/Cargo.toml b/identity_stronghold/Cargo.toml index d6b0825cba..b7c61a998f 100644 --- a/identity_stronghold/Cargo.toml +++ b/identity_stronghold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_stronghold" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -14,8 +14,8 @@ description = "Secure JWK storage with Stronghold for IOTA Identity" [dependencies] async-trait = { version = "0.1.64", default-features = false } bls12_381_plus = { workspace = true, optional = true } -identity_storage = { version = "=1.3.1", path = "../identity_storage", default-features = false } -identity_verification = { version = "=1.3.1", path = "../identity_verification", default-features = false } +identity_storage = { version = "=1.4.0", path = "../identity_storage", default-features = false } +identity_verification = { version = "=1.4.0", path = "../identity_verification", default-features = false } iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519"] } iota-sdk = { version = "1.1.5", default-features = false, features = ["client", "stronghold"] } iota_stronghold = { version = "2.1.0", default-features = false } @@ -28,8 +28,8 @@ zkryptium = { workspace = true, optional = true } [dev-dependencies] anyhow = "1.0.82" bls12_381_plus = { workspace = true } -identity_did = { version = "=1.3.1", path = "../identity_did", default-features = false } -identity_storage = { version = "=1.3.1", path = "../identity_storage", default-features = false, features = ["jpt-bbs-plus"] } +identity_did = { version = "=1.4.0", path = "../identity_did", default-features = false } +identity_storage = { version = "=1.4.0", path = "../identity_storage", default-features = false, features = ["jpt-bbs-plus"] } json-proof-token = { workspace = true } tokio = { version = "1.29.0", default-features = false, features = ["macros", "sync", "rt"] } zkryptium = { workspace = true } diff --git a/identity_stronghold/src/storage/stronghold_jwk_storage.rs b/identity_stronghold/src/storage/stronghold_jwk_storage.rs index b0400c8f65..efe0f6531b 100644 --- a/identity_stronghold/src/storage/stronghold_jwk_storage.rs +++ b/identity_stronghold/src/storage/stronghold_jwk_storage.rs @@ -36,7 +36,7 @@ impl JwkStorage for StrongholdStorage { let client = get_client(&stronghold)?; let key_type = StrongholdKeyType::try_from(&key_type)?; - check_key_alg_compatibility(key_type, alg)?; + check_key_alg_compatibility(key_type, &alg)?; let keytype: ProceduresKeyType = match key_type { StrongholdKeyType::Ed25519 => ProceduresKeyType::Ed25519, @@ -106,7 +106,7 @@ impl JwkStorage for StrongholdStorage { Some(alg) => { let alg: JwsAlgorithm = JwsAlgorithm::from_str(alg) .map_err(|err| KeyStorageError::new(KeyStorageErrorKind::UnsupportedSignatureAlgorithm).with_source(err))?; - check_key_alg_compatibility(key_type, alg)?; + check_key_alg_compatibility(key_type, &alg)?; } None => { return Err( diff --git a/identity_stronghold/src/utils.rs b/identity_stronghold/src/utils.rs index 3a9ae72842..0bf83e1f18 100644 --- a/identity_stronghold/src/utils.rs +++ b/identity_stronghold/src/utils.rs @@ -24,7 +24,7 @@ pub fn random_key_id() -> KeyId { } /// Check that the key type can be used with the algorithm. -pub fn check_key_alg_compatibility(key_type: StrongholdKeyType, alg: JwsAlgorithm) -> KeyStorageResult<()> { +pub fn check_key_alg_compatibility(key_type: StrongholdKeyType, alg: &JwsAlgorithm) -> KeyStorageResult<()> { match (key_type, alg) { (StrongholdKeyType::Ed25519, JwsAlgorithm::EdDSA) => Ok(()), (key_type, alg) => Err( diff --git a/identity_verification/Cargo.toml b/identity_verification/Cargo.toml index 8990dd96e0..46fcc5ac24 100644 --- a/identity_verification/Cargo.toml +++ b/identity_verification/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "identity_verification" -version = "1.3.1" +version = "1.4.0" authors.workspace = true edition.workspace = true homepage.workspace = true @@ -10,9 +10,9 @@ rust-version.workspace = true description = "Verification data types and functionality for identity.rs" [dependencies] -identity_core = { version = "=1.3.1", path = "./../identity_core" } -identity_did = { version = "=1.3.1", path = "./../identity_did", default-features = false } -identity_jose = { version = "=1.3.1", path = "./../identity_jose", default-features = false } +identity_core = { version = "=1.4.0", path = "./../identity_core" } +identity_did = { version = "=1.4.0", path = "./../identity_did", default-features = false } +identity_jose = { version = "=1.4.0", path = "./../identity_jose", default-features = false } serde.workspace = true serde_json.workspace = true strum.workspace = true