diff --git a/.github/actions/iota-sandbox/tear-down/action.yml b/.github/actions/iota-sandbox/tear-down/action.yml index 8a0da1906e..c8e6225d0b 100644 --- a/.github/actions/iota-sandbox/tear-down/action.yml +++ b/.github/actions/iota-sandbox/tear-down/action.yml @@ -7,6 +7,6 @@ runs: shell: bash run: | cd iota-sandbox/sandbox - docker-compose down + docker compose down cd ../.. sudo rm -rf iota-sandbox diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 553774bb46..ba3b7ef0b1 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -126,15 +126,18 @@ jobs: run: cargo clean # Build the library, tests, and examples without running them to avoid recompilation in the run tests step - - name: Build with all features - run: cargo build --workspace --tests --examples --all-features --release + - name: Build with default features + run: cargo build --workspace --tests --examples --release - name: Start iota sandbox if: matrix.os == 'ubuntu-latest' uses: './.github/actions/iota-sandbox/setup' - - name: Run tests - run: cargo test --workspace --all-features --release + - name: Run tests excluding `custom_time` feature + run: cargo test --workspace --release + + - name: Run tests with `custom_time` feature + run: cargo test --test custom_time --features="custom_time" - name: Run Rust examples # run examples only on ubuntu for now @@ -157,7 +160,7 @@ jobs: - name: Tear down iota sandbox if: matrix.os == 'ubuntu-latest' && always() uses: './.github/actions/iota-sandbox/tear-down' - + - name: Stop sccache uses: './.github/actions/rust/sccache/stop-sccache' with: @@ -253,7 +256,7 @@ jobs: load: true - name: Run cypress - run: docker run --network host cypress-test test:browser:firefox + run: docker run --network host cypress-test test:browser:parallel:firefox - name: Tear down iota sandbox if: always() diff --git a/Cargo.toml b/Cargo.toml index 0b349651b5..7dfcbcadd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ exclude = ["bindings/wasm", "bindings/grpc"] [workspace.dependencies] -bls12_381_plus = { version = "=0.8.15" } +bls12_381_plus = { version = "0.8.17" } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } thiserror = { version = "1.0", default-features = false } strum = { version = "0.25", default-features = false, features = ["std", "derive"] } diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 9e264b3b6d..1acaf0ce96 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -17,7 +17,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] async-trait = { version = "0.1", default-features = false } -bls12_381_plus = "=0.8.15" +bls12_381_plus = "0.8.17" console_error_panic_hook = { version = "0.1" } futures = { version = "0.3" } identity_eddsa_verifier = { path = "../../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } diff --git a/bindings/wasm/cypress.config.ts b/bindings/wasm/cypress.config.ts index f7cb0a6a1a..c59929872a 100644 --- a/bindings/wasm/cypress.config.ts +++ b/bindings/wasm/cypress.config.ts @@ -10,14 +10,20 @@ export default defineConfig({ }, e2e: { supportFile: false, - // Fix to make subtle crypto work in cypress firefox - // https://github.com/cypress-io/cypress/issues/18217 setupNodeEvents(on, config) { on("before:browser:launch", (browser, launchOptions) => { if (browser.family === "firefox") { + // Fix to make subtle crypto work in cypress firefox + // https://github.com/cypress-io/cypress/issues/18217 launchOptions.preferences[ "network.proxy.testing_localhost_is_secure_when_hijacked" ] = true; + // Temporary fix to allow cypress to control Firefox via CDP + // https://github.com/cypress-io/cypress/issues/29713 + // https://fxdx.dev/deprecating-cdp-support-in-firefox-embracing-the-future-with-webdriver-bidi/ + launchOptions.preferences[ + "remote.active-protocols" + ] = 3; } return launchOptions; }); diff --git a/identity_core/Cargo.toml b/identity_core/Cargo.toml index 0f7a8a34eb..f8aa615b28 100644 --- a/identity_core/Cargo.toml +++ b/identity_core/Cargo.toml @@ -12,7 +12,6 @@ rust-version.workspace = true description = "The core traits and types for the identity-rs library." [dependencies] -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "random", "sha", "x25519", "std"] } multibase = { version = "0.9", default-features = false, features = ["std"] } serde = { workspace = true, features = ["std"] } serde_json = { workspace = true, features = ["std"] } @@ -22,7 +21,7 @@ time = { version = "0.3.23", default-features = false, features = ["std", "serde url = { version = "2.4", default-features = false, features = ["serde"] } zeroize = { version = "1.6", default-features = false } -[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] +[target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(feature = "custom_time")))'.dependencies] js-sys = { version = "0.3.55", default-features = false } [dev-dependencies] @@ -38,3 +37,11 @@ rustdoc-args = ["--cfg", "docsrs"] [lints] workspace = true + +[features] +# Enables a macro to provide a custom time (Timestamp::now_utc) implementation, see src/custom_time.rs +custom_time = [] + +[[test]] +name = "custom_time" +required-features = ["custom_time"] diff --git a/identity_core/src/common/timestamp.rs b/identity_core/src/common/timestamp.rs index 8de1832409..4f03db2cea 100644 --- a/identity_core/src/common/timestamp.rs +++ b/identity_core/src/common/timestamp.rs @@ -42,7 +42,10 @@ impl Timestamp { /// fractional seconds truncated. /// /// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production). - #[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] + #[cfg(all( + not(all(target_arch = "wasm32", not(target_os = "wasi"))), + not(feature = "custom_time") + ))] pub fn now_utc() -> Self { Self(truncate_fractional_seconds(OffsetDateTime::now_utc())) } @@ -51,7 +54,7 @@ impl Timestamp { /// fractional seconds truncated. /// /// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production). - #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] + #[cfg(all(target_arch = "wasm32", not(target_os = "wasi"), not(feature = "custom_time")))] pub fn now_utc() -> Self { let milliseconds_since_unix_epoch: i64 = js_sys::Date::now() as i64; let seconds: i64 = milliseconds_since_unix_epoch / 1000; @@ -59,6 +62,15 @@ impl Timestamp { Self::from_unix(seconds).expect("Timestamp failed to convert system datetime") } + /// Creates a new `Timestamp` with the current date and time, normalized to UTC+00:00 with + /// fractional seconds truncated. + /// + /// See the [`datetime` DID-core specification](https://www.w3.org/TR/did-core/#production). + #[cfg(feature = "custom_time")] + pub fn now_utc() -> Self { + crate::custom_time::now_utc_custom() + } + /// Returns the `Timestamp` as an [RFC 3339](https://tools.ietf.org/html/rfc3339) `String`. pub fn to_rfc3339(&self) -> String { // expect is okay, constructors ensure RFC 3339 compatible timestamps. diff --git a/identity_core/src/custom_time.rs b/identity_core/src/custom_time.rs new file mode 100644 index 0000000000..ef509a19de --- /dev/null +++ b/identity_core/src/custom_time.rs @@ -0,0 +1,88 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! An implementation of `now_utc` which calls out to an externally defined function. +use crate::common::Timestamp; + +/// Register a function to be invoked by `identity_core` in order to get a [Timestamp] representing +/// "now". +/// +/// ## Writing a custom `now_utc` implementation +/// +/// The function to register must have the same signature as +/// [`Timestamp::now_utc`](Timestamp::now_utc). The function can be defined +/// wherever you want, either in root crate or a dependent crate. +/// +/// For example, if we wanted a `static_now_utc` crate containing an +/// implementation that always returns the same timestamp, we would first depend on `identity_core` +/// (for the [`Timestamp`] type) in `static_now_utc/Cargo.toml`: +/// ```toml +/// [dependencies] +/// identity_core = "1" +/// ``` +/// Note that the crate containing this function does **not** need to enable the +/// `"custom_time"` Cargo feature. +/// +/// Next, in `static_now_utc/src/lib.rs`, we define our function: +/// ```rust +/// use identity_core::common::Timestamp; +/// +/// // Some fixed timestamp +/// const MY_FIXED_TIMESTAMP: i64 = 1724402964; +/// pub fn static_now_utc() -> Timestamp { +/// Timestamp::from_unix(MY_FIXED_TIMESTAMP).unwrap() +/// } +/// ``` +/// +/// ## Registering a custom `now_utc` implementation +/// +/// Functions can only be registered in the root binary crate. Attempting to +/// register a function in a non-root crate will result in a linker error. +/// This is similar to +/// [`#[panic_handler]`](https://doc.rust-lang.org/nomicon/panic-handler.html) or +/// [`#[global_allocator]`](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html), +/// where helper crates define handlers/allocators but only the binary crate +/// actually _uses_ the functionality. +/// +/// To register the function, we first depend on `static_now_utc` _and_ +/// `identity_core` in `Cargo.toml`: +/// ```toml +/// [dependencies] +/// static_now_utc = "0.1" +/// identity_core = { version = "1", features = ["custom_time"] } +/// ``` +/// +/// Then, we register the function in `src/main.rs`: +/// ```rust +/// # mod static_now_utc { pub fn static_now_utc() -> Timestamp { unimplemented!() } } +/// +/// use identity_core::register_custom_now_utc; +/// use static_now_utc::static_now_utc; +/// +/// register_custom_now_utc!(static_now_utc); +/// ``` +/// +/// Now any user of `now_utc` (direct or indirect) on this target will use the +/// registered function. +#[macro_export] +macro_rules! register_custom_now_utc { + ($path:path) => { + const __GET_TIME_INTERNAL: () = { + // We use Rust ABI to be safe against potential panics in the passed function. + #[no_mangle] + unsafe fn __now_utc_custom() -> Timestamp { + // Make sure the passed function has the type of `now_utc_custom` + type F = fn() -> Timestamp; + let f: F = $path; + f() + } + }; + }; +} + +pub(crate) fn now_utc_custom() -> Timestamp { + extern "Rust" { + fn __now_utc_custom() -> Timestamp; + } + unsafe { __now_utc_custom() } +} diff --git a/identity_core/src/lib.rs b/identity_core/src/lib.rs index b915fcdeba..0e439441e4 100644 --- a/identity_core/src/lib.rs +++ b/identity_core/src/lib.rs @@ -1,7 +1,6 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -#![forbid(unsafe_code)] #![doc = include_str!("./../README.md")] #![allow(clippy::upper_case_acronyms)] #![warn( @@ -19,9 +18,15 @@ #[doc(inline)] pub use serde_json::json; +#[forbid(unsafe_code)] pub mod common; +#[forbid(unsafe_code)] pub mod convert; +#[forbid(unsafe_code)] pub mod error; +#[cfg(feature = "custom_time")] +pub mod custom_time; + pub use self::error::Error; pub use self::error::Result; diff --git a/identity_core/tests/custom_time.rs b/identity_core/tests/custom_time.rs new file mode 100644 index 0000000000..9c700d523e --- /dev/null +++ b/identity_core/tests/custom_time.rs @@ -0,0 +1,18 @@ +// Copyright 2020-2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Timestamp; +use identity_core::register_custom_now_utc; + +const STATIC_TIME: i64 = 1724402964; // 2024-08-23T11:33:30+00:00 +pub fn static_now_utc() -> Timestamp { + Timestamp::from_unix(STATIC_TIME).unwrap() +} + +register_custom_now_utc!(static_now_utc); + +#[test] +fn should_use_registered_static_time() { + let timestamp = Timestamp::now_utc(); + assert_eq!(timestamp.to_unix(), STATIC_TIME) +} diff --git a/identity_credential/Cargo.toml b/identity_credential/Cargo.toml index 2a4b11d09c..aaba6c974e 100644 --- a/identity_credential/Cargo.toml +++ b/identity_credential/Cargo.toml @@ -39,7 +39,7 @@ zkryptium = { workspace = true, optional = true } [dev-dependencies] anyhow = "1.0.62" identity_eddsa_verifier = { path = "../identity_eddsa_verifier", default-features = false, features = ["ed25519"] } -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519", "std", "random"] } +iota-crypto = { version = "0.23.2", default-features = false, features = ["ed25519", "std", "random"] } proptest = { version = "1.4.0", default-features = false, features = ["std"] } tokio = { version = "1.35.0", default-features = false, features = ["rt-multi-thread", "macros"] } diff --git a/identity_eddsa_verifier/Cargo.toml b/identity_eddsa_verifier/Cargo.toml index eedd1d652e..97308beebf 100644 --- a/identity_eddsa_verifier/Cargo.toml +++ b/identity_eddsa_verifier/Cargo.toml @@ -13,7 +13,7 @@ description = "JWS EdDSA signature verification for IOTA Identity" [dependencies] identity_jose = { version = "=1.3.1", path = "../identity_jose", default-features = false } -iota-crypto = { version = "0.23", default-features = false, features = ["std"] } +iota-crypto = { version = "0.23.2", default-features = false, features = ["std"] } [features] ed25519 = ["iota-crypto/ed25519"] diff --git a/identity_iota_core/Cargo.toml b/identity_iota_core/Cargo.toml index 0ddb2bd6b9..f44a3ca27c 100644 --- a/identity_iota_core/Cargo.toml +++ b/identity_iota_core/Cargo.toml @@ -31,7 +31,7 @@ thiserror.workspace = true [dev-dependencies] anyhow = { version = "1.0.57" } -iota-crypto = { version = "0.23", default-features = false, features = ["bip39", "bip39-en"] } +iota-crypto = { version = "0.23.2", default-features = false, features = ["bip39", "bip39-en"] } proptest = { version = "1.0.0", default-features = false, features = ["std"] } tokio = { version = "1.29.0", default-features = false, features = ["rt-multi-thread", "macros"] } diff --git a/identity_jose/Cargo.toml b/identity_jose/Cargo.toml index 32d3824a9a..da8ddba3d2 100644 --- a/identity_jose/Cargo.toml +++ b/identity_jose/Cargo.toml @@ -14,7 +14,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", default-features = false } -iota-crypto = { version = "0.23", default-features = false, features = ["std", "sha"] } +iota-crypto = { version = "0.23.2", default-features = false, features = ["std", "sha"] } json-proof-token.workspace = true serde.workspace = true serde_json = { version = "1.0", default-features = false, features = ["std"] } @@ -24,7 +24,7 @@ zeroize = { version = "1.6", default-features = false, features = ["std", "zeroi [dev-dependencies] anyhow = "1" -iota-crypto = { version = "0.23", features = ["ed25519", "random", "hmac"] } +iota-crypto = { version = "0.23.2", features = ["ed25519", "random", "hmac"] } p256 = { version = "0.12.0", default-features = false, features = ["std", "ecdsa", "ecdsa-core"] } signature = { version = "2", default-features = false } diff --git a/identity_jose/src/tests/rfc8037.rs b/identity_jose/src/tests/rfc8037.rs index d83f22eb89..27bb755979 100644 --- a/identity_jose/src/tests/rfc8037.rs +++ b/identity_jose/src/tests/rfc8037.rs @@ -50,6 +50,9 @@ fn test_rfc8037_ed25519() { .and_then(|decoded| decoded.verify(&jws_verifier, &public)) .unwrap(); + assert_eq!(token.protected, header); + assert_eq!(token.claims, tv.payload.as_bytes()); + let jws_signature_verifier = JwsVerifierFn::from(|input: VerificationInput, key: &Jwk| match input.alg { JwsAlgorithm::EdDSA => ed25519::verify(input, key), other => unimplemented!("{other}"), @@ -62,7 +65,5 @@ fn test_rfc8037_ed25519() { .unwrap(); assert_eq!(token, token_with_default); - assert_eq!(token.protected, header); - assert_eq!(token.claims, tv.payload.as_bytes()); } } diff --git a/identity_storage/Cargo.toml b/identity_storage/Cargo.toml index 729c6319b1..fbbe93b346 100644 --- a/identity_storage/Cargo.toml +++ b/identity_storage/Cargo.toml @@ -22,7 +22,7 @@ identity_did = { version = "=1.3.1", path = "../identity_did", default-features 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 } -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519"], optional = true } +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 } seahash = { version = "4.1.0", default-features = false } @@ -47,7 +47,12 @@ send-sync-storage = [] # Implements the JwkStorageDocumentExt trait for IotaDocument iota-document = ["dep:identity_iota_core"] # Enables JSON Proof Token & BBS+ related features -jpt-bbs-plus = ["identity_credential/jpt-bbs-plus", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] +jpt-bbs-plus = [ + "identity_credential/jpt-bbs-plus", + "dep:zkryptium", + "dep:bls12_381_plus", + "dep:json-proof-token", +] [lints] workspace = true diff --git a/identity_stronghold/Cargo.toml b/identity_stronghold/Cargo.toml index f9f8bfacf7..d6b0825cba 100644 --- a/identity_stronghold/Cargo.toml +++ b/identity_stronghold/Cargo.toml @@ -16,7 +16,7 @@ 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 } -iota-crypto = { version = "0.23", default-features = false, features = ["ed25519"] } +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 } json-proof-token = { workspace = true, optional = true } @@ -38,7 +38,12 @@ zkryptium = { workspace = true } default = [] # Enables `Send` + `Sync` bounds for the trait implementations on `StrongholdStorage`. send-sync-storage = ["identity_storage/send-sync-storage"] -bbs-plus = ["identity_storage/jpt-bbs-plus", "dep:zkryptium", "dep:bls12_381_plus", "dep:json-proof-token"] +bbs-plus = [ + "identity_storage/jpt-bbs-plus", + "dep:zkryptium", + "dep:bls12_381_plus", + "dep:json-proof-token", +] [lints] workspace = true