diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f66efa6..82d0c21 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,7 @@ on: env: PKG_NAME: didcomm + PKG_NAME_NODEJS: didcomm-node jobs: @@ -25,8 +26,12 @@ jobs: current_version: ${{ steps.current_version.outputs.current_version }} release_info: ${{ steps.release_info.outputs.release_info }} asset_crate_url: ${{ steps.release_info.outputs.asset_crate_url }} + asset_npm_pkg_bundler_url: ${{ steps.release_info.outputs.asset_npm_pkg_bundler_url }} + asset_npm_pkg_nodejs_url: ${{ steps.release_info.outputs.asset_npm_pkg_nodejs_url }} upload_url: ${{ steps.release_info.outputs.upload_url }} already_in_crates_io: ${{ steps.check_in_crates_io.outputs.already_in_crates_io != '' }} + already_in_npm_bundler: ${{ steps.check_in_npm.outputs.already_in_npm_bundler != '' }} + already_in_npm_nodejs: ${{ steps.check_in_npm.outputs.already_in_npm_nodejs != '' }} steps: - uses: actions/checkout@v2 @@ -53,6 +58,16 @@ jobs: echo "::set-output name=asset_crate_url::$asset_crate_url" echo "$asset_crate_url" + asset_npm_pkg_bundler_url="$(echo "$release_info" \ + | jq -r '.assets[] | select(.name | match("^${{ env.PKG_NAME }}-${{ steps.current_version.outputs.current_version }}\\.tgz$")) | .browser_download_url')" + echo "::set-output name=asset_npm_pkg_bundler_url::$asset_npm_pkg_bundler_url" + echo "$asset_npm_pkg_bundler_url" + + asset_npm_pkg_nodejs_url="$(echo "$release_info" \ + | jq -r '.assets[] | select(.name | match("^${{ env.PKG_NAME_NODEJS }}-${{ steps.current_version.outputs.current_version }}\\.tgz$")) | .browser_download_url')" + echo "::set-output name=asset_npm_pkg_nodejs_url::$asset_npm_pkg_nodejs_url" + echo "$asset_npm_pkg_nodejs_url" + upload_url="$(echo "$release_info" | jq -r '.upload_url')" echo "::set-output name=upload_url::$upload_url" echo "$upload_url" @@ -67,6 +82,21 @@ jobs: echo "::set-output name=already_in_crates_io::$out" shell: bash {0} # to opt-out of default fail-fast behavior + - name: check if already deployed to npm + id: check_in_npm + run: | + out="$(npm view ${{ env.PKG_NAME }}@${{ steps.current_version.outputs.current_version }} --json 2>/dev/null \ + | jq -r '.versions | select (.!=null)')" + echo "in npm check for ${{ env.PKG_NAME }} : $out" + echo "::set-output name=already_in_npm_bundler::$out" + + out="$(npm view ${{ env.PKG_NAME_NODEJS }}@${{ steps.current_version.outputs.current_version }} --json 2>/dev/null \ + | jq -r '.versions | select (.!=null)')" + echo "in npm check for ${{ env.PKG_NAME_NODEJS }}: $out" + echo "::set-output name=already_in_npm_nodejs::$out" + shell: bash {0} # to opt-out of default fail-fast behavior + + release: name: Release if: github.ref == 'refs/heads/stable' @@ -81,20 +111,8 @@ jobs: rustup toolchain install stable shell: bash - - name: package and verify the crate - id: build_assets - if: ${{ !needs.checks.outputs.asset_crate_url }} - run: | - cargo package - - # TODO - # - verify that it's not more than crates.io limit (10 MB) - # - explore wthere we need to upload another artifact (without extension) - ls -la target/package - cargo package --list - - asset_crate_name="$(find target/package -name '*.crate' -printf '%f')" - echo "::set-output name=asset_crate_name::$asset_crate_name" + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh shell: bash - name: Create GitHub Release @@ -109,7 +127,7 @@ jobs: - name: Set upload url id: upload_url - if: ${{ !needs.checks.outputs.asset_crate_url }} + if: ${{ !(needs.checks.outputs.asset_crate_url && needs.checks.outputs.asset_npm_pkg_bundler_url && needs.checks.outputs.asset_npm_pkg_nodejs_url) }} run: | if [[ -n "${{ needs.checks.outputs.upload_url }}" ]]; then echo "::set-output name=value::${{ needs.checks.outputs.upload_url }}" @@ -117,29 +135,106 @@ jobs: echo "::set-output name=value::${{ steps.create_release.outputs.upload_url }}" fi - - name: Upload to GitHub + - name: package and verify (crate) + id: build_assets_crate + if: ${{ !needs.checks.outputs.asset_crate_url }} + run: | + cargo package + + # TODO + # - verify that it's not more than crates.io limit (10 MB) + # - explore whether we need to upload another artifact (without extension) + ls -la target/package + cargo package --list + + asset_name="$(find target/package -name '*.crate' -printf '%f')" + echo "::set-output name=asset_name::$asset_name" + shell: bash + + - name: upload to GitHub (crate) if: ${{ !needs.checks.outputs.asset_crate_url }} uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.upload_url.outputs.value }} - asset_path: target/package/${{ steps.build_assets.outputs.asset_crate_name }} - asset_name: ${{ steps.build_assets.outputs.asset_crate_name }} + asset_path: target/package/${{ steps.build_assets_crate.outputs.asset_name }} + asset_name: ${{ steps.build_assets_crate.outputs.asset_name }} asset_content_type: application/octet-stream # TODO check for less generic type + - name: package and verify (npm bundler) + id: build_assets_npm_bundler + if: ${{ !needs.checks.outputs.asset_npm_pkg_bundler_url }} + run: | + # build, install (verify) and pack + make -C wasm build install pack + asset_name="$(find wasm/pkg -name '*.tgz' -printf '%f')" + echo "::set-output name=asset_name::$asset_name" + shell: bash + + - name: upload to GitHub (npm bundler) + if: ${{ !needs.checks.outputs.asset_npm_pkg_bundler_url }} + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.upload_url.outputs.value }} + asset_path: wasm/pkg/${{ steps.build_assets_npm_bundler.outputs.asset_name }} + asset_name: ${{ steps.build_assets_npm_bundler.outputs.asset_name }} + asset_content_type: application/x-gtar + + - name: package and verify (npm nodejs) + id: build_assets_npm_nodejs + if: ${{ !needs.checks.outputs.asset_npm_pkg_nodejs_url }} + run: | + # build, install (verify) and pack + WASM_TARGET=nodejs PKG_NAME=${{ env.PKG_NAME_NODEJS }} make -C wasm build install pack + asset_name="$(find wasm/pkg -name '*.tgz' -printf '%f')" + echo "::set-output name=asset_name::$asset_name" + shell: bash + + - name: upload to GitHub (npm nodejs) + if: ${{ !needs.checks.outputs.asset_npm_pkg_nodejs_url }} + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.upload_url.outputs.value }} + asset_path: wasm/pkg/${{ steps.build_assets_npm_nodejs.outputs.asset_name }} + asset_name: ${{ steps.build_assets_npm_nodejs.outputs.asset_name }} + asset_content_type: application/x-gtar + # NOTE looks like there is no option to skip packaging here # and use already prepared artifacts - - name: Publish to crates.io + + - name: publish to crates.io if: needs.checks.outputs.already_in_crates_io == 'false' env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: cargo publish shell: bash + - name: publish to npm (bundler) + if: needs.checks.outputs.already_in_npm_bundler == 'false' + env: + NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + run: | + # build and publish + make -C wasm build publish + shell: bash + + - name: publish to npm (nodejs) + if: needs.checks.outputs.already_in_npm_nodejs == 'false' + env: + NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + run: | + # build and publish + WASM_TARGET=nodejs PKG_NAME=${{ env.PKG_NAME_NODEJS }} make -C wasm build publish + shell: bash - deploy-dev-crates-io: - name: Publish dev to crates.io + + deploy-dev: + name: publish dev to crates.io if: github.ref != 'refs/heads/stable' && github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: @@ -151,21 +246,36 @@ jobs: rustup toolchain install stable shell: bash + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + shell: bash + - name: set dev version run: | sed -i -r "0,/version/{s~^version = (['\"])(.+)['\"]~version = \1\2-0.dev.${{ github.event.inputs.devN }}\1~}" ./Cargo.toml grep version ./Cargo.toml + sed -i -r "0,/version/{s~^version = (['\"])(.+)['\"]~version = \1\2-0.dev.${{ github.event.inputs.devN }}\1~}" ./wasm/Cargo.toml + grep version ./wasm/Cargo.toml shell: bash - - name: verify the package + - name: build, verify and publish (crates.io) + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: | cargo package --allow-dirty ls -la target/package cargo package --allow-dirty --list + cargo publish --allow-dirty shell: bash - - name: publish + - name: build, verify and publish (npm bundler) env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish --allow-dirty + NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + run: WASM_OPTS_PUBLISH="--tag dev" make -C wasm build install publish + shell: bash + + - name: build, verify and publish (npm nodejs) + env: + NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + run: WASM_TARGET=nodejs PKG_NAME=${{ env.PKG_NAME_NODEJS }} WASM_OPTS_PUBLISH="--tag dev" make -C wasm build install publish shell: bash diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index dd3b892..fad0af7 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -6,6 +6,8 @@ on: env: PKG_NAME: didcomm + PKG_NAME_JS_TMP: didcomm-js + PKG_NAME_NODEJS: didcomm-node jobs: @@ -13,6 +15,10 @@ jobs: release-ready: runs-on: ubuntu-latest if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'stable' + defaults: + run: + shell: bash + steps: - uses: actions/checkout@v2 @@ -23,13 +29,17 @@ jobs: | jq -r '.packages[] | select(.name == "${{ env.PKG_NAME }}") | .version')" echo "$version" echo "::set-output name=current_version::$version" - shell: bash + + cd wasm + version="$(cargo -q metadata --no-deps \ + | jq -r '.packages[] | select(.name == "${{ env.PKG_NAME_JS_TMP }}") | .version')" + echo "$version" + echo "::set-output name=current_wasm_version::$version" - name: Check version format run: | # verify the version has "MAJOR.MINOR.PATCH" parts only echo "${{ steps.current_version.outputs.current_version }}" | grep -e '^[0-9]\+\.[0-9]\+\.[0-9]\+$' - shell: bash # TODO improve (DRY): copy-paste from release.yml - name: Get release info @@ -39,20 +49,31 @@ jobs: | jq '.[] | select(.name == "v${{ steps.current_version.outputs.current_version }}")')" echo "::set-output name=release_info::$release_info" echo "$release_info" - shell: bash - name: check version bumped # TODO check if greater than latest tag / release (?) if: steps.release_info.outputs.release_info run: exit 1 - - name: check it can be packaged + - name: check rust and wasm versions are the same + if: steps.current_version.outputs.current_version != steps.current_version.outputs.current_wasm_version + run: exit 1 + + - name: check it can be packaged (crate) run: | cargo package # TODO verify that it's not more than crates.io limit (10 MB) ls -la target/package cargo package --list - shell: bash + + - name: check it can be packaged (npm) + run: | + curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + # verify bundler target + make -C wasm build install + # verify nodejs target + WASM_TARGET=nodejs PKG_NAME=${{ env.PKG_NAME_NODEJS }} make -C wasm build install verify: strategy: @@ -68,7 +89,147 @@ jobs: run: | rustup set profile minimal rustup toolchain install stable + + - name: Get timestamp for cache + id: date + run: echo ::set-output name=yearmo::$(date +%Y%m) + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}-${{steps.date.outputs.yearmo}} + + - name: Cargo fmt + # TODO enable that once https://github.com/rust-lang/rustfmt/issues/4477 + # is resolved + if: runner.os != 'Windows' + run: cargo fmt --all -- --check + + - name: Debug build + run: cargo build --verbose + + - name: Test + run: cargo test --verbose + + verify-wasm: + strategy: + matrix: + os: [ macos-latest, windows-latest, ubuntu-latest ] + node: [ 14, 16 ] + fail-fast: false + runs-on: ${{ matrix.os }} + defaults: + run: shell: bash + working-directory: wasm + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set rustup profile & toolchain + run: | + rustup set profile minimal + rustup toolchain install stable + + - name: Get timestamp for cache + id: date + run: echo ::set-output name=yearmo::$(date +%Y%m) + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}-${{steps.date.outputs.yearmo}} + + - name: Cargo fmt + # TODO enable that once https://github.com/rust-lang/rustfmt/issues/4477 + # is resolved + if: runner.os != 'Windows' + run: cargo fmt --all -- --check + + - name: Cargo checks + run: cargo check --all-targets + + # TODO caching, makes sense for demo and tests-js where lock files are presented + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Build pkg (bundler) + run: make + + - name: Install tests-js (bundler) + run: cd tests-js && npm install + + - name: Lint tests-js + run: cd tests-js && npm run check + + - name: Test tests-js (bundler) + if: false # TODO that check is not supported yet for bundler build + run: cd tests-js && npm test + + - name: Test tests-js in browser (bundler) + if: false # TODO that check is not supported yet for bundler build + run: cd tests-js && npm run test-puppeteer + + - name: Install demo (bundler) + run: cd demo && npm install + + - name: Lint demo + run: cd demo && npm run check + + - name: Test demo (bundler) + if: false # TODO that check is not supported yet for bundler build + run: cd demo && npm run start + + - name: Build pkg (nodejs) + run: WASM_TARGET=nodejs make + + - name: Install tests-js (nodejs) + run: cd tests-js && rm -rf node_modules && npm install + + - name: Test tests-js (nodejs) + run: cd tests-js && npm test + + - name: Test tests-js in browser (nodejs) + run: cd tests-js && npm run test-puppeteer + + - name: Install demo (nodejs) + run: cd demo && rm -rf node_modules && npm install + + - name: Test demo (nodejs) + run: cd demo && npm run start + + verify-uniffi: + strategy: + matrix: + os: [ macos-latest, windows-latest, ubuntu-latest ] + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + working-directory: uniffi + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set rustup profile & toolchain + run: | + rustup set profile minimal + rustup toolchain install stable - name: Get timestamp for cache id: date diff --git a/.gitignore b/.gitignore index e0f0476..cbc74be 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ Cargo.lock # vim *.swp + +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 0c254c5..99f3025 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,29 @@ +[[bench]] +name = 'pack_signed' +harness = false + +[[bench]] +name = 'pack_encrypted' +harness = false + +[[example]] +name = 'attachments' + +[[example]] +name = 'advanced_params' + +[[example]] +name = 'basic' + +[[example]] +name = 'rotate_did' + +[[example]] +name = 'routing' + [package] name = 'didcomm' -version = '0.2.0' +version = '0.3.0' authors = ['Vyacheslav Gudkov '] edition = '2018' description = 'DIDComm for Rust' @@ -16,6 +39,9 @@ thiserror = '1.0' serde_json = '1.0' serde-enum-str = '0.1' sha2 = '0.9' +bs58 = "0.4.0" +varint = "0.9.0" +lazy_static = { version = "1.4.0", optional = true } [dependencies.serde] version = '1.0' @@ -27,6 +53,10 @@ features = ['std'] git = 'https://github.com/hyperledger/aries-askar' rev = '4f29d43d584c4a1f1f982c4511824421aeccd2db' +[dependencies.uuid] +version = "0.8" +features = ["v4"] + [dev-dependencies] lazy_static = '1.4.0' @@ -37,18 +67,14 @@ features = [ 'macros', ] +[dev-dependencies.getrandom] +version = '0.2' +features = ['js'] + [dev-dependencies.criterion] version = '0.3' features = ['async_futures'] -[[bench]] -name = "pack_signed" -harness = false - -[[bench]] -name = "pack_encrypted" -harness = false - -[[example]] -name = "basic" - +[features] +uniffi = [] +testvectors = ["lazy_static"] \ No newline at end of file diff --git a/README.md b/README.md index 46206f0..7e5bb3a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,21 @@ -# DIDComm Rust +# DIDComm Rust + JavaScript/TypeScript + Swift [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Unit Tests](https://github.com/sicpa-dlab/didcomm-rust/workflows/verify/badge.svg)](https://github.com/sicpa-dlab/didcomm-rust/actions/workflows/verify.yml) -[![Rust Package](https://img.shields.io/crates/v/didcomm)](https://crates.io/crates/actix/) +[![Rust Package](https://img.shields.io/crates/v/didcomm)](https://crates.io/crates/didcomm/) -Basic [DIDComm v2](https://identity.foundation/didcomm-messaging/spec) support in Rust. +The repository consists of the following main components: +- Basic [DIDComm v2](https://identity.foundation/didcomm-messaging/spec) support in Rust. +- [Wasm](https://webassembly.org/) - based DIDComm JavaScript/TypeScript, see [wasm](/wasm). +- [uniffi-rs](https://github.com/mozilla/uniffi-rs) - based wrappers + - [uniffi](/uniffi) - callback-based Rust wrapper with uniffi-rs support + - [wrappers/swift](/wrappers/swift) - Swift wrapper generated via uniffi-rs + +The docs below are provided for the main DIDComm Rust. + +See [wasm/README.md](/wasm/README.md) for DIDComm JavaScript/TypeScript docs. + +See [wrappers/swift/README.md](/wrappers/swift/README.md) for DIDComm Swift docs. ## Usage @@ -12,7 +23,7 @@ To use `didcomm`, add this to your `Cargo.toml`: ```toml [dependencies] -didcomm = "0.2" +didcomm = "0.3" ``` ## Run examples @@ -24,7 +35,10 @@ Use `cargo run --example {example-name}` for example `cargo run --example basic` - In order to use the library, `SecretsResolver` and `DIDResolver` traits must be implemented on the application level. Implementation of that traits is out of DIDComm library scope, but we provide 2 simple implementation `ExampleDIDResolver` and `ExampleSecretsResolver` that allows resolve locally known DID docs and secrets for tests/demo purposes. - - Verification materials are expected in JWK. + - Verification materials are expected in JWK, Base58 and Multibase (internally Base58 only) formats. + - In Base58 and Multibase formats, keys using only X25519 and Ed25519 curves are supported. + - For private keys in Base58 and Multibase formats, the verification material value contains both private and public parts (concatenated bytes). + - In Multibase format, bytes of the verification material value is prefixed with the corresponding Multicodec code. - Key IDs (kids) used in `SecretsResolver` must match the corresponding key IDs from DID Doc verification methods. - Key IDs (kids) in DID Doc verification methods and secrets must be a full [DID Fragment](https://www.w3.org/TR/did-core/#fragment), that is `did#key-id`. - Verification methods referencing another DID Document are not supported (see [Referring to Verification Methods](https://www.w3.org/TR/did-core/#referring-to-verification-methods)). @@ -39,14 +53,10 @@ Use `cargo run --example {example-name}` for example `cargo run --example basic` - Signing: - Curves: Ed25519, Secp256k1, P-256 - Algorithms: EdDSA (with crv=Ed25519), ES256, ES256K +- Forward protocol is implemented and used by default. +- DID rotation (`fromPrior` field) is supported. - DIDComm has been implemented under the following [Assumptions](https://hackmd.io/i3gLqgHQR2ihVFV5euyhqg) -### **Features that will be supported in next versions** - -- *Base58 and Multibase (internally Base58 only) formats for secrets and verification methods.* -- *Forward protocol.* -- *DID rotation (`fromPrior` field).* - ## Examples @@ -101,10 +111,7 @@ let (msg, metadata) = msg None, &did_resolver, &secrets_resolver, - &PackEncryptedOptions { - forward: false, // Forward wrapping is unsupported in current version - ..PackEncryptedOptions::default() - }, + &PackEncryptedOptions::default(), ) .await .expect("Unable pack_encrypted"); @@ -122,9 +129,7 @@ let (msg, metadata) = Message::unpack( &msg, &did_resolver, &secrets_resolver, - &UnpackOptions { - ..UnpackOptions::default() - }, + &UnpackOptions::default(), ) .await .expect("Unable unpack"); @@ -143,10 +148,7 @@ let (msg, metadata) = msg None, &did_resolver, &secrets_resolver, - &PackEncryptedOptions { - forward: false, // Forward wrapping is unsupported in current version - ..PackEncryptedOptions::default() - }, + &PackEncryptedOptions::default(), ) .await .expect("Unable pack_encrypted"); @@ -159,14 +161,10 @@ let (msg, metadata) = msg .pack_encrypted( BOB_DID, Some(ALICE_DID), - None, + Some(ALICE_DID), // Provide information about signer here &did_resolver, &secrets_resolver, - &PackEncryptedOptions { - sign_by: Some(ALICE_DID), // Provide information about signer here - forward: false, // Forward wrapping is unsupported in current version - ..PackEncryptedOptions::default() - }, + &PackEncryptedOptions::default(), ) .await .expect("Unable pack_encrypted"); @@ -196,7 +194,7 @@ let msg = Message::build( .finalize(); let (msg, metadata) = msg - .pack_signed(ALICE_DID, did_resolver, secrets_resolver) + .pack_signed(ALICE_DID, &did_resolver, &secrets_resolver) .await .expect("Unable pack_signed"); @@ -205,9 +203,7 @@ let (msg, metadata) = Message::unpack( &msg, &did_resolver, &secrets_resolver, - &UnpackOptions { - ..UnpackOptions::default() - }, + &UnpackOptions::default(), ) .await .expect("Unable unpack"); @@ -234,7 +230,7 @@ let msg = Message::build( .finalize(); let msg = msg - .pack_plaintext() + .pack_plaintext(&did_resolver) .expect("Unable pack_plaintext"); // BOB @@ -242,9 +238,7 @@ let (msg, metadata) = Message::unpack( &msg, &did_resolver, &secrets_resolver, - &UnpackOptions { - ..UnpackOptions::default() - }, + &UnpackOptions::default(), ) .await .expect("Unable unpack"); diff --git a/docs/release.md b/docs/release.md index 3ec1437..758256c 100644 --- a/docs/release.md +++ b/docs/release.md @@ -7,8 +7,12 @@ Assumptions: The steps: 1. **release**: - 1. **review adjust if needed the release version in `main`** to match the changes from the latest release following the [SemVer rules](https://semver.org/#summary). + 1. **review and adjust if needed the release version in `main`** to match the changes from the latest release following the [SemVer rules](https://semver.org/#summary). 2. [create](https://github.com/sicpa-dlab/didcomm-rust/compare/stable...main) a **PR from `main` to `stable`** (you may likely want to name it as `release-`) - 3. once merged [release pipeline](https://github.com/sicpa-dlab/didcomm-rust/actions/workflows/release.yml) will publish the release to [crates.io](https://crates.io/crates/didcomm) + 3. once merged [release pipeline](https://github.com/sicpa-dlab/didcomm-rust/actions/workflows/release.yml) will publish the release: + * to [crates.io](https://crates.io/crates/didcomm) + * to NPM: + * as Bundler(Webpack) compatible [package](https://www.npmjs.com/package/didcomm) + * as Node.js (CommonJS) compatible [package](https://www.npmjs.com/package/didcomm-node) 2. **bump next release version in `main`** * **Note** decision about the next release version should be based on the same [SemVer](https://semver.org/) rules and the expected changes. Usually it would be either a MINOR or MAJOR (if incompatible changes are planned) release. diff --git a/examples/advanced_params.rs b/examples/advanced_params.rs new file mode 100644 index 0000000..a0c1f15 --- /dev/null +++ b/examples/advanced_params.rs @@ -0,0 +1,124 @@ +#[allow(unused_imports, dead_code)] +#[path = "../src/test_vectors/mod.rs"] +mod test_vectors; + +// TODO: look for better solution +// Allows test vectors usage inside and outside crate +pub(crate) use didcomm; + +use didcomm::{ + algorithms::{AnonCryptAlg, AuthCryptAlg}, + did::resolvers::ExampleDIDResolver, + protocols::routing::try_parse_forward, + secrets::resolvers::ExampleSecretsResolver, + Message, PackEncryptedOptions, UnpackOptions, +}; +use serde_json::json; +use std::collections::HashMap; +use std::iter::FromIterator; +use test_vectors::{ + ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, BOB_DID, BOB_DID_DOC, BOB_SECRETS, MEDIATOR1_DID_DOC, + MEDIATOR1_SECRETS, +}; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + // --- Building message from ALICE to BOB --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .from(ALICE_DID.to_owned()) + .to(BOB_DID.to_owned()) + .created_time(1516269022) + .expires_time(1516385931) + .finalize(); + + // --- Packing encrypted and authenticated message --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let (msg, metadata) = msg + .pack_encrypted( + "did:example:bob#key-p256-1", + "did:example:alice#key-p256-1".into(), + "did:example:alice#key-2".into(), + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + protect_sender: true, + forward: true, + forward_headers: Some(HashMap::from_iter([( + "expires_time".to_string(), + json!(99999), + )])), + messaging_service: Some("did:example:bob#didcomm-1".to_string()), + enc_alg_auth: AuthCryptAlg::A256cbcHs512Ecdh1puA256kw, + enc_alg_anon: AnonCryptAlg::A256gcmEcdhEsA256kw, + }, + ) + .await + .expect("Unable pack_encrypted"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Sending message by Alice --- + println!("Alice is sending message \n{}\n", msg); + + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); +} diff --git a/examples/attachments.rs b/examples/attachments.rs new file mode 100644 index 0000000..6984d0d --- /dev/null +++ b/examples/attachments.rs @@ -0,0 +1,123 @@ +#[allow(unused_imports, dead_code)] +#[path = "../src/test_vectors/mod.rs"] +mod test_vectors; + +// TODO: look for better solution +// Allows test vectors usage inside and outside crate +pub(crate) use didcomm; + +use didcomm::{ + did::resolvers::ExampleDIDResolver, protocols::routing::try_parse_forward, + secrets::resolvers::ExampleSecretsResolver, Attachment, AttachmentData, JsonAttachmentData, + Message, PackEncryptedOptions, UnpackOptions, +}; +use serde_json::json; +use test_vectors::{ + ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, BOB_DID, BOB_DID_DOC, BOB_SECRETS, MEDIATOR1_DID_DOC, + MEDIATOR1_SECRETS, +}; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + // --- Building message from ALICE to BOB --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(BOB_DID.to_owned()) + .from(ALICE_DID.to_owned()) + .attachment(Attachment { + data: AttachmentData::Json { + value: JsonAttachmentData { + json: json!({"foo": "bar"}), + jws: None, + }, + }, + id: Some("123".to_string()), + description: Some("example attachment".to_string()), + filename: None, + media_type: Some("application/didcomm-encrypted+json".to_string()), + format: None, + lastmod_time: None, + byte_count: None, + }) + .finalize(); + + // --- Packing encrypted and authenticated message --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let (msg, metadata) = msg + .pack_encrypted( + BOB_DID, + Some(ALICE_DID), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Alice is sending message --- + println!("Alice is sending message \n{}\n", msg); + + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); +} diff --git a/examples/basic.rs b/examples/basic.rs index e78c7e9..b4f0ecc 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -7,67 +7,622 @@ mod test_vectors; pub(crate) use didcomm; use didcomm::{ - did::resolvers::ExampleDIDResolver, secrets::resolvers::ExampleSecretsResolver, Message, - PackEncryptedOptions, UnpackOptions, + did::resolvers::ExampleDIDResolver, protocols::routing::try_parse_forward, + secrets::resolvers::ExampleSecretsResolver, Message, PackEncryptedOptions, UnpackOptions, }; use serde_json::json; -use test_vectors::{ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, BOB_DID, BOB_DID_DOC, BOB_SECRETS}; +use test_vectors::{ + ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, BOB_DID, BOB_DID_DOC, BOB_SECRETS, CHARLIE_DID, + CHARLIE_DID_DOC, CHARLIE_SECRETS, MEDIATOR1_DID_DOC, MEDIATOR1_SECRETS, MEDIATOR2_DID_DOC, + MEDIATOR2_SECRETS, MEDIATOR3_DID_DOC, MEDIATOR3_SECRETS, +}; #[tokio::main(flavor = "current_thread")] async fn main() { - // --- Build message from ALICE to BOB --- + println!("=================== NON REPUDIABLE ENCRYPTION ==================="); + non_repudiable_encryption().await; + println!("=================== MULTI RECIPIENT ==================="); + multi_recipient().await; + println!("=================== REPUDIABLE AUTHENTICATED ENCRYPTION ==================="); + repudiable_authenticated_encryption().await; + println!("=================== REPUDIABLE NON AUTHENTICATED ENCRYPTION ==================="); + repudiable_non_authenticated_encryption().await; + println!("=================== SIGNED UNENCRYPTED ==================="); + signed_unencrypted().await; + println!("=================== PLAINTEXT UNENCRYPTED ==================="); + plaintext_unencrypted().await; +} +async fn non_repudiable_encryption() { + // --- Building message from ALICE to BOB --- let msg = Message::build( "example-1".to_owned(), "example/v1".to_owned(), json!("example-body"), ) - .to(ALICE_DID.to_owned()) - .from(BOB_DID.to_owned()) + .to(BOB_DID.to_owned()) + .from(ALICE_DID.to_owned()) .finalize(); // --- Packing encrypted and authenticated message --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); - let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); let (msg, metadata) = msg + .pack_encrypted( + BOB_DID, + Some(ALICE_DID), + Some(ALICE_DID), + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Sending message by Alice --- + println!("Alice is sending message \n{}\n", msg); + + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); +} + +async fn multi_recipient() { + // --- Building message from ALICE to BOB and CHARLIE --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to_many(vec![BOB_DID.to_owned(), CHARLIE_DID.to_owned()]) + .from(ALICE_DID.to_owned()) + .finalize(); + + // --- Packing encrypted and authenticated message for Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let (msg_bob, metadata_bob) = msg .pack_encrypted( BOB_DID, Some(ALICE_DID), None, &did_resolver, &secrets_resolver, - &PackEncryptedOptions { - forward: false, // Forward wrapping is unsupported in current version - ..PackEncryptedOptions::default() - }, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + // --- Sending message by Alice to Bob --- + println!("Alice is sending message to Bob \n{}\n", msg_bob); + println!("Encryption metadata for Bob is\n{:?}\n", metadata_bob); + + // --- Packing encrypted and authenticated message for Charlie--- + + let (msg_charlie, metadata_charlie) = msg + .pack_encrypted( + CHARLIE_DID, + Some(ALICE_DID), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + // --- Sending message by Alice to Charlie --- + println!("Alice is sending message to Charlie \n{}\n", msg_charlie); + + println!( + "Encryption metadata for Charlie is\n{:?}\n", + metadata_charlie + ); + + // --- Unpacking message for Bob by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg_bob, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); + + // --- Unpacking message for Charlie by Mediator3 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR3_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg_charlie, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator3 received message is \n{:?}\n", msg); + + println!( + "Mediator3 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator3 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator3 is forwarding message \n{}\n", msg); + + // --- Unpacking message for Charlie by Mediator2 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR2_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator2 received message is \n{:?}\n", msg); + + println!( + "Mediator2 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator2 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator2 is forwarding message \n{}\n", msg); + + // --- Unpacking message for Charlie by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Charlie --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(CHARLIE_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Charlie received message is \n{:?}\n", msg); + + println!( + "Charlie received message unpack metadata is \n{:?}\n", + metadata + ); +} + +async fn repudiable_authenticated_encryption() { + // --- Building message from ALICE to BOB --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(BOB_DID.to_owned()) + .from(ALICE_DID.to_owned()) + .finalize(); + + // --- Packing encrypted and authenticated message --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let (msg, metadata) = msg + .pack_encrypted( + BOB_DID, + Some(ALICE_DID), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), ) .await .expect("Unable pack_encrypted"); println!("Encryption metadata is\n{:?}\n", metadata); - // --- Send message --- + // --- Sending message by Alice --- + println!("Alice is sending message \n{}\n", msg); - println!("Sending message \n{}\n", msg); + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); - // // --- Unpacking message --- + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); - let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); let (msg, metadata) = Message::unpack( &msg, &did_resolver, &secrets_resolver, - &UnpackOptions { - ..UnpackOptions::default() - }, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); +} + +async fn repudiable_non_authenticated_encryption() { + // --- Building message from ALICE to BOB --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(BOB_DID.to_owned()) + .from(ALICE_DID.to_owned()) + .finalize(); + + // --- Packing encrypted message --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(vec![]); + + let (msg, metadata) = msg + .pack_encrypted( + BOB_DID, + None, + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Sending message by Alice --- + println!("Alice is sending message \n{}\n", msg); + + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); +} + +async fn signed_unencrypted() { + // --- Building message from ALICE to BOB --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(BOB_DID.to_owned()) + .from(ALICE_DID.to_owned()) + .finalize(); + + // --- Packing signed message --- + let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let (msg, metadata) = msg + .pack_signed(ALICE_DID, &did_resolver, &secrets_resolver) + .await + .expect("Unable pack_signed"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Sending message --- + println!("Sending message \n{}\n", msg); + + // --- Unpacking message --- + let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + let secrets_resolver = ExampleSecretsResolver::new(vec![]); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Received message is \n{:?}\n", msg); + println!("Received message unpack metadata is \n{:?}\n", metadata); +} + +async fn plaintext_unencrypted() { + // --- Building message from ALICE to BOB --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(BOB_DID.to_owned()) + .from(ALICE_DID.to_owned()) + .finalize(); + + // --- Packing plaintext message --- + let did_resolver = ExampleDIDResolver::new(vec![]); + + let msg = msg + .pack_plaintext(&did_resolver) + .await + .expect("Unable pack_plaintext"); + + // --- Sending message --- + println!("Sending message \n{}\n", msg); + + // --- Unpacking message --- + let did_resolver = ExampleDIDResolver::new(vec![]); + let secrets_resolver = ExampleSecretsResolver::new(vec![]); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), ) .await - .expect("Ubable unpack"); + .expect("Unable unpack"); - println!("Receved message is \n{:?}\n", msg); - println!("Receved message unpack metadata is \n{:?}\n", metadata); + println!("Received message is \n{:?}\n", msg); + println!("Received message unpack metadata is \n{:?}\n", metadata); } diff --git a/examples/rotate_did.rs b/examples/rotate_did.rs new file mode 100644 index 0000000..5f63067 --- /dev/null +++ b/examples/rotate_did.rs @@ -0,0 +1,135 @@ +#[allow(unused_imports, dead_code)] +#[path = "../src/test_vectors/mod.rs"] +mod test_vectors; + +// TODO: look for better solution +// Allows test vectors usage inside and outside crate +pub(crate) use didcomm; + +use didcomm::{ + did::resolvers::ExampleDIDResolver, protocols::routing::try_parse_forward, + secrets::resolvers::ExampleSecretsResolver, FromPrior, Message, PackEncryptedOptions, + UnpackOptions, +}; +use serde_json::json; +use test_vectors::{ + ALICE_DID, ALICE_DID_DOC, BOB_DID, BOB_DID_DOC, BOB_SECRETS, CHARLIE_DID, CHARLIE_DID_DOC, + CHARLIE_ROTATED_TO_ALICE_SECRETS, MEDIATOR1_DID_DOC, MEDIATOR1_SECRETS, +}; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(CHARLIE_ROTATED_TO_ALICE_SECRETS.clone()); + + // --- Building from_prior header + let from_prior = FromPrior::build(CHARLIE_DID.into(), ALICE_DID.into()) + .aud("123".into()) + .exp(1234) + .nbf(12345) + .iat(123456) + .jti("dfg".into()) + .finalize(); + + println!("Original from_prior is\n{:?}\n", from_prior); + + let (from_prior, issuer_kid) = from_prior + .pack(None, &did_resolver, &secrets_resolver) + .await + .expect("Unable pack from_prior"); + + println!("Packed from_prior is\n{}\n", from_prior); + println!("from_prior issuer kid is\n{}\n", issuer_kid); + + // --- Building message from ALICE (ex-CHARLIE) to BOB --- + let msg = Message::build( + "1234567890".to_owned(), + "http://example.com/protocols/lets_do_lunch/1.0/proposal".to_owned(), + json!({"messagespecificattribute": "and its value"}), + ) + .from(ALICE_DID.to_owned()) + .to(BOB_DID.to_owned()) + .created_time(1516269022) + .expires_time(1516385931) + .from_prior(from_prior) + .finalize(); + + println!("Original message is\n{:?}\n", msg); + + // --- Packing encrypted and authenticated message --- + let (msg, metadata) = msg + .pack_encrypted( + BOB_DID, + Some(ALICE_DID), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Sending message by Alice --- + println!("Alice is sending message \n{}\n", msg); + + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); +} diff --git a/examples/routing.rs b/examples/routing.rs new file mode 100644 index 0000000..8151379 --- /dev/null +++ b/examples/routing.rs @@ -0,0 +1,536 @@ +#[allow(unused_imports, dead_code)] +#[path = "../src/test_vectors/mod.rs"] +mod test_vectors; + +// TODO: look for better solution +// Allows test vectors usage inside and outside crate +pub(crate) use didcomm; + +use didcomm::{ + algorithms::AnonCryptAlg, + did::resolvers::ExampleDIDResolver, + protocols::routing::{try_parse_forward, wrap_in_forward}, + secrets::resolvers::ExampleSecretsResolver, + Message, PackEncryptedOptions, UnpackOptions, +}; +use serde_json::json; +use test_vectors::{ + ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, BOB_DID, BOB_DID_DOC, BOB_SECRETS, CHARLIE_DID, + CHARLIE_DID_DOC, CHARLIE_SECRETS, MEDIATOR1_DID_DOC, MEDIATOR1_SECRETS, MEDIATOR2_DID_DOC, + MEDIATOR2_SECRETS, MEDIATOR3_DID_DOC, MEDIATOR3_SECRETS, +}; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + println!("=================== SINGLE MEDIATOR ==================="); + single_mediator().await; + + println!( + "=================== MULTIPLE MEDIATORS WITH ALTERNATIVE ENDPOINTS ===================" + ); + multiple_mediators_with_alternative_endpoints().await; + + println!("=================== REWRAPPING FOR FINAL RECIPIENT ==================="); + re_wrapping_for_final_recipient().await; + + println!("=================== REWRAPPING FOR MEDIATOR UNKNOWN TO SENDER ==================="); + re_wrapping_for_mediator_unknown_to_sender().await; +} + +async fn single_mediator() { + // --- Building message from ALICE to BOB --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(BOB_DID.to_owned()) + .from(ALICE_DID.to_owned()) + .finalize(); + + // --- Packing encrypted and authenticated message --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let (msg, metadata) = msg + .pack_encrypted( + BOB_DID, + Some(ALICE_DID), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Sending message by Alice --- + println!("Alice is sending message \n{}\n", msg); + + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); +} + +async fn multiple_mediators_with_alternative_endpoints() { + // --- Building message from ALICE to CHARLIE --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(CHARLIE_DID.to_owned()) + .from(ALICE_DID.to_owned()) + .finalize(); + + // --- Packing encrypted and authenticated message --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let (msg, metadata) = msg + .pack_encrypted( + CHARLIE_DID, + Some(ALICE_DID), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Sending message by Alice --- + println!("Alice is sending message \n{}\n", msg); + + // --- Unpacking message by Mediator3 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR3_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator3 received message is \n{:?}\n", msg); + + println!( + "Mediator3 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator3 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator3 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Mediator2 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR2_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator2 received message is \n{:?}\n", msg); + + println!( + "Mediator2 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator2 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator2 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator1 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator1 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Charlie --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(CHARLIE_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Charlie received message is \n{:?}\n", msg); + println!( + "Charlie received message unpack metadata is \n{:?}\n", + metadata + ); +} + +async fn re_wrapping_for_final_recipient() { + // --- Building message from ALICE to BOB --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(BOB_DID.to_owned()) + .from(ALICE_DID.to_owned()) + .finalize(); + + // --- Packing encrypted and authenticated message --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let (msg, metadata) = msg + .pack_encrypted( + BOB_DID, + Some(ALICE_DID), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Sending message by Alice --- + println!("Alice is sending message \n{}\n", msg); + + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Re-wrapping forwarded message for final recipient by Mediator1 --- + let parsed_forward = try_parse_forward(&msg).unwrap(); + let msg = serde_json::to_string(&parsed_forward.forwarded_msg).unwrap(); + + println!("Mediator1 retrieved forwarded message \n{}\n", msg); + + let msg = wrap_in_forward( + &msg, + None, + &parsed_forward.next, + &vec![parsed_forward.next.clone()], + &AnonCryptAlg::default(), + &did_resolver, + ) + .await + .expect("Unable wrap in forward"); + + // --- Forwarding re-wrapped message by Mediator1 --- + println!( + "Mediator1 is forwarding re-wrapped message to final recipient \n{}\n", + msg + ); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); +} + +async fn re_wrapping_for_mediator_unknown_to_sender() { + // --- Building message from ALICE to BOB --- + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(BOB_DID.to_owned()) + .from(ALICE_DID.to_owned()) + .finalize(); + + // --- Packing encrypted and authenticated message --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let (msg, metadata) = msg + .pack_encrypted( + BOB_DID, + Some(ALICE_DID), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable pack_encrypted"); + + println!("Encryption metadata is\n{:?}\n", metadata); + + // --- Sending message by Alice --- + println!("Alice is sending message \n{}\n", msg); + + // --- Unpacking message by Mediator1 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator1 received message is \n{:?}\n", msg); + + println!( + "Mediator1 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Re-wrapping forwarded message for mediator unknown to sender by Mediator1 --- + let parsed_forward = try_parse_forward(&msg).unwrap(); + let msg = serde_json::to_string(&parsed_forward.forwarded_msg).unwrap(); + + println!("Mediator1 retrieved forwarded message \n{}\n", msg); + + let msg = wrap_in_forward( + &msg, + None, + &parsed_forward.next, + &vec!["did:example:mediator2#key-x25519-1".to_owned()], + &AnonCryptAlg::default(), + &did_resolver, + ) + .await + .expect("Unable wrap in forward"); + + // --- Forwarding re-wrapped message by Mediator1 --- + println!( + "Mediator1 is forwarding re-wrapped message to mediator unknown to sender \n{}\n", + msg + ); + + // --- Unpacking message by Mediator2 --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(MEDIATOR2_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Mediator2 received message is \n{:?}\n", msg); + println!( + "Mediator2 received message unpack metadata is \n{:?}\n", + metadata + ); + + // --- Forwarding message by Mediator2 --- + let msg = serde_json::to_string(&try_parse_forward(&msg).unwrap().forwarded_msg).unwrap(); + + println!("Mediator2 is forwarding message \n{}\n", msg); + + // --- Unpacking message by Bob --- + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (msg, metadata) = Message::unpack( + &msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + println!("Bob received message is \n{:?}\n", msg); + println!("Bob received message unpack metadata is \n{:?}\n", metadata); +} diff --git a/src/algorithms.rs b/src/algorithms.rs index c2cff75..455a547 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -1,5 +1,7 @@ +use serde::{Deserialize, Serialize}; + /// Algorithms for anonymous encryption -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub enum AnonCryptAlg { /// AES256-CBC + HMAC-SHA512 with a 512 bit key content encryption, /// ECDH-ES key agreement with A256KW key wrapping @@ -14,14 +16,26 @@ pub enum AnonCryptAlg { A256gcmEcdhEsA256kw, } -#[derive(Debug, PartialEq, Eq, Clone)] +impl Default for AnonCryptAlg { + fn default() -> Self { + AnonCryptAlg::Xc20pEcdhEsA256kw + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub enum AuthCryptAlg { /// AES256-CBC + HMAC-SHA512 with a 512 bit key content encryption, /// ECDH-1PU key agreement with A256KW key wrapping A256cbcHs512Ecdh1puA256kw, } -#[derive(Debug, PartialEq, Eq, Clone)] +impl Default for AuthCryptAlg { + fn default() -> Self { + AuthCryptAlg::A256cbcHs512Ecdh1puA256kw + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub enum SignAlg { EdDSA, ES256, diff --git a/src/did/did_doc.rs b/src/did/did_doc.rs index cd4aa2d..6ada163 100644 --- a/src/did/did_doc.rs +++ b/src/did/did_doc.rs @@ -1,9 +1,10 @@ //! Set of interfaces that describe DID Document (https://www.w3.org/TR/did-core/) +use serde::{Deserialize, Serialize}; use serde_json::Value; /// Represents DID Document (https://www.w3.org/TR/did-core/) -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct DIDDoc { /// DID for the given DID Doc pub did: String, @@ -19,62 +20,84 @@ pub struct DIDDoc { /// All local verification methods including embedded to /// key agreement and authentication sections. /// See https://www.w3.org/TR/did-core/#verification-methods. - // TODO: Remove allow pub verification_methods: Vec, /// All services (https://www.w3.org/TR/did-core/#services) - // TODO: Remove allow pub services: Vec, } /// Represents verification method record in DID Document /// (https://www.w3.org/TR/did-core/#verification-methods). -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct VerificationMethod { pub id: String, + #[serde(rename = "type")] pub type_: VerificationMethodType, pub controller: String, pub verification_material: VerificationMaterial, } -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub enum VerificationMethodType { JsonWebKey2020, X25519KeyAgreementKey2019, Ed25519VerificationKey2018, EcdsaSecp256k1VerificationKey2019, - Other(String), + X25519KeyAgreementKey2020, + Ed25519VerificationKey2020, + Other, } /// Represents verification material (https://www.w3.org/TR/did-core/#verification-material) -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub enum VerificationMaterial { - JWK(Value), - Multibase(String), - Base58(String), - Hex(String), - Other(Value), + JWK { + #[serde(flatten)] + value: Value, + }, + Multibase { + #[serde(flatten)] + value: String, + }, + Base58 { + #[serde(flatten)] + value: String, + }, + Hex { + #[serde(flatten)] + value: String, + }, + Other { + #[serde(flatten)] + value: Value, + }, } /// Represents service record in DID Document (https://www.w3.org/TR/did-core/#services). -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct Service { pub id: String, pub kind: ServiceKind, } /// Represents additional service properties defined for specific Service type. -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub enum ServiceKind { - DIDCommMessaging(DIDCommMessagingService), - Other(Value), + DIDCommMessaging { + #[serde(flatten)] + value: DIDCommMessagingService, + }, + Other { + #[serde(flatten)] + value: Value, + }, } /// Properties for DIDCommMessagingService /// (https://identity.foundation/didcomm-messaging/spec/#did-document-service-endpoint). -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct DIDCommMessagingService { pub service_endpoint: String, pub accept: Vec, - pub route_keys: Vec, + pub routing_keys: Vec, } diff --git a/src/did/did_resolver.rs b/src/did/did_resolver.rs index 8dea517..7f2750e 100644 --- a/src/did/did_resolver.rs +++ b/src/did/did_resolver.rs @@ -5,7 +5,26 @@ use async_trait::async_trait; use crate::{did::did_doc::DIDDoc, error::Result}; /// Represents DID Doc resolver (https://www.w3.org/TR/did-core/#did-resolution). +#[cfg(feature = "uniffi")] #[async_trait] +pub trait DIDResolver: Sync { + /// Resolves a DID document by the given DID. + /// + /// # Params + /// - `did` a DID to be resolved. + /// + /// # Returns + /// An instance of resolved DID DOC or None if DID is not found. + /// + /// # Errors + /// - `IoError` IO error during resolving + /// - `InvalidState` indicates a bug in resolver code + async fn resolve(&self, did: &str) -> Result>; +} + +/// Represents DID Doc resolver (https://www.w3.org/TR/did-core/#did-resolution). +#[cfg(not(feature = "uniffi"))] +#[async_trait(?Send)] pub trait DIDResolver { /// Resolves a DID document by the given DID. /// diff --git a/src/did/resolvers/example.rs b/src/did/resolvers/example.rs index d6b5cfe..1b7a6d2 100644 --- a/src/did/resolvers/example.rs +++ b/src/did/resolvers/example.rs @@ -16,7 +16,8 @@ impl ExampleDIDResolver { } } -#[async_trait] +#[cfg_attr(feature = "uniffi", async_trait)] +#[cfg_attr(not(feature = "uniffi"), async_trait(?Send))] impl DIDResolver for ExampleDIDResolver { async fn resolve(&self, did: &str) -> Result> { Ok(self diff --git a/src/did/resolvers/mock.rs b/src/did/resolvers/mock.rs new file mode 100644 index 0000000..6d4c3c3 --- /dev/null +++ b/src/did/resolvers/mock.rs @@ -0,0 +1,25 @@ +use async_trait::async_trait; +use std::cell::RefCell; +use std::sync::Mutex; + +use crate::did::{DIDDoc, DIDResolver}; + +pub struct MockDidResolver { + results: Mutex>>>>, +} + +impl MockDidResolver { + pub fn new(res: Vec>>) -> Self { + Self { + results: Mutex::new(RefCell::new(res)), + } + } +} + +#[cfg_attr(feature = "uniffi", async_trait)] +#[cfg_attr(not(feature = "uniffi"), async_trait(?Send))] +impl DIDResolver for MockDidResolver { + async fn resolve(&self, _did: &str) -> crate::error::Result> { + self.results.lock().unwrap().borrow_mut().pop().unwrap() + } +} diff --git a/src/did/resolvers/mod.rs b/src/did/resolvers/mod.rs index 9b8abc6..9a7bafb 100644 --- a/src/did/resolvers/mod.rs +++ b/src/did/resolvers/mod.rs @@ -1,3 +1,9 @@ mod example; +#[cfg(test)] +mod mock; + pub use example::ExampleDIDResolver; + +#[cfg(test)] +pub(crate) use mock::MockDidResolver; diff --git a/src/error.rs b/src/error.rs index 94e9d66..27d5f02 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,14 @@ -use serde::Serialize; use std::fmt; +use serde::Serialize; +use serde_json::error::Category; + #[derive(thiserror::Error, Debug, Copy, Clone, Eq, PartialEq, Serialize)] pub enum ErrorKind { #[error("DID not resolved")] DIDNotResolved, - #[error("DID not resolved")] + #[error("DID URL not found")] DIDUrlNotFound, #[error("Secret not found")] @@ -26,6 +28,9 @@ pub enum ErrorKind { #[error("Unsupported crypto or method")] Unsupported, + + #[error("Illegal argument")] + IllegalArgument, } #[derive(Debug, thiserror::Error)] @@ -84,6 +89,27 @@ where } } +pub trait ResultExtNoContext { + fn to_error_kind(self, kind: ErrorKind) -> std::result::Result; + + fn kind_no_context(self, kind: ErrorKind, msg: D) -> Result + where + D: fmt::Display + fmt::Debug + Send + Sync + 'static; +} + +impl ResultExtNoContext for std::result::Result { + fn to_error_kind(self, kind: ErrorKind) -> std::result::Result { + self.map_err(|_| kind) + } + + fn kind_no_context(self, kind: ErrorKind, msg: D) -> Result + where + D: fmt::Display + fmt::Debug + Send + Sync + 'static, + { + self.map_err(|_| Error::msg(kind, msg)) + } +} + pub trait ResultContext { fn context(self, msg: D) -> Result where @@ -106,11 +132,70 @@ impl ResultContext for Result { } } +pub trait ToResult { + fn to_didcomm(self, msg: D) -> Result + where + D: fmt::Display + fmt::Debug + Send + Sync + 'static; +} + +impl ToResult for serde_json::Result { + fn to_didcomm(self, msg: D) -> Result + where + D: fmt::Display + fmt::Debug + Send + Sync + 'static, + { + ResultContext::context(self.map_err(|e| e.into()), msg) + } +} + +impl ToResult for bs58::decode::Result { + fn to_didcomm(self, msg: D) -> Result + where + D: fmt::Display + fmt::Debug + Send + Sync + 'static, + { + ResultContext::context(self.map_err(|e| e.into()), msg) + } +} + +impl ToResult for bs58::encode::Result { + fn to_didcomm(self, msg: D) -> Result + where + D: fmt::Display + fmt::Debug + Send + Sync + 'static, + { + ResultContext::context(self.map_err(|e| e.into()), msg) + } +} + +impl From for Error { + fn from(err: serde_json::Error) -> Self { + match err.classify() { + Category::Io | Category::Eof => Error::msg(ErrorKind::InvalidState, err.to_string()), + _ => Error::msg(ErrorKind::Malformed, err.to_string()), + } + } +} + +impl From for Error { + fn from(err: bs58::decode::Error) -> Self { + match err { + bs58::decode::Error::BufferTooSmall => { + Error::msg(ErrorKind::InvalidState, err.to_string()) + } + _ => Error::msg(ErrorKind::Malformed, err.to_string()), + } + } +} + +impl From for Error { + fn from(err: bs58::encode::Error) -> Self { + match err { + _ => Error::msg(ErrorKind::InvalidState, err.to_string()), + } + } +} + pub fn err_msg(kind: ErrorKind, msg: D) -> Error where D: fmt::Display + fmt::Debug + Send + Sync + 'static, { Error::msg(kind, msg) } - -// TODO: Provide `From` implementation for serde and base64 errors to explicitly split malformed and no-memory errors diff --git a/src/jwe/decrypt.rs b/src/jwe/decrypt.rs index b7cdd9c..5aea6d5 100644 --- a/src/jwe/decrypt.rs +++ b/src/jwe/decrypt.rs @@ -16,7 +16,7 @@ impl<'a, 'b> ParsedJWE<'a, 'b> { pub(crate) fn decrypt( &self, sender: Option<(&str, &KE)>, - recepient: (&str, &KE), + recipient: (&str, &KE), ) -> Result> where CE: KeyAeadInPlace + KeySecretBytes, @@ -29,7 +29,7 @@ impl<'a, 'b> ParsedJWE<'a, 'b> { None => (None, None), }; - let (kid, key) = recepient; + let (kid, key) = recipient; if skid.map(str::as_bytes) != self.apu.as_deref() { Err(err_msg(ErrorKind::InvalidState, "Wrong skid used"))? @@ -41,7 +41,7 @@ impl<'a, 'b> ParsedJWE<'a, 'b> { .recipients .iter() .find(|r| r.header.kid == kid) - .ok_or_else(|| err_msg(ErrorKind::InvalidState, "Recepient not found"))? + .ok_or_else(|| err_msg(ErrorKind::InvalidState, "Recipient not found"))? .encrypted_key; base64::decode_config(encrypted_key, base64::URL_SAFE_NO_PAD) @@ -297,7 +297,7 @@ mod tests { fn _decrypt_works( sender: Option<(&str, &str)>, - recepient: (&str, &str), + recipient: (&str, &str), msg: &str, payload: &str, ) where @@ -306,7 +306,7 @@ mod tests { KE: KeyExchange + KeyGen + ToJwkValue + FromJwkValue, KW: KeyWrap + FromKeyDerivation, { - let res = _decrypt::(sender, recepient, msg); + let res = _decrypt::(sender, recipient, msg); let res = res.expect("res is err"); assert_eq!(res, payload.as_bytes()); } @@ -363,7 +363,7 @@ mod tests { } #[test] - fn decrypt_works_recepient_not_found() { + fn decrypt_works_recipient_not_found() { let res = _decrypt::< AesKey, Ecdh1PU<'_, P256KeyPair>, @@ -377,7 +377,7 @@ mod tests { let err = res.expect_err("res is ok"); assert_eq!(err.kind(), ErrorKind::InvalidState); - assert_eq!(format!("{}", err), "Invalid state: Recepient not found"); + assert_eq!(format!("{}", err), "Invalid state: Recipient not found"); } #[test] @@ -511,8 +511,8 @@ mod tests { } #[test] - fn decrypt_works_different_recepient_key() { - _decrypt_works_different_recepient_key::< + fn decrypt_works_different_recipient_key() { + _decrypt_works_different_recipient_key::< Chacha20Key, EcdhEs<'_, X25519KeyPair>, X25519KeyPair, @@ -523,7 +523,7 @@ mod tests { MSG_ANONCRYPT_X25519_XC20P, ); - _decrypt_works_different_recepient_key::< + _decrypt_works_different_recipient_key::< AesKey, EcdhEs<'_, X25519KeyPair>, X25519KeyPair, @@ -534,7 +534,7 @@ mod tests { MSG_ANONCRYPT_X25519_A256CBC, ); - _decrypt_works_different_recepient_key::< + _decrypt_works_different_recipient_key::< AesKey, EcdhEs<'_, X25519KeyPair>, X25519KeyPair, @@ -545,7 +545,7 @@ mod tests { MSG_ANONCRYPT_X25519_A256GSM, ); - _decrypt_works_different_recepient_key::< + _decrypt_works_different_recipient_key::< Chacha20Key, EcdhEs<'_, P256KeyPair>, P256KeyPair, @@ -556,7 +556,7 @@ mod tests { MSG_ANONCRYPT_P256_XC20P, ); - _decrypt_works_different_recepient_key::< + _decrypt_works_different_recipient_key::< AesKey, EcdhEs<'_, P256KeyPair>, P256KeyPair, @@ -567,7 +567,7 @@ mod tests { MSG_ANONCRYPT_P256_A256CBC, ); - _decrypt_works_different_recepient_key::< + _decrypt_works_different_recipient_key::< AesKey, EcdhEs<'_, P256KeyPair>, P256KeyPair, @@ -578,7 +578,7 @@ mod tests { MSG_ANONCRYPT_P256_A256GSM, ); - _decrypt_works_different_recepient_key::< + _decrypt_works_different_recipient_key::< AesKey, Ecdh1PU<'_, X25519KeyPair>, X25519KeyPair, @@ -589,7 +589,7 @@ mod tests { MSG_AUTHCRYPT_X25519_A256CBC, ); - _decrypt_works_different_recepient_key::< + _decrypt_works_different_recipient_key::< AesKey, Ecdh1PU<'_, P256KeyPair>, P256KeyPair, @@ -600,9 +600,9 @@ mod tests { MSG_AUTHCRYPT_P256_A256CBC, ); - fn _decrypt_works_different_recepient_key( + fn _decrypt_works_different_recipient_key( sender: Option<(&str, &str)>, - recepient: (&str, &str), + recipient: (&str, &str), msg: &str, ) where CE: KeyAeadInPlace + KeySecretBytes, @@ -610,7 +610,7 @@ mod tests { KE: KeyExchange + KeyGen + ToJwkValue + FromJwkValue, KW: KeyWrap + FromKeyDerivation, { - let res = _decrypt::(sender, recepient, msg); + let res = _decrypt::(sender, recipient, msg); let err = res.expect_err("res is ok"); assert_eq!(err.kind(), ErrorKind::Malformed); @@ -714,7 +714,7 @@ mod tests { fn _decrypt_works_changed_enc_key( sender: Option<(&str, &str)>, - recepient: (&str, &str), + recipient: (&str, &str), msg: &str, ) where CE: KeyAeadInPlace + KeySecretBytes, @@ -722,7 +722,7 @@ mod tests { KE: KeyExchange + KeyGen + ToJwkValue + FromJwkValue, KW: KeyWrap + FromKeyDerivation, { - let res = _decrypt::(sender, recepient, msg); + let res = _decrypt::(sender, recipient, msg); let err = res.expect_err("res is ok"); assert_eq!(err.kind(), ErrorKind::Malformed); @@ -834,7 +834,7 @@ mod tests { fn decrypt_works_changed_second_enc_key( sender: Option<(&str, &str)>, - recepient: (&str, &str), + recipient: (&str, &str), msg: &str, payload: &str, ) where @@ -843,7 +843,7 @@ mod tests { KE: KeyExchange + KeyGen + ToJwkValue + FromJwkValue, KW: KeyWrap + FromKeyDerivation, { - let res = _decrypt::(sender, recepient, msg); + let res = _decrypt::(sender, recipient, msg); let res = res.expect("res is err"); assert_eq!(&res, payload.as_bytes()); } @@ -941,7 +941,7 @@ mod tests { fn _decrypt_works_changed_ciphertext( sender: Option<(&str, &str)>, - recepient: (&str, &str), + recipient: (&str, &str), msg: &str, ) where CE: KeyAeadInPlace + KeySecretBytes, @@ -949,7 +949,7 @@ mod tests { KE: KeyExchange + KeyGen + ToJwkValue + FromJwkValue, KW: KeyWrap + FromKeyDerivation, { - let res = _decrypt::(sender, recepient, msg); + let res = _decrypt::(sender, recipient, msg); let err = res.expect_err("res is ok"); assert_eq!(err.kind(), ErrorKind::Malformed); @@ -963,7 +963,7 @@ mod tests { fn _decrypt( sender: Option<(&str, &str)>, - recepient: (&str, &str), + recipient: (&str, &str), msg: &str, ) -> Result, Error> where @@ -975,15 +975,15 @@ mod tests { let _sender = sender.map(|(kid, k)| (kid, KE::from_jwk(k).expect("Unable from_jwk"))); let sender = _sender.as_ref().map(|(kid, k)| (*kid, k)); - let recepient = ( - recepient.0, - &KE::from_jwk(recepient.1).expect("Unable from_jwk"), + let recipient = ( + recipient.0, + &KE::from_jwk(recipient.1).expect("Unable from_jwk"), ); let mut buf = vec![]; let msg = jwe::parse(&msg, &mut buf).expect("Unable parse"); - msg.decrypt::(sender, recepient) + msg.decrypt::(sender, recipient) } const PAYLOAD: &str = r#"{"id":"1234567890","typ":"application/didcomm-plain+json","type":"http://example.com/protocols/lets_do_lunch/1.0/proposal","from":"did:example:alice","to":["did:example:bob"],"created_time":1516269022,"expires_time":1516385931,"body":{"messagespecificattribute":"and its value"}}"#; diff --git a/src/jwe/encrypt.rs b/src/jwe/encrypt.rs index 90934a7..a5ec722 100644 --- a/src/jwe/encrypt.rs +++ b/src/jwe/encrypt.rs @@ -10,7 +10,7 @@ use sha2::{Digest, Sha256}; use crate::{ error::{ErrorKind, Result, ResultExt}, - jwe::envelope::{Algorithm, EncAlgorithm, PerRecipientHeader, ProtectedHeader, Recepient, JWE}, + jwe::envelope::{Algorithm, EncAlgorithm, PerRecipientHeader, ProtectedHeader, Recipient, JWE}, jwk::ToJwkValue, utils::crypto::{JoseKDF, KeyWrap}, }; @@ -104,7 +104,7 @@ where &tag_raw, false, ) - .kind(ErrorKind::InvalidState, "Unable derive kw")?; + .kind(ErrorKind::InvalidState, "Unable derive kw")?; //TODO Check this test and move to decrypt let encrypted_key = kw .wrap_key(&cek) @@ -119,7 +119,7 @@ where let recipients: Vec<_> = encrypted_keys .iter() - .map(|(kid, encrypted_key)| Recepient { + .map(|(kid, encrypted_key)| Recipient { header: PerRecipientHeader { kid }, encrypted_key: &encrypted_key, }) @@ -336,6 +336,15 @@ mod tests { EncAlgorithm::Xc20P, ); + _encrypt_works::, EcdhEs<'_, P256KeyPair>, P256KeyPair, AesKey>( + None, + &[ + (BOB_KID_P256_1, BOB_KEY_P256_1, BOB_PKEY_P256_1), + (BOB_KID_P256_2, BOB_KEY_P256_2, BOB_PKEY_P256_2), + ], + Algorithm::Other("otherAlg".to_owned()), + EncAlgorithm::A256Gcm, + ); /// TODO: P-384 and P-521 support after solving https://github.com/hyperledger/aries-askar/issues/10 fn _encrypt_works( @@ -399,7 +408,7 @@ mod tests { let bob_edge_priv = bob_priv .iter() .find(|b| recipient.header.kid == b.0) - .expect("recepint not found."); + .expect("recipient not found."); let plaintext_ = msg .decrypt::(alice_pub, *bob_edge_priv) diff --git a/src/jwe/envelope.rs b/src/jwe/envelope.rs index ede2654..42c9086 100644 --- a/src/jwe/envelope.rs +++ b/src/jwe/envelope.rs @@ -11,7 +11,7 @@ pub(crate) struct JWE<'a> { pub protected: &'a str, /// Array of recipient-specific objects - pub recipients: Vec>, + pub recipients: Vec>, /// BASE64URL(JWE Initialization Vector) pub iv: &'a str, @@ -57,7 +57,7 @@ pub(crate) struct ProtectedHeader<'a> { } /// Recipient part of authcrypt/anoncrypt-specific JWE #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub(crate) struct Recepient<'a> { +pub(crate) struct Recipient<'a> { /// Per-recipient header /// Note it isn't serialized and not integrity protected pub header: PerRecipientHeader<'a>, diff --git a/src/jwe/parse.rs b/src/jwe/parse.rs index 72e9e35..a22cc3b 100644 --- a/src/jwe/parse.rs +++ b/src/jwe/parse.rs @@ -1,5 +1,6 @@ use sha2::{Digest, Sha256}; +use crate::error::ToResult; use crate::{ error::{err_msg, ErrorKind, Result, ResultExt}, jwe::envelope::{ProtectedHeader, JWE}, @@ -14,31 +15,39 @@ pub(crate) struct ParsedJWE<'a, 'b> { } pub(crate) fn parse<'a, 'b>(jwe: &'a str, buf: &'b mut Vec) -> Result> { - let jwe: JWE = serde_json::from_str(jwe).kind(ErrorKind::Malformed, "Unable parse jwe")?; + JWE::from_str(jwe)?.parse(buf) +} - base64::decode_config_buf(jwe.protected, base64::URL_SAFE_NO_PAD, buf) - .kind(ErrorKind::Malformed, "Unable decode protected header")?; +impl<'a> JWE<'a> { + pub(crate) fn from_str(s: &str) -> Result { + serde_json::from_str(s).to_didcomm("Unable parse jwe") + } - let protected: ProtectedHeader = - serde_json::from_slice(buf).kind(ErrorKind::Malformed, "Unable parse protected header")?; + pub(crate) fn parse<'b>(self, buf: &'b mut Vec) -> Result> { + base64::decode_config_buf(self.protected, base64::URL_SAFE_NO_PAD, buf) + .kind(ErrorKind::Malformed, "Unable decode protected header")?; - let apv = base64::decode_config(protected.apv, base64::URL_SAFE_NO_PAD) - .kind(ErrorKind::Malformed, "Unable decode apv")?; + let protected: ProtectedHeader = + serde_json::from_slice(buf).to_didcomm("Unable parse protected header")?; - let apu = protected - .apu - .map(|apu| base64::decode_config(apu, base64::URL_SAFE_NO_PAD)) - .transpose() - .kind(ErrorKind::Malformed, "Unable decode apv")?; + let apv = base64::decode_config(protected.apv, base64::URL_SAFE_NO_PAD) + .kind(ErrorKind::Malformed, "Unable decode apv")?; - let jwe = ParsedJWE { - jwe, - protected, - apu, - apv, - }; + let apu = protected + .apu + .map(|apu| base64::decode_config(apu, base64::URL_SAFE_NO_PAD)) + .transpose() + .kind(ErrorKind::Malformed, "Unable decode apu")?; + + let jwe = ParsedJWE { + jwe: self, + protected, + apu, + apv, + }; - Ok(jwe) + Ok(jwe) + } } impl<'a, 'b> ParsedJWE<'a, 'b> { @@ -87,7 +96,7 @@ mod tests { error::ErrorKind, jwe::{ self, - envelope::{EncAlgorithm, PerRecipientHeader, ProtectedHeader, Recepient, JWE}, + envelope::{EncAlgorithm, PerRecipientHeader, ProtectedHeader, Recipient, JWE}, ParsedJWE, }, }; @@ -131,15 +140,15 @@ mod tests { jwe: JWE { protected: "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkpIanNtSVJaQWFCMHpSR193TlhMVjJyUGdnRjAwaGRIYlc1cmo4ZzBJMjQifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInR5cCI6ImFwcGxpY2F0aW9uL2RpZGNvbW0tZW5jcnlwdGVkK2pzb24iLCJlbmMiOiJYQzIwUCIsImFsZyI6IkVDREgtRVMrQTI1NktXIn0", recipients: vec![ - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-1" }, encrypted_key: "3n1olyBR3nY7ZGAprOx-b7wYAKza6cvOYjNwVg3miTnbLwPP_FmE1A", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-2" }, encrypted_key: "j5eSzn3kCrIkhQAWPnEwrFPMW6hG0zF_y37gUvvc5gvlzsuNX4hXrQ", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-3" }, encrypted_key: "TEWlqlq-ao7Lbynf0oZYhxs7ZB39SUWBCK4qjqQqfeItfwmNyDm73A", }, @@ -208,15 +217,15 @@ mod tests { jwe: JWE { protected: "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkpIanNtSVJaQWFCMHpSR193TlhMVjJyUGdnRjAwaGRIYlc1cmo4ZzBJMjQifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInR5cCI6ImFwcGxpY2F0aW9uL2RpZGNvbW0tZW5jcnlwdGVkK2pzb24iLCJlbmMiOiJYQzIwUCIsImFsZyI6IkVDREgtRVMrQTI1NktXIn0", recipients: vec![ - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-1" }, encrypted_key: "3n1olyBR3nY7ZGAprOx-b7wYAKza6cvOYjNwVg3miTnbLwPP_FmE1A", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-2" }, encrypted_key: "j5eSzn3kCrIkhQAWPnEwrFPMW6hG0zF_y37gUvvc5gvlzsuNX4hXrQ", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-3" }, encrypted_key: "TEWlqlq-ao7Lbynf0oZYhxs7ZB39SUWBCK4qjqQqfeItfwmNyDm73A", }, @@ -284,15 +293,15 @@ mod tests { jwe: JWE { protected: "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkpIanNtSVJaQWFCMHpSR193TlhMVjJyUGdnRjAwaGRIYlc1cmo4ZzBJMjQifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInR5cCI6ImFwcGxpY2F0aW9uL2RpZGNvbW0tZW5jcnlwdGVkK2pzb24iLCJlbmMiOiJYQzIwUCIsImFsZyI6IkVDREgtRVMrQTI1NktXIiwiZXh0cmEiOiJ2YWx1ZSJ9", recipients: vec![ - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-1" }, encrypted_key: "3n1olyBR3nY7ZGAprOx-b7wYAKza6cvOYjNwVg3miTnbLwPP_FmE1A", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-2" }, encrypted_key: "j5eSzn3kCrIkhQAWPnEwrFPMW6hG0zF_y37gUvvc5gvlzsuNX4hXrQ", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-3" }, encrypted_key: "TEWlqlq-ao7Lbynf0oZYhxs7ZB39SUWBCK4qjqQqfeItfwmNyDm73A", }, @@ -360,15 +369,15 @@ mod tests { jwe: JWE { protected: "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkdGY01vcEpsamY0cExaZmNoNGFfR2hUTV9ZQWY2aU5JMWRXREd5VkNhdzAifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInNraWQiOiJkaWQ6ZXhhbXBsZTphbGljZSNrZXkteDI1NTE5LTEiLCJhcHUiOiJaR2xrT21WNFlXMXdiR1U2WVd4cFkyVWphMlY1TFhneU5UVXhPUzB4IiwidHlwIjoiYXBwbGljYXRpb24vZGlkY29tbS1lbmNyeXB0ZWQranNvbiIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJhbGciOiJFQ0RILTFQVStBMjU2S1cifQ", recipients: vec![ - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-1" }, encrypted_key: "o0FJASHkQKhnFo_rTMHTI9qTm_m2mkJp-wv96mKyT5TP7QjBDuiQ0AMKaPI_RLLB7jpyE-Q80Mwos7CvwbMJDhIEBnk2qHVB", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-2" }, encrypted_key: "rYlafW0XkNd8kaXCqVbtGJ9GhwBC3lZ9AihHK4B6J6V2kT7vjbSYuIpr1IlAjvxYQOw08yqEJNIwrPpB0ouDzKqk98FVN7rK", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-3" }, encrypted_key: "aqfxMY2sV-njsVo-_9Ke9QbOf6hxhGrUVh_m-h_Aq530w3e_4IokChfKWG1tVJvXYv_AffY7vxj0k5aIfKZUxiNmBwC_QsNo", }, @@ -437,15 +446,15 @@ mod tests { jwe: JWE { protected: "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkdGY01vcEpsamY0cExaZmNoNGFfR2hUTV9ZQWY2aU5JMWRXREd5VkNhdzAifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInNraWQiOiJkaWQ6ZXhhbXBsZTphbGljZSNrZXkteDI1NTE5LTEiLCJhcHUiOiJaR2xrT21WNFlXMXdiR1U2WVd4cFkyVWphMlY1TFhneU5UVXhPUzB4IiwidHlwIjoiYXBwbGljYXRpb24vZGlkY29tbS1lbmNyeXB0ZWQranNvbiIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJhbGciOiJFQ0RILTFQVStBMjU2S1cifQ", recipients: vec![ - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-1" }, encrypted_key: "o0FJASHkQKhnFo_rTMHTI9qTm_m2mkJp-wv96mKyT5TP7QjBDuiQ0AMKaPI_RLLB7jpyE-Q80Mwos7CvwbMJDhIEBnk2qHVB", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-2" }, encrypted_key: "rYlafW0XkNd8kaXCqVbtGJ9GhwBC3lZ9AihHK4B6J6V2kT7vjbSYuIpr1IlAjvxYQOw08yqEJNIwrPpB0ouDzKqk98FVN7rK", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-3" }, encrypted_key: "aqfxMY2sV-njsVo-_9Ke9QbOf6hxhGrUVh_m-h_Aq530w3e_4IokChfKWG1tVJvXYv_AffY7vxj0k5aIfKZUxiNmBwC_QsNo", }, @@ -513,15 +522,15 @@ mod tests { jwe: JWE { protected: "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkdGY01vcEpsamY0cExaZmNoNGFfR2hUTV9ZQWY2aU5JMWRXREd5VkNhdzAifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInNraWQiOiJkaWQ6ZXhhbXBsZTphbGljZSNrZXkteDI1NTE5LTEiLCJhcHUiOiJaR2xrT21WNFlXMXdiR1U2WVd4cFkyVWphMlY1TFhneU5UVXhPUzB4IiwidHlwIjoiYXBwbGljYXRpb24vZGlkY29tbS1lbmNyeXB0ZWQranNvbiIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJhbGciOiJFQ0RILTFQVStBMjU2S1ciLCJleHRyYSI6InZhbHVlIn0=", recipients: vec![ - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-1" }, encrypted_key: "o0FJASHkQKhnFo_rTMHTI9qTm_m2mkJp-wv96mKyT5TP7QjBDuiQ0AMKaPI_RLLB7jpyE-Q80Mwos7CvwbMJDhIEBnk2qHVB", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-2" }, encrypted_key: "rYlafW0XkNd8kaXCqVbtGJ9GhwBC3lZ9AihHK4B6J6V2kT7vjbSYuIpr1IlAjvxYQOw08yqEJNIwrPpB0ouDzKqk98FVN7rK", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-3" }, encrypted_key: "aqfxMY2sV-njsVo-_9Ke9QbOf6hxhGrUVh_m-h_Aq530w3e_4IokChfKWG1tVJvXYv_AffY7vxj0k5aIfKZUxiNmBwC_QsNo", }, @@ -803,7 +812,7 @@ mod tests { assert_eq!( format!("{}", err), - "Malformed: Unable decode apv: Encoded text cannot have a 6-bit remainder." + "Malformed: Unable decode apu: Encoded text cannot have a 6-bit remainder." ); } @@ -938,15 +947,15 @@ mod tests { jwe: JWE { protected: "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkpIanNtSVJaQWFCMHpSR193TlhMVjJyUGdnRjAwaGRIYlc1cmo4ZzBJMjQifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInR5cCI6ImFwcGxpY2F0aW9uL2RpZGNvbW0tZW5jcnlwdGVkK2pzb24iLCJlbmMiOiJYQzIwUCIsImFsZyI6IkVDREgtRVMrQTI1NktXIn0", recipients: vec![ - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-1" }, encrypted_key: "3n1olyBR3nY7ZGAprOx-b7wYAKza6cvOYjNwVg3miTnbLwPP_FmE1A", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-2" }, encrypted_key: "j5eSzn3kCrIkhQAWPnEwrFPMW6hG0zF_y37gUvvc5gvlzsuNX4hXrQ", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-3" }, encrypted_key: "TEWlqlq-ao7Lbynf0oZYhxs7ZB39SUWBCK4qjqQqfeItfwmNyDm73A", }, @@ -1017,15 +1026,15 @@ mod tests { jwe: JWE { protected: "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkdGY01vcEpsamY0cExaZmNoNGFfR2hUTV9ZQWY2aU5JMWRXREd5VkNhdzAifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInNraWQiOiJkaWQ6ZXhhbXBsZTphbGljZSNrZXkteDI1NTE5LTEiLCJhcHUiOiJaR2xrT21WNFlXMXdiR1U2WVd4cFkyVWphMlY1TFhneU5UVXhPUzB4IiwidHlwIjoiYXBwbGljYXRpb24vZGlkY29tbS1lbmNyeXB0ZWQranNvbiIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJhbGciOiJFQ0RILTFQVStBMjU2S1cifQ", recipients: vec![ - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-1" }, encrypted_key: "o0FJASHkQKhnFo_rTMHTI9qTm_m2mkJp-wv96mKyT5TP7QjBDuiQ0AMKaPI_RLLB7jpyE-Q80Mwos7CvwbMJDhIEBnk2qHVB", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-2" }, encrypted_key: "rYlafW0XkNd8kaXCqVbtGJ9GhwBC3lZ9AihHK4B6J6V2kT7vjbSYuIpr1IlAjvxYQOw08yqEJNIwrPpB0ouDzKqk98FVN7rK", }, - Recepient { + Recipient { header: PerRecipientHeader { kid: "did:example:bob#key-x25519-3" }, encrypted_key: "aqfxMY2sV-njsVo-_9Ke9QbOf6hxhGrUVh_m-h_Aq530w3e_4IokChfKWG1tVJvXYv_AffY7vxj0k5aIfKZUxiNmBwC_QsNo", }, diff --git a/src/jws/envelope.rs b/src/jws/envelope.rs index 76cafb2..6a34aa4 100644 --- a/src/jws/envelope.rs +++ b/src/jws/envelope.rs @@ -50,6 +50,19 @@ pub(crate) struct Header<'a> { pub kid: &'a str, } +/// Header of compactly serialized JWS. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +pub(crate) struct CompactHeader<'a> { + /// Media type of this complete JWS. + pub typ: &'a str, + + /// Cryptographic algorithm used to produce signature. + pub alg: Algorithm, + + /// KID used to produce signature as DID URL. + pub kid: &'a str, +} + /// Represents possible values for `alg` header. /// Cryptographic algorithm used to produce signature over JWS payload. #[derive(Deserialize_enum_str, Serialize_enum_str, Debug, Clone, Eq, PartialEq)] @@ -73,9 +86,10 @@ impl Algorithm { Algorithm::EdDSA => SignatureType::EdDSA, Algorithm::Es256 => SignatureType::ES256, Algorithm::Es256K => SignatureType::ES256K, - Algorithm::Other(_) => { - Err(err_msg(ErrorKind::Unsupported, "Unsuported signature type"))? - } + Algorithm::Other(_) => Err(err_msg( + ErrorKind::Unsupported, + "Unsupported signature type", + ))?, }; Ok(sig_type) diff --git a/src/jws/mod.rs b/src/jws/mod.rs index b79384c..f7f5e80 100644 --- a/src/jws/mod.rs +++ b/src/jws/mod.rs @@ -16,15 +16,15 @@ mod verify; // TODO: Remove allow #[allow(unused_imports)] -pub(crate) use envelope::{Algorithm, Header, ProtectedHeader, Signature, JWS}; +pub(crate) use envelope::{Algorithm, CompactHeader, Header, ProtectedHeader, Signature, JWS}; // TODO: Remove allow #[allow(unused_imports)] -pub(crate) use sign::sign; +pub(crate) use sign::{sign, sign_compact}; // TODO: Remove allow #[allow(unused_imports)] -pub(crate) use parse::{parse, ParsedJWS}; +pub(crate) use parse::{parse, parse_compact, ParsedCompactJWS, ParsedJWS}; #[cfg(test)] mod tests { diff --git a/src/jws/parse.rs b/src/jws/parse.rs index 0bdd05b..9b1eda4 100644 --- a/src/jws/parse.rs +++ b/src/jws/parse.rs @@ -1,6 +1,7 @@ +use crate::error::ToResult; use crate::{ error::{err_msg, ErrorKind, Result, ResultExt}, - jws::envelope::{ProtectedHeader, JWS}, + jws::envelope::{CompactHeader, ProtectedHeader, JWS}, }; #[derive(Debug, PartialEq, Eq)] @@ -10,36 +11,86 @@ pub(crate) struct ParsedJWS<'a, 'b> { } pub(crate) fn parse<'a, 'b>(jws: &'a str, buf: &'b mut Vec>) -> Result> { - let jws: JWS = serde_json::from_str(jws).kind(ErrorKind::Malformed, "Unable parse jws")?; + JWS::from_str(jws)?.parse(buf) +} - let protected = { - let len = jws.signatures.len(); - let mut protected = Vec::::with_capacity(len); - buf.resize(len, vec![]); +impl<'a> JWS<'a> { + pub(crate) fn from_str(s: &str) -> Result { + serde_json::from_str(s).to_didcomm("Unable parse jws") + } - for (i, b) in buf.iter_mut().enumerate() { - let signature = jws - .signatures - .get(i) - .ok_or_else(|| err_msg(ErrorKind::InvalidState, "Invalid signature index"))?; + pub(crate) fn parse<'b>(self, buf: &'b mut Vec>) -> Result> { + let protected = { + let len = self.signatures.len(); + let mut protected = Vec::::with_capacity(len); + buf.resize(len, vec![]); - base64::decode_config_buf(signature.protected, base64::URL_SAFE_NO_PAD, b) - .kind(ErrorKind::Malformed, "Unable decode protected header")?; + for (i, b) in buf.iter_mut().enumerate() { + let signature = self + .signatures + .get(i) + .ok_or_else(|| err_msg(ErrorKind::InvalidState, "Invalid signature index"))?; - let p: ProtectedHeader = serde_json::from_slice(b) - .kind(ErrorKind::Malformed, "Unable parse protected header")?; + base64::decode_config_buf(signature.protected, base64::URL_SAFE_NO_PAD, b) + .kind(ErrorKind::Malformed, "Unable decode protected header")?; - protected.push(p); - } + let p: ProtectedHeader = + serde_json::from_slice(b).to_didcomm("Unable parse protected header")?; - protected - }; + protected.push(p); + } + + protected + }; + + Ok(ParsedJWS { + jws: self, + protected, + }) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct ParsedCompactJWS<'a> { + pub(crate) header: &'a str, + pub(crate) parsed_header: CompactHeader<'a>, + pub(crate) payload: &'a str, + pub(crate) signature: &'a str, +} + +pub(crate) fn parse_compact<'a>( + compact_jws: &'a str, + buf: &'a mut Vec, +) -> Result> { + let segments: Vec<&str> = compact_jws.split('.').collect(); + if segments.len() != 3 { + return Err(err_msg( + ErrorKind::Malformed, + "Unable to parse compactly serialized JWS", + )); + } + + let header = segments[0]; + let payload = segments[1]; + let signature = segments[2]; + + base64::decode_config_buf(header, base64::URL_SAFE_NO_PAD, buf) + .kind(ErrorKind::Malformed, "Unable decode header")?; - Ok(ParsedJWS { jws, protected }) + let parsed_header: CompactHeader = + serde_json::from_slice(buf).kind(ErrorKind::Malformed, "Unable parse header")?; + + Ok(ParsedCompactJWS { + header, + parsed_header, + payload, + signature, + }) } #[cfg(test)] mod tests { + use crate::jws::{CompactHeader, ParsedCompactJWS}; use crate::{ error::ErrorKind, jws::{ @@ -246,11 +297,11 @@ mod tests { let mut buf = vec![]; let res = jws::parse(&msg, &mut buf); - let res = res.expect_err("res is ok"); - assert_eq!(res.kind(), ErrorKind::Malformed); + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); assert_eq!( - format!("{}", res), + format!("{}", err), "Malformed: Unable parse jws: trailing comma at line 10 column 19" ); } @@ -274,11 +325,11 @@ mod tests { let mut buf = vec![]; let res = jws::parse(&msg, &mut buf); - let res = res.expect_err("res is ok"); - assert_eq!(res.kind(), ErrorKind::Malformed); + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); assert_eq!( - format!("{}", res), + format!("{}", err), "Malformed: Unable parse jws: missing field `kid` at line 9 column 17" ); } @@ -303,11 +354,11 @@ mod tests { let mut buf = vec![]; let res = jws::parse(&msg, &mut buf); - let res = res.expect_err("res is ok"); - assert_eq!(res.kind(), ErrorKind::Malformed); + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); assert_eq!( - format!("{}", res), + format!("{}", err), "Malformed: Unable decode protected header: Invalid byte 33, offset 0." ); } @@ -332,11 +383,11 @@ mod tests { let mut buf = vec![]; let res = jws::parse(&msg, &mut buf); - let res = res.expect_err("res is ok"); - assert_eq!(res.kind(), ErrorKind::Malformed); + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); assert_eq!( - format!("{}", res), + format!("{}", err), "Malformed: Unable parse protected header: key must be a string at line 1 column 2" ); } @@ -348,7 +399,7 @@ mod tests { "payload":"eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVhdGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNzYWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19", "signatures":[ { - "protected":"InR5cCI6ImFwcGxpY2F0aW9uL2RpZGNvbW0tc2lnbmVkK2pzb24ifQ", + "protected":"eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXNpZ25lZCtqc29uIn0", "signature":"FW33NnvOHV0Ted9-F7GZbkia-vYAfBKtH4oBxbrttWAhBZ6UFJMxcGjL3lwOl4YohI3kyyd08LHPWNMgP2EVCQ", "header":{ "kid":"did:example:alice#key-1" @@ -361,12 +412,223 @@ mod tests { let mut buf = vec![]; let res = jws::parse(&msg, &mut buf); - let res = res.expect_err("res is ok"); - assert_eq!(res.kind(), ErrorKind::Malformed); + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); + + assert_eq!( + format!("{}", err), + "Malformed: Unable parse protected header: missing field `alg` at line 1 column 41" + ); + } + + #[test] + fn parse_compact_works() { + let msg = + "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSJ9\ + .\ + eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19\ + .\ + iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg"; + + let mut buf = vec![]; + let res = jws::parse_compact(&msg, &mut buf); + let res = res.expect("res is err"); + + let exp = ParsedCompactJWS { + header: "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSJ9", + parsed_header: CompactHeader { + typ: "example-typ-1", + alg: Algorithm::EdDSA, + kid: "did:example:alice#key-1", + }, + payload: "eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19", + signature: "iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg", + }; + + assert_eq!(res, exp); + } + + #[test] + fn parse_compact_works_header_unknown_fields() { + let msg = + "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSIsImV4dHJhIjoidmFsdWUifQ\ + .\ + eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19\ + .\ + iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg"; + + let mut buf = vec![]; + let res = jws::parse_compact(&msg, &mut buf); + let res = res.expect("res is err"); + + let exp = ParsedCompactJWS { + header: "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSIsImV4dHJhIjoidmFsdWUifQ", + parsed_header: CompactHeader { + typ: "example-typ-1", + alg: Algorithm::EdDSA, + kid: "did:example:alice#key-1", + }, + payload: "eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19", + signature: "iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg", + }; + + assert_eq!(res, exp); + } + + #[test] + fn parse_compact_works_too_few_segments() { + let msg = + "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSJ9\ + .\ + eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19"; + + let mut buf = vec![]; + let res = jws::parse_compact(&msg, &mut buf); + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); + + assert_eq!( + format!("{}", err), + "Malformed: Unable to parse compactly serialized JWS" + ); + } + + #[test] + fn parse_compact_works_too_many_segments() { + let msg = + "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSJ9\ + .\ + eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19\ + .\ + iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg\ + .\ + eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSJ9"; + + let mut buf = vec![]; + let res = jws::parse_compact(&msg, &mut buf); + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); + + assert_eq!( + format!("{}", err), + "Malformed: Unable to parse compactly serialized JWS" + ); + } + + #[test] + fn parse_compact_works_undecodable_header() { + let msg = + "!eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSJ9\ + .\ + eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19\ + .\ + iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg"; + + let mut buf = vec![]; + let res = jws::parse_compact(&msg, &mut buf); + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); + + assert_eq!( + format!("{}", err), + "Malformed: Unable decode header: Encoded text cannot have a 6-bit remainder." + ); + } + + #[test] + fn parse_compact_works_unparsable_header() { + let msg = + "ey4idHlwIjoiZXhhbXBsZS10eXAtMSIsImFsZyI6IkVkRFNBIiwia2lkIjoiZGlkOmV4YW1wbGU6YWxp\ + Y2Uja2V5LTEifQ\ + .\ + eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19\ + .\ + iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg"; + + let mut buf = vec![]; + let res = jws::parse_compact(&msg, &mut buf); + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); + + assert_eq!( + format!("{}", err), + "Malformed: Unable parse header: key must be a string at line 1 column 2" + ); + } + + #[test] + fn parse_compact_works_misstructured_header() { + let msg = "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwia2lkIjoiZGlkOmV4YW1wbGU6YWxpY2Uja2V5LTEifQ\ + .\ + eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19\ + .\ + iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg"; + + let mut buf = vec![]; + let res = jws::parse_compact(&msg, &mut buf); + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); assert_eq!( - format!("{}", res), - "Malformed: Unable parse protected header: invalid type: string \"typ\", expected struct ProtectedHeader at line 1 column 5" + format!("{}", err), + "Malformed: Unable parse header: missing field `alg` at line 1 column 55" ); } } diff --git a/src/jws/sign.rs b/src/jws/sign.rs index 776e103..3c20d57 100644 --- a/src/jws/sign.rs +++ b/src/jws/sign.rs @@ -2,7 +2,7 @@ use askar_crypto::sign::KeySign; use crate::{ error::{ErrorKind, Result, ResultExt}, - jws::envelope::{Algorithm, Header, ProtectedHeader, Signature, JWS}, + jws::envelope::{Algorithm, CompactHeader, Header, ProtectedHeader, Signature, JWS}, }; pub(crate) fn sign( @@ -21,7 +21,7 @@ pub(crate) fn sign( }; let protected = serde_json::to_string(&protected) - .kind(ErrorKind::InvalidState, "Unable serialize protectd header")?; + .kind(ErrorKind::InvalidState, "Unable serialize protected header")?; base64::encode_config(protected, base64::URL_SAFE_NO_PAD) }; @@ -57,6 +57,45 @@ pub(crate) fn sign( Ok(jws) } +pub(crate) fn sign_compact( + payload: &[u8], + signer: (&str, &Key), + typ: &str, + alg: Algorithm, +) -> Result { + let (kid, key) = signer; + + let sig_type = alg.sig_type()?; + + let header = { + let header = CompactHeader { typ, alg, kid }; + + let header = serde_json::to_string(&header) + .kind(ErrorKind::InvalidState, "Unable serialize header")?; + + base64::encode_config(header, base64::URL_SAFE_NO_PAD) + }; + + let payload = base64::encode_config(payload, base64::URL_SAFE_NO_PAD); + + let signature = { + // JWS Signing Input + // The input to the digital signature or MAC computation. Its value + // is ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload)). + let sign_input = format!("{}.{}", header, payload); + + let signature = key + .create_signature(sign_input.as_bytes(), Some(sig_type)) + .kind(ErrorKind::InvalidState, "Unable create signature")?; + + base64::encode_config(&signature, base64::URL_SAFE_NO_PAD) + }; + + let compact_jws = format!("{}.{}.{}", header, payload, signature); + + Ok(compact_jws) +} + #[cfg(test)] mod tests { use askar_crypto::{ @@ -108,7 +147,7 @@ mod tests { let msg = res.expect("Unable _sign"); let mut buf = vec![]; - let msg = jws::parse(&msg, &mut buf).expect("Unable parse."); + let msg = jws::parse(&msg, &mut buf).expect("Unable parse"); assert_eq!( msg.jws.payload, @@ -122,7 +161,7 @@ mod tests { assert_eq!(msg.protected[0].alg, alg); assert_eq!(msg.protected[0].typ, "application/didcomm-signed+json"); - let pkey = K::from_jwk(pkey).expect("unable from_jwk"); + let pkey = K::from_jwk(pkey).expect("Unable from_jwk"); let valid = msg.verify::((kid, &pkey)).expect("Unable verify"); assert!(valid); @@ -241,7 +280,197 @@ mod tests { assert_eq!( format!("{}", err), - "Unsupported crypto or method: Unsuported signature type" + "Unsupported crypto or method: Unsupported signature type" + ); + } + } + + #[test] + fn sign_compact_works() { + _sign_compact_works::( + ALICE_KID_ED25519, + ALICE_KEY_ED25519, + ALICE_PKEY_ED25519, + "example-typ-1", + Algorithm::EdDSA, + PAYLOAD, + ); + + _sign_compact_works::( + ALICE_KID_P256, + ALICE_KEY_P256, + ALICE_PKEY_P256, + "example-typ-2", + Algorithm::Es256, + PAYLOAD, + ); + + _sign_compact_works::( + ALICE_KID_K256, + ALICE_KEY_K256, + ALICE_PKEY_K256, + "example-typ-3", + Algorithm::Es256K, + PAYLOAD, + ); + + fn _sign_compact_works( + kid: &str, + key: &str, + pkey: &str, + typ: &str, + alg: Algorithm, + payload: &str, + ) { + let res = _sign_compact::(kid, key, typ, alg.clone(), payload); + + let msg = res.expect("Unable _sign_compact"); + + let mut buf = vec![]; + let msg = jws::parse_compact(&msg, &mut buf).expect("Unable parse_compact"); + + assert_eq!( + msg.payload, + base64::encode_config(payload, base64::URL_SAFE_NO_PAD) + ); + + assert_eq!(msg.parsed_header.typ, typ); + assert_eq!(msg.parsed_header.alg, alg); + assert_eq!(msg.parsed_header.kid, kid); + + let pkey = K::from_jwk(pkey).expect("Unable from_jwk"); + let valid = msg.verify::(&pkey).expect("Unable verify"); + + assert!(valid); + } + } + + #[test] + fn sign_compact_works_incompatible_alg() { + _sign_compact_works_incompatible_alg::( + ALICE_KID_ED25519, + ALICE_KEY_ED25519, + "example-typ-1", + Algorithm::Es256, + PAYLOAD, + ); + + _sign_compact_works_incompatible_alg::( + ALICE_KID_ED25519, + ALICE_KEY_ED25519, + "example-typ-1", + Algorithm::Es256K, + PAYLOAD, + ); + + _sign_compact_works_incompatible_alg::( + ALICE_KID_P256, + ALICE_KEY_P256, + "example-typ-1", + Algorithm::Es256K, + PAYLOAD, + ); + + _sign_compact_works_incompatible_alg::( + ALICE_KID_P256, + ALICE_KEY_P256, + "example-typ-1", + Algorithm::EdDSA, + PAYLOAD, + ); + + _sign_compact_works_incompatible_alg::( + ALICE_KID_P256, + ALICE_KEY_P256, + "example-typ-1", + Algorithm::Es256K, + PAYLOAD, + ); + + _sign_compact_works_incompatible_alg::( + ALICE_KID_K256, + ALICE_KEY_K256, + "example-typ-1", + Algorithm::Es256, + PAYLOAD, + ); + + _sign_compact_works_incompatible_alg::( + ALICE_KID_K256, + ALICE_KEY_K256, + "example-typ-1", + Algorithm::EdDSA, + PAYLOAD, + ); + + _sign_compact_works_incompatible_alg::( + ALICE_KID_K256, + ALICE_KEY_K256, + "example-typ-1", + Algorithm::Es256, + PAYLOAD, + ); + + fn _sign_compact_works_incompatible_alg( + kid: &str, + key: &str, + typ: &str, + alg: Algorithm, + payload: &str, + ) { + let res = _sign_compact::(kid, key, typ, alg.clone(), payload); + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::InvalidState); + + assert_eq!( + format!("{}", err), + "Invalid state: Unable create signature: Unsupported signature type" + ); + } + } + + #[test] + fn sign_compact_works_unknown_alg() { + _sign_compact_works_unknown_alg::( + ALICE_KID_ED25519, + ALICE_KEY_ED25519, + "example-typ-1", + Algorithm::Other("bls".to_owned()), + PAYLOAD, + ); + + _sign_compact_works_unknown_alg::( + ALICE_KID_P256, + ALICE_KEY_P256, + "example-typ-1", + Algorithm::Other("bls".to_owned()), + PAYLOAD, + ); + + _sign_compact_works_unknown_alg::( + ALICE_KID_K256, + ALICE_KEY_K256, + "example-typ-1", + Algorithm::Other("bls".to_owned()), + PAYLOAD, + ); + + fn _sign_compact_works_unknown_alg( + kid: &str, + key: &str, + typ: &str, + alg: Algorithm, + payload: &str, + ) { + let res = _sign_compact::(kid, key, typ, alg.clone(), payload); + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Unsupported); + + assert_eq!( + format!("{}", err), + "Unsupported crypto or method: Unsupported signature type" ); } } @@ -252,10 +481,21 @@ mod tests { alg: Algorithm, payload: &str, ) -> Result { - let key = K::from_jwk(key).expect("unable from_jwk."); + let key = K::from_jwk(key).expect("Unable from_jwk"); jws::sign(payload.as_bytes(), (&kid, &key), alg.clone()) } + fn _sign_compact( + kid: &str, + key: &str, + typ: &str, + alg: Algorithm, + payload: &str, + ) -> Result { + let key = K::from_jwk(key).expect("Unable from_jwk"); + jws::sign_compact(payload.as_bytes(), (&kid, &key), typ, alg.clone()) + } + const ALICE_KID_ED25519: &str = "did:example:alice#key-1"; const ALICE_KEY_ED25519: &str = r#" @@ -317,7 +557,5 @@ mod tests { } "#; - const PAYLOAD: &str = r#" - {"id":"1234567890","typ":"application/didcomm-plain+json","type":"http://example.com/protocols/lets_do_lunch/1.0/proposal","from":"did:example:alice","to":["did:example:bob"],"created_time":1516269022,"expires_time":1516385931,"body":{"messagespecificattribute":"and its value"}} - "#; + const PAYLOAD: &str = r#"{"id":"1234567890","typ":"application/didcomm-plain+json","type":"http://example.com/protocols/lets_do_lunch/1.0/proposal","from":"did:example:alice","to":["did:example:bob"],"created_time":1516269022,"expires_time":1516385931,"body":{"messagespecificattribute":"and its value"}}"#; } diff --git a/src/jws/verify.rs b/src/jws/verify.rs index 716abd8..a59809b 100644 --- a/src/jws/verify.rs +++ b/src/jws/verify.rs @@ -2,7 +2,7 @@ use askar_crypto::sign::KeySigVerify; use crate::{ error::{err_msg, ErrorKind, Result, ResultExt}, - jws::ParsedJWS, + jws::{ParsedCompactJWS, ParsedJWS}, }; impl<'a, 'b> ParsedJWS<'a, 'b> { @@ -36,6 +36,22 @@ impl<'a, 'b> ParsedJWS<'a, 'b> { } } +impl<'a> ParsedCompactJWS<'a> { + pub(crate) fn verify(&self, key: &Key) -> Result { + let sig_type = self.parsed_header.alg.sig_type()?; + let sign_input = format!("{}.{}", self.header, self.payload); + + let signature = base64::decode_config(self.signature, base64::URL_SAFE_NO_PAD) + .kind(ErrorKind::Malformed, "Unable decode signature")?; + + let valid = key + .verify_signature(sign_input.as_bytes(), &signature, Some(sig_type)) + .kind(ErrorKind::Malformed, "Unable verify signature")?; + + Ok(valid) + } +} + #[cfg(test)] mod tests { use askar_crypto::{ @@ -251,6 +267,61 @@ mod tests { } } + #[test] + fn verify_compact_works() { + let res = _verify_compact::(ALICE_PKEY_ED25519, ALICE_COMPACT_MSG_ED25519); + let res = res.expect("res is err"); + assert_eq!(res, true); + } + + #[test] + fn verify_compact_works_different_key() { + // use Dave's public key instead of Alice's one + let res = + _verify_compact::(CHARLIE_PKEY_ED25519, ALICE_COMPACT_MSG_ED25519); + let res = res.expect("res is err"); + assert_eq!(res, false); + } + + #[test] + fn verify_compact_works_changed_payload() { + let res = _verify_compact::( + ALICE_PKEY_ED25519, + ALICE_COMPACT_MSG_ED25519_CHANGED_PAYLOAD, + ); + + let res = res.expect("res is err"); + assert_eq!(res, false); + } + + #[test] + fn verify_compact_works_different_curve() { + let res = _verify_compact::(ALICE_PKEY_P256, ALICE_COMPACT_MSG_ED25519); + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); + + assert_eq!( + format!("{}", err), + "Malformed: Unable verify signature: Unsupported signature type" + ); + } + + #[test] + fn verify_compact_works_undecodable_sig() { + let res = _verify_compact::( + ALICE_PKEY_ED25519, + ALICE_COMPACT_MSG_ED25519_UNDECODABLE_SIG, + ); + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Malformed); + + assert_eq!( + format!("{}", err), + "Malformed: Unable decode signature: Invalid byte 33, offset 0." + ); + } + fn _verify( kid: &str, key: &str, @@ -264,6 +335,15 @@ mod tests { msg.verify((kid, &key)) } + fn _verify_compact(key: &str, msg: &str) -> Result { + let key = Key::from_jwk(key).expect("unable from_jwk."); + + let mut buf = vec![]; + let msg = jws::parse_compact(&msg, &mut buf).expect("unable parse."); + + msg.verify(&key) + } + const ALICE_KID_ED25519: &str = "did:example:alice#key-1"; const ALICE_KEY_ED25519: &str = r#" @@ -489,6 +569,45 @@ mod tests { } "#; + const ALICE_COMPACT_MSG_ED25519: &str = + "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSJ9\ + .\ + eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19\ + .\ + iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg"; + + const ALICE_COMPACT_MSG_ED25519_CHANGED_PAYLOAD: &str = + "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSJ9\ + .\ + eyJpZCI6IjAyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19\ + .\ + iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg"; + + const ALICE_COMPACT_MSG_ED25519_UNDECODABLE_SIG: &str = + "eyJ0eXAiOiJleGFtcGxlLXR5cC0xIiwiYWxnIjoiRWREU0EiLCJraWQiOiJkaWQ6ZXhhbXBsZTphbGlj\ + ZSNrZXktMSJ9\ + .\ + eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0\ + eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3Nh\ + bCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVh\ + dGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNz\ + YWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19\ + .\ + !iMi3kOWHTWoKiuTT4JxD9CkcUwSby9ekpOQk0Xdm9_H6jDpLPuhfX4U2EYgdPIJERl95MIecEhrufvO4\ + bHgtCg"; + /* Bob key Ed25519 {"crv":"Ed25519","kty":"OKP","x":"ECEdOp9caYYVMgGomcV-bHjvJcRvh68COWd_GO-npGI","d":"CKMS_Kt8x6to6jD88d6EGo_H_fSG3L2SAEccLBGQG3U"} @@ -541,4 +660,12 @@ mod tests { "payload":"eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVhdGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNzYWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19" } "#; + + const CHARLIE_PKEY_ED25519: &str = r#" + { + "kty":"OKP", + "crv":"Ed25519", + "x":"VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE" + } + "#; } diff --git a/src/lib.rs b/src/lib.rs index 43f5957..5c857ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,15 +11,22 @@ pub(crate) use crate as didcomm; #[cfg(test)] mod test_vectors; +#[cfg(feature = "testvectors")] +pub(crate) use crate as didcomm; + +#[cfg(feature = "testvectors")] +pub mod test_vectors; + pub mod algorithms; pub mod did; pub mod error; +pub mod protocols; pub mod secrets; pub use message::{ - Attachment, AttachmentBuilder, AttachmentData, Base64AttachmentData, JsonAttachmentData, - LinksAttachmentData, Message, MessageBuilder, PackEncryptedMetadata, PackEncryptedOptions, - PackSignedMetadata, UnpackMetadata, UnpackOptions, + Attachment, AttachmentBuilder, AttachmentData, Base64AttachmentData, FromPrior, + JsonAttachmentData, LinksAttachmentData, Message, MessageBuilder, MessagingServiceMetadata, + PackEncryptedMetadata, PackEncryptedOptions, PackSignedMetadata, UnpackMetadata, UnpackOptions, }; #[cfg(test)] @@ -37,14 +44,14 @@ mod tests { // --- Build message --- let sender = "did:example:1"; - let recepient = "did:example:2"; + let recipient = "did:example:2"; let msg = Message::build( "example-1".into(), "example/v1".into(), json!("example-body"), ) - .to(recepient.into()) + .to(recipient.into()) .from(sender.into()) .finalize(); @@ -55,14 +62,12 @@ mod tests { let (packed_msg, metadata) = msg .pack_encrypted( - recepient, + recipient, Some(sender), None, &sender_did_resolver, &sender_secrets_resolver, - &PackEncryptedOptions { - ..PackEncryptedOptions::default() - }, + &PackEncryptedOptions::default(), ) .await .expect("pack is ok."); @@ -78,16 +83,14 @@ mod tests { // --- Unpacking message --- - let recepient_did_resolver = ExampleDIDResolver::new(vec![]); - let recepient_secrets_resolver = ExampleSecretsResolver::new(vec![]); + let recipient_did_resolver = ExampleDIDResolver::new(vec![]); + let recipient_secrets_resolver = ExampleSecretsResolver::new(vec![]); let (msg, metadata) = Message::unpack( &packed_msg, - &recepient_did_resolver, - &recepient_secrets_resolver, - &UnpackOptions { - ..UnpackOptions::default() - }, + &recipient_did_resolver, + &recipient_secrets_resolver, + &UnpackOptions::default(), ) .await .expect("unpack is ok."); @@ -95,10 +98,10 @@ mod tests { assert!(metadata.encrypted); assert!(metadata.authenticated); assert!(metadata.encrypted_from_kid.is_some()); - assert!(metadata.encrypted_from_kid.unwrap().starts_with(&recepient)); + assert!(metadata.encrypted_from_kid.unwrap().starts_with(&recipient)); assert_eq!(msg.from, Some(sender.into())); - assert_eq!(msg.to, Some(vec![recepient.into()])); + assert_eq!(msg.to, Some(vec![recipient.into()])); assert_eq!(msg.body, json!("example-body")); } } diff --git a/src/message/attachment.rs b/src/message/attachment.rs index 47fd5a5..327173e 100644 --- a/src/message/attachment.rs +++ b/src/message/attachment.rs @@ -48,22 +48,25 @@ pub struct Attachment { impl Attachment { pub fn base64(base64: String) -> AttachmentBuilder { - AttachmentBuilder::new(AttachmentData::Base64(Base64AttachmentData { - base64, - jws: None, - })) + AttachmentBuilder::new(AttachmentData::Base64 { + value: Base64AttachmentData { base64, jws: None }, + }) } pub fn json(json: Value) -> AttachmentBuilder { - AttachmentBuilder::new(AttachmentData::Json(JsonAttachmentData { json, jws: None })) + AttachmentBuilder::new(AttachmentData::Json { + value: JsonAttachmentData { json, jws: None }, + }) } pub fn links(links: Vec, hash: String) -> AttachmentBuilder { - AttachmentBuilder::new(AttachmentData::Links(LinksAttachmentData { - links, - hash, - jws: None, - })) + AttachmentBuilder::new(AttachmentData::Links { + value: LinksAttachmentData { + links, + hash, + jws: None, + }, + }) } } @@ -129,9 +132,9 @@ impl AttachmentBuilder { pub fn jws(mut self, jws: String) -> Self { match self.data { - AttachmentData::Base64(ref mut data) => data.jws = Some(jws), - AttachmentData::Json(ref mut data) => data.jws = Some(jws), - AttachmentData::Links(ref mut data) => data.jws = Some(jws), + AttachmentData::Base64 { ref mut value } => value.jws = Some(jws), + AttachmentData::Json { ref mut value } => value.jws = Some(jws), + AttachmentData::Links { ref mut value } => value.jws = Some(jws), } self @@ -160,9 +163,18 @@ impl AttachmentBuilder { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(untagged)] pub enum AttachmentData { - Base64(Base64AttachmentData), - Json(JsonAttachmentData), - Links(LinksAttachmentData), + Base64 { + #[serde(flatten)] + value: Base64AttachmentData, + }, + Json { + #[serde(flatten)] + value: JsonAttachmentData, + }, + Links { + #[serde(flatten)] + value: LinksAttachmentData, + }, } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] @@ -219,7 +231,7 @@ mod tests { .finalize(); let data = match attachment.data { - AttachmentData::Base64(ref data) => data, + AttachmentData::Base64 { ref value } => value, _ => panic!("data isn't base64."), }; @@ -253,7 +265,7 @@ mod tests { .finalize(); let data = match attachment.data { - AttachmentData::Json(ref data) => data, + AttachmentData::Json { ref value } => value, _ => panic!("data isn't json."), }; @@ -290,7 +302,7 @@ mod tests { .finalize(); let data = match attachment.data { - AttachmentData::Links(ref data) => data, + AttachmentData::Links { ref value } => value, _ => panic!("data isn't links."), }; diff --git a/src/message/from_prior/mod.rs b/src/message/from_prior/mod.rs new file mode 100644 index 0000000..6d71316 --- /dev/null +++ b/src/message/from_prior/mod.rs @@ -0,0 +1,97 @@ +use serde::{Deserialize, Serialize}; + +mod pack; +mod unpack; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +/// `from_prior` header value as a plaintext. +/// https://identity.foundation/didcomm-messaging/spec/#did-rotation +pub struct FromPrior { + pub iss: String, + + pub sub: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub aud: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub exp: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub nbf: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub iat: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub jti: Option, +} + +const JWT_TYP: &str = "JWT"; + +impl FromPrior { + pub fn build(iss: String, sub: String) -> FromPriorBuilder { + FromPriorBuilder::new(iss, sub) + } +} + +pub struct FromPriorBuilder { + iss: String, + sub: String, + aud: Option, + exp: Option, + nbf: Option, + iat: Option, + jti: Option, +} + +impl FromPriorBuilder { + fn new(iss: String, sub: String) -> Self { + FromPriorBuilder { + iss, + sub, + aud: None, + exp: None, + nbf: None, + iat: None, + jti: None, + } + } + + pub fn aud(mut self, aud: String) -> Self { + self.aud = Some(aud); + self + } + + pub fn exp(mut self, exp: u64) -> Self { + self.exp = Some(exp); + self + } + + pub fn nbf(mut self, nbf: u64) -> Self { + self.nbf = Some(nbf); + self + } + + pub fn iat(mut self, iat: u64) -> Self { + self.iat = Some(iat); + self + } + + pub fn jti(mut self, jti: String) -> Self { + self.jti = Some(jti); + self + } + + pub fn finalize(self) -> FromPrior { + FromPrior { + iss: self.iss, + sub: self.sub, + aud: self.aud, + exp: self.exp, + nbf: self.nbf, + iat: self.iat, + jti: self.jti, + } + } +} diff --git a/src/message/from_prior/pack.rs b/src/message/from_prior/pack.rs new file mode 100644 index 0000000..f44ec79 --- /dev/null +++ b/src/message/from_prior/pack.rs @@ -0,0 +1,375 @@ +use crate::{ + did::DIDResolver, + error::{err_msg, ErrorKind, Result, ResultContext, ResultExt}, + jws::{self, Algorithm}, + message::from_prior::JWT_TYP, + secrets::SecretsResolver, + utils::{ + crypto::{AsKnownKeyPair, KnownKeyPair}, + did::{did_or_url, is_did}, + }, + FromPrior, +}; + +impl FromPrior { + /// Packs a plaintext `from_prior` value into a signed JWT. + /// https://identity.foundation/didcomm-messaging/spec/#did-rotation + /// + /// # Parameters + /// - `issuer_kid` (optional) identifier of the issuer key being used to sign `from_prior` JWT value. + /// - `did_resolver` instance of `DIDResolver` to resolve DIDs. + /// - `secrets_resolver` instance of `SecretsResolver` to resolve issuer DID keys secrets. + /// + /// # Returns + /// Tuple (signed `from_prior` JWT, identifier of the issuer key actually used to sign `from_prior`) + /// + /// # Errors + /// - `Malformed` `from_prior` plaintext value has invalid format. + /// - `IllegalArgument` `issuer_kid` is invalid or does not consist with `from_prior` plaintext value. + /// - `DIDNotResolved` Issuer DID not found. + /// - `DIDUrlNotFound` Issuer authentication verification method is not found. + /// - `SecretNotFound` Issuer secret is not found. + /// - `Unsupported` Used crypto or method is unsupported. + /// - `InvalidState` Indicates a library error. + pub async fn pack<'dr, 'sr>( + &self, + issuer_kid: Option<&str>, + did_resolver: &'dr (dyn DIDResolver + 'dr), + secrets_resolver: &'sr (dyn SecretsResolver + 'sr), + ) -> Result<(String, String)> { + self.validate_pack(issuer_kid)?; + + let from_prior_str = serde_json::to_string(self) + .kind(ErrorKind::InvalidState, "Unable serialize message")?; + + let did_doc = did_resolver + .resolve(&self.iss) + .await + .context("Unable to resolve from_prior issuer DID")? + .ok_or_else(|| { + err_msg( + ErrorKind::DIDNotResolved, + "from_prior issuer DIDDoc is not found", + ) + })?; + + let authentication_kids: Vec<&str> = if let Some(issuer_kid) = issuer_kid { + let (did, kid) = did_or_url(issuer_kid); + + let kid = kid.ok_or_else(|| { + err_msg( + ErrorKind::IllegalArgument, + "issuer_kid content is not DID URL", + ) + })?; + + if did != &self.iss { + Err(err_msg( + ErrorKind::IllegalArgument, + "from_prior issuer kid does not belong to from_prior `iss`", + ))? + } + + let kid = did_doc + .authentications + .iter() + .find(|a| *a == kid) + .ok_or_else(|| { + err_msg( + ErrorKind::DIDUrlNotFound, + "Provided issuer_kid is not found in DIDDoc", + ) + })?; + + vec![kid] + } else { + did_doc.authentications.iter().map(|s| s.as_str()).collect() + }; + + let kid = *secrets_resolver + .find_secrets(&authentication_kids) + .await + .context("Unable to find secrets")? + .get(0) + .ok_or_else(|| { + err_msg( + ErrorKind::SecretNotFound, + "No from_prior issuer secrets found", + ) + })?; + + let secret = secrets_resolver + .get_secret(kid) + .await + .context("Unable to find secret")? + .ok_or_else(|| { + err_msg( + ErrorKind::SecretNotFound, + "from_prior issuer secret not found", + ) + })?; + + let sign_key = secret + .as_key_pair() + .context("Unable to instantiate from_prior issuer key")?; + + let from_prior_jwt = match sign_key { + KnownKeyPair::Ed25519(ref key) => jws::sign_compact( + from_prior_str.as_bytes(), + (kid, key), + JWT_TYP, + Algorithm::EdDSA, + ), + KnownKeyPair::P256(ref key) => jws::sign_compact( + from_prior_str.as_bytes(), + (kid, key), + JWT_TYP, + Algorithm::Es256, + ), + KnownKeyPair::K256(ref key) => jws::sign_compact( + from_prior_str.as_bytes(), + (kid, key), + JWT_TYP, + Algorithm::Es256K, + ), + _ => Err(err_msg(ErrorKind::Unsupported, "Unsupported signature alg"))?, + } + .context("Unable to produce signature")?; + + Ok((from_prior_jwt, String::from(kid))) + } + + pub(crate) fn validate_pack(&self, issuer_kid: Option<&str>) -> Result<()> { + if !is_did(&self.iss) || did_or_url(&self.iss).1.is_some() { + Err(err_msg( + ErrorKind::Malformed, + "from_prior `iss` must be a non-fragment DID", + ))?; + } + + if !is_did(&self.sub) || did_or_url(&self.sub).1.is_some() { + Err(err_msg( + ErrorKind::Malformed, + "from_prior `sub` must be a non-fragment DID", + ))?; + } + + if &self.iss == &self.sub { + Err(err_msg( + ErrorKind::Malformed, + "from_prior `iss` and `sub` values must not be equal", + ))?; + } + + if let Some(issuer_kid) = issuer_kid { + let (did, kid) = did_or_url(issuer_kid); + + if kid.is_none() { + Err(err_msg( + ErrorKind::IllegalArgument, + "issuer_kid content is not DID URL", + ))?; + }; + + if did != &self.iss { + Err(err_msg( + ErrorKind::IllegalArgument, + "from_prior issuer kid does not belong to from_prior `iss`", + ))?; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + did::resolvers::ExampleDIDResolver, + error::ErrorKind, + secrets::resolvers::ExampleSecretsResolver, + test_vectors::{ + ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, ALICE_SECRET_AUTH_KEY_ED25519, CHARLIE_DID, + CHARLIE_DID_DOC, CHARLIE_ROTATED_TO_ALICE_SECRETS, CHARLIE_SECRET_AUTH_KEY_ED25519, + FROM_PRIOR_FULL, FROM_PRIOR_INVALID_EQUAL_ISS_AND_SUB, FROM_PRIOR_INVALID_ISS, + FROM_PRIOR_INVALID_ISS_DID_URL, FROM_PRIOR_INVALID_SUB, FROM_PRIOR_INVALID_SUB_DID_URL, + FROM_PRIOR_MINIMAL, + }, + utils::did::did_or_url, + FromPrior, + }; + + #[tokio::test] + async fn from_prior_pack_works_with_issuer_kid() { + _from_prior_pack_works_with_issuer_kid(&FROM_PRIOR_MINIMAL).await; + _from_prior_pack_works_with_issuer_kid(&FROM_PRIOR_FULL).await; + + async fn _from_prior_pack_works_with_issuer_kid(from_prior: &FromPrior) { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), CHARLIE_DID_DOC.clone()]); + let charlie_rotated_to_alice_secrets_resolver = + ExampleSecretsResolver::new(CHARLIE_ROTATED_TO_ALICE_SECRETS.clone()); + + let (from_prior_jwt, pack_kid) = from_prior + .pack( + Some(&CHARLIE_SECRET_AUTH_KEY_ED25519.id), + &did_resolver, + &charlie_rotated_to_alice_secrets_resolver, + ) + .await + .expect("Unable to pack FromPrior"); + + assert_eq!(pack_kid, CHARLIE_SECRET_AUTH_KEY_ED25519.id); + + let (unpacked_from_prior, unpack_kid) = + FromPrior::unpack(&from_prior_jwt, &did_resolver) + .await + .expect("Unable to unpack FromPrior JWT"); + + assert_eq!(&unpacked_from_prior, from_prior); + assert_eq!(unpack_kid, CHARLIE_SECRET_AUTH_KEY_ED25519.id); + } + } + + #[tokio::test] + async fn from_prior_pack_works_without_issuer_kid() { + _from_prior_pack_works_without_issuer_kid(&FROM_PRIOR_MINIMAL).await; + _from_prior_pack_works_without_issuer_kid(&FROM_PRIOR_FULL).await; + + async fn _from_prior_pack_works_without_issuer_kid(from_prior: &FromPrior) { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), CHARLIE_DID_DOC.clone()]); + let charlie_rotated_to_alice_secrets_resolver = + ExampleSecretsResolver::new(CHARLIE_ROTATED_TO_ALICE_SECRETS.clone()); + + let (from_prior_jwt, pack_kid) = from_prior + .pack( + None, + &did_resolver, + &charlie_rotated_to_alice_secrets_resolver, + ) + .await + .expect("Unable to pack FromPrior"); + + let (did, kid) = did_or_url(&pack_kid); + assert!(kid.is_some()); + assert_eq!(did, CHARLIE_DID); + + let (unpacked_from_prior, unpack_kid) = + FromPrior::unpack(&from_prior_jwt, &did_resolver) + .await + .expect("Unable to unpack FromPrior JWT"); + + assert_eq!(&unpacked_from_prior, from_prior); + assert_eq!(unpack_kid, pack_kid); + } + } + + #[tokio::test] + async fn from_prior_pack_works_wrong_issuer_kid() { + _from_prior_pack_works_wrong_issuer_kid( + &FROM_PRIOR_FULL, + &ALICE_SECRET_AUTH_KEY_ED25519.id, + ErrorKind::IllegalArgument, + "Illegal argument: from_prior issuer kid does not belong to from_prior `iss`", + ) + .await; + + _from_prior_pack_works_wrong_issuer_kid( + &FROM_PRIOR_FULL, + ALICE_DID, + ErrorKind::IllegalArgument, + "Illegal argument: issuer_kid content is not DID URL", + ) + .await; + + _from_prior_pack_works_wrong_issuer_kid( + &FROM_PRIOR_FULL, + "invalid", + ErrorKind::IllegalArgument, + "Illegal argument: issuer_kid content is not DID URL", + ) + .await; + + async fn _from_prior_pack_works_wrong_issuer_kid( + from_prior: &FromPrior, + issuer_kid: &str, + err_kind: ErrorKind, + err_mgs: &str, + ) { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), CHARLIE_DID_DOC.clone()]); + let alice_secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let err = from_prior + .pack(Some(issuer_kid), &did_resolver, &alice_secrets_resolver) + .await + .expect_err("res is ok"); + + assert_eq!(err.kind(), err_kind); + assert_eq!(format!("{}", err), err_mgs); + } + } + + #[tokio::test] + async fn from_prior_pack_works_invalid() { + _from_prior_pack_works_invalid( + &FROM_PRIOR_INVALID_ISS, + ErrorKind::Malformed, + "Malformed: from_prior `iss` must be a non-fragment DID", + ) + .await; + + _from_prior_pack_works_invalid( + &FROM_PRIOR_INVALID_ISS_DID_URL, + ErrorKind::Malformed, + "Malformed: from_prior `iss` must be a non-fragment DID", + ) + .await; + + _from_prior_pack_works_invalid( + &FROM_PRIOR_INVALID_SUB, + ErrorKind::Malformed, + "Malformed: from_prior `sub` must be a non-fragment DID", + ) + .await; + + _from_prior_pack_works_invalid( + &FROM_PRIOR_INVALID_SUB_DID_URL, + ErrorKind::Malformed, + "Malformed: from_prior `sub` must be a non-fragment DID", + ) + .await; + + _from_prior_pack_works_invalid( + &FROM_PRIOR_INVALID_EQUAL_ISS_AND_SUB, + ErrorKind::Malformed, + "Malformed: from_prior `iss` and `sub` values must not be equal", + ) + .await; + + async fn _from_prior_pack_works_invalid( + from_prior: &FromPrior, + err_kind: ErrorKind, + err_mgs: &str, + ) { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), CHARLIE_DID_DOC.clone()]); + let charlie_rotated_to_alice_secrets_resolver = + ExampleSecretsResolver::new(CHARLIE_ROTATED_TO_ALICE_SECRETS.clone()); + + let err = from_prior + .pack( + None, + &did_resolver, + &charlie_rotated_to_alice_secrets_resolver, + ) + .await + .expect_err("res is ok"); + + assert_eq!(err.kind(), err_kind); + assert_eq!(format!("{}", err), err_mgs); + } + } +} diff --git a/src/message/from_prior/unpack.rs b/src/message/from_prior/unpack.rs new file mode 100644 index 0000000..81a4976 --- /dev/null +++ b/src/message/from_prior/unpack.rs @@ -0,0 +1,195 @@ +use crate::{ + did::DIDResolver, + error::{err_msg, ErrorKind, Result, ResultContext, ResultExt}, + jws, + utils::{crypto::AsKnownKeyPair, did::did_or_url}, + FromPrior, +}; +use askar_crypto::alg::{ed25519::Ed25519KeyPair, k256::K256KeyPair, p256::P256KeyPair}; + +impl FromPrior { + /// Unpacks a plaintext value from a signed `from_prior` JWT. + /// https://identity.foundation/didcomm-messaging/spec/#did-rotation + /// + /// # Parameters + /// - `from_prior_jwt` signed `from_prior` JWT. + /// - `did_resolver` instance of `DIDResolver` to resolve DIDs. + /// + /// # Returns + /// Tuple (plaintext `from_prior` value, identifier of the issuer key used to sign `from_prior`) + /// + /// # Errors + /// - `Malformed` Signed `from_prior` JWT is malformed. + /// - `DIDNotResolved` Issuer DID not found. + /// - `DIDUrlNotFound` Issuer authentication verification method is not found. + /// - `Unsupported` Used crypto or method is unsupported. + pub async fn unpack<'dr>( + from_prior_jwt: &str, + did_resolver: &'dr (dyn DIDResolver + 'dr), + ) -> Result<(FromPrior, String)> { + let mut buf = vec![]; + let parsed = jws::parse_compact(from_prior_jwt, &mut buf)?; + + let typ = parsed.parsed_header.typ; + let alg = parsed.parsed_header.alg.clone(); + let kid = parsed.parsed_header.kid; + + if typ != "JWT" { + Err(err_msg( + ErrorKind::Malformed, + "from_prior is malformed: typ is not JWT", + ))?; + } + + let (did, did_url) = did_or_url(kid); + + if did_url.is_none() { + Err(err_msg( + ErrorKind::Malformed, + "from_prior kid is not DID URL", + ))? + } + + let did_doc = did_resolver + .resolve(did) + .await + .context("Unable to resolve from_prior issuer DID")? + .ok_or_else(|| { + err_msg( + ErrorKind::DIDNotResolved, + "from_prior issuer DIDDoc not found", + ) + })?; + + let kid = did_doc + .authentications + .iter() + .find(|&k| k.as_str() == kid) + .ok_or_else(|| { + err_msg( + ErrorKind::DIDUrlNotFound, + "from_prior issuer kid not found in DIDDoc", + ) + })? + .as_str(); + + let key = did_doc + .verification_methods + .iter() + .find(|&vm| &vm.id == kid) + .ok_or_else(|| { + err_msg( + ErrorKind::DIDUrlNotFound, + "from_prior issuer verification method not found in DIDDoc", + ) + })?; + + let valid = match alg { + jws::Algorithm::EdDSA => { + let key = key + .as_ed25519() + .context("Unable to instantiate from_prior issuer key")?; + + parsed + .verify::(&key) + .context("Unable to verify from_prior signature")? + } + jws::Algorithm::Es256 => { + let key = key + .as_p256() + .context("Unable to instantiate from_prior issuer key")?; + + parsed + .verify::(&key) + .context("Unable to verify from_prior signature")? + } + jws::Algorithm::Es256K => { + let key = key + .as_k256() + .context("Unable to instantiate from_prior issuer key")?; + + parsed + .verify::(&key) + .context("Unable to verify from_prior signature")? + } + jws::Algorithm::Other(_) => Err(err_msg( + ErrorKind::Unsupported, + "Unsupported signature algorithm", + ))?, + }; + + if !valid { + Err(err_msg(ErrorKind::Malformed, "Wrong from_prior signature"))? + } + + let payload = base64::decode_config(parsed.payload, base64::URL_SAFE_NO_PAD).kind( + ErrorKind::Malformed, + "from_prior payload is not a valid base64", + )?; + + let payload = String::from_utf8(payload).kind( + ErrorKind::Malformed, + "Decoded from_prior payload is not a valid UTF-8", + )?; + + let from_prior: FromPrior = serde_json::from_str(&payload) + .kind(ErrorKind::Malformed, "Unable to parse from_prior")?; + + Ok((from_prior, kid.into())) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + did::resolvers::ExampleDIDResolver, + error::ErrorKind, + test_vectors::{ + ALICE_DID_DOC, CHARLIE_AUTH_METHOD_25519, CHARLIE_DID_DOC, FROM_PRIOR_FULL, + FROM_PRIOR_JWT_FULL, FROM_PRIOR_JWT_INVALID, FROM_PRIOR_JWT_INVALID_SIGNATURE, + }, + FromPrior, + }; + + #[tokio::test] + async fn from_prior_unpack_works() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), CHARLIE_DID_DOC.clone()]); + + let (from_prior, issuer_kid) = FromPrior::unpack(FROM_PRIOR_JWT_FULL, &did_resolver) + .await + .expect("unpack FromPrior failed"); + + assert_eq!(&from_prior, &*FROM_PRIOR_FULL); + assert_eq!(issuer_kid, CHARLIE_AUTH_METHOD_25519.id); + } + + #[tokio::test] + async fn from_prior_unpack_works_invalid() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), CHARLIE_DID_DOC.clone()]); + + let err = FromPrior::unpack(FROM_PRIOR_JWT_INVALID, &did_resolver) + .await + .expect_err("res is ok"); + + assert_eq!(err.kind(), ErrorKind::Malformed); + assert_eq!( + format!("{}", err), + "Malformed: Unable to parse compactly serialized JWS" + ); + } + + #[tokio::test] + async fn from_prior_unpack_works_invalid_signature() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), CHARLIE_DID_DOC.clone()]); + + let err = FromPrior::unpack(FROM_PRIOR_JWT_INVALID_SIGNATURE, &did_resolver) + .await + .expect_err("res is ok"); + + assert_eq!(err.kind(), ErrorKind::Malformed); + assert_eq!(format!("{}", err), "Malformed: Unable to verify from_prior signature: Unable decode signature: Invalid last symbol 66, offset 85."); + } +} diff --git a/src/message/message.rs b/src/message/message.rs index 1d91469..94e50c0 100644 --- a/src/message/message.rs +++ b/src/message/message.rs @@ -3,14 +3,15 @@ use serde_json::Value; use std::collections::HashMap; use super::Attachment; +use crate::error::{err_msg, ErrorKind, Result, ToResult}; -/// Wrapper for plain message. Provides helpers for message building and packing/unpacking. +/// Wrapper for plain message. Provides helpers for message building and packing/unpacking. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Message { /// Message id. Must be unique to the sender. pub id: String, - /// Must be + /// Must be "application/didcomm-plain+json" pub typ: String, /// Message type attribute value MUST be a valid Message Type URI, @@ -63,15 +64,35 @@ pub struct Message { #[serde(skip_serializing_if = "Option::is_none")] pub expires_time: Option, + /// from_prior is a compactly serialized signed JWT containing FromPrior value + #[serde(skip_serializing_if = "Option::is_none")] + pub from_prior: Option, + /// Message attachments #[serde(skip_serializing_if = "Option::is_none")] pub attachments: Option>, } +const PLAINTEXT_TYP: &str = "application/didcomm-plain+json"; + impl Message { pub fn build(id: String, type_: String, body: Value) -> MessageBuilder { MessageBuilder::new(id, type_, body) } + + pub(crate) fn from_str(s: &str) -> Result { + serde_json::from_str(s).to_didcomm("Unable deserialize jwm") + } + + pub(crate) fn validate(self) -> Result { + if self.typ != PLAINTEXT_TYP { + Err(err_msg( + ErrorKind::Malformed, + format!("`typ` must be \"{}\"", PLAINTEXT_TYP), + ))?; + } + Ok(self) + } } pub struct MessageBuilder { @@ -85,6 +106,7 @@ pub struct MessageBuilder { extra_headers: HashMap, created_time: Option, expires_time: Option, + from_prior: Option, attachments: Option>, } @@ -101,6 +123,7 @@ impl MessageBuilder { extra_headers: HashMap::new(), created_time: None, expires_time: None, + from_prior: None, attachments: None, } } @@ -156,23 +179,28 @@ impl MessageBuilder { self } - pub fn attachement(mut self, attachement: Attachment) -> Self { + pub fn from_prior(mut self, from_prior: String) -> Self { + self.from_prior = Some(from_prior); + self + } + + pub fn attachment(mut self, attachment: Attachment) -> Self { if let Some(ref mut attachments) = self.attachments { - attachments.push(attachement); + attachments.push(attachment); self } else { - self.attachments = Some(vec![attachement]); + self.attachments = Some(vec![attachment]); self } } - pub fn attachements(mut self, attachements: Vec) -> Self { + pub fn attachments(mut self, attachments: Vec) -> Self { if let Some(ref mut sattachments) = self.attachments { - let mut attachements = attachements; - sattachments.append(&mut attachements); + let mut attachments = attachments; + sattachments.append(&mut attachments); self } else { - self.attachments = Some(attachements); + self.attachments = Some(attachments); self } } @@ -180,7 +208,7 @@ impl MessageBuilder { pub fn finalize(self) -> Message { Message { id: self.id, - typ: "application/didcomm-plain+json".to_owned(), + typ: PLAINTEXT_TYP.to_owned(), type_: self.type_, body: self.body, to: self.to, @@ -190,6 +218,7 @@ impl MessageBuilder { extra_headers: self.extra_headers, created_time: self.created_time, expires_time: self.expires_time, + from_prior: self.from_prior, attachments: self.attachments, } } @@ -217,12 +246,12 @@ mod tests { .header("example-header-2".into(), json!("example-header-2-value")) .created_time(10000) .expires_time(20000) - .attachement( + .attachment( Attachment::base64("ZXhhbXBsZQ==".into()) .id("attachment1".into()) .finalize(), ) - .attachements(vec![ + .attachments(vec![ Attachment::json(json!("example")) .id("attachment2".into()) .finalize(), diff --git a/src/message/mod.rs b/src/message/mod.rs index 45480be..f52a060 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -1,4 +1,5 @@ mod attachment; +mod from_prior; mod message; mod pack_encrypted; mod pack_plaintext; @@ -10,7 +11,11 @@ pub use attachment::{ LinksAttachmentData, }; +pub use from_prior::FromPrior; + pub use message::{Message, MessageBuilder}; -pub use pack_encrypted::{PackEncryptedMetadata, PackEncryptedOptions}; +pub use pack_encrypted::{MessagingServiceMetadata, PackEncryptedMetadata, PackEncryptedOptions}; pub use pack_signed::PackSignedMetadata; pub use unpack::{UnpackMetadata, UnpackOptions}; + +pub(crate) use pack_encrypted::anoncrypt; diff --git a/src/message/pack_encrypted/anoncrypt.rs b/src/message/pack_encrypted/anoncrypt.rs index c2e0112..d82a2bb 100644 --- a/src/message/pack_encrypted/anoncrypt.rs +++ b/src/message/pack_encrypted/anoncrypt.rs @@ -32,8 +32,8 @@ pub(crate) async fn anoncrypt<'dr, 'sr>( let to_ddoc = did_resolver .resolve(to_did) .await - .context("Unable resolve recepient did")? - .ok_or_else(|| err_msg(ErrorKind::DIDNotResolved, "Recepient did not found"))?; + .context("Unable resolve recipient did")? + .ok_or_else(|| err_msg(ErrorKind::DIDNotResolved, "Recipient did not found"))?; // Initial list of recipient key ids is all key_agreements of recipient did doc // or one key if url was explicitly provided @@ -47,7 +47,7 @@ pub(crate) async fn anoncrypt<'dr, 'sr>( if to_kids.is_empty() { Err(err_msg( ErrorKind::DIDUrlNotFound, - "No recepient key agreements found", + "No recipient key agreements found", ))? } @@ -78,7 +78,7 @@ pub(crate) async fn anoncrypt<'dr, 'sr>( .ok_or_else(|| { err_msg( ErrorKind::InvalidState, - "No key agreement keys found for recepient", + "No key agreement keys found for recipient", ) })?; @@ -197,7 +197,7 @@ pub(crate) async fn anoncrypt<'dr, 'sr>( } _ => Err(err_msg( ErrorKind::InvalidState, - "Unsupported recepient key agreement alg", + "Unsupported recipient key agreement alg", ))?, }; diff --git a/src/message/pack_encrypted/authcrypt.rs b/src/message/pack_encrypted/authcrypt.rs index ce2a405..ad0c6da 100644 --- a/src/message/pack_encrypted/authcrypt.rs +++ b/src/message/pack_encrypted/authcrypt.rs @@ -37,8 +37,8 @@ pub(crate) async fn authcrypt<'dr, 'sr>( let to_ddoc = did_resolver .resolve(to_did) .await - .context("Unable resolve recepient did")? - .ok_or_else(|| err_msg(ErrorKind::DIDNotResolved, "Recepient did not found"))?; + .context("Unable resolve recipient did")? + .ok_or_else(|| err_msg(ErrorKind::DIDNotResolved, "Recipient did not found"))?; let (from_did, from_kid) = did_or_url(from); @@ -110,7 +110,7 @@ pub(crate) async fn authcrypt<'dr, 'sr>( if to_kids.is_empty() { Err(err_msg( ErrorKind::DIDUrlNotFound, - "No recepient key agreements found", + "No recipient key agreements found", ))? } @@ -127,7 +127,7 @@ pub(crate) async fn authcrypt<'dr, 'sr>( err_msg( ErrorKind::Malformed, format!( - "No verification material found for recepient key agreement {}", + "No verification material found for recipient key agreement {}", kid ), ) @@ -150,7 +150,7 @@ pub(crate) async fn authcrypt<'dr, 'sr>( .ok_or_else(|| { err_msg( ErrorKind::NoCompatibleCrypto, - "No common keys between sender and recepient found", + "No common keys between sender and recipient found", ) })?; @@ -318,7 +318,7 @@ pub(crate) async fn authcrypt<'dr, 'sr>( } _ => Err(err_msg( ErrorKind::Unsupported, - "Unsupported recepient key agreement method", + "Unsupported recipient key agreement method", ))?, }; diff --git a/src/message/pack_encrypted/mod.rs b/src/message/pack_encrypted/mod.rs index 0e87a24..88397ca 100644 --- a/src/message/pack_encrypted/mod.rs +++ b/src/message/pack_encrypted/mod.rs @@ -1,17 +1,24 @@ mod anoncrypt; mod authcrypt; +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::{ algorithms::{AnonCryptAlg, AuthCryptAlg}, did::DIDResolver, error::{err_msg, ErrorKind, Result, ResultContext}, + protocols::routing::wrap_in_forward_if_needed, secrets::SecretsResolver, + utils::did::{did_or_url, is_did}, Message, PackSignedMetadata, }; -use self::{anoncrypt::anoncrypt, authcrypt::authcrypt}; +pub(crate) use self::anoncrypt::anoncrypt; + +use self::authcrypt::authcrypt; impl Message { /// Produces `DIDComm Encrypted Message` @@ -60,7 +67,7 @@ impl Message { /// /// # Errors /// - `DIDNotResolved` Sender or recipient DID not found. - /// - `DIDUrlNotResolved` DID doesn't contain mentioned DID Urls (for ex., key id) + /// - `DIDUrlNotFound` DID doesn't contain mentioned DID Urls (for ex., key id) /// - `SecretNotFound` Sender secret is not found. /// - `NoCompatibleCrypto` No compatible keys are found between sender and recipient. /// - `Unsupported` Used crypto or method is unsupported. @@ -76,17 +83,17 @@ impl Message { secrets_resolver: &'sr (dyn SecretsResolver + 'sr), options: &PackEncryptedOptions, ) -> Result<(String, PackEncryptedMetadata)> { - // TODO: Support `forward` protocol wrapping - if options.forward { - Err(err_msg( - ErrorKind::Unsupported, - "Forward protocol wrapping is unsupported in this version", - ))? - }; - + self._validate_pack_encrypted(to, from, sign_by)?; // TODO: Think how to avoid resolving of did multiple times // and perform async operations in parallel + // TODO: + // 1. Extract JWE-related steps to a separate method, so that pack_encrypted uses + // the extarcted method for JWE steps and wrap_in_forward_if_needed for Routing steps. + // 2. Make anoncrypt/authcrypt separate non-public modules (not sub-modules), so that + // both pack_encrypted and Routing implementation use them (to avoid cross dependencies + // between message::pack_encrypted and protocols::routing modules). + let (msg, sign_by_kid) = if let Some(sign_by) = sign_by { let (msg, PackSignedMetadata { sign_by_kid }) = self .pack_signed(sign_by, did_resolver, secrets_resolver) @@ -95,7 +102,10 @@ impl Message { (msg, Some(sign_by_kid)) } else { - let msg = self.pack_plaintext().context("Unable produce plaintext")?; + let msg = self + .pack_plaintext(did_resolver) + .await + .context("Unable produce plaintext")?; (msg, None) }; @@ -120,8 +130,14 @@ impl Message { (msg, None, to_kids) }; + let (msg, messaging_service) = + match wrap_in_forward_if_needed(&msg, to, did_resolver, options).await? { + Some((forward_msg, messaging_service)) => (forward_msg, Some(messaging_service)), + None => (msg, None), + }; + let metadata = PackEncryptedMetadata { - messaging_service: None, + messaging_service, from_kid, sign_by_kid, to_kids, @@ -129,32 +145,88 @@ impl Message { Ok((msg, metadata)) } + + fn _validate_pack_encrypted( + &self, + to: &str, + from: Option<&str>, + sign_by: Option<&str>, + ) -> Result<()> { + if !is_did(to) { + Err(err_msg( + ErrorKind::IllegalArgument, + "`to` value is not a valid DID or DID URL", + ))?; + } + + match from { + Some(from) if !is_did(from) => Err(err_msg( + ErrorKind::IllegalArgument, + "`from` value is not a valid DID or DID URL", + ))?, + _ => {} + } + + match sign_by { + Some(sign_by) if !is_did(sign_by) => Err(err_msg( + ErrorKind::IllegalArgument, + "`sign_from` value is not a valid DID or DID URL", + ))?, + _ => {} + } + + let (to_did, _) = did_or_url(to); + + match self.to { + Some(ref sto) if !sto.contains(&to_did.into()) => { + Err(err_msg( + ErrorKind::IllegalArgument, + "`message.to` value does not contain `to` value's DID", + ))?; + } + _ => {} + } + + match (from, &self.from) { + (Some(ref from), Some(ref sfrom)) if did_or_url(from).0 != sfrom => Err(err_msg( + ErrorKind::IllegalArgument, + "`message.from` value is not equal to `from` value's DID", + ))?, + _ => {} + } + + Ok(()) + } } /// Allow fine configuration of packing process. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] pub struct PackEncryptedOptions { /// If `true` and message is authenticated than information about sender will be protected from mediators, but /// additional re-encryption will be required. For anonymous messages this property will be ignored. + #[serde(default)] pub protect_sender: bool, /// Whether the encrypted messages need to be wrapped into `Forward` messages to be sent to Mediators /// as defined by the `Forward` protocol. + #[serde(default = "crate::utils::serde::_true")] pub forward: bool, /// if forward is enabled these optional headers can be passed to the wrapping `Forward` messages. /// If forward is disabled this property will be ignored. - pub forward_headers: Option>, + pub forward_headers: Option>, /// Identifier (DID URL) of messaging service (https://identity.foundation/didcomm-messaging/spec/#did-document-service-endpoint). - /// If DID contains multiple messaging services it allows specify what service to use. + /// If DID doc contains multiple messaging services it allows specify what service to use. /// If not present first service will be used. pub messaging_service: Option, /// Algorithm used for authenticated encryption + #[serde(default)] pub enc_alg_auth: AuthCryptAlg, /// Algorithm used for anonymous encryption + #[serde(default)] pub enc_alg_anon: AnonCryptAlg, } @@ -165,15 +237,15 @@ impl Default for PackEncryptedOptions { forward: true, forward_headers: None, messaging_service: None, - enc_alg_auth: AuthCryptAlg::A256cbcHs512Ecdh1puA256kw, - enc_alg_anon: AnonCryptAlg::Xc20pEcdhEsA256kw, + enc_alg_auth: AuthCryptAlg::default(), + enc_alg_anon: AnonCryptAlg::default(), } } } /// Additional metadata about this `encrypt` method execution like used keys identifiers, /// used messaging service. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct PackEncryptedMetadata { /// Information about messaging service used for message preparation. /// Practically `service_endpoint` field can be used to transport the message. @@ -191,7 +263,7 @@ pub struct PackEncryptedMetadata { /// Information about messaging service used for message preparation. /// Practically `service_endpoint` field can be used to transport the message. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct MessagingServiceMetadata { /// Identifier (DID URL) of used messaging service. pub id: String, @@ -202,6 +274,8 @@ pub struct MessagingServiceMetadata { #[cfg(test)] mod tests { + use std::{collections::HashMap, iter::FromIterator}; + use askar_crypto::{ alg::{ aes::{A256CbcHs512, A256Gcm, A256Kw, AesKey}, @@ -216,25 +290,39 @@ mod tests { repr::{KeyGen, KeySecretBytes}, sign::KeySigVerify, }; - use serde_json::Value; + + use serde_json::{json, Value}; use crate::{ algorithms::AnonCryptAlg, did::{resolvers::ExampleDIDResolver, VerificationMaterial, VerificationMethod}, + error::ErrorKind, jwe, jwk::{FromJwkValue, ToJwkValue}, jws, + message::MessagingServiceMetadata, + protocols::routing::{try_parse_forward, wrap_in_forward}, secrets::{resolvers::ExampleSecretsResolver, Secret, SecretMaterial}, test_vectors::{ ALICE_AUTH_METHOD_25519, ALICE_AUTH_METHOD_P256, ALICE_AUTH_METHOD_SECPP256K1, - ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, ALICE_VERIFICATION_METHOD_KEY_AGREEM_P256, - ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519, BOB_DID, BOB_DID_DOC, - BOB_SECRET_KEY_AGREEMENT_KEY_P256_1, BOB_SECRET_KEY_AGREEMENT_KEY_P256_2, + ALICE_DID, ALICE_DID_DOC, ALICE_DID_DOC_WITH_NO_SECRETS, ALICE_SECRETS, + ALICE_VERIFICATION_METHOD_KEY_AGREEM_P256, ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519, + BOB_DID, BOB_DID_COMM_MESSAGING_SERVICE, BOB_DID_DOC, BOB_DID_DOC_NO_SECRETS, + BOB_SECRETS, BOB_SECRET_KEY_AGREEMENT_KEY_P256_1, BOB_SECRET_KEY_AGREEMENT_KEY_P256_2, BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1, BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2, - BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3, MESSAGE_SIMPLE, PLAINTEXT_MSG_SIMPLE, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3, BOB_SERVICE, CHARLIE_DID, CHARLIE_DID_DOC, + CHARLIE_ROTATED_TO_ALICE_SECRETS, CHARLIE_SECRETS, CHARLIE_SECRET_AUTH_KEY_ED25519, + CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519, CHARLIE_SERVICE, FROM_PRIOR_FULL, + MEDIATOR1_DID_DOC, MEDIATOR1_SECRETS, MEDIATOR2_DID_DOC, MEDIATOR2_SECRETS, + MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_X25519_1, + MEDIATOR3_DID_COMM_MESSAGING_SERVICE, MEDIATOR3_DID_DOC, MEDIATOR3_SECRETS, + MESSAGE_FROM_PRIOR_FULL, MESSAGE_SIMPLE, PLAINTEXT_MSG_SIMPLE, }, - utils::crypto::{JoseKDF, KeyWrap}, - PackEncryptedMetadata, PackEncryptedOptions, + utils::{ + crypto::{JoseKDF, KeyWrap}, + did::did_or_url, + }, + Message, PackEncryptedMetadata, PackEncryptedOptions, UnpackOptions, }; #[tokio::test] @@ -1425,6 +1513,1322 @@ mod tests { } } + #[tokio::test] + async fn pack_encrypted_works_single_mediator() { + _pack_encrypted_works_single_mediator(BOB_DID, None, None).await; + + _pack_encrypted_works_single_mediator(BOB_DID, None, Some(ALICE_DID)).await; + + _pack_encrypted_works_single_mediator(BOB_DID, Some(ALICE_DID), None).await; + + _pack_encrypted_works_single_mediator(BOB_DID, Some(ALICE_DID), Some(ALICE_DID)).await; + + _pack_encrypted_works_single_mediator( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + None, + None, + ) + .await; + + _pack_encrypted_works_single_mediator( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + None, + Some(ALICE_DID), + ) + .await; + + _pack_encrypted_works_single_mediator( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + Some(ALICE_DID), + None, + ) + .await; + + _pack_encrypted_works_single_mediator( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + Some(ALICE_DID), + Some(ALICE_DID), + ) + .await; + + async fn _pack_encrypted_works_single_mediator( + to: &str, + from: Option<&str>, + sign_by: Option<&str>, + ) { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let alice_secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let bob_secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let mediator1_secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, pack_metadata) = MESSAGE_SIMPLE + .pack_encrypted( + to, + from, + sign_by, + &did_resolver, + &alice_secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable encrypt"); + + assert_eq!( + pack_metadata.messaging_service.as_ref(), + Some(&MessagingServiceMetadata { + id: BOB_SERVICE.id.clone(), + service_endpoint: BOB_DID_COMM_MESSAGING_SERVICE.service_endpoint.clone(), + }) + ); + + assert_eq!( + pack_metadata.from_kid.map(|k| did_or_url(&k).0.to_owned()), + from.map(|d| d.to_owned()) + ); + assert_eq!( + pack_metadata + .sign_by_kid + .map(|k| did_or_url(&k).0.to_owned()), + sign_by.map(|d| d.to_owned()) + ); + + match did_or_url(to) { + (_, Some(to_kid)) => { + assert_eq!( + pack_metadata + .to_kids + .iter() + .map(|k| k.as_str()) + .collect::>(), + vec![to_kid] + ) + } + (to_did, None) => { + for metadata_to_kid in pack_metadata.to_kids { + assert_eq!(did_or_url(&metadata_to_kid).0, to_did); + } + } + } + + let (unpacked_msg_mediator1, unpack_metadata_mediator1) = Message::unpack( + &msg, + &did_resolver, + &mediator1_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + let forward = + try_parse_forward(&unpacked_msg_mediator1).expect("Message is not Forward"); + + assert_eq!(forward.msg, &unpacked_msg_mediator1); + assert_eq!(&forward.next, to); + + assert!(unpack_metadata_mediator1.encrypted); + assert!(!unpack_metadata_mediator1.authenticated); + assert!(!unpack_metadata_mediator1.non_repudiation); + assert!(unpack_metadata_mediator1.anonymous_sender); + assert!(!unpack_metadata_mediator1.re_wrapped_in_forward); + + let forwarded_msg = serde_json::to_string(&forward.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let (unpacked_msg, unpack_metadata) = Message::unpack( + &forwarded_msg, + &did_resolver, + &bob_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + assert_eq!(&unpacked_msg, &*MESSAGE_SIMPLE); + + assert!(unpack_metadata.encrypted); + assert_eq!( + unpack_metadata.authenticated, + from.is_some() || sign_by.is_some() + ); + assert_eq!(unpack_metadata.non_repudiation, sign_by.is_some()); + assert_eq!(unpack_metadata.anonymous_sender, from.is_none()); + assert!(!unpack_metadata.re_wrapped_in_forward); + } + } + + #[tokio::test] + async fn pack_encrypted_works_multiple_mediators_alternative_endpoints() { + _pack_encrypted_works_multiple_mediators_alternative_endpoints(CHARLIE_DID, None, None) + .await; + + _pack_encrypted_works_multiple_mediators_alternative_endpoints( + CHARLIE_DID, + None, + Some(ALICE_DID), + ) + .await; + + _pack_encrypted_works_multiple_mediators_alternative_endpoints( + CHARLIE_DID, + Some(ALICE_DID), + None, + ) + .await; + + _pack_encrypted_works_multiple_mediators_alternative_endpoints( + CHARLIE_DID, + Some(ALICE_DID), + Some(ALICE_DID), + ) + .await; + + _pack_encrypted_works_multiple_mediators_alternative_endpoints( + &CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519.id, + None, + None, + ) + .await; + + _pack_encrypted_works_multiple_mediators_alternative_endpoints( + &CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519.id, + None, + Some(ALICE_DID), + ) + .await; + + _pack_encrypted_works_multiple_mediators_alternative_endpoints( + &CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519.id, + Some(ALICE_DID), + None, + ) + .await; + + _pack_encrypted_works_multiple_mediators_alternative_endpoints( + &CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519.id, + Some(ALICE_DID), + Some(ALICE_DID), + ) + .await; + + async fn _pack_encrypted_works_multiple_mediators_alternative_endpoints( + to: &str, + from: Option<&str>, + sign_by: Option<&str>, + ) { + let msg = Message::build( + "1234567890".to_owned(), + "http://example.com/protocols/lets_do_lunch/1.0/proposal".to_owned(), + json!({"messagespecificattribute": "and its value"}), + ) + .from(ALICE_DID.to_owned()) + .to(CHARLIE_DID.to_owned()) + .created_time(1516269022) + .expires_time(1516385931) + .finalize(); + + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ]); + + let alice_secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let charlie_secrets_resolver = ExampleSecretsResolver::new(CHARLIE_SECRETS.clone()); + + let mediator1_secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let mediator2_secrets_resolver = ExampleSecretsResolver::new(MEDIATOR2_SECRETS.clone()); + + let mediator3_secrets_resolver = ExampleSecretsResolver::new(MEDIATOR3_SECRETS.clone()); + + let (packed_msg, pack_metadata) = msg + .pack_encrypted( + to, + from, + sign_by, + &did_resolver, + &alice_secrets_resolver, + &PackEncryptedOptions { + forward_headers: Some(HashMap::from_iter([ + ("example-header-1".into(), json!("example-header-1-value")), + ("example-header-2".into(), json!("example-header-2-value")), + ])), + ..PackEncryptedOptions::default() + }, + ) + .await + .expect("Unable encrypt"); + + assert_eq!( + pack_metadata.messaging_service.as_ref(), + Some(&MessagingServiceMetadata { + id: CHARLIE_SERVICE.id.clone(), + service_endpoint: MEDIATOR3_DID_COMM_MESSAGING_SERVICE + .service_endpoint + .clone(), + }) + ); + + assert_eq!( + pack_metadata.from_kid.map(|k| did_or_url(&k).0.to_owned()), + from.map(|d| d.to_owned()) + ); + assert_eq!( + pack_metadata + .sign_by_kid + .map(|k| did_or_url(&k).0.to_owned()), + sign_by.map(|d| d.to_owned()) + ); + + match did_or_url(to) { + (_, Some(to_kid)) => { + assert_eq!( + pack_metadata + .to_kids + .iter() + .map(|k| k.as_str()) + .collect::>(), + vec![to_kid] + ) + } + (to_did, None) => { + for metadata_to_kid in pack_metadata.to_kids { + assert_eq!(did_or_url(&metadata_to_kid).0, to_did); + } + } + } + + let (unpacked_msg_mediator3, unpack_metadata_mediator3) = Message::unpack( + &packed_msg, + &did_resolver, + &mediator3_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + let forward_at_mediator3 = + try_parse_forward(&unpacked_msg_mediator3).expect("Message is not Forward"); + + assert_eq!(forward_at_mediator3.msg, &unpacked_msg_mediator3); + + assert_eq!( + &forward_at_mediator3.msg.extra_headers, + &HashMap::from_iter([ + ("example-header-1".into(), json!("example-header-1-value")), + ("example-header-2".into(), json!("example-header-2-value")), + ]) + ); + + assert_eq!( + &forward_at_mediator3.next, + "did:example:mediator2#key-x25519-1" + ); + + assert!(unpack_metadata_mediator3.encrypted); + assert!(!unpack_metadata_mediator3.authenticated); + assert!(!unpack_metadata_mediator3.non_repudiation); + assert!(unpack_metadata_mediator3.anonymous_sender); + assert!(!unpack_metadata_mediator3.re_wrapped_in_forward); + + let forwarded_msg_at_mediator3 = + serde_json::to_string(&forward_at_mediator3.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let (unpacked_msg_mediator2, unpack_metadata_mediator2) = Message::unpack( + &forwarded_msg_at_mediator3, + &did_resolver, + &mediator2_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + let forward_at_mediator2 = + try_parse_forward(&unpacked_msg_mediator2).expect("Message is not Forward"); + + assert_eq!(forward_at_mediator2.msg, &unpacked_msg_mediator2); + + assert_eq!( + &forward_at_mediator2.msg.extra_headers, + &HashMap::from_iter([ + ("example-header-1".into(), json!("example-header-1-value")), + ("example-header-2".into(), json!("example-header-2-value")), + ]) + ); + + assert_eq!( + &forward_at_mediator2.next, + "did:example:mediator1#key-x25519-1" + ); + + assert!(unpack_metadata_mediator2.encrypted); + assert!(!unpack_metadata_mediator2.authenticated); + assert!(!unpack_metadata_mediator2.non_repudiation); + assert!(unpack_metadata_mediator2.anonymous_sender); + assert!(!unpack_metadata_mediator2.re_wrapped_in_forward); + + let forwarded_msg_at_mediator2 = + serde_json::to_string(&forward_at_mediator2.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let (unpacked_msg_mediator1, unpack_metadata_mediator1) = Message::unpack( + &forwarded_msg_at_mediator2, + &did_resolver, + &mediator1_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + let forward_at_mediator1 = + try_parse_forward(&unpacked_msg_mediator1).expect("Message is not Forward"); + + assert_eq!(forward_at_mediator1.msg, &unpacked_msg_mediator1); + + assert_eq!( + &forward_at_mediator1.msg.extra_headers, + &HashMap::from_iter([ + ("example-header-1".into(), json!("example-header-1-value")), + ("example-header-2".into(), json!("example-header-2-value")), + ]) + ); + + assert_eq!(&forward_at_mediator1.next, to); + + assert!(unpack_metadata_mediator1.encrypted); + assert!(!unpack_metadata_mediator1.authenticated); + assert!(!unpack_metadata_mediator1.non_repudiation); + assert!(unpack_metadata_mediator1.anonymous_sender); + assert!(!unpack_metadata_mediator1.re_wrapped_in_forward); + + let forwarded_msg_at_mediator1 = + serde_json::to_string(&forward_at_mediator1.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let (unpacked_msg, unpack_metadata) = Message::unpack( + &forwarded_msg_at_mediator1, + &did_resolver, + &charlie_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + assert_eq!(&unpacked_msg, &msg); + + assert!(unpack_metadata.encrypted); + assert_eq!( + unpack_metadata.authenticated, + from.is_some() || sign_by.is_some() + ); + assert_eq!(unpack_metadata.non_repudiation, sign_by.is_some()); + assert_eq!(unpack_metadata.anonymous_sender, from.is_none()); + assert!(!unpack_metadata.re_wrapped_in_forward); + } + } + + #[tokio::test] + async fn wrap_in_forward_works_mediator_unknown_to_sender() { + _wrap_in_forward_works_mediator_unknown_to_sender(BOB_DID, None, None).await; + + _wrap_in_forward_works_mediator_unknown_to_sender(BOB_DID, None, Some(ALICE_DID)).await; + + _wrap_in_forward_works_mediator_unknown_to_sender(BOB_DID, Some(ALICE_DID), None).await; + + _wrap_in_forward_works_mediator_unknown_to_sender( + BOB_DID, + Some(ALICE_DID), + Some(ALICE_DID), + ) + .await; + + _wrap_in_forward_works_mediator_unknown_to_sender( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + None, + None, + ) + .await; + + _wrap_in_forward_works_mediator_unknown_to_sender( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + None, + Some(ALICE_DID), + ) + .await; + + _wrap_in_forward_works_mediator_unknown_to_sender( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + Some(ALICE_DID), + None, + ) + .await; + + _wrap_in_forward_works_mediator_unknown_to_sender( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + Some(ALICE_DID), + Some(ALICE_DID), + ) + .await; + + async fn _wrap_in_forward_works_mediator_unknown_to_sender( + to: &str, + from: Option<&str>, + sign_by: Option<&str>, + ) { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + ]); + + let alice_secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let bob_secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let mediator1_secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let mediator2_secrets_resolver = ExampleSecretsResolver::new(MEDIATOR2_SECRETS.clone()); + + let (msg, pack_metadata) = MESSAGE_SIMPLE + .pack_encrypted( + to, + from, + sign_by, + &did_resolver, + &alice_secrets_resolver, + &PackEncryptedOptions { + messaging_service: Some(BOB_SERVICE.id.clone()), + ..PackEncryptedOptions::default() + }, + ) + .await + .expect("Unable encrypt"); + + assert_eq!( + pack_metadata.messaging_service.as_ref(), + Some(&MessagingServiceMetadata { + id: BOB_SERVICE.id.clone(), + service_endpoint: BOB_DID_COMM_MESSAGING_SERVICE.service_endpoint.clone(), + }) + ); + + let (unpacked_msg_mediator1, unpack_metadata_mediator1) = Message::unpack( + &msg, + &did_resolver, + &mediator1_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + let forward_at_mediator1 = + try_parse_forward(&unpacked_msg_mediator1).expect("Message is not Forward"); + + assert_eq!(forward_at_mediator1.msg, &unpacked_msg_mediator1); + assert_eq!(&forward_at_mediator1.next, to); + + assert!(unpack_metadata_mediator1.encrypted); + assert!(!unpack_metadata_mediator1.authenticated); + assert!(!unpack_metadata_mediator1.non_repudiation); + assert!(unpack_metadata_mediator1.anonymous_sender); + assert!(!unpack_metadata_mediator1.re_wrapped_in_forward); + + let forwarded_msg_at_mediator1 = + serde_json::to_string(&forward_at_mediator1.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let forward_msg_for_mediator2 = wrap_in_forward( + &forwarded_msg_at_mediator1, + None, + &forward_at_mediator1.next, + &vec![MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_X25519_1.id.clone()], + &AnonCryptAlg::default(), + &did_resolver, + ) + .await + .expect("Unable wrap in forward"); + + let (unpacked_msg_mediator2, unpack_metadata_mediator2) = Message::unpack( + &forward_msg_for_mediator2, + &did_resolver, + &mediator2_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + let forward_at_mediator2 = + try_parse_forward(&unpacked_msg_mediator2).expect("Message is not Forward"); + + assert_eq!(forward_at_mediator2.msg, &unpacked_msg_mediator2); + assert_eq!(&forward_at_mediator2.next, to); + + assert!(unpack_metadata_mediator2.encrypted); + assert!(!unpack_metadata_mediator2.authenticated); + assert!(!unpack_metadata_mediator2.non_repudiation); + assert!(unpack_metadata_mediator2.anonymous_sender); + assert!(!unpack_metadata_mediator2.re_wrapped_in_forward); + + let forwarded_msg_at_mediator2 = + serde_json::to_string(&forward_at_mediator2.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let (unpacked_msg, unpack_metadata) = Message::unpack( + &forwarded_msg_at_mediator2, + &did_resolver, + &bob_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + assert_eq!(&unpacked_msg, &*MESSAGE_SIMPLE); + + assert!(unpack_metadata.encrypted); + assert_eq!( + unpack_metadata.authenticated, + from.is_some() || sign_by.is_some() + ); + assert_eq!(unpack_metadata.non_repudiation, sign_by.is_some()); + assert_eq!(unpack_metadata.anonymous_sender, from.is_none()); + assert!(!unpack_metadata.re_wrapped_in_forward); + } + } + + // TODO: Add negative tests for Routing protocol + + #[tokio::test] + async fn pack_encrypted_works_from_not_did_or_did_url() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_encrypted( + BOB_DID, + "not-a-did".into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `from` value is not a valid DID or DID URL" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_to_not_did_or_did_url() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_encrypted( + "not-a-did".into(), + None, + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `to` value is not a valid DID or DID URL" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_sign_by_not_did_or_did_url() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_encrypted( + BOB_DID, + ALICE_DID.into(), + "not-a-did".into(), + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `sign_from` value is not a valid DID or DID URL" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_from_differs_msg_from() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.from = CHARLIE_DID.to_string().into(); + let res = msg + .pack_encrypted( + BOB_DID, + ALICE_DID.into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `message.from` value is not equal to `from` value's DID" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_to_differs_msg_to() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.to = Some(vec![CHARLIE_DID.to_string()]); + let res = msg + .pack_encrypted( + BOB_DID, + ALICE_DID.into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `message.to` value does not contain `to` value's DID" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_to_presented_in_msg_to() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.to = Some(vec![CHARLIE_DID.to_string(), BOB_DID.to_string()]); + let _ = msg + .pack_encrypted( + BOB_DID, + ALICE_DID.into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + } + + #[tokio::test] + async fn pack_encrypted_works_from_not_did_or_did_url_in_msg() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.from = "not-a-did".to_string().into(); + let res = msg + .pack_encrypted( + BOB_DID, + "not-a-did".into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `from` value is not a valid DID or DID URL" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_to_not_did_or_did_url_in_msg() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.to = Some(vec!["not-a-did".to_string()]); + let res = msg + .pack_encrypted( + "not-a-did".into(), + ALICE_DID.into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `to` value is not a valid DID or DID URL" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_from_did_url_from_msg_did_positive() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let _ = MESSAGE_SIMPLE + .pack_encrypted( + BOB_DID, + "did:example:alice#key-x25519-1".into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + } + + #[tokio::test] + async fn pack_encrypted_works_to_did_url_to_msg_did_positive() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.to = Some(vec![ALICE_DID.to_string(), BOB_DID.to_string()]); + let _ = msg + .pack_encrypted( + "did:example:bob#key-x25519-1".into(), + None, + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + } + + #[tokio::test] + async fn pack_encrypted_works_sign_by_differs_msg_from_positive() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let _ = MESSAGE_SIMPLE + .pack_encrypted( + BOB_DID, + ALICE_DID.into(), + CHARLIE_DID.into(), + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + } + + #[tokio::test] + async fn pack_encrypted_works_from_did_from_msg_did_url() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.from = "did:example:alice#key-x25519-1".to_string().into(); + + let res = msg + .pack_encrypted( + BOB_DID, + ALICE_DID.into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `message.from` value is not equal to `from` value's DID" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_to_did_to_msg_did_url() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.to = Some(vec!["did:example:bob#key-x25519-1".into()]); + let res = msg + .pack_encrypted( + BOB_DID, + None, + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `message.to` value does not contain `to` value's DID" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_from_unknown_did() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.from = "did:example:unknown".to_string().into(); + let res = msg + .pack_encrypted( + BOB_DID, + "did:example:unknown".into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::DIDNotResolved); + + assert_eq!(format!("{}", err), "DID not resolved: Sender did not found"); + } + + #[tokio::test] + async fn pack_encrypted_works_from_unknown_did_url() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let from = ALICE_DID.to_string() + "#unknown-key"; + let res = MESSAGE_SIMPLE + .pack_encrypted( + BOB_DID, + from.as_str().into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::DIDUrlNotFound); + + assert_eq!( + format!("{}", err), + "DID URL not found: No sender key agreements found" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_to_unknown_did() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let mut msg = MESSAGE_SIMPLE.clone(); + msg.to = Some(vec!["did:example:unknown".into()]); + let res = msg + .pack_encrypted( + "did:example:unknown", + None, + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::DIDNotResolved); + + assert_eq!( + format!("{}", err), + "DID not resolved: Recipient did not found" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_to_unknown_did_url() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let to = BOB_DID.to_string() + "#unknown-key"; + let res = MESSAGE_SIMPLE + .pack_encrypted( + to.as_str(), + ALICE_DID.into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::DIDUrlNotFound); + + assert_eq!( + format!("{}", err), + "DID URL not found: No recipient key agreements found" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_sign_by_unknown_did_url() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let sign_by = ALICE_DID.to_string() + "#unknown-key"; + let res = MESSAGE_SIMPLE + .pack_encrypted( + BOB_DID, + ALICE_DID.into(), + sign_by.as_str().into(), + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::DIDUrlNotFound); + + assert_eq!( + format!("{}", err), + "DID URL not found: Unable produce sign envelope: Signer key id not found in did doc" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_from_not_in_secrets() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_encrypted( + BOB_DID, + "did:example:alice#key-x25519-not-in-secrets-1".into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::SecretNotFound); + + assert_eq!( + format!("{}", err), + "Secret not found: No sender secrets found" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_sign_by_not_in_secrets() { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC_WITH_NO_SECRETS.clone(), + BOB_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_encrypted( + BOB_DID, + ALICE_DID.into(), + "did:example:alice#key-not-in-secrets-1".into(), + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::SecretNotFound); + + assert_eq!( + format!("{}", err), + "Secret not found: Unable produce sign envelope: No signer secrets found" + ); + } + + #[tokio::test] + async fn pack_encrypted_works_to_not_in_secrets_positive() { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC_NO_SECRETS.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let to = "did:example:bob#key-x25519-not-secrets-1"; + let _ = MESSAGE_SIMPLE + .pack_encrypted( + to, + ALICE_DID.into(), + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + } + + #[tokio::test] + async fn pack_encrypted_works_to_from_different_curves() { + _pack_encrypted_works_to_from_different_curves( + "did:example:alice#key-x25519-1".into(), + "did:example:bob#key-p256-1", + ) + .await; + _pack_encrypted_works_to_from_different_curves( + "did:example:alice#key-x25519-1".into(), + "did:example:bob#key-p384-1", + ) + .await; + _pack_encrypted_works_to_from_different_curves( + "did:example:alice#key-x25519-1".into(), + "did:example:bob#key-p521-1", + ) + .await; + _pack_encrypted_works_to_from_different_curves( + "did:example:alice#key-p256-1".into(), + "did:example:bob#key-p384-1", + ) + .await; + _pack_encrypted_works_to_from_different_curves( + "did:example:alice#key-p256-1".into(), + "did:example:bob#key-p521-1", + ) + .await; + _pack_encrypted_works_to_from_different_curves( + "did:example:alice#key-p521-1".into(), + "did:example:bob#key-p384-1", + ) + .await; + + async fn _pack_encrypted_works_to_from_different_curves(from: Option<&str>, to: &str) { + let did_resolver = + ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_encrypted( + to, + from, + None, + &did_resolver, + &secrets_resolver, + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::NoCompatibleCrypto); + + assert_eq!( + format!("{}", err), + "No compatible crypto: No common keys between sender and recipient found" + ); + } + } + + #[tokio::test] + async fn pack_encrypted_works_from_prior() { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + ]); + let charlie_rotated_to_alice_secrets_resolver = + ExampleSecretsResolver::new(CHARLIE_ROTATED_TO_ALICE_SECRETS.clone()); + let bob_secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (packed_msg, _pack_metadata) = MESSAGE_FROM_PRIOR_FULL + .pack_encrypted( + BOB_DID, + Some(ALICE_DID), + None, + &did_resolver, + &charlie_rotated_to_alice_secrets_resolver, + &&PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + ) + .await + .expect("Unable pack_encrypted"); + + let (unpacked_msg, unpack_metadata) = Message::unpack( + &packed_msg, + &did_resolver, + &bob_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + assert_eq!(&unpacked_msg, &*MESSAGE_FROM_PRIOR_FULL); + assert_eq!( + unpack_metadata.from_prior_issuer_kid.as_ref(), + Some(&CHARLIE_SECRET_AUTH_KEY_ED25519.id) + ); + assert_eq!(unpack_metadata.from_prior.as_ref(), Some(&*FROM_PRIOR_FULL)); + } + fn _verify_authcrypt( msg: &str, to_keys: Vec<&Secret>, @@ -1464,15 +2868,15 @@ mod tests { let to_kid = &to_key.id; let from_key = match from_key.verification_material { - VerificationMaterial::JWK(ref jwk) => { - KE::from_jwk_value(jwk).expect("Unable from_jwk_value") + VerificationMaterial::JWK { ref value } => { + KE::from_jwk_value(value).expect("Unable from_jwk_value") } _ => panic!("Unexpected verification method"), }; let to_key = match to_key.secret_material { - SecretMaterial::JWK(ref jwk) => { - KE::from_jwk_value(jwk).expect("Unable from_jwk_value") + SecretMaterial::JWK { ref value } => { + KE::from_jwk_value(value).expect("Unable from_jwk_value") } _ => panic!("Unexpected verification method"), }; @@ -1531,8 +2935,8 @@ mod tests { let to_kid = &to_key.id; let to_key = match to_key.secret_material { - SecretMaterial::JWK(ref jwk) => { - KE::from_jwk_value(jwk).expect("Unable from_jwk_value") + SecretMaterial::JWK { ref value } => { + KE::from_jwk_value(value).expect("Unable from_jwk_value") } _ => panic!("Unexpected verification method"), }; @@ -1579,8 +2983,8 @@ mod tests { let sign_key_id = &sign_key.id; let sign_key = match sign_key.verification_material { - VerificationMaterial::JWK(ref jwk) => { - Key::from_jwk_value(jwk).expect("Unable from_jwk_value") + VerificationMaterial::JWK { ref value } => { + Key::from_jwk_value(value).expect("Unable from_jwk_value") } _ => panic!("Unexpected verification_material"), }; diff --git a/src/message/pack_plaintext.rs b/src/message/pack_plaintext.rs index 465ecaf..f14054e 100644 --- a/src/message/pack_plaintext.rs +++ b/src/message/pack_plaintext.rs @@ -1,6 +1,7 @@ use crate::{ - error::{ErrorKind, Result, ResultExt}, - Message, + did::DIDResolver, + error::{err_msg, ErrorKind, Result, ResultExt}, + FromPrior, Message, }; impl Message { @@ -18,9 +19,51 @@ impl Message { /// - a DIDComm plaintext message s JSON string /// /// # Errors - /// - InvalidState - pub fn pack_plaintext(&self) -> Result { - serde_json::to_string(self).kind(ErrorKind::InvalidState, "Unable serialize message") + /// - `Malformed` Signed `from_prior` JWT is malformed. + /// - `DIDNotResolved` `from_prior` issuer DID not found. + /// - `DIDUrlNotFound` `from_prior` issuer authentication verification method is not found. + /// - `Unsupported` Crypto or method used for signing `from_prior` is unsupported. + /// - `InvalidState` Indicates a library error. + pub async fn pack_plaintext<'dr, 'sr>( + &self, + did_resolver: &'dr (dyn DIDResolver + 'dr), + ) -> Result { + let (from_prior, from_prior_issuer_kid) = match self.from_prior { + Some(ref from_prior) => { + let (from_prior, from_prior_issuer_kid) = + FromPrior::unpack(from_prior, did_resolver).await?; + (Some(from_prior), Some(from_prior_issuer_kid)) + } + None => (None, None), + }; + + self._validate_pack_plaintext(from_prior.as_ref(), from_prior_issuer_kid.as_deref())?; + + let msg = serde_json::to_string(self) + .kind(ErrorKind::InvalidState, "Unable to serialize message")?; + + Ok(msg) + } + + fn _validate_pack_plaintext( + &self, + from_prior: Option<&FromPrior>, + from_prior_issuer_kid: Option<&str>, + ) -> Result<()> { + if let Some(from_prior) = from_prior { + from_prior.validate_pack(from_prior_issuer_kid)?; + + if let Some(ref from) = self.from { + if &from_prior.sub != from { + Err(err_msg( + ErrorKind::Malformed, + "from_prior `sub` value is not equal to message `from` value", + ))?; + } + } + } + + Ok(()) } } @@ -29,42 +72,107 @@ mod tests { use serde_json::Value; use crate::{ + did::resolvers::ExampleDIDResolver, + error::ErrorKind, + secrets::resolvers::ExampleSecretsResolver, test_vectors::{ - MESSAGE_ATTACHMENT_BASE64, MESSAGE_ATTACHMENT_JSON, MESSAGE_ATTACHMENT_LINKS, - MESSAGE_ATTACHMENT_MULTI_1, MESSAGE_ATTACHMENT_MULTI_2, MESSAGE_MINIMAL, - MESSAGE_SIMPLE, PLAINTEXT_MSG_ATTACHMENT_BASE64, PLAINTEXT_MSG_ATTACHMENT_JSON, + ALICE_DID_DOC, BOB_DID_DOC, BOB_SECRETS, CHARLIE_DID_DOC, + CHARLIE_SECRET_AUTH_KEY_ED25519, FROM_PRIOR_FULL, MESSAGE_ATTACHMENT_BASE64, + MESSAGE_ATTACHMENT_JSON, MESSAGE_ATTACHMENT_LINKS, MESSAGE_ATTACHMENT_MULTI_1, + MESSAGE_ATTACHMENT_MULTI_2, MESSAGE_FROM_PRIOR_FULL, + MESSAGE_FROM_PRIOR_MISMATCHED_SUB_AND_FROM, MESSAGE_MINIMAL, MESSAGE_SIMPLE, + PLAINTEXT_MSG_ATTACHMENT_BASE64, PLAINTEXT_MSG_ATTACHMENT_JSON, PLAINTEXT_MSG_ATTACHMENT_LINKS, PLAINTEXT_MSG_ATTACHMENT_MULTI_1, PLAINTEXT_MSG_ATTACHMENT_MULTI_2, PLAINTEXT_MSG_MINIMAL, PLAINTEXT_MSG_SIMPLE, }, - Message, + Message, UnpackOptions, }; - #[test] - fn pack_plaintext_works() { - _pack_plaintext_works(&MESSAGE_SIMPLE, PLAINTEXT_MSG_SIMPLE); - _pack_plaintext_works(&MESSAGE_MINIMAL, PLAINTEXT_MSG_MINIMAL); + #[tokio::test] + async fn pack_plaintext_works() { + _pack_plaintext_works(&MESSAGE_SIMPLE, PLAINTEXT_MSG_SIMPLE).await; + _pack_plaintext_works(&MESSAGE_MINIMAL, PLAINTEXT_MSG_MINIMAL).await; - _pack_plaintext_works(&MESSAGE_ATTACHMENT_BASE64, PLAINTEXT_MSG_ATTACHMENT_BASE64); + _pack_plaintext_works(&MESSAGE_ATTACHMENT_BASE64, PLAINTEXT_MSG_ATTACHMENT_BASE64).await; - _pack_plaintext_works(&MESSAGE_ATTACHMENT_JSON, PLAINTEXT_MSG_ATTACHMENT_JSON); - _pack_plaintext_works(&MESSAGE_ATTACHMENT_LINKS, PLAINTEXT_MSG_ATTACHMENT_LINKS); + _pack_plaintext_works(&MESSAGE_ATTACHMENT_JSON, PLAINTEXT_MSG_ATTACHMENT_JSON).await; + _pack_plaintext_works(&MESSAGE_ATTACHMENT_LINKS, PLAINTEXT_MSG_ATTACHMENT_LINKS).await; _pack_plaintext_works( &MESSAGE_ATTACHMENT_MULTI_1, PLAINTEXT_MSG_ATTACHMENT_MULTI_1, - ); + ) + .await; _pack_plaintext_works( &MESSAGE_ATTACHMENT_MULTI_2, PLAINTEXT_MSG_ATTACHMENT_MULTI_2, - ); + ) + .await; + + async fn _pack_plaintext_works(msg: &Message, exp_msg: &str) { + let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone()]); - fn _pack_plaintext_works(msg: &Message, exp_msg: &str) { - let msg = msg.pack_plaintext().expect("Unable pack_plaintext"); + let msg = msg + .pack_plaintext(&did_resolver) + .await + .expect("Unable pack_plaintext"); let msg: Value = serde_json::from_str(&msg).expect("Unable from_str"); let exp_msg: Value = serde_json::from_str(exp_msg).expect("Unable from_str"); - assert_eq!(msg, exp_msg) + assert_eq!(msg, exp_msg); } } + + #[tokio::test] + async fn pack_plaintext_works_from_prior() { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + ]); + let bob_secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let packed_msg = MESSAGE_FROM_PRIOR_FULL + .pack_plaintext(&did_resolver) + .await + .expect("Unable pack_plaintext"); + + let (unpacked_msg, unpack_metadata) = Message::unpack( + &packed_msg, + &did_resolver, + &bob_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + assert_eq!(&unpacked_msg, &*MESSAGE_FROM_PRIOR_FULL); + assert_eq!( + unpack_metadata.from_prior_issuer_kid.as_ref(), + Some(&CHARLIE_SECRET_AUTH_KEY_ED25519.id) + ); + assert_eq!(unpack_metadata.from_prior.as_ref(), Some(&*FROM_PRIOR_FULL)); + } + + #[tokio::test] + async fn pack_plaintext_works_mismatched_from_prior_sub_and_message_from() { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + ]); + + let err = MESSAGE_FROM_PRIOR_MISMATCHED_SUB_AND_FROM + .pack_plaintext(&did_resolver) + .await + .expect_err("res is ok"); + + assert_eq!(err.kind(), ErrorKind::Malformed); + + assert_eq!( + format!("{}", err), + "Malformed: from_prior `sub` value is not equal to message `from` value" + ); + } } diff --git a/src/message/pack_signed.rs b/src/message/pack_signed.rs index 26d0f40..3dfc88e 100644 --- a/src/message/pack_signed.rs +++ b/src/message/pack_signed.rs @@ -1,11 +1,13 @@ +use serde::Serialize; + use crate::{ did::DIDResolver, - error::{err_msg, ErrorKind, Result, ResultContext, ResultExt}, + error::{err_msg, ErrorKind, Result, ResultContext}, jws::{self, Algorithm}, secrets::SecretsResolver, utils::{ crypto::{AsKnownKeyPair, KnownKeyPair}, - did::did_or_url, + did::{did_or_url, is_did}, }, Message, }; @@ -34,7 +36,7 @@ impl Message { /// /// # Errors /// - `DIDNotResolved` Sender or recipient DID not found. - /// - `DIDUrlNotResolved` DID doesn't contain mentioned DID Urls (for ex., key id) + /// - `DIDUrlNotFound` DID doesn't contain mentioned DID Urls (for ex., key id) /// - `SecretNotFound` Sender secret is not found. /// - `Unsupported` Used crypto or method is unsupported. /// - `InvalidState` Indicates library error. @@ -46,6 +48,8 @@ impl Message { did_resolver: &'dr (dyn DIDResolver + 'dr), secrets_resolver: &'sr (dyn SecretsResolver + 'sr), ) -> Result<(String, PackSignedMetadata)> { + self._validate_pack_signed(sign_by)?; + let (did, key_id) = did_or_url(sign_by); let did_doc = did_resolver @@ -88,8 +92,7 @@ impl Message { .as_key_pair() .context("Unable instantiate sign key")?; - let payload = serde_json::to_string(self) - .kind(ErrorKind::InvalidState, "Unable serialize message")?; + let payload = self.pack_plaintext(did_resolver).await?; let msg = match sign_key { KnownKeyPair::Ed25519(ref key) => { @@ -111,10 +114,21 @@ impl Message { Ok((msg, metadata)) } + + fn _validate_pack_signed(&self, sign_by: &str) -> Result<()> { + if !is_did(sign_by) { + Err(err_msg( + ErrorKind::IllegalArgument, + "`sign_from` value is not a valid DID or DID URL", + ))?; + } + + Ok(()) + } } /// Additional metadata about this `pack` method execution like used key identifiers. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct PackSignedMetadata { /// Identifier (DID URL) of sign key. pub sign_by_kid: String, @@ -126,18 +140,28 @@ mod tests { alg::{ed25519::Ed25519KeyPair, k256::K256KeyPair, p256::P256KeyPair}, sign::KeySigVerify, }; + use serde_json::Value; use crate::{ - did::{resolvers::ExampleDIDResolver, DIDResolver, VerificationMaterial}, + did::{ + resolvers::{ExampleDIDResolver, MockDidResolver}, + DIDResolver, VerificationMaterial, + }, + error::{err_msg, ErrorKind}, jwk::FromJwkValue, jws::{self, Algorithm, Header, ProtectedHeader}, - secrets::{resolvers::ExampleSecretsResolver, SecretsResolver}, + secrets::{ + resolvers::ExampleSecretsResolver, Secret, SecretMaterial, SecretType, SecretsResolver, + }, test_vectors::{ ALICE_AUTH_METHOD_25519, ALICE_AUTH_METHOD_P256, ALICE_AUTH_METHOD_SECPP256K1, - ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, MESSAGE_SIMPLE, PLAINTEXT_MSG_SIMPLE, + ALICE_DID, ALICE_DID_DOC, ALICE_DID_DOC_WITH_NO_SECRETS, ALICE_SECRETS, BOB_DID_DOC, + BOB_SECRETS, CHARLIE_DID_DOC, CHARLIE_ROTATED_TO_ALICE_SECRETS, + CHARLIE_SECRET_AUTH_KEY_ED25519, FROM_PRIOR_FULL, MESSAGE_FROM_PRIOR_FULL, + MESSAGE_SIMPLE, PLAINTEXT_MSG_SIMPLE, }, - Message, PackSignedMetadata, + Message, PackSignedMetadata, UnpackOptions, }; #[tokio::test] @@ -211,7 +235,7 @@ mod tests { assert_eq!( metadata, PackSignedMetadata { - sign_by_kid: sign_by_kid.into() + sign_by_kid: sign_by_kid.into(), } ); @@ -246,8 +270,8 @@ mod tests { ); let signer_key = match verification_material { - VerificationMaterial::JWK(ref jwk) => { - Key::from_jwk_value(jwk).expect("Unable from_jwk_value") + VerificationMaterial::JWK { ref value } => { + Key::from_jwk_value(value).expect("Unable from_jwk_value") } _ => panic!("Unexpected verification_material"), }; @@ -259,4 +283,180 @@ mod tests { assert!(valid); } } + + #[tokio::test] + async fn pack_signed_works_signer_did_not_found() { + let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone()]); + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_signed("did:example:unknown", &did_resolver, &secrets_resolver) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::DIDNotResolved); + + assert_eq!(format!("{}", err), "DID not resolved: Signer did not found"); + } + + #[tokio::test] + async fn pack_signed_works_signer_is_not_did_our_did_url() { + let mut did_doc = ALICE_DID_DOC.clone(); + did_doc.did = "not-a-did".into(); + let did_resolver = ExampleDIDResolver::new(vec![did_doc]); + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_signed("not-a-did", &did_resolver, &secrets_resolver) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::IllegalArgument); + + assert_eq!( + format!("{}", err), + "Illegal argument: `sign_from` value is not a valid DID or DID URL" + ); + } + + #[tokio::test] + async fn pack_signed_works_signer_did_url_not_found() { + let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone()]); + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_signed( + &format!("{}#unkown", ALICE_DID), + &did_resolver, + &secrets_resolver, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::DIDUrlNotFound); + + assert_eq!( + format!("{}", err), + "DID URL not found: Signer key id not found in did doc" + ); + } + + #[tokio::test] + async fn pack_signed_works_signer_did_resolving_err() { + let did_resolver = + MockDidResolver::new(vec![Err(err_msg(ErrorKind::InvalidState, "Mock error"))]); + + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_signed(ALICE_DID, &did_resolver, &secrets_resolver) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::InvalidState); + + assert_eq!( + format!("{}", err), + "Invalid state: Unable resolve signer did: Mock error" + ); + } + + #[tokio::test] + async fn pack_signed_works_signer_secrets_not_found() { + let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC_WITH_NO_SECRETS.clone()]); + let secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let res = MESSAGE_SIMPLE + .pack_signed( + &"did:example:alice#key-not-in-secrets-1", + &did_resolver, + &secrets_resolver, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::SecretNotFound); + + assert_eq!( + format!("{}", err), + "Secret not found: No signer secrets found" + ); + } + + #[tokio::test] + async fn pack_signed_works_unable_instantiate_sign_key() { + let mut did_doc = ALICE_DID_DOC.clone(); + did_doc + .authentications + .push("did:example:alice#key-d25519-1".into()); + let mut secrets = ALICE_SECRETS.clone(); + secrets.push(Secret { + id: "did:example:alice#key-d25519-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: serde_json::json!({ + "kty": "EC", + "d": "sB0bYtpaXyp-h17dDpMx91N3Du1AdN4z1FUq02GbmLw", + "crv": "A-256", + "x": "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", + "y": "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", + }), + }, + }); + let did_resolver = ExampleDIDResolver::new(vec![did_doc]); + let secrets_resolver = ExampleSecretsResolver::new(secrets); + + let res = MESSAGE_SIMPLE + .pack_signed( + &"did:example:alice#key-d25519-1", + &did_resolver, + &secrets_resolver, + ) + .await; + + let err = res.expect_err("res is ok"); + assert_eq!(err.kind(), ErrorKind::Unsupported); + + assert_eq!( + format!("{}", err), + "Unsupported crypto or method: Unable instantiate sign key: Unsupported key type or curve" + ); + } + + #[tokio::test] + async fn pack_signed_works_from_prior() { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + ]); + let charlie_rotated_to_alice_secrets_resolver = + ExampleSecretsResolver::new(CHARLIE_ROTATED_TO_ALICE_SECRETS.clone()); + let bob_secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let (packed_msg, _pack_metadata) = MESSAGE_FROM_PRIOR_FULL + .pack_signed( + ALICE_DID, + &did_resolver, + &charlie_rotated_to_alice_secrets_resolver, + ) + .await + .expect("Unable pack_signed"); + + let (unpacked_msg, unpack_metadata) = Message::unpack( + &packed_msg, + &did_resolver, + &bob_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + assert_eq!(&unpacked_msg, &*MESSAGE_FROM_PRIOR_FULL); + assert_eq!( + unpack_metadata.from_prior_issuer_kid.as_ref(), + Some(&CHARLIE_SECRET_AUTH_KEY_ED25519.id) + ); + assert_eq!(unpack_metadata.from_prior.as_ref(), Some(&*FROM_PRIOR_FULL)); + } } diff --git a/src/message/unpack/anoncrypt.rs b/src/message/unpack/anoncrypt.rs index 5817d6a..4ae804a 100644 --- a/src/message/unpack/anoncrypt.rs +++ b/src/message/unpack/anoncrypt.rs @@ -11,7 +11,7 @@ use askar_crypto::{ use crate::{ algorithms::AnonCryptAlg, error::{err_msg, ErrorKind, Result, ResultExt}, - jwe, + jwe::{self, envelope::JWE}, secrets::SecretsResolver, utils::{ crypto::{AsKnownKeyPair, KnownKeyPair}, @@ -26,26 +26,32 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( opts: &UnpackOptions, metadata: &mut UnpackMetadata, ) -> Result> { - let mut buf = vec![]; - - let msg = if let Ok(msg) = jwe::parse(msg, &mut buf) { - msg - } else { - return Ok(None); + let jwe = match JWE::from_str(msg) { + Ok(m) => m, + Err(e) if e.kind() == ErrorKind::Malformed => return Ok(None), + Err(e) => Err(e)?, }; - if msg.protected.alg != jwe::Algorithm::EcdhEsA256kw { + let mut buf = vec![]; + let parsed_jwe = jwe.parse(&mut buf)?; + + if parsed_jwe.protected.alg != jwe::Algorithm::EcdhEsA256kw { return Ok(None); } - let msg = msg.verify_didcomm()?; + let parsed_jwe = parsed_jwe.verify_didcomm()?; - let to_kids: Vec<_> = msg.jwe.recipients.iter().map(|r| r.header.kid).collect(); + let to_kids: Vec<_> = parsed_jwe + .jwe + .recipients + .iter() + .map(|r| r.header.kid) + .collect(); let to_kid = to_kids .first() .map(|&k| k) - .ok_or_else(|| err_msg(ErrorKind::Malformed, "No recepient keys found"))?; + .ok_or_else(|| err_msg(ErrorKind::Malformed, "No recipient keys found"))?; let (to_did, _) = did_or_url(to_kid); @@ -55,7 +61,7 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( }) { Err(err_msg( ErrorKind::Malformed, - "Recepient keys are outside of one did or can't be resolved to key agreement", + "Recipient keys are outside of one did or can't be resolved to key agreement", ))?; } @@ -68,7 +74,7 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( if to_kids_found.is_empty() { Err(err_msg( ErrorKind::SecretNotFound, - "No recepient secrets found", + "No recipient secrets found", ))?; } @@ -81,16 +87,16 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( .ok_or_else(|| { err_msg( ErrorKind::InvalidState, - "Recepient secret not found after existence checking", + "Recipient secret not found after existence checking", ) })? .as_key_pair()?; - let _payload = match (to_key, &msg.protected.enc) { + let _payload = match (to_key, &parsed_jwe.protected.enc) { (KnownKeyPair::X25519(ref to_key), jwe::EncAlgorithm::A256cbcHs512) => { metadata.enc_alg_anon = Some(AnonCryptAlg::A256cbcHs512EcdhEsA256kw); - msg.decrypt::< + parsed_jwe.decrypt::< AesKey, EcdhEs<'_, X25519KeyPair>, X25519KeyPair, @@ -100,7 +106,7 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( (KnownKeyPair::X25519(ref to_key), jwe::EncAlgorithm::Xc20P) => { metadata.enc_alg_anon = Some(AnonCryptAlg::Xc20pEcdhEsA256kw); - msg.decrypt::< + parsed_jwe.decrypt::< Chacha20Key, EcdhEs<'_, X25519KeyPair>, X25519KeyPair, @@ -110,7 +116,7 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( (KnownKeyPair::X25519(ref to_key), jwe::EncAlgorithm::A256Gcm) => { metadata.enc_alg_anon = Some(AnonCryptAlg::A256gcmEcdhEsA256kw); - msg.decrypt::< + parsed_jwe.decrypt::< AesKey, EcdhEs<'_, X25519KeyPair>, X25519KeyPair, @@ -120,7 +126,7 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( (KnownKeyPair::P256(ref to_key), jwe::EncAlgorithm::A256cbcHs512) => { metadata.enc_alg_anon = Some(AnonCryptAlg::A256cbcHs512EcdhEsA256kw); - msg.decrypt::< + parsed_jwe.decrypt::< AesKey, EcdhEs<'_, P256KeyPair>, P256KeyPair, @@ -130,7 +136,7 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( (KnownKeyPair::P256(ref to_key), jwe::EncAlgorithm::Xc20P) => { metadata.enc_alg_anon = Some(AnonCryptAlg::Xc20pEcdhEsA256kw); - msg.decrypt::< + parsed_jwe.decrypt::< Chacha20Key, EcdhEs<'_, P256KeyPair>, P256KeyPair, @@ -140,7 +146,7 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( (KnownKeyPair::P256(ref to_key), jwe::EncAlgorithm::A256Gcm) => { metadata.enc_alg_anon = Some(AnonCryptAlg::A256gcmEcdhEsA256kw); - msg.decrypt::< + parsed_jwe.decrypt::< AesKey, EcdhEs<'_, P256KeyPair>, P256KeyPair, @@ -149,7 +155,7 @@ pub(crate) async fn _try_unpack_anoncrypt<'sr>( } _ => Err(err_msg( ErrorKind::Unsupported, - "Unsupported recepient key agreement method", + "Unsupported recipient key agreement method", ))?, }; diff --git a/src/message/unpack/authcrypt.rs b/src/message/unpack/authcrypt.rs index 3d9d649..491da63 100644 --- a/src/message/unpack/authcrypt.rs +++ b/src/message/unpack/authcrypt.rs @@ -7,6 +7,7 @@ use askar_crypto::{ kdf::ecdh_1pu::Ecdh1PU, }; +use crate::jwe::envelope::JWE; use crate::{ algorithms::AuthCryptAlg, did::DIDResolver, @@ -27,24 +28,26 @@ pub(crate) async fn _try_unpack_authcrypt<'dr, 'sr>( opts: &UnpackOptions, metadata: &mut UnpackMetadata, ) -> Result> { - let mut buf = vec![]; - - let msg = if let Ok(msg) = jwe::parse(msg, &mut buf) { - msg - } else { - return Ok(None); + let jwe = match JWE::from_str(msg) { + Ok(m) => m, + Err(e) if e.kind() == ErrorKind::Malformed => return Ok(None), + Err(e) => Err(e)?, }; - if msg.protected.alg != jwe::Algorithm::Ecdh1puA256kw { + let mut buf = vec![]; + let parsed_jwe = jwe.parse(&mut buf)?; + + if parsed_jwe.protected.alg != jwe::Algorithm::Ecdh1puA256kw { return Ok(None); } - let msg = msg.verify_didcomm()?; + let parsed_jwe = parsed_jwe.verify_didcomm()?; let from_kid = std::str::from_utf8( - msg.apu + parsed_jwe + .apu .as_deref() - .ok_or_else(|| err_msg(ErrorKind::Malformed, "No apu presend for authcryot"))?, + .ok_or_else(|| err_msg(ErrorKind::Malformed, "No apu presented for authcrypt"))?, ) .kind(ErrorKind::Malformed, "apu is invalid utf8")?; @@ -81,12 +84,17 @@ pub(crate) async fn _try_unpack_authcrypt<'dr, 'sr>( })? .as_key_pair()?; - let to_kids: Vec<_> = msg.jwe.recipients.iter().map(|r| r.header.kid).collect(); + let to_kids: Vec<_> = parsed_jwe + .jwe + .recipients + .iter() + .map(|r| r.header.kid) + .collect(); let to_kid = to_kids .first() .map(|&k| k) - .ok_or_else(|| err_msg(ErrorKind::Malformed, "No recepient keys found"))?; + .ok_or_else(|| err_msg(ErrorKind::Malformed, "No recipient keys found"))?; let (to_did, _) = did_or_url(to_kid); @@ -96,7 +104,7 @@ pub(crate) async fn _try_unpack_authcrypt<'dr, 'sr>( }) { Err(err_msg( ErrorKind::Malformed, - "Recepient keys are outside of one did or can't be resolved to key agreement", + "Recipient keys are outside of one did or can't be resolved to key agreement", ))?; } @@ -115,7 +123,7 @@ pub(crate) async fn _try_unpack_authcrypt<'dr, 'sr>( if to_kids_found.is_empty() { Err(err_msg( ErrorKind::SecretNotFound, - "No recepient secrets found", + "No recipient secrets found", ))?; } @@ -128,12 +136,12 @@ pub(crate) async fn _try_unpack_authcrypt<'dr, 'sr>( .ok_or_else(|| { err_msg( ErrorKind::InvalidState, - "Recepient secret not found after existence checking", + "Recipient secret not found after existence checking", ) })? .as_key_pair()?; - let _payload = match (&from_key, &to_key, &msg.protected.enc) { + let _payload = match (&from_key, &to_key, &parsed_jwe.protected.enc) { ( KnownKeyPair::X25519(ref from_key), KnownKeyPair::X25519(ref to_key), @@ -141,12 +149,12 @@ pub(crate) async fn _try_unpack_authcrypt<'dr, 'sr>( ) => { metadata.enc_alg_auth = Some(AuthCryptAlg::A256cbcHs512Ecdh1puA256kw); - msg.decrypt::< - AesKey, - Ecdh1PU<'_, X25519KeyPair>, - X25519KeyPair, - AesKey, - >(Some((from_kid, from_key)), (to_kid, to_key))? + parsed_jwe.decrypt::< + AesKey, + Ecdh1PU<'_, X25519KeyPair>, + X25519KeyPair, + AesKey, + >(Some((from_kid, from_key)), (to_kid, to_key))? } ( KnownKeyPair::P256(ref from_key), @@ -155,20 +163,20 @@ pub(crate) async fn _try_unpack_authcrypt<'dr, 'sr>( ) => { metadata.enc_alg_auth = Some(AuthCryptAlg::A256cbcHs512Ecdh1puA256kw); - msg.decrypt::< - AesKey, - Ecdh1PU<'_, P256KeyPair>, - P256KeyPair, - AesKey, - >(Some((from_kid, from_key)), (to_kid, to_key))? + parsed_jwe.decrypt::< + AesKey, + Ecdh1PU<'_, P256KeyPair>, + P256KeyPair, + AesKey, + >(Some((from_kid, from_key)), (to_kid, to_key))? } (KnownKeyPair::X25519(_), KnownKeyPair::P256(_), _) => Err(err_msg( ErrorKind::Malformed, - "Incompatible sender and recepient key agreement curves", + "Incompatible sender and recipient key agreement curves", ))?, (KnownKeyPair::P256(_), KnownKeyPair::X25519(_), _) => Err(err_msg( ErrorKind::Malformed, - "Incompatible sender and recepient key agreement curves", + "Incompatible sender and recipient key agreement curves", ))?, _ => Err(err_msg( ErrorKind::Unsupported, diff --git a/src/message/unpack/mod.rs b/src/message/unpack/mod.rs index 755bd95..6f20e5d 100644 --- a/src/message/unpack/mod.rs +++ b/src/message/unpack/mod.rs @@ -1,18 +1,24 @@ -mod anoncrypt; -mod authcrypt; -mod sign; +use serde::{Deserialize, Serialize}; + +use anoncrypt::_try_unpack_anoncrypt; +use authcrypt::_try_unpack_authcrypt; +use sign::_try_unapck_sign; use crate::{ algorithms::{AnonCryptAlg, AuthCryptAlg, SignAlg}, did::DIDResolver, error::{err_msg, ErrorKind, Result, ResultExt}, + message::unpack::plaintext::_try_unpack_plaintext, + protocols::routing::try_parse_forward, secrets::SecretsResolver, - Message, + utils::did::did_or_url, + FromPrior, Message, }; -use anoncrypt::_try_unpack_anoncrypt; -use authcrypt::_try_unpack_authcrypt; -use sign::_try_unapck_sign; +mod anoncrypt; +mod authcrypt; +mod plaintext; +mod sign; impl Message { /// Unpacks the packed message by doing decryption and verifying the signatures. @@ -37,7 +43,7 @@ impl Message { /// /// # Errors /// - `DIDNotResolved` Sender or recipient DID not found. - /// - `DIDUrlNotResolved` DID doesn't contain mentioned DID Urls (for ex., key id) + /// - `DIDUrlNotFound` DID doesn't contain mentioned DID Urls (for ex., key id) /// - `MessageMalformed` message doesn't correspond to DID Comm or has invalid encryption or signatures. /// - `Unsupported` Used crypto or method is unsupported. /// - `SecretNotFound` No recipient secrets found. @@ -50,13 +56,6 @@ impl Message { secrets_resolver: &'sr (dyn SecretsResolver + 'sr), options: &UnpackOptions, ) -> Result<(Self, UnpackMetadata)> { - if options.unwrap_re_wrapping_forward { - Err(err_msg( - ErrorKind::Unsupported, - "Forward unwrapping is unsupported by this version", - ))?; - } - let mut metadata = UnpackMetadata { encrypted: false, authenticated: false, @@ -66,43 +65,106 @@ impl Message { encrypted_from_kid: None, encrypted_to_kids: None, sign_from: None, + from_prior_issuer_kid: None, enc_alg_auth: None, enc_alg_anon: None, sign_alg: None, signed_message: None, + from_prior: None, }; - let anoncryted = - _try_unpack_anoncrypt(msg, secrets_resolver, options, &mut metadata).await?; + let mut msg: &str = msg; + let mut anoncrypted: Option; + let mut forwarded_msg: String; + + loop { + anoncrypted = + _try_unpack_anoncrypt(msg, secrets_resolver, options, &mut metadata).await?; + + if options.unwrap_re_wrapping_forward && anoncrypted.is_some() { + let forwarded_msg_opt = Self::_try_unwrap_forwarded_message( + anoncrypted.as_deref().unwrap(), + did_resolver, + secrets_resolver, + ) + .await?; + + if forwarded_msg_opt.is_some() { + forwarded_msg = forwarded_msg_opt.unwrap(); + msg = &forwarded_msg; + + metadata.re_wrapped_in_forward = true; - let msg = anoncryted.as_deref().unwrap_or(msg); + continue; + } + } + + break; + } + + let msg = anoncrypted.as_deref().unwrap_or(msg); let authcrypted = _try_unpack_authcrypt(msg, did_resolver, secrets_resolver, options, &mut metadata) .await?; - let msg = authcrypted.as_deref().unwrap_or(msg); let signed = _try_unapck_sign(msg, did_resolver, options, &mut metadata).await?; - let msg = signed.as_deref().unwrap_or(msg); - let msg: Self = - serde_json::from_str(msg).kind(ErrorKind::Malformed, "Unable deserialize jwm")?; + let msg = _try_unpack_plaintext(msg, did_resolver, &mut metadata) + .await? + .ok_or_else(|| { + err_msg( + ErrorKind::Malformed, + "Message is not a valid JWE, JWS or JWM", + ) + })?; Ok((msg, metadata)) } + + async fn _try_unwrap_forwarded_message<'dr, 'sr>( + msg: &str, + did_resolver: &'dr (dyn DIDResolver + 'dr), + secrets_resolver: &'sr (dyn SecretsResolver + 'sr), + ) -> Result> { + let plaintext = match Message::from_str(msg) { + Ok(m) => m, + Err(e) if e.kind() == ErrorKind::Malformed => return Ok(None), + Err(e) => Err(e)?, + }; + + if let Some(forward_msg) = try_parse_forward(&plaintext) { + if has_key_agreement_secret(&forward_msg.next, did_resolver, secrets_resolver).await? { + // TODO: Think how to avoid extra serialization of forwarded_msg here. + // (This serializtion is a double work because forwarded_msg will then + // be deserialized in _try_unpack_anoncrypt.) + let forwarded_msg = serde_json::to_string(&forward_msg.forwarded_msg).kind( + ErrorKind::InvalidState, + "Unable serialize forwarded message", + )?; + + return Ok(Some(forwarded_msg)); + } + } + + Ok(None) + } } /// Allows fine customization of unpacking process +#[derive(Debug, PartialEq, Eq, Deserialize, Clone)] pub struct UnpackOptions { /// Whether the plaintext must be decryptable by all keys resolved by the secrets resolver. False by default. + #[serde(default)] pub expect_decrypt_by_all_keys: bool, /// If `true` and the packed message is a `Forward` /// wrapping a plaintext packed for the given recipient, then both Forward and packed plaintext are unpacked automatically, /// and the unpacked plaintext will be returned instead of unpacked Forward. /// False by default. + #[serde(default)] pub unwrap_re_wrapping_forward: bool, } @@ -110,14 +172,14 @@ impl Default for UnpackOptions { fn default() -> Self { UnpackOptions { expect_decrypt_by_all_keys: false, - - // TODO: make it true before first stable release - unwrap_re_wrapping_forward: false, + unwrap_re_wrapping_forward: true, } } } -#[derive(Debug, PartialEq, Eq, Clone)] +/// Additional metadata about this `unpack` method execution like trust predicates +/// and used keys identifiers. +#[derive(Debug, PartialEq, Eq, Clone, Serialize)] pub struct UnpackMetadata { /// Whether the plaintext has been encrypted pub encrypted: bool, @@ -128,7 +190,7 @@ pub struct UnpackMetadata { /// Whether the plaintext has been signed pub non_repudiation: bool, - /// Whether the sender ID was protected + /// Whether the sender ID was hidden or protected pub anonymous_sender: bool, /// Whether the plaintext was re-wrapped in a forward message by a mediator @@ -143,6 +205,9 @@ pub struct UnpackMetadata { /// Key ID used for signature if the plaintext has been signed pub sign_from: Option, + /// Key ID used for from_prior header signature if from_prior header is present + pub from_prior_issuer_kid: Option, + /// Algorithm used for authenticated encryption pub enc_alg_auth: Option, @@ -154,34 +219,80 @@ pub struct UnpackMetadata { /// If the plaintext has been signed, the JWS is returned for non-repudiation purposes pub signed_message: Option, + + /// If plaintext contains from_prior header, its unpacked value is returned + pub from_prior: Option, +} + +async fn has_key_agreement_secret<'dr, 'sr>( + did_or_kid: &str, + did_resolver: &'dr (dyn DIDResolver + 'dr), + secrets_resolver: &'sr (dyn SecretsResolver + 'sr), +) -> Result { + let kids = match did_or_url(did_or_kid) { + (_, Some(kid)) => { + vec![kid.to_owned()] + } + (did, None) => { + let did_doc = did_resolver + .resolve(did) + .await? + .ok_or_else(|| err_msg(ErrorKind::DIDNotResolved, "Next DID doc not found"))?; + did_doc.key_agreements + } + }; + + let kids = kids.iter().map(|k| k as &str).collect::>(); + + let secrets_ids = secrets_resolver.find_secrets(&kids[..]).await?; + + return Ok(!secrets_ids.is_empty()); } #[cfg(test)] mod test { - use super::*; - use crate::{ did::resolvers::ExampleDIDResolver, + message::MessagingServiceMetadata, + protocols::routing::wrap_in_forward, secrets::resolvers::ExampleSecretsResolver, test_vectors::{ + remove_field, remove_protected_field, update_field, update_protected_field, ALICE_AUTH_METHOD_25519, ALICE_AUTH_METHOD_P256, ALICE_AUTH_METHOD_SECPP256K1, ALICE_DID, ALICE_DID_DOC, ALICE_SECRETS, ALICE_VERIFICATION_METHOD_KEY_AGREEM_P256, - ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519, BOB_DID, BOB_DID_DOC, BOB_SECRETS, - BOB_SECRET_KEY_AGREEMENT_KEY_P256_1, BOB_SECRET_KEY_AGREEMENT_KEY_P256_2, - BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1, BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2, - BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3, ENCRYPTED_MSG_ANON_XC20P_1, + ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519, BOB_DID, BOB_DID_COMM_MESSAGING_SERVICE, + BOB_DID_DOC, BOB_SECRETS, BOB_SECRET_KEY_AGREEMENT_KEY_P256_1, + BOB_SECRET_KEY_AGREEMENT_KEY_P256_2, BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2, BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3, + BOB_SERVICE, CHARLIE_AUTH_METHOD_25519, CHARLIE_DID_DOC, ENCRYPTED_MSG_ANON_XC20P_1, ENCRYPTED_MSG_ANON_XC20P_2, ENCRYPTED_MSG_AUTH_P256, ENCRYPTED_MSG_AUTH_P256_SIGNED, - ENCRYPTED_MSG_AUTH_X25519, MESSAGE_ATTACHMENT_BASE64, MESSAGE_ATTACHMENT_JSON, - MESSAGE_ATTACHMENT_LINKS, MESSAGE_ATTACHMENT_MULTI_1, MESSAGE_ATTACHMENT_MULTI_2, - MESSAGE_MINIMAL, MESSAGE_SIMPLE, PLAINTEXT_MSG_ATTACHMENT_BASE64, - PLAINTEXT_MSG_ATTACHMENT_JSON, PLAINTEXT_MSG_ATTACHMENT_LINKS, - PLAINTEXT_MSG_ATTACHMENT_MULTI_1, PLAINTEXT_MSG_ATTACHMENT_MULTI_2, - PLAINTEXT_MSG_MINIMAL, PLAINTEXT_MSG_SIMPLE, SIGNED_MSG_ALICE_KEY_1, - SIGNED_MSG_ALICE_KEY_2, SIGNED_MSG_ALICE_KEY_3, + ENCRYPTED_MSG_AUTH_X25519, FROM_PRIOR_FULL, + INVALID_ENCRYPTED_MSG_ANON_P256_EPK_WRONG_POINT, + INVALID_PLAINTEXT_MSG_ATTACHMENTS_AS_INT_ARRAY, + INVALID_PLAINTEXT_MSG_ATTACHMENTS_AS_STRING, + INVALID_PLAINTEXT_MSG_ATTACHMENTS_EMPTY_DATA, + INVALID_PLAINTEXT_MSG_ATTACHMENTS_LINKS_NO_HASH, + INVALID_PLAINTEXT_MSG_ATTACHMENTS_NO_DATA, INVALID_PLAINTEXT_MSG_ATTACHMENTS_NULL_DATA, + INVALID_PLAINTEXT_MSG_ATTACHMENTS_WRONG_DATA, + INVALID_PLAINTEXT_MSG_ATTACHMENTS_WRONG_ID, INVALID_PLAINTEXT_MSG_EMPTY, + INVALID_PLAINTEXT_MSG_EMPTY_ATTACHMENTS, INVALID_PLAINTEXT_MSG_NO_BODY, + INVALID_PLAINTEXT_MSG_NO_ID, INVALID_PLAINTEXT_MSG_NO_TYP, + INVALID_PLAINTEXT_MSG_NO_TYPE, INVALID_PLAINTEXT_MSG_STRING, + INVALID_PLAINTEXT_MSG_WRONG_TYP, MEDIATOR1_DID_DOC, MEDIATOR1_SECRETS, + MESSAGE_ATTACHMENT_BASE64, MESSAGE_ATTACHMENT_JSON, MESSAGE_ATTACHMENT_LINKS, + MESSAGE_ATTACHMENT_MULTI_1, MESSAGE_ATTACHMENT_MULTI_2, MESSAGE_FROM_PRIOR_FULL, + MESSAGE_MINIMAL, MESSAGE_SIMPLE, PLAINTEXT_FROM_PRIOR, + PLAINTEXT_FROM_PRIOR_INVALID_SIGNATURE, PLAINTEXT_INVALID_FROM_PRIOR, + PLAINTEXT_MSG_ATTACHMENT_BASE64, PLAINTEXT_MSG_ATTACHMENT_JSON, + PLAINTEXT_MSG_ATTACHMENT_LINKS, PLAINTEXT_MSG_ATTACHMENT_MULTI_1, + PLAINTEXT_MSG_ATTACHMENT_MULTI_2, PLAINTEXT_MSG_MINIMAL, PLAINTEXT_MSG_SIMPLE, + SIGNED_MSG_ALICE_KEY_1, SIGNED_MSG_ALICE_KEY_2, SIGNED_MSG_ALICE_KEY_3, }, PackEncryptedOptions, }; + use super::*; + #[tokio::test] async fn unpack_works_plaintext() { let plaintext_metadata = UnpackMetadata { @@ -196,6 +307,8 @@ mod test { encrypted_to_kids: None, sign_from: None, signed_message: None, + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }; @@ -250,7 +363,12 @@ mod test { _unpack_works_plaintext_2way(&MESSAGE_ATTACHMENT_MULTI_2).await; async fn _unpack_works_plaintext_2way(msg: &Message) { - let packed = msg.pack_plaintext().expect("Unable pack_plaintext"); + let did_resolver = ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone()]); + + let packed = msg + .pack_plaintext(&did_resolver) + .await + .expect("Unable pack_plaintext"); _verify_unpack( &packed, @@ -267,6 +385,8 @@ mod test { encrypted_to_kids: None, sign_from: None, signed_message: None, + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }, ) @@ -288,6 +408,8 @@ mod test { encrypted_to_kids: None, sign_from: None, signed_message: None, + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }; @@ -391,6 +513,8 @@ mod test { enc_alg_anon: None, encrypted_from_kid: None, encrypted_to_kids: None, + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }, ) @@ -412,6 +536,8 @@ mod test { encrypted_to_kids: None, sign_from: None, signed_message: None, + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }; @@ -448,6 +574,298 @@ mod test { // TODO: Check P-521 curve support } + #[tokio::test] + async fn unpack_works_unwrap_re_wrapping_forward_on() { + _unpack_works_unwrap_re_wrapping_forward_on(BOB_DID, None, None).await; + + _unpack_works_unwrap_re_wrapping_forward_on(BOB_DID, None, Some(ALICE_DID)).await; + + _unpack_works_unwrap_re_wrapping_forward_on(BOB_DID, Some(ALICE_DID), None).await; + + _unpack_works_unwrap_re_wrapping_forward_on(BOB_DID, Some(ALICE_DID), Some(ALICE_DID)) + .await; + + _unpack_works_unwrap_re_wrapping_forward_on( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + None, + None, + ) + .await; + + _unpack_works_unwrap_re_wrapping_forward_on( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + None, + Some(ALICE_DID), + ) + .await; + + _unpack_works_unwrap_re_wrapping_forward_on( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + Some(ALICE_DID), + None, + ) + .await; + + _unpack_works_unwrap_re_wrapping_forward_on( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + Some(ALICE_DID), + Some(ALICE_DID), + ) + .await; + + async fn _unpack_works_unwrap_re_wrapping_forward_on( + to: &str, + from: Option<&str>, + sign_by: Option<&str>, + ) { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let alice_secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let bob_secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let mediator1_secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, pack_metadata) = MESSAGE_SIMPLE + .pack_encrypted( + to, + from, + sign_by, + &did_resolver, + &alice_secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable encrypt"); + + assert_eq!( + pack_metadata.messaging_service.as_ref(), + Some(&MessagingServiceMetadata { + id: BOB_SERVICE.id.clone(), + service_endpoint: BOB_DID_COMM_MESSAGING_SERVICE.service_endpoint.clone(), + }) + ); + + let (unpacked_msg_mediator1, unpack_metadata_mediator1) = Message::unpack( + &msg, + &did_resolver, + &mediator1_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + let forward = + try_parse_forward(&unpacked_msg_mediator1).expect("Message is not Forward"); + + assert_eq!(forward.msg, &unpacked_msg_mediator1); + assert_eq!(&forward.next, to); + + assert!(unpack_metadata_mediator1.encrypted); + assert!(!unpack_metadata_mediator1.authenticated); + assert!(!unpack_metadata_mediator1.non_repudiation); + assert!(unpack_metadata_mediator1.anonymous_sender); + assert!(!unpack_metadata_mediator1.re_wrapped_in_forward); + + let forwarded_msg = serde_json::to_string(&forward.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let re_wrapping_forward_msg = wrap_in_forward( + &forwarded_msg, + None, + to, + &vec![to.to_owned()], + &AnonCryptAlg::default(), + &did_resolver, + ) + .await + .expect("Unable wrap in forward"); + + let (unpacked_msg, unpack_metadata) = Message::unpack( + &re_wrapping_forward_msg, + &did_resolver, + &bob_secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect("Unable unpack"); + + assert_eq!(&unpacked_msg, &*MESSAGE_SIMPLE); + assert!(unpack_metadata.re_wrapped_in_forward); + } + } + + #[tokio::test] + async fn unpack_works_unwrap_re_wrapping_forward_off() { + _unpack_works_unwrap_re_wrapping_forward_off(BOB_DID, None, None).await; + + _unpack_works_unwrap_re_wrapping_forward_off(BOB_DID, None, Some(ALICE_DID)).await; + + _unpack_works_unwrap_re_wrapping_forward_off(BOB_DID, Some(ALICE_DID), None).await; + + _unpack_works_unwrap_re_wrapping_forward_off(BOB_DID, Some(ALICE_DID), Some(ALICE_DID)) + .await; + + _unpack_works_unwrap_re_wrapping_forward_off( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + None, + None, + ) + .await; + + _unpack_works_unwrap_re_wrapping_forward_off( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + None, + Some(ALICE_DID), + ) + .await; + + _unpack_works_unwrap_re_wrapping_forward_off( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + Some(ALICE_DID), + None, + ) + .await; + + _unpack_works_unwrap_re_wrapping_forward_off( + &BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + Some(ALICE_DID), + Some(ALICE_DID), + ) + .await; + + async fn _unpack_works_unwrap_re_wrapping_forward_off( + to: &str, + from: Option<&str>, + sign_by: Option<&str>, + ) { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + ]); + + let alice_secrets_resolver = ExampleSecretsResolver::new(ALICE_SECRETS.clone()); + + let bob_secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let mediator1_secrets_resolver = ExampleSecretsResolver::new(MEDIATOR1_SECRETS.clone()); + + let (msg, pack_metadata) = MESSAGE_SIMPLE + .pack_encrypted( + to, + from, + sign_by, + &did_resolver, + &alice_secrets_resolver, + &PackEncryptedOptions::default(), + ) + .await + .expect("Unable encrypt"); + + assert_eq!( + pack_metadata.messaging_service.as_ref(), + Some(&MessagingServiceMetadata { + id: BOB_SERVICE.id.clone(), + service_endpoint: BOB_DID_COMM_MESSAGING_SERVICE.service_endpoint.clone(), + }) + ); + + let (unpacked_msg_mediator1, unpack_metadata_mediator1) = Message::unpack( + &msg, + &did_resolver, + &mediator1_secrets_resolver, + &UnpackOptions { + unwrap_re_wrapping_forward: false, + ..UnpackOptions::default() + }, + ) + .await + .expect("Unable unpack"); + + let forward_at_mediator1 = + try_parse_forward(&unpacked_msg_mediator1).expect("Message is not Forward"); + + assert_eq!(forward_at_mediator1.msg, &unpacked_msg_mediator1); + assert_eq!(&forward_at_mediator1.next, to); + + assert!(unpack_metadata_mediator1.encrypted); + assert!(!unpack_metadata_mediator1.authenticated); + assert!(!unpack_metadata_mediator1.non_repudiation); + assert!(unpack_metadata_mediator1.anonymous_sender); + assert!(!unpack_metadata_mediator1.re_wrapped_in_forward); + + let forwarded_msg_at_mediator1 = + serde_json::to_string(&forward_at_mediator1.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let re_wrapping_forward_msg = wrap_in_forward( + &forwarded_msg_at_mediator1, + None, + to, + &vec![to.to_owned()], + &AnonCryptAlg::default(), + &did_resolver, + ) + .await + .expect("Unable wrap in forward"); + + let (unpacked_once_msg, unpack_once_metadata) = Message::unpack( + &re_wrapping_forward_msg, + &did_resolver, + &bob_secrets_resolver, + &UnpackOptions { + unwrap_re_wrapping_forward: false, + ..UnpackOptions::default() + }, + ) + .await + .expect("Unable unpack"); + + let forward_at_bob = + try_parse_forward(&unpacked_once_msg).expect("Message is not Forward"); + + assert_eq!(forward_at_bob.msg, &unpacked_once_msg); + assert_eq!(&forward_at_bob.next, to); + + assert!(unpack_once_metadata.encrypted); + assert!(!unpack_once_metadata.authenticated); + assert!(!unpack_once_metadata.non_repudiation); + assert!(unpack_once_metadata.anonymous_sender); + assert!(!unpack_once_metadata.re_wrapped_in_forward); + + let forwarded_msg_at_bob = serde_json::to_string(&forward_at_bob.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let (unpacked_twice_msg, unpack_twice_metadata) = Message::unpack( + &forwarded_msg_at_bob, + &did_resolver, + &bob_secrets_resolver, + &UnpackOptions { + unwrap_re_wrapping_forward: false, + ..UnpackOptions::default() + }, + ) + .await + .expect("Unable unpack"); + + assert_eq!(&unpacked_twice_msg, &*MESSAGE_SIMPLE); + + assert!(unpack_twice_metadata.encrypted); + assert_eq!( + unpack_twice_metadata.authenticated, + from.is_some() || sign_by.is_some() + ); + assert_eq!(unpack_twice_metadata.non_repudiation, sign_by.is_some()); + assert_eq!(unpack_twice_metadata.anonymous_sender, from.is_none()); + assert!(!unpack_twice_metadata.re_wrapped_in_forward); + } + } + #[tokio::test] async fn unpack_works_anoncrypted_2way() { _unpack_works_anoncrypted_2way( @@ -612,6 +1030,8 @@ mod test { enc_alg_anon: Some(enc_alg), encrypted_from_kid: None, encrypted_to_kids: Some(to_kids.iter().map(|&k| k.to_owned()).collect()), + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }, ) @@ -744,6 +1164,8 @@ mod test { enc_alg_anon: Some(enc_alg), encrypted_from_kid: None, encrypted_to_kids: Some(to_kids.iter().map(|&k| k.to_owned()).collect()), + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }, ) @@ -765,6 +1187,8 @@ mod test { encrypted_to_kids: None, sign_from: None, signed_message: None, + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }; @@ -930,6 +1354,8 @@ mod test { enc_alg_anon: None, encrypted_from_kid: Some(from_kid.into()), encrypted_to_kids: Some(to_kids.iter().map(|&k| k.to_owned()).collect()), + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }, ) @@ -1105,6 +1531,8 @@ mod test { enc_alg_anon: Some(enc_alg_anon), encrypted_from_kid: Some(from_kid.into()), encrypted_to_kids: Some(to_kids.iter().map(|&k| k.to_owned()).collect()), + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }, ) @@ -1215,6 +1643,8 @@ mod test { enc_alg_anon: Some(enc_alg_anon), encrypted_from_kid: Some(from_kid.into()), encrypted_to_kids: Some(to_kids.iter().map(|&k| k.to_owned()).collect()), + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }, ) @@ -1329,6 +1759,8 @@ mod test { enc_alg_anon: None, encrypted_from_kid: Some(from_kid.into()), encrypted_to_kids: Some(to_kids.iter().map(|&k| k.to_owned()).collect()), + from_prior_issuer_kid: None, + from_prior: None, re_wrapped_in_forward: false, }, ) @@ -1336,9 +1768,332 @@ mod test { } } + #[tokio::test] + async fn unpack_works_invalid_epk_point() { + _verify_unpack_malformed( + &INVALID_ENCRYPTED_MSG_ANON_P256_EPK_WRONG_POINT, + "Malformed: Unable instantiate epk: Unable produce jwk: Invalid key data", + ) + .await; + } + + #[tokio::test] + async fn unpack_works_malformed_anoncrypt_msg() { + _verify_unpack_malformed( + update_field(ENCRYPTED_MSG_ANON_XC20P_1, "protected", "invalid").as_str(), + "Malformed: Unable decode protected header: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_field(ENCRYPTED_MSG_ANON_XC20P_1, "protected").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + update_field(ENCRYPTED_MSG_ANON_XC20P_1, "iv", "invalid").as_str(), + "Malformed: Unable decode iv: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_field(ENCRYPTED_MSG_ANON_XC20P_1, "iv").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + update_field(ENCRYPTED_MSG_ANON_XC20P_1, "ciphertext", "invalid").as_str(), + "Malformed: Unable decode ciphertext: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_field(ENCRYPTED_MSG_ANON_XC20P_1, "ciphertext").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + update_field(ENCRYPTED_MSG_ANON_XC20P_1, "tag", "invalid").as_str(), + "Malformed: Unable decode tag: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_field(ENCRYPTED_MSG_ANON_XC20P_1, "tag").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + update_protected_field(ENCRYPTED_MSG_ANON_XC20P_1, "apv", "invalid").as_str(), + "Malformed: Unable decode apv: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_protected_field(ENCRYPTED_MSG_ANON_XC20P_1, "apv").as_str(), + "Malformed: Unable parse protected header: missing field `apv` at line 1 column 166", + ) + .await; + } + + #[tokio::test] + async fn unpack_works_malformed_authcrypt_msg() { + _verify_unpack_malformed( + update_field(ENCRYPTED_MSG_AUTH_X25519, "protected", "invalid").as_str(), + "Malformed: Unable decode protected header: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_field(ENCRYPTED_MSG_AUTH_X25519, "protected").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + update_field(ENCRYPTED_MSG_AUTH_X25519, "iv", "invalid").as_str(), + "Malformed: Unable decode iv: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_field(ENCRYPTED_MSG_AUTH_X25519, "iv").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + update_field(ENCRYPTED_MSG_AUTH_X25519, "ciphertext", "invalid").as_str(), + "Malformed: Unable decode ciphertext: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_field(ENCRYPTED_MSG_AUTH_X25519, "ciphertext").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + update_field(ENCRYPTED_MSG_AUTH_X25519, "tag", "invalid").as_str(), + "Malformed: Unable decode tag: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_field(ENCRYPTED_MSG_AUTH_X25519, "tag").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + update_protected_field(ENCRYPTED_MSG_AUTH_X25519, "apv", "invalid").as_str(), + "Malformed: Unable decode apv: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_protected_field(ENCRYPTED_MSG_AUTH_X25519, "apv").as_str(), + "Malformed: Unable parse protected header: missing field `apv` at line 1 column 264", + ) + .await; + + _verify_unpack_malformed( + update_protected_field(ENCRYPTED_MSG_AUTH_X25519, "apu", "invalid").as_str(), + "Malformed: Unable decode apu: Invalid last symbol 100, offset 6.", + ) + .await; + + _verify_unpack_malformed( + remove_protected_field(ENCRYPTED_MSG_AUTH_X25519, "apu").as_str(), + "Malformed: SKID present, but no apu", + ) + .await; + } + + #[tokio::test] + async fn unpack_works_malformed_signed_msg() { + _verify_unpack_malformed( + update_field(SIGNED_MSG_ALICE_KEY_1, "payload", "invalid").as_str(), + "Malformed: Wrong signature", + ) + .await; + + _verify_unpack_malformed( + remove_field(SIGNED_MSG_ALICE_KEY_1, "payload").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + update_field(SIGNED_MSG_ALICE_KEY_1, "signatures", "invalid").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + remove_field(SIGNED_MSG_ALICE_KEY_1, "signatures").as_str(), + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + } + + #[tokio::test] + async fn unpack_works_malformed_plaintext_msg() { + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_EMPTY, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_STRING, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_NO_ID, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_NO_TYP, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_NO_TYPE, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_NO_BODY, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_WRONG_TYP, + "Malformed: `typ` must be \"application/didcomm-plain+json\"", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_EMPTY_ATTACHMENTS, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_ATTACHMENTS_NO_DATA, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_ATTACHMENTS_EMPTY_DATA, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_ATTACHMENTS_LINKS_NO_HASH, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_ATTACHMENTS_AS_STRING, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_ATTACHMENTS_AS_INT_ARRAY, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_ATTACHMENTS_WRONG_DATA, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_ATTACHMENTS_WRONG_ID, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + + _verify_unpack_malformed( + &INVALID_PLAINTEXT_MSG_ATTACHMENTS_NULL_DATA, + "Malformed: Message is not a valid JWE, JWS or JWM", + ) + .await; + } + + #[tokio::test] + async fn unpack_plaintext_works_from_prior() { + let exp_metadata = UnpackMetadata { + anonymous_sender: false, + authenticated: false, + non_repudiation: false, + encrypted: false, + enc_alg_auth: None, + enc_alg_anon: None, + sign_alg: None, + encrypted_from_kid: None, + encrypted_to_kids: None, + sign_from: None, + signed_message: None, + from_prior_issuer_kid: Some(CHARLIE_AUTH_METHOD_25519.id.clone()), + from_prior: Some(FROM_PRIOR_FULL.clone()), + re_wrapped_in_forward: false, + }; + + _verify_unpack( + PLAINTEXT_FROM_PRIOR, + &MESSAGE_FROM_PRIOR_FULL, + &exp_metadata, + ) + .await; + } + + #[tokio::test] + async fn unpack_plaintext_works_invalid_from_prior() { + _verify_unpack_returns_error( + PLAINTEXT_INVALID_FROM_PRIOR, + ErrorKind::Malformed, + "Malformed: Unable to parse compactly serialized JWS", + ) + .await; + } + + #[tokio::test] + async fn unpack_plaintext_works_invalid_from_prior_signature() { + _verify_unpack_returns_error( + PLAINTEXT_FROM_PRIOR_INVALID_SIGNATURE, + ErrorKind::Malformed, + "Malformed: Unable to verify from_prior signature: Unable decode signature: Invalid last symbol 66, offset 85.", + ) + .await; + } + async fn _verify_unpack(msg: &str, exp_msg: &Message, exp_metadata: &UnpackMetadata) { - let did_resolver = - ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + ]); let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); @@ -1361,8 +2116,11 @@ mod test { exp_msg: &Message, exp_metadata: &UnpackMetadata, ) { - let did_resolver = - ExampleDIDResolver::new(vec![ALICE_DID_DOC.clone(), BOB_DID_DOC.clone()]); + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + ]); let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); @@ -1380,4 +2138,30 @@ mod test { metadata.signed_message = exp_metadata.signed_message.clone(); assert_eq!(&metadata, exp_metadata); } + + async fn _verify_unpack_malformed(msg: &str, exp_error_str: &str) { + _verify_unpack_returns_error(msg, ErrorKind::Malformed, exp_error_str).await + } + + async fn _verify_unpack_returns_error(msg: &str, exp_err_kind: ErrorKind, exp_err_msg: &str) { + let did_resolver = ExampleDIDResolver::new(vec![ + ALICE_DID_DOC.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + ]); + + let secrets_resolver = ExampleSecretsResolver::new(BOB_SECRETS.clone()); + + let err = Message::unpack( + msg, + &did_resolver, + &secrets_resolver, + &UnpackOptions::default(), + ) + .await + .expect_err("res is ok"); + + assert_eq!(err.kind(), exp_err_kind); + assert_eq!(format!("{}", err), exp_err_msg); + } } diff --git a/src/message/unpack/plaintext.rs b/src/message/unpack/plaintext.rs new file mode 100644 index 0000000..dc5a5de --- /dev/null +++ b/src/message/unpack/plaintext.rs @@ -0,0 +1,26 @@ +use crate::did::DIDResolver; +use crate::error::{ErrorKind, Result}; +use crate::{FromPrior, Message, UnpackMetadata}; + +pub(crate) async fn _try_unpack_plaintext<'dr, 'sr>( + msg: &str, + did_resolver: &'dr (dyn DIDResolver + 'dr), + metadata: &mut UnpackMetadata, +) -> Result> { + let msg = match Message::from_str(msg) { + Ok(m) => m, + Err(e) if e.kind() == ErrorKind::Malformed => return Ok(None), + Err(e) => Err(e)?, + } + .validate()?; + + if let Some(from_prior) = &msg.from_prior { + let (unpacked_from_prior, from_prior_issuer_kid) = + FromPrior::unpack(from_prior, did_resolver).await?; + + metadata.from_prior = Some(unpacked_from_prior); + metadata.from_prior_issuer_kid = Some(from_prior_issuer_kid); + }; + + Ok(Some(msg)) +} diff --git a/src/message/unpack/sign.rs b/src/message/unpack/sign.rs index dbdd67b..f8f7606 100644 --- a/src/message/unpack/sign.rs +++ b/src/message/unpack/sign.rs @@ -1,5 +1,6 @@ use askar_crypto::alg::{ed25519::Ed25519KeyPair, k256::K256KeyPair, p256::P256KeyPair}; +use crate::jws::JWS; use crate::{ algorithms::SignAlg, did::DIDResolver, @@ -15,23 +16,25 @@ pub(crate) async fn _try_unapck_sign<'dr>( _opts: &UnpackOptions, metadata: &mut UnpackMetadata, ) -> Result> { - let jws = msg; - let mut buf = vec![]; + let jws_json = msg; - let msg = if let Ok(msg) = jws::parse(msg, &mut buf) { - msg - } else { - return Ok(None); + let jws = match JWS::from_str(msg) { + Ok(m) => m, + Err(e) if e.kind() == ErrorKind::Malformed => return Ok(None), + Err(e) => Err(e)?, }; - if msg.protected.len() != 1 { + let mut buf = vec![]; + let parsed_jws = jws.parse(&mut buf)?; + + if parsed_jws.protected.len() != 1 { Err(err_msg( ErrorKind::Malformed, "Wrong amount of signatures for jws", ))? } - let alg = &msg + let alg = &parsed_jws .protected .first() .ok_or_else(|| { @@ -42,7 +45,7 @@ pub(crate) async fn _try_unapck_sign<'dr>( })? .alg; - let signer_kid = msg + let signer_kid = parsed_jws .jws .signatures .first() @@ -96,7 +99,8 @@ pub(crate) async fn _try_unapck_sign<'dr>( .as_ed25519() .context("Unable instantiate signer key")?; - msg.verify::((signer_kid, &signer_key)) + parsed_jws + .verify::((signer_kid, &signer_key)) .context("Unable verify sign envelope")? } jws::Algorithm::Es256 => { @@ -106,7 +110,8 @@ pub(crate) async fn _try_unapck_sign<'dr>( .as_p256() .context("Unable instantiate signer key")?; - msg.verify::((signer_kid, &signer_key)) + parsed_jws + .verify::((signer_kid, &signer_key)) .context("Unable verify sign envelope")? } jws::Algorithm::Es256K => { @@ -116,7 +121,8 @@ pub(crate) async fn _try_unapck_sign<'dr>( .as_k256() .context("Unable instantiate signer key")?; - msg.verify::((signer_kid, &signer_key)) + parsed_jws + .verify::((signer_kid, &signer_key)) .context("Unable verify sign envelope")? } jws::Algorithm::Other(_) => Err(err_msg( @@ -130,7 +136,7 @@ pub(crate) async fn _try_unapck_sign<'dr>( } // TODO: More precise error conversion - let payload = base64::decode_config(msg.jws.payload, base64::URL_SAFE_NO_PAD) + let payload = base64::decode_config(parsed_jws.jws.payload, base64::URL_SAFE_NO_PAD) .kind(ErrorKind::Malformed, "Signed payloa is invalid base64")?; let payload = @@ -139,7 +145,7 @@ pub(crate) async fn _try_unapck_sign<'dr>( metadata.authenticated = true; metadata.non_repudiation = true; metadata.sign_from = Some(signer_kid.into()); - metadata.signed_message = Some(jws.into()); + metadata.signed_message = Some(jws_json.into()); Ok(Some(payload)) } diff --git a/src/protocols/mod.rs b/src/protocols/mod.rs new file mode 100644 index 0000000..e78cad0 --- /dev/null +++ b/src/protocols/mod.rs @@ -0,0 +1 @@ +pub mod routing; diff --git a/src/protocols/routing/forward.rs b/src/protocols/routing/forward.rs new file mode 100644 index 0000000..7222e7f --- /dev/null +++ b/src/protocols/routing/forward.rs @@ -0,0 +1,12 @@ +use serde::Serialize; +use serde_json::Value; + +use crate::Message; + +/// Utility structure providing convinient access to Forward plaintext message fields. +#[derive(Debug, PartialEq, Eq, Serialize, Clone)] +pub struct ParsedForward<'a> { + pub msg: &'a Message, + pub next: String, + pub forwarded_msg: Value, +} diff --git a/src/protocols/routing/mod.rs b/src/protocols/routing/mod.rs new file mode 100644 index 0000000..357670e --- /dev/null +++ b/src/protocols/routing/mod.rs @@ -0,0 +1,306 @@ +mod forward; + +use std::collections::HashMap; + +use serde_json::{json, Value}; +use uuid::Uuid; + +use crate::{ + algorithms::AnonCryptAlg, + did::{DIDCommMessagingService, DIDResolver, Service, ServiceKind}, + error::{err_msg, ErrorKind, Result, ResultContext, ResultExt}, + message::{anoncrypt, MessagingServiceMetadata}, + utils::did::{did_or_url, is_did}, + Attachment, AttachmentData, Message, PackEncryptedOptions, +}; + +pub use self::forward::ParsedForward; + +pub(crate) const FORWARD_MSG_TYPE: &str = "https://didcomm.org/routing/2.0/forward"; + +pub(crate) const DIDCOMM_V2_PROFILE: &str = "didcomm/v2"; + +async fn find_did_comm_service<'dr>( + did: &str, + service_id: Option<&str>, + did_resolver: &'dr (dyn DIDResolver + 'dr), +) -> Result> { + let did_doc = did_resolver + .resolve(did) + .await + .context("Unable resolve DID")? + .ok_or_else(|| err_msg(ErrorKind::DIDNotResolved, "DID not found"))?; + + match service_id { + Some(service_id) => { + let service: &Service = did_doc + .services + .iter() + .find(|&service| service.id == service_id) + .ok_or_else(|| { + err_msg( + ErrorKind::IllegalArgument, + "Service with the specified ID not found", + ) + })?; + + match service.kind { + ServiceKind::DIDCommMessaging { ref value } => { + if value.accept.contains(&DIDCOMM_V2_PROFILE.into()) { + Ok(Some((service.id.clone(), value.clone()))) + } else { + Err(err_msg( + ErrorKind::IllegalArgument, + "Service with the specified ID does not accept didcomm/v2 profile", + )) + } + } + _ => Err(err_msg( + ErrorKind::IllegalArgument, + "Service with the specified ID is not of DIDCommMessaging type", + )), + } + } + + None => Ok(did_doc + .services + .iter() + .find_map(|service| match service.kind { + ServiceKind::DIDCommMessaging { ref value } + if value.accept.contains(&DIDCOMM_V2_PROFILE.into()) => + { + Some((service.id.clone(), value.clone())) + } + _ => None, + })), + } +} + +async fn resolve_did_comm_services_chain<'dr>( + to: &str, + service_id: Option<&str>, + did_resolver: &'dr (dyn DIDResolver + 'dr), +) -> Result> { + let (to_did, _) = did_or_url(to); + + let service = find_did_comm_service(to_did, service_id, did_resolver).await?; + + if service.is_none() { + return Ok(vec![]); + } + + let mut service = service.unwrap(); + + let mut services = vec![service.clone()]; + let mut service_endpoint = &service.1.service_endpoint; + + while is_did(service_endpoint) { + // Now alternative endpoints recursion is not supported + // (because it should not be used according to the specification) + if services.len() > 1 { + return Err(err_msg( + ErrorKind::InvalidState, + "DID doc defines alternative endpoints recursively", + )); + } + + service = find_did_comm_service(service_endpoint, None, did_resolver) + .await? + .ok_or_else(|| { + err_msg( + // TODO: Think on introducing a more appropriate error kind + ErrorKind::InvalidState, + "Referenced mediator does not provide any DIDCommMessaging services", + ) + })?; + + services.insert(0, service.clone()); + service_endpoint = &service.1.service_endpoint; + } + + Ok(services) +} + +fn generate_message_id() -> String { + Uuid::new_v4().to_string() +} + +fn build_forward_message( + forwarded_msg: &str, + next: &str, + headers: Option<&HashMap>, +) -> Result { + let body = json!({ "next": next }); + + // TODO: Think how to avoid extra deserialization of forwarded_msg here. + // (This deserializtion is a double work because the whole Forward message with the attachments + // will then be serialized.) + let attachment = Attachment::json( + serde_json::from_str(forwarded_msg) + .kind(ErrorKind::Malformed, "Unable deserialize forwarded message")?, + ) + .finalize(); + + let mut msg_builder = Message::build(generate_message_id(), FORWARD_MSG_TYPE.to_owned(), body); + + if let Some(headers) = headers { + for (name, value) in headers { + msg_builder = msg_builder.header(name.to_owned(), value.to_owned()); + } + } + + msg_builder = msg_builder.attachment(attachment); + + let msg = msg_builder.finalize(); + + serde_json::to_string(&msg).kind(ErrorKind::InvalidState, "Unable serialize forward message") +} + +/// Tries to parse plaintext message into `ParsedForward` structure if the message is Forward. +/// (https://identity.foundation/didcomm-messaging/spec/#messages) +/// +/// # Parameters +/// - `msg` plaintext message to try to parse into `ParsedForward` structure +/// +/// # Returns +/// `Some` with `ParsedForward` structure if `msg` is Forward message, otherwise `None`. +pub fn try_parse_forward(msg: &Message) -> Option { + if msg.type_ != FORWARD_MSG_TYPE { + return None; + } + + let next = match msg.body { + Value::Object(ref body) => match body.get("next") { + Some(&Value::String(ref next)) => Some(next), + _ => None, + }, + _ => None, + }; + + if next.is_none() { + return None; + } + + let next = next.unwrap(); + + let json_attachment_data = match msg.attachments { + Some(ref attachments) => match &attachments[..] { + [attachment, ..] => match &attachment.data { + AttachmentData::Json { ref value } => Some(value), + _ => None, + }, + _ => None, + }, + None => None, + }; + + if json_attachment_data.is_none() { + return None; + } + + let forwarded_msg = &json_attachment_data.unwrap().json; + + Some(ParsedForward { + msg, + next: next.clone(), + forwarded_msg: forwarded_msg.clone(), + }) +} + +/// Wraps an anoncrypt or authcrypt message into a Forward onion (nested Forward messages). +/// https://identity.foundation/didcomm-messaging/spec/#messages +/// +/// # Parameters +/// - `msg` Anoncrypt or authcrypt message to wrap into Forward onion. +/// - `headers` (optional) Additional headers to each Forward message of the onion. +/// - `to` Recipient (a key identifier or DID) of the message being wrapped into Forward onion. +/// - `routing_keys` Routing keys (each one is a key identifier or DID) to use for encryption of +/// Forward messages in the onion. The keys must be ordered along the route (so in the opposite +/// direction to the wrapping steps). +/// - `enc_alg_anon` Algorithm to use for wrapping into each Forward message of the onion. +/// - `did_resolver` instance of `DIDResolver` to resolve DIDs. +/// +/// # Returns +/// `Result` with the message wrapped into Forward onion or `Error`. +/// +/// # Errors +/// - `Malformed` The message to wrap is malformed. +/// - `DIDNotResolved` Issuer DID not found. +/// - `DIDUrlNotFound` Issuer authentication verification method is not found. +/// - `Unsupported` Used crypto or method is unsupported. +/// - `InvalidState` Indicates a library error. +pub async fn wrap_in_forward<'dr>( + msg: &str, + headers: Option<&HashMap>, + to: &str, + routing_keys: &Vec, + enc_alg_anon: &AnonCryptAlg, + did_resolver: &'dr (dyn DIDResolver + 'dr), +) -> Result { + let mut tos = routing_keys.clone(); + + let mut nexts = tos.clone(); + nexts.remove(0); + nexts.push(to.to_owned()); + + tos.reverse(); + nexts.reverse(); + + let mut msg = msg.to_owned(); + + for (to_, next_) in tos.iter().zip(nexts.iter()) { + msg = build_forward_message(&msg, next_, headers)?; + msg = anoncrypt(to_, did_resolver, msg.as_bytes(), enc_alg_anon) + .await? + .0; + } + + Ok(msg) +} + +pub(crate) async fn wrap_in_forward_if_needed<'dr>( + msg: &str, + to: &str, + did_resolver: &'dr (dyn DIDResolver + 'dr), + options: &PackEncryptedOptions, +) -> Result> { + if !options.forward { + return Ok(None); + } + + let services_chain = + resolve_did_comm_services_chain(to, options.messaging_service.as_deref(), did_resolver) + .await?; + + if services_chain.is_empty() { + return Ok(None); + } + + let mut routing_keys = services_chain[1..] + .iter() + .map(|service| service.1.service_endpoint.clone()) + .collect::>(); + + routing_keys.append(&mut services_chain.last().unwrap().1.routing_keys.clone()); + + if routing_keys.is_empty() { + return Ok(None); + } + + let forward_msg = wrap_in_forward( + msg, + options.forward_headers.as_ref(), + to, + &routing_keys, + &options.enc_alg_anon, + did_resolver, + ) + .await?; + + let messaging_service = MessagingServiceMetadata { + id: services_chain.last().unwrap().0.clone(), + service_endpoint: services_chain.first().unwrap().1.service_endpoint.clone(), + }; + + Ok(Some((forward_msg, messaging_service))) +} diff --git a/src/secrets/mod.rs b/src/secrets/mod.rs index e3880fb..a47fa70 100644 --- a/src/secrets/mod.rs +++ b/src/secrets/mod.rs @@ -3,13 +3,44 @@ pub mod resolvers; use async_trait::async_trait; +use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::error::Result; /// Interface for secrets resolver. /// Resolves secrets such as private keys to be used for signing and encryption. +#[cfg(feature = "uniffi")] #[async_trait] +pub trait SecretsResolver: Sync { + /// Finds secret (usually private key) identified by the given key ID. + /// + /// # Parameters + /// - `secret_id` the ID (in form of DID URL) identifying a secret + /// + /// # Returns + /// A secret (usually private key) or None of there is no secret for the given ID + /// + /// # Errors + /// - IOError + /// - InvalidState + async fn get_secret(&self, secret_id: &str) -> Result>; + + /// Find all secrets that have one of the given IDs. + /// Return secrets only for key IDs for which a secret is present. + /// + /// # Parameters + /// - `secret_ids` the IDs find secrets for + /// + /// # Returns + /// possible empty list of all secrets that have one of the given IDs. + async fn find_secrets<'a>(&self, secret_ids: &'a [&'a str]) -> Result>; +} + +/// Interface for secrets resolver. +/// Resolves secrets such as private keys to be used for signing and encryption. +#[cfg(not(feature = "uniffi"))] +#[async_trait(?Send)] pub trait SecretsResolver { /// Finds secret (usually private key) identified by the given key ID. /// @@ -36,12 +67,13 @@ pub trait SecretsResolver { } /// Represents secret. -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct Secret { /// A key ID identifying a secret (private key). pub id: String, /// Must have the same semantics as type ('type' field) of the corresponding method in DID Doc containing a public key. + #[serde(rename = "type")] pub type_: SecretType, /// Value of the secret (private key) @@ -49,21 +81,38 @@ pub struct Secret { } /// Must have the same semantics as type ('type' field) of the corresponding method in DID Doc containing a public key. -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub enum SecretType { JsonWebKey2020, X25519KeyAgreementKey2019, + X25519KeyAgreementKey2020, Ed25519VerificationKey2018, + Ed25519VerificationKey2020, EcdsaSecp256k1VerificationKey2019, - Other(String), + Other, } /// Represents secret crypto material. -#[derive(Clone, Debug)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub enum SecretMaterial { - JWK(Value), - Multibase(String), - Base58(String), - Hex(String), - Other(Value), + JWK { + #[serde(flatten)] + value: Value, + }, + Multibase { + #[serde(flatten)] + value: String, + }, + Base58 { + #[serde(flatten)] + value: String, + }, + Hex { + #[serde(flatten)] + value: String, + }, + Other { + #[serde(flatten)] + value: Value, + }, } diff --git a/src/secrets/resolvers/example.rs b/src/secrets/resolvers/example.rs index 3988772..56f6633 100644 --- a/src/secrets/resolvers/example.rs +++ b/src/secrets/resolvers/example.rs @@ -15,7 +15,8 @@ impl ExampleSecretsResolver { } } -#[async_trait] +#[cfg_attr(feature = "uniffi", async_trait)] +#[cfg_attr(not(feature = "uniffi"), async_trait(?Send))] impl SecretsResolver for ExampleSecretsResolver { async fn get_secret(&self, secret_id: &str) -> Result> { Ok(self diff --git a/src/test_vectors/common.rs b/src/test_vectors/common.rs index 3cb5f51..9429da3 100644 --- a/src/test_vectors/common.rs +++ b/src/test_vectors/common.rs @@ -1,6 +1,61 @@ -pub(crate) const ALICE_DID: &str = "did:example:alice"; -pub(crate) const BOB_DID: &str = "did:example:bob"; +use serde_json::{Map, Value}; -// TODO: Remove allow -#[allow(dead_code)] -pub(crate) const CHARLIE_DID: &str = "did:example:charlie"; +pub const ALICE_DID: &str = "did:example:alice"; +pub const BOB_DID: &str = "did:example:bob"; +pub const CHARLIE_DID: &str = "did:example:charlie"; + +pub fn update_field(msg: &str, field: &str, value: &str) -> String { + let parsed: Value = serde_json::from_str(&msg).unwrap(); + let mut msg_dict: Map = parsed.as_object().unwrap().clone(); + msg_dict.insert(String::from(field), value.into()); + serde_json::to_string(&msg_dict).unwrap() +} + +pub fn remove_field(msg: &str, field: &str) -> String { + let parsed: Value = serde_json::from_str(&msg).unwrap(); + let mut msg_dict: Map = parsed.as_object().unwrap().clone(); + msg_dict.remove(field); + serde_json::to_string(&msg_dict).unwrap() +} + +pub fn update_protected_field(msg: &str, field: &str, value: &str) -> String { + let parsed: Value = serde_json::from_str(&msg).unwrap(); + let mut msg_dict: Map = parsed.as_object().unwrap().clone(); + + let mut buffer = Vec::::new(); + base64::decode_config_buf( + msg_dict.get("protected").unwrap().as_str().unwrap(), + base64::URL_SAFE_NO_PAD, + &mut buffer, + ) + .unwrap(); + let parsed_protected: Value = serde_json::from_slice(&buffer).unwrap(); + let mut protected_dict: Map = parsed_protected.as_object().unwrap().clone(); + protected_dict.insert(String::from(field), value.into()); + let protected_str = serde_json::to_string(&protected_dict).unwrap(); + println!("{}", &protected_str); + let protected_str_base64 = base64::encode_config(protected_str, base64::URL_SAFE_NO_PAD); + msg_dict.insert(String::from("protected"), protected_str_base64.into()); + serde_json::to_string(&msg_dict).unwrap() +} + +pub fn remove_protected_field(msg: &str, field: &str) -> String { + let parsed: Value = serde_json::from_str(&msg).unwrap(); + let mut msg_dict: Map = parsed.as_object().unwrap().clone(); + + let mut buffer = Vec::::new(); + base64::decode_config_buf( + msg_dict.get("protected").unwrap().as_str().unwrap(), + base64::URL_SAFE_NO_PAD, + &mut buffer, + ) + .unwrap(); + let parsed_protected: Value = serde_json::from_slice(&buffer).unwrap(); + let mut protected_dict: Map = parsed_protected.as_object().unwrap().clone(); + protected_dict.remove(field); + let protected_str = serde_json::to_string(&protected_dict).unwrap(); + let protected_str_base64 = base64::encode_config(protected_str, base64::URL_SAFE_NO_PAD); + + msg_dict.insert(String::from("protected"), protected_str_base64.into()); + serde_json::to_string(&msg_dict).unwrap() +} diff --git a/src/test_vectors/did_doc/alice.rs b/src/test_vectors/did_doc/alice.rs index 86044d6..cb8a46d 100644 --- a/src/test_vectors/did_doc/alice.rs +++ b/src/test_vectors/did_doc/alice.rs @@ -6,96 +6,111 @@ use crate::didcomm::did::{ }; lazy_static! { - pub(crate) static ref ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519_NOT_IN_SECRET: VerificationMethod = + pub static ref ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519_NOT_IN_SECRET: VerificationMethod = VerificationMethod { id: "did:example:alice#key-x25519-not-in-secrets-1".into(), controller: "did:example:alice#key-x25519-not-in-secrets-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!({ - "kty": "OKP", - "crv": "X25519", - "x": "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", - })), + verification_material: VerificationMaterial::JWK { + value: json!({ + "kty": "OKP", + "crv": "X25519", + "x": "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", + }) + }, }; - pub(crate) static ref ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519: VerificationMethod = + pub static ref ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519: VerificationMethod = VerificationMethod { id: "did:example:alice#key-x25519-1".into(), controller: "did:example:alice#key-x25519-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!({ - "kty": "OKP", - "crv": "X25519", - "x": "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", - })), + verification_material: VerificationMaterial::JWK { + value: json!({ + "kty": "OKP", + "crv": "X25519", + "x": "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", + }) + }, }; - pub(crate) static ref ALICE_VERIFICATION_METHOD_KEY_AGREEM_P256: VerificationMethod = + pub static ref ALICE_VERIFICATION_METHOD_KEY_AGREEM_P256: VerificationMethod = VerificationMethod { id: "did:example:alice#key-p256-1".into(), controller: "did:example:alice#key-p256-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!({ - "kty": "EC", - "crv": "P-256", - "x": "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", - "y": "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", - })), + verification_material: VerificationMaterial::JWK { + value: json!({ + "kty": "EC", + "crv": "P-256", + "x": "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", + "y": "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", + }) + }, }; - pub(crate) static ref ALICE_VERIFICATION_METHOD_KEY_AGREEM_P521: VerificationMethod = + pub static ref ALICE_VERIFICATION_METHOD_KEY_AGREEM_P521: VerificationMethod = VerificationMethod { id: "did:example:alice#key-p521-1".into(), controller: "did:example:alice#key-p521-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!({ - "kty": "EC", - "crv": "P-521", - "x": "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", - "y": "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", - })), + verification_material: VerificationMaterial::JWK { + value: json!({ + "kty": "EC", + "crv": "P-521", + "x": "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", + "y": "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", + }) + }, }; - pub(crate) static ref ALICE_AUTH_METHOD_25519_NOT_IN_SECRET: VerificationMethod = - VerificationMethod { - id: "did:example:alice#key-not-in-secrets-1".into(), - controller: "did:example:alice#key-not-in-secrets-1".into(), - type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!({ + pub static ref ALICE_AUTH_METHOD_25519_NOT_IN_SECRET: VerificationMethod = VerificationMethod { + id: "did:example:alice#key-not-in-secrets-1".into(), + controller: "did:example:alice#key-not-in-secrets-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!({ "kty": "OKP", "crv": "Ed25519", "x": "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", - })), - }; - pub(crate) static ref ALICE_AUTH_METHOD_25519: VerificationMethod = VerificationMethod { + }) + }, + }; + pub static ref ALICE_AUTH_METHOD_25519: VerificationMethod = VerificationMethod { id: "did:example:alice#key-1".into(), controller: "did:example:alice#key-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!({ - "kty": "OKP", - "crv": "Ed25519", - "x": "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", - })), + verification_material: VerificationMaterial::JWK { + value: json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", + }) + }, }; - pub(crate) static ref ALICE_AUTH_METHOD_P256: VerificationMethod = VerificationMethod { + pub static ref ALICE_AUTH_METHOD_P256: VerificationMethod = VerificationMethod { id: "did:example:alice#key-2".into(), controller: "did:example:alice#key-2".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!({ - "kty": "EC", - "crv": "P-256", - "x": "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", - "y": "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", - })), + verification_material: VerificationMaterial::JWK { + value: json!({ + "kty": "EC", + "crv": "P-256", + "x": "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", + "y": "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", + }) + }, }; - pub(crate) static ref ALICE_AUTH_METHOD_SECPP256K1: VerificationMethod = VerificationMethod { + pub static ref ALICE_AUTH_METHOD_SECPP256K1: VerificationMethod = VerificationMethod { id: "did:example:alice#key-3".into(), controller: "did:example:alice#key-3".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!({ - "kty": "EC", - "crv": "secp256k1", - "x": "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", - "y": "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", - })), + verification_material: VerificationMaterial::JWK { + value: json!({ + "kty": "EC", + "crv": "secp256k1", + "x": "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", + "y": "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", + }) + }, }; - pub(crate) static ref ALICE_DID_DOC: DIDDoc = DIDDoc { + pub static ref ALICE_DID_DOC: DIDDoc = DIDDoc { did: "did:example:alice".into(), authentications: vec![ "did:example:alice#key-1".into(), @@ -119,7 +134,7 @@ lazy_static! { ALICE_AUTH_METHOD_SECPP256K1.clone(), ], }; - pub(crate) static ref ALICE_DID_DOC_WITH_NO_SECRETS: DIDDoc = DIDDoc { + pub static ref ALICE_DID_DOC_WITH_NO_SECRETS: DIDDoc = DIDDoc { did: "did:example:alice".into(), authentications: vec![ "did:example:alice#key-not-in-secrets-1".into(), diff --git a/src/test_vectors/did_doc/bob.rs b/src/test_vectors/did_doc/bob.rs index 84915d4..09d3fd3 100644 --- a/src/test_vectors/did_doc/bob.rs +++ b/src/test_vectors/did_doc/bob.rs @@ -2,176 +2,215 @@ use lazy_static::lazy_static; use serde_json::json; use crate::didcomm::did::{ - DIDDoc, VerificationMaterial, VerificationMethod, VerificationMethodType, + DIDCommMessagingService, DIDDoc, Service, ServiceKind, VerificationMaterial, + VerificationMethod, VerificationMethodType, }; lazy_static! { - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_1: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_1: VerificationMethod = VerificationMethod { id: "did:example:bob#key-x25519-1".into(), controller: "did:example:bob#key-x25519-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "OKP", - "crv": "X25519", - "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "OKP", + "crv": "X25519", + "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_2: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_2: VerificationMethod = VerificationMethod { id: "did:example:bob#key-x25519-2".into(), controller: "did:example:bob#key-x25519-2".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "OKP", - "crv": "X25519", - "x": "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "OKP", + "crv": "X25519", + "x": "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_3: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_3: VerificationMethod = VerificationMethod { id: "did:example:bob#key-x25519-3".into(), controller: "did:example:bob#key-x25519-3".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "OKP", - "crv": "X25519", - "x": "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "OKP", + "crv": "X25519", + "x": "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_NOT_IN_SECRETS_1: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_NOT_IN_SECRETS_1: VerificationMethod = VerificationMethod { id: "did:example:bob#key-x25519-not-secrets-1".into(), controller: "did:example:bob#key-x25519-not-secrets-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "OKP", - "crv": "X25519", - "x": "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "OKP", + "crv": "X25519", + "x": "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_1: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_1: VerificationMethod = VerificationMethod { id: "did:example:bob#key-p256-1".into(), controller: "did:example:bob#key-p256-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "EC", - "crv": "P-256", - "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", - "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-256", + "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_2: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_2: VerificationMethod = VerificationMethod { id: "did:example:bob#key-p256-2".into(), controller: "did:example:bob#key-p256-2".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "EC", - "crv": "P-256", - "x": "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", - "y": "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-256", + "x": "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", + "y": "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_NOT_IN_SECRETS_1: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_NOT_IN_SECRETS_1: VerificationMethod = VerificationMethod { id: "did:example:bob#key-p256-not-secrets-1".into(), controller: "did:example:bob#key-p256-not-secrets-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "EC", - "crv": "P-256", - "x": "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", - "y": "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-256", + "x": "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", + "y": "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_1: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_1: VerificationMethod = VerificationMethod { id: "did:example:bob#key-p384-1".into(), controller: "did:example:bob#key-p384-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "EC", - "crv": "P-384", - "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", - "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-384", + "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_2: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_2: VerificationMethod = VerificationMethod { id: "did:example:bob#key-p384-2".into(), controller: "did:example:bob#key-p384-2".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "EC", - "crv": "P-384", - "x": "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", - "y": "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-384", + "x": "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", + "y": "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_NOT_IN_SECRETS_1: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_NOT_IN_SECRETS_1: VerificationMethod = VerificationMethod { id: "did:example:bob#key-p384-not-secrets-1".into(), controller: "did:example:bob#key-p384-not-secrets-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "EC", - "crv": "P-384", - "x": "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", - "y": "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-384", + "x": "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", + "y": "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_1: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_1: VerificationMethod = VerificationMethod { id: "did:example:bob#key-p521-1".into(), controller: "did:example:bob#key-p521-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "EC", - "crv": "P-521", - "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", - "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-521", + "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_2: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_2: VerificationMethod = VerificationMethod { id: "did:example:bob#key-p521-2".into(), controller: "did:example:bob#key-p521-2".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "EC", - "crv": "P-521", - "x": "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", - "y": "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-521", + "x": "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", + "y": "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", + }) + }, }; - pub(crate) static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_NOT_IN_SECRETS_1: VerificationMethod = + pub static ref BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_NOT_IN_SECRETS_1: VerificationMethod = VerificationMethod { id: "did:example:bob#key-p521-not-secrets-1".into(), controller: "did:example:bob#key-p521-not-secrets-1".into(), type_: VerificationMethodType::JsonWebKey2020, - verification_material: VerificationMaterial::JWK(json!( - { - "kty": "EC", - "crv": "P-521", - "x": "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", - "y": "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", - })), + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-521", + "x": "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", + "y": "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", + }) + }, }; - pub(crate) static ref BOB_DID_DOC: DIDDoc = DIDDoc { + pub static ref BOB_DID_COMM_MESSAGING_SERVICE: DIDCommMessagingService = + DIDCommMessagingService { + service_endpoint: "http://example.com/path".into(), + accept: vec!["didcomm/v2".into(), "didcomm/aip2;env=rfc587".into()], + routing_keys: vec!["did:example:mediator1#key-x25519-1".into()], + }; + pub static ref BOB_SERVICE: Service = Service { + id: "did:example:bob#didcomm-1".into(), + kind: ServiceKind::DIDCommMessaging { + value: BOB_DID_COMM_MESSAGING_SERVICE.clone() + }, + }; + pub static ref BOB_DID_DOC: DIDDoc = DIDDoc { did: "did:example:bob".into(), authentications: vec![], key_agreements: vec![ @@ -185,7 +224,7 @@ lazy_static! { "did:example:bob#key-p521-1".into(), "did:example:bob#key-p521-2".into(), ], - services: vec![], + services: vec![BOB_SERVICE.clone()], verification_methods: vec![ BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_1.clone(), BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_2.clone(), @@ -198,7 +237,7 @@ lazy_static! { BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_2.clone(), ], }; - pub(crate) static ref BOB_DID_DOC_NO_SECRETS: DIDDoc = DIDDoc { + pub static ref BOB_DID_DOC_NO_SECRETS: DIDDoc = DIDDoc { did: "did:example:bob".into(), authentications: vec![], key_agreements: vec![ @@ -216,7 +255,7 @@ lazy_static! { "did:example:bob#key-p521-2".into(), "did:example:bob#key-p521-not-secrets-1".into(), ], - services: vec![], // TODO: Add forward service + services: vec![BOB_SERVICE.clone()], verification_methods: vec![ BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_1.clone(), BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_2.clone(), diff --git a/src/test_vectors/did_doc/charlie.rs b/src/test_vectors/did_doc/charlie.rs new file mode 100644 index 0000000..053a52a --- /dev/null +++ b/src/test_vectors/did_doc/charlie.rs @@ -0,0 +1,62 @@ +use lazy_static::lazy_static; +use serde_json::json; + +use crate::didcomm::did::{ + DIDCommMessagingService, DIDDoc, Service, ServiceKind, VerificationMaterial, + VerificationMethod, VerificationMethodType, +}; + +lazy_static! { + pub static ref CHARLIE_VERIFICATION_METHOD_KEY_AGREEM_X25519: VerificationMethod = + VerificationMethod { + id: "did:example:charlie#key-x25519-1".into(), + controller: "did:example:charlie#key-x25519-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "OKP", + "crv": "X25519", + "x": "nTiVFj7DChMsETDdxd5dIzLAJbSQ4j4UG6ZU1ogLNlw", + }) + }, + }; + pub static ref CHARLIE_AUTH_METHOD_25519: VerificationMethod = VerificationMethod { + id: "did:example:charlie#key-1".into(), + controller: "did:example:charlie#key-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "OKP", + "crv": "Ed25519", + "x": "VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE", + }) + }, + }; + pub static ref CHARLIE_DID_COMM_MESSAGING_SERVICE: DIDCommMessagingService = + DIDCommMessagingService { + service_endpoint: "did:example:mediator3".into(), + accept: vec!["didcomm/v2".into(), "didcomm/aip2;env=rfc587".into()], + routing_keys: vec![ + "did:example:mediator2#key-x25519-1".into(), + "did:example:mediator1#key-x25519-1".into(), + ], + }; + pub static ref CHARLIE_SERVICE: Service = Service { + id: "did:example:charlie#didcomm-1".into(), + kind: ServiceKind::DIDCommMessaging { + value: CHARLIE_DID_COMM_MESSAGING_SERVICE.clone() + }, + }; + pub static ref CHARLIE_DID_DOC: DIDDoc = DIDDoc { + did: "did:example:charlie".into(), + authentications: vec!["did:example:charlie#key-1".into()], + key_agreements: vec!["did:example:charlie#key-x25519-1".into()], + services: vec![CHARLIE_SERVICE.clone()], + verification_methods: vec![ + CHARLIE_VERIFICATION_METHOD_KEY_AGREEM_X25519.clone(), + CHARLIE_AUTH_METHOD_25519.clone(), + ], + }; +} diff --git a/src/test_vectors/did_doc/mediator1.rs b/src/test_vectors/did_doc/mediator1.rs new file mode 100644 index 0000000..f558fc0 --- /dev/null +++ b/src/test_vectors/did_doc/mediator1.rs @@ -0,0 +1,85 @@ +use lazy_static::lazy_static; +use serde_json::json; + +use crate::didcomm::did::{ + DIDDoc, VerificationMaterial, VerificationMethod, VerificationMethodType, +}; + +lazy_static! { + pub static ref MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_X25519_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator1#key-x25519-1".into(), + controller: "did:example:mediator1#key-x25519-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "OKP", + "crv": "X25519", + "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }) + }, + }; + pub static ref MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P256_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator1#key-p256-1".into(), + controller: "did:example:mediator1#key-p256-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-256", + "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }) + }, + }; + pub static ref MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P384_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator1#key-p384-1".into(), + controller: "did:example:mediator1#key-p384-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-384", + "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }) + }, + }; + pub static ref MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P521_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator1#key-p521-1".into(), + controller: "did:example:mediator1#key-p521-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-521", + "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }) + }, + }; + pub static ref MEDIATOR1_DID_DOC: DIDDoc = DIDDoc { + did: "did:example:mediator1".into(), + authentications: vec![], + key_agreements: vec![ + "did:example:mediator1#key-x25519-1".into(), + "did:example:mediator1#key-p256-1".into(), + "did:example:mediator1#key-p384-1".into(), + "did:example:mediator1#key-p521-1".into(), + ], + services: vec![], + verification_methods: vec![ + MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_X25519_1.clone(), + MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P256_1.clone(), + MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P384_1.clone(), + MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P521_1.clone(), + ], + }; +} diff --git a/src/test_vectors/did_doc/mediator2.rs b/src/test_vectors/did_doc/mediator2.rs new file mode 100644 index 0000000..e7c5d46 --- /dev/null +++ b/src/test_vectors/did_doc/mediator2.rs @@ -0,0 +1,85 @@ +use lazy_static::lazy_static; +use serde_json::json; + +use crate::didcomm::did::{ + DIDDoc, VerificationMaterial, VerificationMethod, VerificationMethodType, +}; + +lazy_static! { + pub static ref MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_X25519_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator2#key-x25519-1".into(), + controller: "did:example:mediator2#key-x25519-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "OKP", + "crv": "X25519", + "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }) + }, + }; + pub static ref MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P256_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator2#key-p256-1".into(), + controller: "did:example:mediator2#key-p256-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-256", + "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }) + }, + }; + pub static ref MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P384_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator2#key-p384-1".into(), + controller: "did:example:mediator2#key-p384-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-384", + "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }) + }, + }; + pub static ref MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P521_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator2#key-p521-1".into(), + controller: "did:example:mediator2#key-p521-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-521", + "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }) + }, + }; + pub static ref MEDIATOR2_DID_DOC: DIDDoc = DIDDoc { + did: "did:example:mediator2".into(), + authentications: vec![], + key_agreements: vec![ + "did:example:mediator2#key-x25519-1".into(), + "did:example:mediator2#key-p256-1".into(), + "did:example:mediator2#key-p384-1".into(), + "did:example:mediator2#key-p521-1".into(), + ], + services: vec![], + verification_methods: vec![ + MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_X25519_1.clone(), + MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P256_1.clone(), + MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P384_1.clone(), + MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P521_1.clone(), + ], + }; +} diff --git a/src/test_vectors/did_doc/mediator3.rs b/src/test_vectors/did_doc/mediator3.rs new file mode 100644 index 0000000..9691699 --- /dev/null +++ b/src/test_vectors/did_doc/mediator3.rs @@ -0,0 +1,98 @@ +use lazy_static::lazy_static; +use serde_json::json; + +use crate::didcomm::did::{ + DIDCommMessagingService, DIDDoc, Service, ServiceKind, VerificationMaterial, + VerificationMethod, VerificationMethodType, +}; + +lazy_static! { + pub static ref MEDIATOR3_VERIFICATION_METHOD_KEY_AGREEM_X25519_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator3#key-x25519-1".into(), + controller: "did:example:mediator3#key-x25519-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "OKP", + "crv": "X25519", + "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }) + }, + }; + pub static ref MEDIATOR3_VERIFICATION_METHOD_KEY_AGREEM_P256_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator3#key-p256-1".into(), + controller: "did:example:mediator3#key-p256-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-256", + "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }) + }, + }; + pub static ref MEDIATOR3_VERIFICATION_METHOD_KEY_AGREEM_P384_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator3#key-p384-1".into(), + controller: "did:example:mediator3#key-p384-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-384", + "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }) + }, + }; + pub static ref MEDIATOR3_VERIFICATION_METHOD_KEY_AGREEM_P521_1: VerificationMethod = + VerificationMethod { + id: "did:example:mediator3#key-p521-1".into(), + controller: "did:example:mediator3#key-p521-1".into(), + type_: VerificationMethodType::JsonWebKey2020, + verification_material: VerificationMaterial::JWK { + value: json!( + { + "kty": "EC", + "crv": "P-521", + "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }) + }, + }; + pub static ref MEDIATOR3_DID_COMM_MESSAGING_SERVICE: DIDCommMessagingService = + DIDCommMessagingService { + service_endpoint: "http://example.com/path".into(), + accept: vec!["didcomm/v2".into(), "didcomm/aip2;env=rfc587".into()], + routing_keys: vec![], + }; + pub static ref MEDIATOR3_SERVICE: Service = Service { + id: "did:example:mediator3#didcomm-1".into(), + kind: ServiceKind::DIDCommMessaging { + value: MEDIATOR3_DID_COMM_MESSAGING_SERVICE.clone() + }, + }; + pub static ref MEDIATOR3_DID_DOC: DIDDoc = DIDDoc { + did: "did:example:mediator3".into(), + authentications: vec![], + key_agreements: vec![ + "did:example:mediator3#key-x25519-1".into(), + "did:example:mediator3#key-p256-1".into(), + "did:example:mediator3#key-p384-1".into(), + "did:example:mediator3#key-p521-1".into(), + ], + services: vec![MEDIATOR3_SERVICE.clone()], + verification_methods: vec![ + MEDIATOR3_VERIFICATION_METHOD_KEY_AGREEM_X25519_1.clone(), + MEDIATOR3_VERIFICATION_METHOD_KEY_AGREEM_P256_1.clone(), + MEDIATOR3_VERIFICATION_METHOD_KEY_AGREEM_P384_1.clone(), + MEDIATOR3_VERIFICATION_METHOD_KEY_AGREEM_P521_1.clone(), + ], + }; +} diff --git a/src/test_vectors/did_doc/mod.rs b/src/test_vectors/did_doc/mod.rs index 0b0f932..0e1f601 100644 --- a/src/test_vectors/did_doc/mod.rs +++ b/src/test_vectors/did_doc/mod.rs @@ -1,10 +1,30 @@ mod alice; mod bob; +mod charlie; +mod mediator1; +mod mediator2; +mod mediator3; // TODO: Remove allow #[allow(unused_imports)] -pub(crate) use alice::*; +pub use mediator1::*; // TODO: Remove allow #[allow(unused_imports)] -pub(crate) use bob::*; +pub use mediator2::*; + +// TODO: Remove allow +#[allow(unused_imports)] +pub use mediator3::*; + +// TODO: Remove allow +#[allow(unused_imports)] +pub use alice::*; + +// TODO: Remove allow +#[allow(unused_imports)] +pub use bob::*; + +// TODO: Remove allow +#[allow(unused_imports)] +pub use charlie::*; diff --git a/src/test_vectors/encrypted.rs b/src/test_vectors/encrypted.rs index 1a8f39a..396696e 100644 --- a/src/test_vectors/encrypted.rs +++ b/src/test_vectors/encrypted.rs @@ -1,4 +1,4 @@ -pub(crate) const ENCRYPTED_MSG_ANON_XC20P_1: &str = r#" +pub const ENCRYPTED_MSG_ANON_XC20P_1: &str = r#" { "ciphertext": "KWS7gJU7TbyJlcT9dPkCw-ohNigGaHSukR9MUqFM0THbCTCNkY-g5tahBFyszlKIKXs7qOtqzYyWbPou2q77XlAeYs93IhF6NvaIjyNqYklvj-OtJt9W2Pj5CLOMdsR0C30wchGoXd6wEQZY4ttbzpxYznqPmJ0b9KW6ZP-l4_DSRYe9B-1oSWMNmqMPwluKbtguC-riy356Xbu2C9ShfWmpmjz1HyJWQhZfczuwkWWlE63g26FMskIZZd_jGpEhPFHKUXCFwbuiw_Iy3R0BIzmXXdK_w7PZMMPbaxssl2UeJmLQgCAP8j8TukxV96EKa6rGgULvlo7qibjJqsS5j03bnbxkuxwbfyu3OxwgVzFWlyHbUH6p", "protected": "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkpIanNtSVJaQWFCMHpSR193TlhMVjJyUGdnRjAwaGRIYlc1cmo4ZzBJMjQifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInR5cCI6ImFwcGxpY2F0aW9uL2RpZGNvbW0tZW5jcnlwdGVkK2pzb24iLCJlbmMiOiJYQzIwUCIsImFsZyI6IkVDREgtRVMrQTI1NktXIn0", @@ -23,7 +23,7 @@ pub(crate) const ENCRYPTED_MSG_ANON_XC20P_1: &str = r#" } "#; -pub(crate) const ENCRYPTED_MSG_ANON_XC20P_2: &str = r#" +pub const ENCRYPTED_MSG_ANON_XC20P_2: &str = r#" { "ciphertext": "912eTUDRKTzhUUqxosPogT1bs9w9wv4s4HmoWkaeU9Uj92V4ENpk-_ZPNSvPyXYLfFj0nc9V2-ux5jq8hqUd17WJpXEM1ReMUjtnTqeUzVa7_xtfkbfhaOZdL8OfgNquPDH1bYcBshN9O9lMT0V52gmGaAB45k4I2PNHcc0A5XWzditCYi8wOkPDm5A7pA39Au5uUNiFQjRYDrz1YvJwV9cdca54vYsBfV1q4c8ncQsv5tNnFYQ1s4rAG7RbyWdAjkC89kE_hIoRRkWZhFyNSfdvRtlUJDlM19uml7lwBWWPnqkmQ3ubiBGmVct3pjrcDvjissOw8Dwkn4E1V1gafec-jDBy4Rndai_RdGjnXjMJs7nRv3Ot", "protected": "eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJFczdpUDNFaExDSGxBclAwS2NZRmNxRXlCYXByMks2WU9BOVc4ZU84YXU4IiwieSI6Ik42QWw3RVR3Q2RwQzZOamRlY3IyS1hBZzFVZVp5X3VmSFJRS3A5RzZLR2sifSwiYXB2Ijoiei1McXB2VlhEYl9zR1luM21qUUxwdXUyQ1FMZXdZdVpvVFdPSVhQSDNGTSIsInR5cCI6ImFwcGxpY2F0aW9uL2RpZGNvbW0tZW5jcnlwdGVkK2pzb24iLCJlbmMiOiJYQzIwUCIsImFsZyI6IkVDREgtRVMrQTI1NktXIn0", @@ -43,7 +43,7 @@ pub(crate) const ENCRYPTED_MSG_ANON_XC20P_2: &str = r#" } "#; -pub(crate) const ENCRYPTED_MSG_AUTH_X25519: &str = r#" +pub const ENCRYPTED_MSG_AUTH_X25519: &str = r#" { "ciphertext": "MJezmxJ8DzUB01rMjiW6JViSaUhsZBhMvYtezkhmwts1qXWtDB63i4-FHZP6cJSyCI7eU-gqH8lBXO_UVuviWIqnIUrTRLaumanZ4q1dNKAnxNL-dHmb3coOqSvy3ZZn6W17lsVudjw7hUUpMbeMbQ5W8GokK9ZCGaaWnqAzd1ZcuGXDuemWeA8BerQsfQw_IQm-aUKancldedHSGrOjVWgozVL97MH966j3i9CJc3k9jS9xDuE0owoWVZa7SxTmhl1PDetmzLnYIIIt-peJtNYGdpd-FcYxIFycQNRUoFEr77h4GBTLbC-vqbQHJC1vW4O2LEKhnhOAVlGyDYkNbA4DSL-LMwKxenQXRARsKSIMn7z-ZIqTE-VCNj9vbtgR", "protected": "eyJlcGsiOnsia3R5IjoiT0tQIiwiY3J2IjoiWDI1NTE5IiwieCI6IkdGY01vcEpsamY0cExaZmNoNGFfR2hUTV9ZQWY2aU5JMWRXREd5VkNhdzAifSwiYXB2IjoiTmNzdUFuclJmUEs2OUEtcmtaMEw5WFdVRzRqTXZOQzNaZzc0QlB6NTNQQSIsInNraWQiOiJkaWQ6ZXhhbXBsZTphbGljZSNrZXkteDI1NTE5LTEiLCJhcHUiOiJaR2xrT21WNFlXMXdiR1U2WVd4cFkyVWphMlY1TFhneU5UVXhPUzB4IiwidHlwIjoiYXBwbGljYXRpb24vZGlkY29tbS1lbmNyeXB0ZWQranNvbiIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJhbGciOiJFQ0RILTFQVStBMjU2S1cifQ", @@ -68,7 +68,7 @@ pub(crate) const ENCRYPTED_MSG_AUTH_X25519: &str = r#" } "#; -pub(crate) const ENCRYPTED_MSG_AUTH_P256: &str = r#" +pub const ENCRYPTED_MSG_AUTH_P256: &str = r#" { "ciphertext": "WCufCs2lMZfkxQ0JCK92lPtLFgwWk_FtRWOMj52bQISa94nEbIYqHDUohIbvLMgbSjRcJVusZO04UthDuOpSSTcV5GBi3O0cMrjyI_PZnTb1yikLXpXma1bT10D2r5TPtzRMxXF3nFsr9y0JKV1TsMtn70Df2fERx2bAGxcflmd-A2sMlSTT8b7QqPtn17Yb-pA8gr4i0Bqb2WfDzwnbfewbukpRmPA2hsEs9oLKypbniAafSpoiQjfb19oDfsYaWWXqsdjTYMflqH__DqSmW52M-SUp6or0xU0ujbHmOkRkcdh9PsR5YsPuIWAqYa2hfjz_KIrGTxvCos0DMiZ4Lh_lPIYQqBufSdFH5AGChoekFbQ1vcyIyYMFugzOHOgZ2TwEzv94GCgokBHQR4_qaU_f4Mva64KPwqOYdm5f4KX16afTJa-IV7ar7__2L-A-LyxmC5KIHeGOedV9kzZBLC7TuzRAuE3vY7pkhLB1jPE6XpTeKXldljaeOSEVcbFUQtsHOSPz9JXuhqZ1fdAx8qV7hUnSAd_YMMDR3S6SXtem8ak2m98WPvKIxhCbcto7W2qoNYMT7MPvvid-QzUvTdKtyovCvLzhyYJzMjZxmn9-EnGhZ5ITPL_xFfLyKxhSSUVz3kSwK9xuOj3KpJnrrD7xrp5FKzEaJVIHWrUW90V_9QVLjriThZ36fA3ipvs8ZJ8QSTnGAmuIQ6Z2u_r4KsjL_mGAgn47qyqRm-OSLEUE4_2qB0Q9Z7EBKakCH8VPt09hTMDR62aYZYwtmpNs9ISu0VPvFjh8UmKbFcQsVrz90-x-r-Q1fTX9JaIFcDy7aqKcI-ai3tVF_HDR60Jaiw", "protected": "eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJObHJ3UHZ0SUluZWNpeUVrYTRzMi00czhPalRidEZFQVhmTC12Z2x5enFvIiwieSI6ImhiMnZkWE5zSzVCQ2U3LVhaQ0dfLTY0R21UT19rNUlNWFBaQ00xdGFUQmcifSwiYXB2Ijoiei1McXB2VlhEYl9zR1luM21qUUxwdXUyQ1FMZXdZdVpvVFdPSVhQSDNGTSIsInNraWQiOiJkaWQ6ZXhhbXBsZTphbGljZSNrZXktcDI1Ni0xIiwiYXB1IjoiWkdsa09tVjRZVzF3YkdVNllXeHBZMlVqYTJWNUxYQXlOVFl0TVEiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLWVuY3J5cHRlZCtqc29uIiwiZW5jIjoiQTI1NkNCQy1IUzUxMiIsImFsZyI6IkVDREgtMVBVK0EyNTZLVyJ9", @@ -88,4 +88,20 @@ pub(crate) const ENCRYPTED_MSG_AUTH_P256: &str = r#" } "#; -pub(crate) const ENCRYPTED_MSG_AUTH_P256_SIGNED: &str = r#"{"payload":"eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVhdGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNzYWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19","signatures":[{"protected":"eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXNpZ25lZCtqc29uIiwiYWxnIjoiRWREU0EifQ","signature":"FW33NnvOHV0Ted9-F7GZbkia-vYAfBKtH4oBxbrttWAhBZ6UFJMxcGjL3lwOl4YohI3kyyd08LHPWNMgP2EVCQ","header":{"kid":"did:example:alice#key-1"}}]}"#; +pub const ENCRYPTED_MSG_AUTH_P256_SIGNED: &str = r#"{"payload":"eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVhdGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNzYWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19","signatures":[{"protected":"eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXNpZ25lZCtqc29uIiwiYWxnIjoiRWREU0EifQ","signature":"FW33NnvOHV0Ted9-F7GZbkia-vYAfBKtH4oBxbrttWAhBZ6UFJMxcGjL3lwOl4YohI3kyyd08LHPWNMgP2EVCQ","header":{"kid":"did:example:alice#key-1"}}]}"#; + +pub const INVALID_ENCRYPTED_MSG_ANON_P256_EPK_WRONG_POINT: &str = r#" +{ + "protected": "eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLWVuY3J5cHRlZCtqc29uIiwiYWxnIjoiRUNESC1FUytBMjU2S1ciLCJlbmMiOiJYQzIwUCIsImFwdSI6bnVsbCwiYXB2Ijoiei1McXB2VlhEYl9zR1luM21qUUxwdXUyQ1FMZXdZdVpvVFdPSVhQSDNGTSIsImVwayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IkZSQW1UQmljUFZJXy1aRnF2WEJwNzZhV2pZM0gzYlpGZlhocHRUNm1ETnciLCJ5IjoiLXZ0LTFIaHRvVjBwN2xrbGIxTnRvMWRhU0lqQnV3cVZzbGIwcC1uOWRrdyJ9fQ==", + "recipients": [{ + "header": {"kid": "did:example:bob#key-p256-1"}, + "encrypted_key": "scQxV9YQ4mQrUHgl6yAnBFDXNZAiIs_15bmoErUmoYm0HtuRclPoQg" + },{ + "header": {"kid": "did:example:bob#key-p256-2"}, + "encrypted_key": "CqZ-HDH2j0NC-eoUueNLKyAuMQXjQyw8bJHYM2f-lxJVm3eXCdmm2g" + }], + "iv": "Vg1uyuQKrU6Kw8OJK38WCpYFxW0suAP9", + "ciphertext": "2nIm3xQcFR3HXbUPF1HS_D92OGVDvL0nIi6O5ol5tnMIa09NxJtbVAYIG7ZrkT9314PqXn_Rq77hgGE6FAOgO7aNYLyUJh0JCC_i2p_XOWuk20BYyBsmmRvVpg0DY3I1Lb-Vg1pT9pEy09gsMSLhbfqk0_TFJB1rcqzR8W0YZB5mX_53nMRf1ZatDEg4rDogSekWEGTBnlTNRua8-zoI4573SfgJ-ONt7Z_KbGO-sdRkmqXhfYNcbUyoMF9JSa-kraVuWHZP9hTz8-7R020EXfb4jodMWVOMMAiJYk1Cd7tetHXpLPdtuokaapofmtL_SNftAX2CB6ULf0axrHUNtvUyjAPvpgvSuvQuMrDlaXn16MQJ_q55", + "tag": "etLTQvKsTvF629fykLiUDg" +} +"#; diff --git a/src/test_vectors/from_prior.rs b/src/test_vectors/from_prior.rs new file mode 100644 index 0000000..aa63735 --- /dev/null +++ b/src/test_vectors/from_prior.rs @@ -0,0 +1,78 @@ +use lazy_static::lazy_static; + +use super::common::{ALICE_DID, CHARLIE_DID}; +use crate::{ + didcomm::FromPrior, + test_vectors::{ALICE_AUTH_METHOD_25519, CHARLIE_AUTH_METHOD_25519}, +}; + +lazy_static! { + pub static ref FROM_PRIOR_MINIMAL: FromPrior = + FromPrior::build(CHARLIE_DID.into(), ALICE_DID.into()).finalize(); +} + +lazy_static! { + pub static ref FROM_PRIOR_FULL: FromPrior = + FromPrior::build(CHARLIE_DID.into(), ALICE_DID.into()) + .aud("123".into()) + .exp(1234) + .nbf(12345) + .iat(123456) + .jti("dfg".into()) + .finalize(); +} + +lazy_static! { + pub static ref FROM_PRIOR_INVALID_ISS: FromPrior = + FromPrior::build("invalid".into(), ALICE_DID.into()) + .aud("123".into()) + .exp(1234) + .nbf(12345) + .iat(123456) + .jti("dfg".into()) + .finalize(); +} + +lazy_static! { + pub static ref FROM_PRIOR_INVALID_ISS_DID_URL: FromPrior = + FromPrior::build(CHARLIE_AUTH_METHOD_25519.id.clone(), ALICE_DID.into()) + .aud("123".into()) + .exp(1234) + .nbf(12345) + .iat(123456) + .jti("dfg".into()) + .finalize(); +} + +lazy_static! { + pub static ref FROM_PRIOR_INVALID_SUB: FromPrior = + FromPrior::build(CHARLIE_DID.into(), "invalid".into()) + .aud("123".into()) + .exp(1234) + .nbf(12345) + .iat(123456) + .jti("dfg".into()) + .finalize(); +} + +lazy_static! { + pub static ref FROM_PRIOR_INVALID_SUB_DID_URL: FromPrior = + FromPrior::build(CHARLIE_DID.into(), ALICE_AUTH_METHOD_25519.id.clone()) + .aud("123".into()) + .exp(1234) + .nbf(12345) + .iat(123456) + .jti("dfg".into()) + .finalize(); +} + +lazy_static! { + pub static ref FROM_PRIOR_INVALID_EQUAL_ISS_AND_SUB: FromPrior = + FromPrior::build(ALICE_DID.into(), ALICE_DID.into()) + .aud("123".into()) + .exp(1234) + .nbf(12345) + .iat(123456) + .jti("dfg".into()) + .finalize(); +} diff --git a/src/test_vectors/from_prior_jwt.rs b/src/test_vectors/from_prior_jwt.rs new file mode 100644 index 0000000..fd79625 --- /dev/null +++ b/src/test_vectors/from_prior_jwt.rs @@ -0,0 +1,5 @@ +pub const FROM_PRIOR_JWT_FULL: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDA"; + +pub const FROM_PRIOR_JWT_INVALID: &str = "invalid"; + +pub const FROM_PRIOR_JWT_INVALID_SIGNATURE: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDB"; diff --git a/src/test_vectors/message.rs b/src/test_vectors/message.rs index 470d769..7ad1d52 100644 --- a/src/test_vectors/message.rs +++ b/src/test_vectors/message.rs @@ -5,11 +5,11 @@ use super::common::{ALICE_DID, BOB_DID}; use crate::didcomm::{Attachment, Message, MessageBuilder}; lazy_static! { - pub(crate) static ref MESSAGE_SIMPLE: Message = _message().finalize(); + pub static ref MESSAGE_SIMPLE: Message = _message().finalize(); } lazy_static! { - pub(crate) static ref MESSAGE_MINIMAL: Message = Message::build( + pub static ref MESSAGE_MINIMAL: Message = Message::build( "1234567890".to_owned(), "http://example.com/protocols/lets_do_lunch/1.0/proposal".to_owned(), json!({}), @@ -18,8 +18,29 @@ lazy_static! { } lazy_static! { - pub(crate) static ref MESSAGE_ATTACHMENT_BASE64: Message = _message() - .attachement( + pub static ref MESSAGE_FROM_PRIOR_FULL: Message = _message() + .from_prior("eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDA".into()) + .finalize(); +} + +lazy_static! { + pub static ref MESSAGE_FROM_PRIOR_MISMATCHED_SUB_AND_FROM: Message = + Message::build( + "1234567890".to_owned(), + "http://example.com/protocols/lets_do_lunch/1.0/proposal".to_owned(), + json!({"messagespecificattribute": "and its value"}), + ) + .from(BOB_DID.to_owned()) + .to(ALICE_DID.to_owned()) + .created_time(1516269022) + .expires_time(1516385931) + .from_prior("eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDA".into()) + .finalize(); +} + +lazy_static! { + pub static ref MESSAGE_ATTACHMENT_BASE64: Message = _message() + .attachment( Attachment::base64("qwerty".to_owned()) .id("23".to_owned()) .finalize(), @@ -28,8 +49,8 @@ lazy_static! { } lazy_static! { - pub(crate) static ref MESSAGE_ATTACHMENT_LINKS: Message = _message() - .attachement( + pub static ref MESSAGE_ATTACHMENT_LINKS: Message = _message() + .attachment( Attachment::links( ["1".to_owned(), "2".to_owned(), "3".to_owned()].into(), "qwerty".into(), @@ -41,8 +62,8 @@ lazy_static! { } lazy_static! { - pub(crate) static ref MESSAGE_ATTACHMENT_JSON: Message = _message() - .attachement( + pub static ref MESSAGE_ATTACHMENT_JSON: Message = _message() + .attachment( Attachment::json(json!({"foo": "bar", "links": [2, 3]})) .id("23".to_owned()) .finalize(), @@ -51,8 +72,8 @@ lazy_static! { } lazy_static! { - pub(crate) static ref MESSAGE_ATTACHMENT_MULTI_1: Message = _message() - .attachements( + pub static ref MESSAGE_ATTACHMENT_MULTI_1: Message = _message() + .attachments( [ Attachment::json(json!({"foo": "bar", "links": [2, 3]})) .id("23".to_owned()) @@ -73,8 +94,8 @@ lazy_static! { } lazy_static! { - pub(crate) static ref MESSAGE_ATTACHMENT_MULTI_2: Message = _message() - .attachements( + pub static ref MESSAGE_ATTACHMENT_MULTI_2: Message = _message() + .attachments( [ Attachment::links( ["1".to_owned(), "2".to_owned(), "3".to_owned()].into(), diff --git a/src/test_vectors/mod.rs b/src/test_vectors/mod.rs index 7c4a6d2..fba4f5f 100644 --- a/src/test_vectors/mod.rs +++ b/src/test_vectors/mod.rs @@ -1,26 +1,26 @@ mod common; mod did_doc; mod encrypted; +mod from_prior; +mod from_prior_jwt; mod message; mod plaintext; mod secrets; mod signed; -// TODO: Remove allow -#[allow(unused_imports)] -pub(crate) use common::*; +pub use common::*; -// TODO: Remove allow -#[allow(unused_imports)] -pub(crate) use did_doc::*; +pub use did_doc::*; -pub(crate) use message::*; -pub(crate) use plaintext::*; +pub use encrypted::*; -// TODO: Remove allow -#[allow(unused_imports)] -pub(crate) use secrets::*; +pub use from_prior::*; -pub(crate) use signed::*; +pub use from_prior_jwt::*; -pub(crate) use encrypted::*; +pub use message::*; +pub use plaintext::*; + +pub use secrets::*; + +pub use signed::*; diff --git a/src/test_vectors/plaintext.rs b/src/test_vectors/plaintext.rs index b2ac954..d539687 100644 --- a/src/test_vectors/plaintext.rs +++ b/src/test_vectors/plaintext.rs @@ -1,4 +1,4 @@ -pub(crate) const PLAINTEXT_MSG_SIMPLE: &str = r#" +pub const PLAINTEXT_MSG_SIMPLE: &str = r#" { "id": "1234567890", "typ": "application/didcomm-plain+json", @@ -11,7 +11,7 @@ pub(crate) const PLAINTEXT_MSG_SIMPLE: &str = r#" } "#; -pub(crate) const PLAINTEXT_MSG_MINIMAL: &str = r#" +pub const PLAINTEXT_MSG_MINIMAL: &str = r#" { "id": "1234567890", "typ": "application/didcomm-plain+json", @@ -20,7 +20,49 @@ pub(crate) const PLAINTEXT_MSG_MINIMAL: &str = r#" } "#; -pub(crate) const PLAINTEXT_MSG_ATTACHMENT_BASE64: &str = r#" +pub const PLAINTEXT_FROM_PRIOR: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "from": "did:example:alice", + "to": ["did:example:bob"], + "created_time": 1516269022, + "expires_time": 1516385931, + "from_prior": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDA", + "body": {"messagespecificattribute": "and its value"} +} +"#; + +pub const PLAINTEXT_INVALID_FROM_PRIOR: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "from": "did:example:alice", + "to": ["did:example:bob"], + "created_time": 1516269022, + "expires_time": 1516385931, + "from_prior": "invalid", + "body": {"messagespecificattribute": "and its value"} +} +"#; + +pub const PLAINTEXT_FROM_PRIOR_INVALID_SIGNATURE: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "from": "did:example:alice", + "to": ["did:example:bob"], + "created_time": 1516269022, + "expires_time": 1516385931, + "from_prior": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDB", + "body": {"messagespecificattribute": "and its value"} +} +"#; + +pub const PLAINTEXT_MSG_ATTACHMENT_BASE64: &str = r#" { "id": "1234567890", "typ": "application/didcomm-plain+json", @@ -34,7 +76,7 @@ pub(crate) const PLAINTEXT_MSG_ATTACHMENT_BASE64: &str = r#" } "#; -pub(crate) const PLAINTEXT_MSG_ATTACHMENT_LINKS: &str = r#" +pub const PLAINTEXT_MSG_ATTACHMENT_LINKS: &str = r#" { "id": "1234567890", "typ": "application/didcomm-plain+json", @@ -50,7 +92,7 @@ pub(crate) const PLAINTEXT_MSG_ATTACHMENT_LINKS: &str = r#" } "#; -pub(crate) const PLAINTEXT_MSG_ATTACHMENT_JSON: &str = r#" +pub const PLAINTEXT_MSG_ATTACHMENT_JSON: &str = r#" { "id": "1234567890", "typ": "application/didcomm-plain+json", @@ -66,7 +108,7 @@ pub(crate) const PLAINTEXT_MSG_ATTACHMENT_JSON: &str = r#" } "#; -pub(crate) const PLAINTEXT_MSG_ATTACHMENT_MULTI_1: &str = r#" +pub const PLAINTEXT_MSG_ATTACHMENT_MULTI_1: &str = r#" { "id": "1234567890", "typ": "application/didcomm-plain+json", @@ -84,7 +126,7 @@ pub(crate) const PLAINTEXT_MSG_ATTACHMENT_MULTI_1: &str = r#" } "#; -pub(crate) const PLAINTEXT_MSG_ATTACHMENT_MULTI_2: &str = r#" +pub const PLAINTEXT_MSG_ATTACHMENT_MULTI_2: &str = r#" { "id": "1234567890", "typ": "application/didcomm-plain+json", @@ -101,3 +143,142 @@ pub(crate) const PLAINTEXT_MSG_ATTACHMENT_MULTI_2: &str = r#" ] } "#; + +pub const INVALID_PLAINTEXT_MSG_EMPTY: &str = r#" +{} +"#; + +pub const INVALID_PLAINTEXT_MSG_STRING: &str = r#" +aaaa +"#; + +pub const INVALID_PLAINTEXT_MSG_NO_ID: &str = r#" +{ + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {} +} +"#; + +pub const INVALID_PLAINTEXT_MSG_NO_TYP: &str = r#" +{ + "id": "1234567890", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {} +} +"#; + +pub const INVALID_PLAINTEXT_MSG_NO_TYPE: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "body": {} +} +"#; + +pub const INVALID_PLAINTEXT_MSG_NO_BODY: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal" +} +"#; + +pub const INVALID_PLAINTEXT_MSG_WRONG_TYP: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json-unknown", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {} +} +"#; + +pub const INVALID_PLAINTEXT_MSG_EMPTY_ATTACHMENTS: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {}, + "attachments": [{}] +} +"#; + +pub const INVALID_PLAINTEXT_MSG_ATTACHMENTS_NO_DATA: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {}, + "attachments": [{"id": "23"}] +} +"#; + +pub const INVALID_PLAINTEXT_MSG_ATTACHMENTS_EMPTY_DATA: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {}, + "attachments": [{"id": "23", "data": {}}] +} +"#; + +pub const INVALID_PLAINTEXT_MSG_ATTACHMENTS_LINKS_NO_HASH: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {}, + "attachments": [{"id": "23", "data": {"links": ["231", "212"]}}] +} +"#; + +pub const INVALID_PLAINTEXT_MSG_ATTACHMENTS_AS_STRING: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {}, + "attachments": "131" +} +"#; + +pub const INVALID_PLAINTEXT_MSG_ATTACHMENTS_AS_INT_ARRAY: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {}, + "attachments": [2131] +} +"#; + +pub const INVALID_PLAINTEXT_MSG_ATTACHMENTS_WRONG_DATA: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {}, + "attachments": [{"id": "1", "data": "invalid"}] +} +"#; + +pub const INVALID_PLAINTEXT_MSG_ATTACHMENTS_WRONG_ID: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {}, + "attachments": [{"id": 2}] +} +"#; + +pub const INVALID_PLAINTEXT_MSG_ATTACHMENTS_NULL_DATA: &str = r#" +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {}, + "attachments": [{"id": "1", "data": null}] +} +"#; diff --git a/src/test_vectors/secrets/alice.rs b/src/test_vectors/secrets/alice.rs index 4d7746b..6f8bb6c 100644 --- a/src/test_vectors/secrets/alice.rs +++ b/src/test_vectors/secrets/alice.rs @@ -4,71 +4,83 @@ use serde_json::json; use crate::didcomm::secrets::{Secret, SecretMaterial, SecretType}; lazy_static! { - pub(crate) static ref ALICE_SECRET_AUTH_KEY_ED25519: Secret = Secret { + pub static ref ALICE_SECRET_AUTH_KEY_ED25519: Secret = Secret { id: "did:example:alice#key-1".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!({ - "kty": "OKP", - "d": "pFRUKkyzx4kHdJtFSnlPA9WzqkDT1HWV0xZ5OYZd2SY", - "crv": "Ed25519", - "x": "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", - })), + secret_material: SecretMaterial::JWK { + value: json!({ + "kty": "OKP", + "d": "pFRUKkyzx4kHdJtFSnlPA9WzqkDT1HWV0xZ5OYZd2SY", + "crv": "Ed25519", + "x": "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", + }) + }, }; - pub(crate) static ref ALICE_SECRET_AUTH_KEY_P256: Secret = Secret { + pub static ref ALICE_SECRET_AUTH_KEY_P256: Secret = Secret { id: "did:example:alice#key-2".into(), type_: SecretType::JsonWebKey2020.into(), - secret_material: SecretMaterial::JWK(json!({ - "kty": "EC", - "d": "7TCIdt1rhThFtWcEiLnk_COEjh1ZfQhM4bW2wz-dp4A", - "crv": "P-256", - "x": "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", - "y": "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", - })), + secret_material: SecretMaterial::JWK { + value: json!({ + "kty": "EC", + "d": "7TCIdt1rhThFtWcEiLnk_COEjh1ZfQhM4bW2wz-dp4A", + "crv": "P-256", + "x": "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", + "y": "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", + }) + }, }; - pub(crate) static ref ALICE_SECRET_AUTH_KEY_SECP256K1: Secret = Secret { + pub static ref ALICE_SECRET_AUTH_KEY_SECP256K1: Secret = Secret { id: "did:example:alice#key-3".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!({ - "kty": "EC", - "d": "N3Hm1LXA210YVGGsXw_GklMwcLu_bMgnzDese6YQIyA", - "crv": "secp256k1", - "x": "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", - "y": "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", - })), + secret_material: SecretMaterial::JWK { + value: json!({ + "kty": "EC", + "d": "N3Hm1LXA210YVGGsXw_GklMwcLu_bMgnzDese6YQIyA", + "crv": "secp256k1", + "x": "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", + "y": "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", + }) + }, }; - pub(crate) static ref ALICE_SECRET_KEY_AGREEMENT_KEY_X25519: Secret = Secret { + pub static ref ALICE_SECRET_KEY_AGREEMENT_KEY_X25519: Secret = Secret { id: "did:example:alice#key-x25519-1".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!({ - "kty": "OKP", - "d": "r-jK2cO3taR8LQnJB1_ikLBTAnOtShJOsHXRUWT-aZA", - "crv": "X25519", - "x": "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", - }),) + secret_material: SecretMaterial::JWK { + value: json!({ + "kty": "OKP", + "d": "r-jK2cO3taR8LQnJB1_ikLBTAnOtShJOsHXRUWT-aZA", + "crv": "X25519", + "x": "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", + }) + }, }; - pub(crate) static ref ALICE_SECRET_KEY_AGREEMENT_KEY_P256: Secret = Secret { + pub static ref ALICE_SECRET_KEY_AGREEMENT_KEY_P256: Secret = Secret { id: "did:example:alice#key-p256-1".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!({ - "kty": "EC", - "d": "sB0bYtpaXyp-h17dDpMx91N3Du1AdN4z1FUq02GbmLw", - "crv": "P-256", - "x": "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", - "y": "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", - })) + secret_material: SecretMaterial::JWK { + value: json!({ + "kty": "EC", + "d": "sB0bYtpaXyp-h17dDpMx91N3Du1AdN4z1FUq02GbmLw", + "crv": "P-256", + "x": "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", + "y": "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", + }) + }, }; - pub(crate) static ref ALICE_SECRET_KEY_AGREEMENT_KEY_P521: Secret = Secret { + pub static ref ALICE_SECRET_KEY_AGREEMENT_KEY_P521: Secret = Secret { id: "did:example:alice#key-p521-1".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!({ - "kty": "EC", - "d": "AQCQKE7rZpxPnX9RgjXxeywrAMp1fJsyFe4cir1gWj-8t8xWaM_E2qBkTTzyjbRBu-JPXHe_auT850iYmE34SkWi", - "crv": "P-521", - "x": "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", - "y": "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", - })), + secret_material: SecretMaterial::JWK { + value: json!({ + "kty": "EC", + "d": "AQCQKE7rZpxPnX9RgjXxeywrAMp1fJsyFe4cir1gWj-8t8xWaM_E2qBkTTzyjbRBu-JPXHe_auT850iYmE34SkWi", + "crv": "P-521", + "x": "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", + "y": "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", + }) + }, }; - pub(crate) static ref ALICE_SECRETS: Vec = vec![ + pub static ref ALICE_SECRETS: Vec = vec![ ALICE_SECRET_AUTH_KEY_ED25519.clone(), ALICE_SECRET_AUTH_KEY_P256.clone(), ALICE_SECRET_AUTH_KEY_SECP256K1.clone(), diff --git a/src/test_vectors/secrets/bob.rs b/src/test_vectors/secrets/bob.rs index 770d702..a648e87 100644 --- a/src/test_vectors/secrets/bob.rs +++ b/src/test_vectors/secrets/bob.rs @@ -4,112 +4,130 @@ use serde_json::json; use crate::didcomm::secrets::{Secret, SecretMaterial, SecretType}; lazy_static! { - pub(crate) static ref BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1: Secret = Secret { + pub static ref BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1: Secret = Secret { id: "did:example:bob#key-x25519-1".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!( - { - "kty": "OKP", - "d": "b9NnuOCB0hm7YGNvaE9DMhwH_wjZA1-gWD6dA0JWdL0", - "crv": "X25519", - "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", - })), + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "OKP", + "d": "b9NnuOCB0hm7YGNvaE9DMhwH_wjZA1-gWD6dA0JWdL0", + "crv": "X25519", + "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }) + }, }; - pub(crate) static ref BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2: Secret = Secret { + pub static ref BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2: Secret = Secret { id: "did:example:bob#key-x25519-2".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!( - { - "kty": "OKP", - "d": "p-vteoF1gopny1HXywt76xz_uC83UUmrgszsI-ThBKk", - "crv": "X25519", - "x": "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", - })), + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "OKP", + "d": "p-vteoF1gopny1HXywt76xz_uC83UUmrgszsI-ThBKk", + "crv": "X25519", + "x": "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", + }) + }, }; - pub(crate) static ref BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3: Secret = Secret { + pub static ref BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3: Secret = Secret { id: "did:example:bob#key-x25519-3".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!( - { - "kty": "OKP", - "d": "f9WJeuQXEItkGM8shN4dqFr5fLQLBasHnWZ-8dPaSo0", - "crv": "X25519", - "x": "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", - })), + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "OKP", + "d": "f9WJeuQXEItkGM8shN4dqFr5fLQLBasHnWZ-8dPaSo0", + "crv": "X25519", + "x": "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", + }) + }, }; - pub(crate) static ref BOB_SECRET_KEY_AGREEMENT_KEY_P256_1: Secret = Secret { + pub static ref BOB_SECRET_KEY_AGREEMENT_KEY_P256_1: Secret = Secret { id: "did:example:bob#key-p256-1".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!( - { - "kty": "EC", - "d": "PgwHnlXxt8pwR6OCTUwwWx-P51BiLkFZyqHzquKddXQ", - "crv": "P-256", - "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", - "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", - })), + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "PgwHnlXxt8pwR6OCTUwwWx-P51BiLkFZyqHzquKddXQ", + "crv": "P-256", + "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }) + }, }; - pub(crate) static ref BOB_SECRET_KEY_AGREEMENT_KEY_P256_2: Secret = Secret { + pub static ref BOB_SECRET_KEY_AGREEMENT_KEY_P256_2: Secret = Secret { id: "did:example:bob#key-p256-2".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!( - { - "kty": "EC", - "d": "agKz7HS8mIwqO40Q2dwm_Zi70IdYFtonN5sZecQoxYU", - "crv": "P-256", - "x": "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", - "y": "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", - })), + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "agKz7HS8mIwqO40Q2dwm_Zi70IdYFtonN5sZecQoxYU", + "crv": "P-256", + "x": "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", + "y": "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", + }) + }, }; - pub(crate) static ref BOB_SECRET_KEY_AGREEMENT_KEY_P384_1: Secret = Secret { + pub static ref BOB_SECRET_KEY_AGREEMENT_KEY_P384_1: Secret = Secret { id: "did:example:bob#key-p384-1".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!( - { - "kty": "EC", - "d": "ajqcWbYA0UDBKfAhkSkeiVjMMt8l-5rcknvEv9t_Os6M8s-HisdywvNCX4CGd_xY", - "crv": "P-384", - "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", - "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", - })), + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "ajqcWbYA0UDBKfAhkSkeiVjMMt8l-5rcknvEv9t_Os6M8s-HisdywvNCX4CGd_xY", + "crv": "P-384", + "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }) + }, }; - pub(crate) static ref BOB_SECRET_KEY_AGREEMENT_KEY_P384_2: Secret = Secret { + pub static ref BOB_SECRET_KEY_AGREEMENT_KEY_P384_2: Secret = Secret { id: "did:example:bob#key-p384-2".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!( - { - "kty": "EC", - "d": "OiwhRotK188BtbQy0XBO8PljSKYI6CCD-nE_ZUzK7o81tk3imDOuQ-jrSWaIkI-T", - "crv": "P-384", - "x": "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", - "y": "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", - })), + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "OiwhRotK188BtbQy0XBO8PljSKYI6CCD-nE_ZUzK7o81tk3imDOuQ-jrSWaIkI-T", + "crv": "P-384", + "x": "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", + "y": "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", + }) + }, }; - pub(crate) static ref BOB_SECRET_KEY_AGREEMENT_KEY_P521_1: Secret = Secret { + pub static ref BOB_SECRET_KEY_AGREEMENT_KEY_P521_1: Secret = Secret { id: "did:example:bob#key-p521-1".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!( - { - "kty": "EC", - "d": "AV5ocjvy7PkPgNrSuvCxtG70NMj6iTabvvjSLbsdd8OdI9HlXYlFR7RdBbgLUTruvaIRhjEAE9gNTH6rWUIdfuj6", - "crv": "P-521", - "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", - "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", - })), + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "AV5ocjvy7PkPgNrSuvCxtG70NMj6iTabvvjSLbsdd8OdI9HlXYlFR7RdBbgLUTruvaIRhjEAE9gNTH6rWUIdfuj6", + "crv": "P-521", + "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }) + }, }; - pub(crate) static ref BOB_SECRET_KEY_AGREEMENT_KEY_P521_2: Secret = Secret { + pub static ref BOB_SECRET_KEY_AGREEMENT_KEY_P521_2: Secret = Secret { id: "did:example:bob#key-p521-2".into(), type_: SecretType::JsonWebKey2020, - secret_material: SecretMaterial::JWK(json!( - { - "kty": "EC", - "d": "ABixMEZHsyT7SRw-lY5HxdNOofTZLlwBHwPEJ3spEMC2sWN1RZQylZuvoyOBGJnPxg4-H_iVhNWf_OtgYODrYhCk", - "crv": "P-521", - "x": "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", - "y": "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", - })), + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "ABixMEZHsyT7SRw-lY5HxdNOofTZLlwBHwPEJ3spEMC2sWN1RZQylZuvoyOBGJnPxg4-H_iVhNWf_OtgYODrYhCk", + "crv": "P-521", + "x": "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", + "y": "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", + }) + }, }; - pub(crate) static ref BOB_SECRETS: Vec = vec![ + pub static ref BOB_SECRETS: Vec = vec![ BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1.clone(), BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.clone(), BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3.clone(), diff --git a/src/test_vectors/secrets/charlie.rs b/src/test_vectors/secrets/charlie.rs new file mode 100644 index 0000000..699e82f --- /dev/null +++ b/src/test_vectors/secrets/charlie.rs @@ -0,0 +1,35 @@ +use lazy_static::lazy_static; +use serde_json::json; + +use crate::didcomm::secrets::{Secret, SecretMaterial, SecretType}; + +lazy_static! { + pub static ref CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519: Secret = Secret { + id: "did:example:charlie#key-x25519-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!({ + "kty": "OKP", + "crv": "X25519", + "x": "nTiVFj7DChMsETDdxd5dIzLAJbSQ4j4UG6ZU1ogLNlw", + "d": "Z-BsgFe-eCvhuZlCBX5BV2XiDE2M92gkaORCe68YdZI", + }) + }, + }; + pub static ref CHARLIE_SECRET_AUTH_KEY_ED25519: Secret = Secret { + id: "did:example:charlie#key-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": "VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE", + "d": "T2azVap7CYD_kB8ilbnFYqwwYb5N-GcD6yjGEvquZXg", + }) + }, + }; + pub static ref CHARLIE_SECRETS: Vec = vec![ + CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519.clone(), + CHARLIE_SECRET_AUTH_KEY_ED25519.clone(), + ]; +} diff --git a/src/test_vectors/secrets/charlie_rotated_to_alice.rs b/src/test_vectors/secrets/charlie_rotated_to_alice.rs new file mode 100644 index 0000000..c4034f7 --- /dev/null +++ b/src/test_vectors/secrets/charlie_rotated_to_alice.rs @@ -0,0 +1,25 @@ +use lazy_static::lazy_static; + +use crate::didcomm::secrets::Secret; + +use super::{ + alice::{ + ALICE_SECRET_AUTH_KEY_ED25519, ALICE_SECRET_AUTH_KEY_P256, ALICE_SECRET_AUTH_KEY_SECP256K1, + ALICE_SECRET_KEY_AGREEMENT_KEY_P256, ALICE_SECRET_KEY_AGREEMENT_KEY_P521, + ALICE_SECRET_KEY_AGREEMENT_KEY_X25519, + }, + charlie::{CHARLIE_SECRET_AUTH_KEY_ED25519, CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519}, +}; + +lazy_static! { + pub static ref CHARLIE_ROTATED_TO_ALICE_SECRETS: Vec = vec![ + CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519.clone(), + CHARLIE_SECRET_AUTH_KEY_ED25519.clone(), + ALICE_SECRET_AUTH_KEY_ED25519.clone(), + ALICE_SECRET_AUTH_KEY_P256.clone(), + ALICE_SECRET_AUTH_KEY_SECP256K1.clone(), + ALICE_SECRET_KEY_AGREEMENT_KEY_X25519.clone(), + ALICE_SECRET_KEY_AGREEMENT_KEY_P256.clone(), + ALICE_SECRET_KEY_AGREEMENT_KEY_P521.clone(), + ]; +} diff --git a/src/test_vectors/secrets/mediator1.rs b/src/test_vectors/secrets/mediator1.rs new file mode 100644 index 0000000..d4f7b5f --- /dev/null +++ b/src/test_vectors/secrets/mediator1.rs @@ -0,0 +1,68 @@ +use lazy_static::lazy_static; +use serde_json::json; + +use crate::didcomm::secrets::{Secret, SecretMaterial, SecretType}; + +lazy_static! { + pub static ref MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_X25519_1: Secret = Secret { + id: "did:example:mediator1#key-x25519-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "OKP", + "d": "b9NnuOCB0hm7YGNvaE9DMhwH_wjZA1-gWD6dA0JWdL0", + "crv": "X25519", + "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }) + }, + }; + pub static ref MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P256_1: Secret = Secret { + id: "did:example:mediator1#key-p256-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "PgwHnlXxt8pwR6OCTUwwWx-P51BiLkFZyqHzquKddXQ", + "crv": "P-256", + "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }) + }, + }; + pub static ref MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P384_1: Secret = Secret { + id: "did:example:mediator1#key-p384-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "ajqcWbYA0UDBKfAhkSkeiVjMMt8l-5rcknvEv9t_Os6M8s-HisdywvNCX4CGd_xY", + "crv": "P-384", + "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }) + }, + }; + pub static ref MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P521_1: Secret = Secret { + id: "did:example:mediator1#key-p521-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "AV5ocjvy7PkPgNrSuvCxtG70NMj6iTabvvjSLbsdd8OdI9HlXYlFR7RdBbgLUTruvaIRhjEAE9gNTH6rWUIdfuj6", + "crv": "P-521", + "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }) + }, + }; + pub static ref MEDIATOR1_SECRETS: Vec = vec![ + MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_X25519_1.clone(), + MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P256_1.clone(), + MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P384_1.clone(), + MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P521_1.clone(), + ]; +} diff --git a/src/test_vectors/secrets/mediator2.rs b/src/test_vectors/secrets/mediator2.rs new file mode 100644 index 0000000..2059b20 --- /dev/null +++ b/src/test_vectors/secrets/mediator2.rs @@ -0,0 +1,68 @@ +use lazy_static::lazy_static; +use serde_json::json; + +use crate::didcomm::secrets::{Secret, SecretMaterial, SecretType}; + +lazy_static! { + pub static ref MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_X25519_1: Secret = Secret { + id: "did:example:mediator2#key-x25519-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "OKP", + "d": "b9NnuOCB0hm7YGNvaE9DMhwH_wjZA1-gWD6dA0JWdL0", + "crv": "X25519", + "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }) + }, + }; + pub static ref MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P256_1: Secret = Secret { + id: "did:example:mediator2#key-p256-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "PgwHnlXxt8pwR6OCTUwwWx-P51BiLkFZyqHzquKddXQ", + "crv": "P-256", + "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }) + }, + }; + pub static ref MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P384_1: Secret = Secret { + id: "did:example:mediator2#key-p384-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "ajqcWbYA0UDBKfAhkSkeiVjMMt8l-5rcknvEv9t_Os6M8s-HisdywvNCX4CGd_xY", + "crv": "P-384", + "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }) + }, + }; + pub static ref MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P521_1: Secret = Secret { + id: "did:example:mediator2#key-p521-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "AV5ocjvy7PkPgNrSuvCxtG70NMj6iTabvvjSLbsdd8OdI9HlXYlFR7RdBbgLUTruvaIRhjEAE9gNTH6rWUIdfuj6", + "crv": "P-521", + "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }) + }, + }; + pub static ref MEDIATOR2_SECRETS: Vec = vec![ + MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_X25519_1.clone(), + MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P256_1.clone(), + MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P384_1.clone(), + MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P521_1.clone(), + ]; +} diff --git a/src/test_vectors/secrets/mediator3.rs b/src/test_vectors/secrets/mediator3.rs new file mode 100644 index 0000000..3a3c3fc --- /dev/null +++ b/src/test_vectors/secrets/mediator3.rs @@ -0,0 +1,68 @@ +use lazy_static::lazy_static; +use serde_json::json; + +use crate::didcomm::secrets::{Secret, SecretMaterial, SecretType}; + +lazy_static! { + pub static ref MEDIATOR3_SECRET_KEY_AGREEMENT_KEY_X25519_1: Secret = Secret { + id: "did:example:mediator3#key-x25519-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "OKP", + "d": "b9NnuOCB0hm7YGNvaE9DMhwH_wjZA1-gWD6dA0JWdL0", + "crv": "X25519", + "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }) + }, + }; + pub static ref MEDIATOR3_SECRET_KEY_AGREEMENT_KEY_P256_1: Secret = Secret { + id: "did:example:mediator3#key-p256-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "PgwHnlXxt8pwR6OCTUwwWx-P51BiLkFZyqHzquKddXQ", + "crv": "P-256", + "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }) + }, + }; + pub static ref MEDIATOR3_SECRET_KEY_AGREEMENT_KEY_P384_1: Secret = Secret { + id: "did:example:mediator3#key-p384-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "ajqcWbYA0UDBKfAhkSkeiVjMMt8l-5rcknvEv9t_Os6M8s-HisdywvNCX4CGd_xY", + "crv": "P-384", + "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }) + }, + }; + pub static ref MEDIATOR3_SECRET_KEY_AGREEMENT_KEY_P521_1: Secret = Secret { + id: "did:example:mediator3#key-p521-1".into(), + type_: SecretType::JsonWebKey2020, + secret_material: SecretMaterial::JWK { + value: json!( + { + "kty": "EC", + "d": "AV5ocjvy7PkPgNrSuvCxtG70NMj6iTabvvjSLbsdd8OdI9HlXYlFR7RdBbgLUTruvaIRhjEAE9gNTH6rWUIdfuj6", + "crv": "P-521", + "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }) + }, + }; + pub static ref MEDIATOR3_SECRETS: Vec = vec![ + MEDIATOR3_SECRET_KEY_AGREEMENT_KEY_X25519_1.clone(), + MEDIATOR3_SECRET_KEY_AGREEMENT_KEY_P256_1.clone(), + MEDIATOR3_SECRET_KEY_AGREEMENT_KEY_P384_1.clone(), + MEDIATOR3_SECRET_KEY_AGREEMENT_KEY_P521_1.clone(), + ]; +} diff --git a/src/test_vectors/secrets/mod.rs b/src/test_vectors/secrets/mod.rs index 0b0f932..de14887 100644 --- a/src/test_vectors/secrets/mod.rs +++ b/src/test_vectors/secrets/mod.rs @@ -1,10 +1,35 @@ mod alice; mod bob; +mod charlie; +mod charlie_rotated_to_alice; +mod mediator1; +mod mediator2; +mod mediator3; // TODO: Remove allow #[allow(unused_imports)] -pub(crate) use alice::*; +pub use mediator1::*; // TODO: Remove allow #[allow(unused_imports)] -pub(crate) use bob::*; +pub use mediator2::*; + +// TODO: Remove allow +#[allow(unused_imports)] +pub use mediator3::*; + +// TODO: Remove allow +#[allow(unused_imports)] +pub use alice::*; + +// TODO: Remove allow +#[allow(unused_imports)] +pub use bob::*; + +// TODO: Remove allow +#[allow(unused_imports)] +pub use charlie::*; + +// TODO: Remove allow +#[allow(unused_imports)] +pub use charlie_rotated_to_alice::*; diff --git a/src/test_vectors/signed.rs b/src/test_vectors/signed.rs index aa29eb2..f301ee4 100644 --- a/src/test_vectors/signed.rs +++ b/src/test_vectors/signed.rs @@ -1,4 +1,4 @@ -pub(crate) const SIGNED_MSG_ALICE_KEY_1: &str = r#" +pub const SIGNED_MSG_ALICE_KEY_1: &str = r#" { "payload": "eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVhdGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNzYWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19", "signatures": [{ @@ -11,7 +11,7 @@ pub(crate) const SIGNED_MSG_ALICE_KEY_1: &str = r#" } "#; -pub(crate) const SIGNED_MSG_ALICE_KEY_2: &str = r#" +pub const SIGNED_MSG_ALICE_KEY_2: &str = r#" { "payload": "eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVhdGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNzYWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19", "signatures": [{ @@ -24,7 +24,7 @@ pub(crate) const SIGNED_MSG_ALICE_KEY_2: &str = r#" } "#; -pub(crate) const SIGNED_MSG_ALICE_KEY_3: &str = r#" +pub const SIGNED_MSG_ALICE_KEY_3: &str = r#" { "payload": "eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXBsYWluK2pzb24iLCJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImZyb20iOiJkaWQ6ZXhhbXBsZTphbGljZSIsInRvIjpbImRpZDpleGFtcGxlOmJvYiJdLCJjcmVhdGVkX3RpbWUiOjE1MTYyNjkwMjIsImV4cGlyZXNfdGltZSI6MTUxNjM4NTkzMSwiYm9keSI6eyJtZXNzYWdlc3BlY2lmaWNhdHRyaWJ1dGUiOiJhbmQgaXRzIHZhbHVlIn19", "signatures": [{ diff --git a/src/utils/did.rs b/src/utils/did.rs index 7439cd5..300f1ab 100644 --- a/src/utils/did.rs +++ b/src/utils/did.rs @@ -1,7 +1,11 @@ use askar_crypto::alg::{ ed25519::Ed25519KeyPair, k256::K256KeyPair, p256::P256KeyPair, x25519::X25519KeyPair, }; +use serde_json::json; +use std::io::Cursor; +use varint::{VarintRead, VarintWrite}; +use crate::error::ToResult; use crate::{ did::{did_doc::VerificationMethodType, VerificationMaterial, VerificationMethod}, error::{err_msg, ErrorKind, Result, ResultExt}, @@ -10,6 +14,11 @@ use crate::{ utils::crypto::{AsKnownKeyPair, KnownKeyAlg, KnownKeyPair}, }; +pub(crate) fn is_did(did: &str) -> bool { + let parts: Vec<_> = did.split(':').collect(); + return parts.len() >= 3 && parts.get(0).unwrap() == &"did"; +} + pub(crate) fn did_or_url(did_or_url: &str) -> (&str, Option<&str>) { // TODO: does it make sense to validate DID here? @@ -20,27 +29,64 @@ pub(crate) fn did_or_url(did_or_url: &str) -> (&str, Option<&str>) { } impl AsKnownKeyPair for VerificationMethod { + fn key_alg(&self) -> KnownKeyAlg { + match (&self.type_, &self.verification_material) { + (VerificationMethodType::JsonWebKey2020, VerificationMaterial::JWK { ref value }) => { + match (value["kty"].as_str(), value["crv"].as_str()) { + (Some(kty), Some(crv)) if kty == "EC" && crv == "P-256" => KnownKeyAlg::P256, + (Some(kty), Some(crv)) if kty == "EC" && crv == "secp256k1" => { + KnownKeyAlg::K256 + } + (Some(kty), Some(crv)) if kty == "OKP" && crv == "Ed25519" => { + KnownKeyAlg::Ed25519 + } + (Some(kty), Some(crv)) if kty == "OKP" && crv == "X25519" => { + KnownKeyAlg::X25519 + } + _ => KnownKeyAlg::Unsupported, + } + } + ( + VerificationMethodType::X25519KeyAgreementKey2019, + VerificationMaterial::Base58 { value: _ }, + ) => KnownKeyAlg::X25519, + ( + VerificationMethodType::Ed25519VerificationKey2018, + VerificationMaterial::Base58 { value: _ }, + ) => KnownKeyAlg::Ed25519, + ( + VerificationMethodType::X25519KeyAgreementKey2020, + VerificationMaterial::Multibase { value: _ }, + ) => KnownKeyAlg::X25519, + ( + VerificationMethodType::Ed25519VerificationKey2020, + VerificationMaterial::Multibase { value: _ }, + ) => KnownKeyAlg::Ed25519, + _ => KnownKeyAlg::Unsupported, + } + } + fn as_key_pair(&self) -> Result { match (&self.type_, &self.verification_material) { - (VerificationMethodType::JsonWebKey2020, VerificationMaterial::JWK(ref jwk)) => { - match (jwk["kty"].as_str(), jwk["crv"].as_str()) { + (VerificationMethodType::JsonWebKey2020, VerificationMaterial::JWK { ref value }) => { + match (value["kty"].as_str(), value["crv"].as_str()) { (Some(kty), Some(crv)) if kty == "EC" && crv == "P-256" => { - P256KeyPair::from_jwk_value(jwk) + P256KeyPair::from_jwk_value(value) .kind(ErrorKind::Malformed, "Unable parse jwk") .map(KnownKeyPair::P256) } (Some(kty), Some(crv)) if kty == "EC" && crv == "secp256k1" => { - K256KeyPair::from_jwk_value(jwk) + K256KeyPair::from_jwk_value(value) .kind(ErrorKind::Malformed, "Unable parse jwk") .map(KnownKeyPair::K256) } (Some(kty), Some(crv)) if kty == "OKP" && crv == "Ed25519" => { - Ed25519KeyPair::from_jwk_value(jwk) + Ed25519KeyPair::from_jwk_value(value) .kind(ErrorKind::Malformed, "Unable parse jwk") .map(KnownKeyPair::Ed25519) } (Some(kty), Some(crv)) if kty == "OKP" && crv == "X25519" => { - X25519KeyPair::from_jwk_value(jwk) + X25519KeyPair::from_jwk_value(value) .kind(ErrorKind::Malformed, "Unable parse jwk") .map(KnownKeyPair::X25519) } @@ -50,17 +96,143 @@ impl AsKnownKeyPair for VerificationMethod { )), } } + + ( + VerificationMethodType::X25519KeyAgreementKey2019, + VerificationMaterial::Base58 { ref value }, + ) => { + let decoded_value = bs58::decode(value) + .into_vec() + .to_didcomm("Wrong base58 value in verification material")?; + let base64_url_value = + base64::encode_config(&decoded_value, base64::URL_SAFE_NO_PAD); + + let jwk = json!({ + "kty": "OKP", + "crv": "X25519", + "x": base64_url_value + }); + + X25519KeyPair::from_jwk_value(&jwk) + .kind( + ErrorKind::Malformed, + "Unable parse base58 verification material", + ) + .map(KnownKeyPair::X25519) + } + + ( + VerificationMethodType::Ed25519VerificationKey2018, + VerificationMaterial::Base58 { ref value }, + ) => { + let decoded_value = bs58::decode(value) + .into_vec() + .to_didcomm("Wrong base58 value in verification material")?; + let base64_url_value = + base64::encode_config(&decoded_value, base64::URL_SAFE_NO_PAD); + + let jwk = json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": base64_url_value + }); + + Ed25519KeyPair::from_jwk_value(&jwk) + .kind( + ErrorKind::Malformed, + "Unable parse base58 verification material", + ) + .map(KnownKeyPair::Ed25519) + } + + ( + VerificationMethodType::X25519KeyAgreementKey2020, + VerificationMaterial::Multibase { ref value }, + ) => { + if !value.starts_with('z') { + Err(err_msg( + ErrorKind::IllegalArgument, + "Multibase value must start with 'z'", + ))? + }; + let decoded_value = bs58::decode(&value[1..]) + .into_vec() + .to_didcomm("Wrong multibase value in verification material")?; + + let (codec, decoded_value) = _from_multicodec(&decoded_value)?; + if codec != Codec::X25519Pub { + Err(err_msg( + ErrorKind::IllegalArgument, + "Wrong codec in multibase secret material", + ))? + } + let base64_url_value = + base64::encode_config(&decoded_value, base64::URL_SAFE_NO_PAD); + + let jwk = json!({ + "kty": "OKP", + "crv": "X25519", + "x": base64_url_value + }); + + X25519KeyPair::from_jwk_value(&jwk) + .kind( + ErrorKind::Malformed, + "Unable parse multibase verification material", + ) + .map(KnownKeyPair::X25519) + } + + ( + VerificationMethodType::Ed25519VerificationKey2020, + VerificationMaterial::Multibase { ref value }, + ) => { + if !value.starts_with('z') { + Err(err_msg( + ErrorKind::IllegalArgument, + "Multibase must start with 'z'", + ))? + } + let decoded_value = bs58::decode(&value[1..]) + .into_vec() + .to_didcomm("Wrong multibase value in verification material")?; + + let (codec, decoded_value) = _from_multicodec(&decoded_value)?; + if codec != Codec::Ed25519Pub { + Err(err_msg( + ErrorKind::IllegalArgument, + "Wrong codec in multibase secret material", + ))? + } + let base64_url_value = + base64::encode_config(&decoded_value, base64::URL_SAFE_NO_PAD); + + let jwk = json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": base64_url_value + }); + + Ed25519KeyPair::from_jwk_value(&jwk) + .kind( + ErrorKind::Malformed, + "Unable parse multibase verification material", + ) + .map(KnownKeyPair::Ed25519) + } _ => Err(err_msg( ErrorKind::Unsupported, "Unsupported verification method type and material combination", )), } } +} +impl AsKnownKeyPair for Secret { fn key_alg(&self) -> KnownKeyAlg { - match (&self.type_, &self.verification_material) { - (VerificationMethodType::JsonWebKey2020, VerificationMaterial::JWK(ref jwk)) => { - match (jwk["kty"].as_str(), jwk["crv"].as_str()) { + match (&self.type_, &self.secret_material) { + (SecretType::JsonWebKey2020, SecretMaterial::JWK { ref value }) => { + match (value["kty"].as_str(), value["crv"].as_str()) { (Some(kty), Some(crv)) if kty == "EC" && crv == "P-256" => KnownKeyAlg::P256, (Some(kty), Some(crv)) if kty == "EC" && crv == "secp256k1" => { KnownKeyAlg::K256 @@ -74,33 +246,43 @@ impl AsKnownKeyPair for VerificationMethod { _ => KnownKeyAlg::Unsupported, } } + (SecretType::X25519KeyAgreementKey2019, SecretMaterial::Base58 { value: _ }) => { + KnownKeyAlg::X25519 + } + (SecretType::Ed25519VerificationKey2018, SecretMaterial::Base58 { value: _ }) => { + KnownKeyAlg::Ed25519 + } + (SecretType::X25519KeyAgreementKey2020, SecretMaterial::Multibase { value: _ }) => { + KnownKeyAlg::X25519 + } + (SecretType::Ed25519VerificationKey2020, SecretMaterial::Multibase { value: _ }) => { + KnownKeyAlg::Ed25519 + } _ => KnownKeyAlg::Unsupported, } } -} -impl AsKnownKeyPair for Secret { fn as_key_pair(&self) -> Result { match (&self.type_, &self.secret_material) { - (SecretType::JsonWebKey2020, SecretMaterial::JWK(ref jwk)) => { - match (jwk["kty"].as_str(), jwk["crv"].as_str()) { + (SecretType::JsonWebKey2020, SecretMaterial::JWK { ref value }) => { + match (value["kty"].as_str(), value["crv"].as_str()) { (Some(kty), Some(crv)) if kty == "EC" && crv == "P-256" => { - P256KeyPair::from_jwk_value(jwk) + P256KeyPair::from_jwk_value(value) .kind(ErrorKind::Malformed, "Unable parse jwk") .map(KnownKeyPair::P256) } (Some(kty), Some(crv)) if kty == "EC" && crv == "secp256k1" => { - K256KeyPair::from_jwk_value(jwk) + K256KeyPair::from_jwk_value(value) .kind(ErrorKind::Malformed, "Unable parse jwk") .map(KnownKeyPair::K256) } (Some(kty), Some(crv)) if kty == "OKP" && crv == "Ed25519" => { - Ed25519KeyPair::from_jwk_value(jwk) + Ed25519KeyPair::from_jwk_value(value) .kind(ErrorKind::Malformed, "Unable parse jwk") .map(KnownKeyPair::Ed25519) } (Some(kty), Some(crv)) if kty == "OKP" && crv == "X25519" => { - X25519KeyPair::from_jwk_value(jwk) + X25519KeyPair::from_jwk_value(value) .kind(ErrorKind::Malformed, "Unable parse jwk") .map(KnownKeyPair::X25519) } @@ -110,38 +292,357 @@ impl AsKnownKeyPair for Secret { )), } } + + (SecretType::X25519KeyAgreementKey2019, SecretMaterial::Base58 { ref value }) => { + let decoded_value = bs58::decode(value) + .into_vec() + .to_didcomm("Wrong base58 value in secret material")?; + + let curve25519_point_size = 32; + let (d_value, x_value) = decoded_value.split_at(curve25519_point_size); + let base64_url_d_value = base64::encode_config(&d_value, base64::URL_SAFE_NO_PAD); + let base64_url_x_value = base64::encode_config(&x_value, base64::URL_SAFE_NO_PAD); + + let jwk = json!({ + "kty": "OKP", + "crv": "X25519", + "x": base64_url_x_value, + "d": base64_url_d_value + }); + + X25519KeyPair::from_jwk_value(&jwk) + .kind(ErrorKind::Malformed, "Unable parse base58 secret material") + .map(KnownKeyPair::X25519) + } + + (SecretType::Ed25519VerificationKey2018, SecretMaterial::Base58 { ref value }) => { + let decoded_value = bs58::decode(value) + .into_vec() + .to_didcomm("Wrong base58 value in secret material")?; + + let curve25519_point_size = 32; + let (d_value, x_value) = decoded_value.split_at(curve25519_point_size); + let base64_url_d_value = base64::encode_config(&d_value, base64::URL_SAFE_NO_PAD); + let base64_url_x_value = base64::encode_config(&x_value, base64::URL_SAFE_NO_PAD); + + let jwk = json!({"kty": "OKP", + "crv": "Ed25519", + "x": base64_url_x_value, + "d": base64_url_d_value + }); + + Ed25519KeyPair::from_jwk_value(&jwk) + .kind(ErrorKind::Malformed, "Unable parse base58 secret material") + .map(KnownKeyPair::Ed25519) + } + + (SecretType::X25519KeyAgreementKey2020, SecretMaterial::Multibase { ref value }) => { + if !value.starts_with('z') { + Err(err_msg( + ErrorKind::IllegalArgument, + "Multibase must start with 'z'", + ))? + } + let decoded_multibase_value = bs58::decode(&value[1..]) + .into_vec() + .to_didcomm("Wrong multibase value in secret material")?; + + let (codec, decoded_value) = _from_multicodec(&decoded_multibase_value)?; + if codec != Codec::X25519Priv { + Err(err_msg( + ErrorKind::IllegalArgument, + "Wrong codec in multibase secret material", + ))? + } + + let curve25519_point_size = 32; + let (d_value, x_value) = decoded_value.split_at(curve25519_point_size); + let base64_url_d_value = base64::encode_config(&d_value, base64::URL_SAFE_NO_PAD); + let base64_url_x_value = base64::encode_config(&x_value, base64::URL_SAFE_NO_PAD); + + let jwk = json!({ + "kty": "OKP", + "crv": "X25519", + "x": base64_url_x_value, + "d": base64_url_d_value + }); + + X25519KeyPair::from_jwk_value(&jwk) + .kind( + ErrorKind::Malformed, + "Unable parse multibase secret material", + ) + .map(KnownKeyPair::X25519) + } + + (SecretType::Ed25519VerificationKey2020, SecretMaterial::Multibase { ref value }) => { + if !value.starts_with('z') { + Err(err_msg( + ErrorKind::IllegalArgument, + "Multibase must start with 'z'", + ))? + } + let decoded_multibase_value = bs58::decode(&value[1..]) + .into_vec() + .to_didcomm("Wrong multibase value in secret material")?; + + let (codec, decoded_value) = _from_multicodec(&decoded_multibase_value)?; + if codec != Codec::Ed25519Priv { + Err(err_msg( + ErrorKind::IllegalArgument, + "Wrong codec in multibase secret material", + ))? + } + + let curve25519_point_size = 32; + let (d_value, x_value) = decoded_value.split_at(curve25519_point_size); + let base64_url_d_value = base64::encode_config(&d_value, base64::URL_SAFE_NO_PAD); + let base64_url_x_value = base64::encode_config(&x_value, base64::URL_SAFE_NO_PAD); + + let jwk = json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": base64_url_x_value, + "d": base64_url_d_value + }); + + Ed25519KeyPair::from_jwk_value(&jwk) + .kind( + ErrorKind::Malformed, + "Unable parse multibase secret material", + ) + .map(KnownKeyPair::Ed25519) + } + _ => Err(err_msg( ErrorKind::Unsupported, - "Unsupported verification method type and material combination", + "Unsupported secret method type and material combination", )), } } +} - fn key_alg(&self) -> KnownKeyAlg { - match (&self.type_, &self.secret_material) { - (SecretType::JsonWebKey2020, SecretMaterial::JWK(ref jwk)) => { - match (jwk["kty"].as_str(), jwk["crv"].as_str()) { - (Some(kty), Some(crv)) if kty == "EC" && crv == "P-256" => KnownKeyAlg::P256, - (Some(kty), Some(crv)) if kty == "EC" && crv == "secp256k1" => { - KnownKeyAlg::K256 - } - (Some(kty), Some(crv)) if kty == "OKP" && crv == "Ed25519" => { - KnownKeyAlg::Ed25519 - } - (Some(kty), Some(crv)) if kty == "OKP" && crv == "X25519" => { - KnownKeyAlg::X25519 - } - _ => KnownKeyAlg::Unsupported, - } - } - _ => KnownKeyAlg::Unsupported, - } +#[derive(Clone, Debug, PartialEq)] +pub enum Codec { + X25519Pub, + Ed25519Pub, + X25519Priv, + Ed25519Priv, +} + +impl Codec { + fn codec_by_prefix(value: u32) -> Result { + return match value { + 0xEC => Ok(Codec::X25519Pub), + 0xED => Ok(Codec::Ed25519Pub), + 0x1302 => Ok(Codec::X25519Priv), + 0x1300 => Ok(Codec::Ed25519Priv), + _ => Err(err_msg(ErrorKind::IllegalArgument, "Unsupported prefix")), + }; } } +fn _from_multicodec(value: &Vec) -> Result<(Codec, &[u8])> { + let mut val: Cursor> = Cursor::new(value.clone()); + let prefix_int = val + .read_unsigned_varint_32() + .kind(ErrorKind::InvalidState, "Cannot read varint")?; + let codec = Codec::codec_by_prefix(prefix_int)?; + + let mut prefix: Cursor> = Cursor::new(Vec::new()); + prefix + .write_unsigned_varint_32(prefix_int) + .kind(ErrorKind::InvalidState, "Cannot write varint")?; + + return Ok((codec, value.split_at(prefix.into_inner().len()).1)); +} + #[cfg(test)] mod tests { - use crate::utils::did::did_or_url; + use crate::did::{VerificationMaterial, VerificationMethod, VerificationMethodType}; + use crate::jwk::FromJwkValue; + use crate::secrets::{Secret, SecretMaterial, SecretType}; + use crate::utils::crypto::{AsKnownKeyPair, KnownKeyPair}; + use crate::utils::did::{did_or_url, is_did}; + use askar_crypto::alg::ed25519::Ed25519KeyPair; + use askar_crypto::alg::x25519::X25519KeyPair; + use serde_json::json; + + #[test] + fn secret_as_key_pair_x25519_2019_base58_works() { + let actual_key = Secret { + id: "did:example:eve#key-x25519-1".to_string(), + type_: SecretType::X25519KeyAgreementKey2019, + secret_material: (SecretMaterial::Base58{ + value:"2b5J8uecvwAo9HUGge5NKQ7HoRNKUKCjZ7Fr4mDgWkwqFyjLPWt7rv5kL3UPeG3e4B9Sy4H2Q2zAuWcP2RNtgJ4t".to_string() + }), + }.as_key_pair().unwrap(); + + let expected_key = X25519KeyPair::from_jwk_value(&json!({ + "kty": "OKP", + "crv": "X25519", + "x": "piw5XSMkceDeklaHQZXPBLQySyAwF8eZ-vddihdURS0", + "d": "T2azVap7CYD_kB8ilbnFYqwwYb5N-GcD6yjGEvquZXg" + })) + .map(KnownKeyPair::X25519) + .unwrap(); + assert_eq!(format!("{:?}", actual_key), format!("{:?}", expected_key)); + } + + #[test] + fn secret_as_key_pair_ed25519_2018_base58_works() { + let actual_key = Secret { + id: "did:example:eve#key-ed25519-1".to_string(), + type_: SecretType::Ed25519VerificationKey2018, + secret_material: (SecretMaterial::Base58{ + value: "2b5J8uecvwAo9HUGge5NKQ7HoRNKUKCjZ7Fr4mDgWkwqATnLmZDx7Seu6NqTuFKkxuHNT27GcoxVZQCkWJhNvaUQ".to_string() + }), + }.as_key_pair().unwrap(); + + let expected_key = Ed25519KeyPair::from_jwk_value(&json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": "VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE", + "d": "T2azVap7CYD_kB8ilbnFYqwwYb5N-GcD6yjGEvquZXg" + })) + .map(KnownKeyPair::Ed25519) + .unwrap(); + assert_eq!(format!("{:?}", actual_key), format!("{:?}", expected_key)); + } + + #[test] + fn secret_as_key_pair_x25519_2020_multibase_works() { + let actual_key = Secret { + id: "did:example:eve#key-x25519-1".to_string(), + type_: SecretType::X25519KeyAgreementKey2020, + secret_material: (SecretMaterial::Multibase{ + value: "zshCmpUZKtFrAfudMf7NzD3oR6yhWe6i2434FDktk9CYZfkndn7suDrqnRWvrVDHk95Z7vBRJChFxTgBF9qzq7D3xPe".to_string() + }), + }.as_key_pair().unwrap(); + + let expected_key = X25519KeyPair::from_jwk_value(&json!({ + "kty": "OKP", + "crv": "X25519", + "x": "piw5XSMkceDeklaHQZXPBLQySyAwF8eZ-vddihdURS0", + "d": "T2azVap7CYD_kB8ilbnFYqwwYb5N-GcD6yjGEvquZXg" + })) + .map(KnownKeyPair::X25519) + .unwrap(); + assert_eq!(format!("{:?}", actual_key), format!("{:?}", expected_key)); + } + + #[test] + fn secret_as_key_pair_ed25519_2020_multibase_works() { + let actual_key = Secret { + id: "did:example:eve#key-ed25519-1".to_string(), + type_: SecretType::Ed25519VerificationKey2020, + secret_material: (SecretMaterial::Multibase{ + value: "zrv2DyJwnoQWzS74nPkHHdM7NYH27BRNFBG9To7Fca9YzWhfBVa9Mek52H9bJexjdNqxML1F3TGCpjLNkCwwgQDvd5J".to_string() + }), + }.as_key_pair().unwrap(); + + let expected_key = Ed25519KeyPair::from_jwk_value(&json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": "VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE", + "d": "T2azVap7CYD_kB8ilbnFYqwwYb5N-GcD6yjGEvquZXg" + })) + .map(KnownKeyPair::Ed25519) + .unwrap(); + assert_eq!(format!("{:?}", actual_key), format!("{:?}", expected_key)); + } + + #[test] + fn verification_method_as_key_pair_x25519_2019_base58_works() { + let actual_key = VerificationMethod { + id: "did:example:eve#key-x25519-1".to_string(), + type_: VerificationMethodType::X25519KeyAgreementKey2019, + controller: "did:example:eve#key-x25519-1".to_string(), + verification_material: (VerificationMaterial::Base58 { + value: "JhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr".to_string(), + }), + } + .as_key_pair() + .unwrap(); + + let expected_key = X25519KeyPair::from_jwk_value(&json!({ + "kty": "OKP", + "crv": "X25519", + "x": "BIiFcQEn3dfvB2pjlhOQQour6jXy9d5s2FKEJNTOJik", + })) + .map(KnownKeyPair::X25519) + .unwrap(); + assert_eq!(format!("{:?}", actual_key), format!("{:?}", expected_key)); + } + + #[test] + fn verification_method_as_key_pair_ed25519_2018_base58_works() { + let actual_key = VerificationMethod { + id: "did:example:eve#key-ed25519-1".to_string(), + type_: VerificationMethodType::Ed25519VerificationKey2018, + controller: "did:example:eve#key-ed25519-1".to_string(), + verification_material: (VerificationMaterial::Base58 { + value: "ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7".to_string(), + }), + } + .as_key_pair() + .unwrap(); + + let expected_key = Ed25519KeyPair::from_jwk_value(&json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": "owBhCbktDjkfS6PdQddT0D3yjSitaSysP3YimJ_YgmA", + })) + .map(KnownKeyPair::Ed25519) + .unwrap(); + assert_eq!(format!("{:?}", actual_key), format!("{:?}", expected_key)); + } + + #[test] + fn verification_method_as_key_pair_x25519_2020_multibase_works() { + let actual_key = VerificationMethod { + id: "did:example:eve#key-x25519-1".to_string(), + type_: VerificationMethodType::X25519KeyAgreementKey2020, + controller: "did:example:eve#key-x25519-1".to_string(), + verification_material: (VerificationMaterial::Multibase { + value: "z6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc".to_string(), + }), + } + .as_key_pair() + .unwrap(); + + let expected_key = X25519KeyPair::from_jwk_value(&json!({ + "kty": "OKP", + "crv": "X25519", + "x": "BIiFcQEn3dfvB2pjlhOQQour6jXy9d5s2FKEJNTOJik", + })) + .map(KnownKeyPair::X25519) + .unwrap(); + assert_eq!(format!("{:?}", actual_key), format!("{:?}", expected_key)); + } + + #[test] + fn verification_method_as_key_pair_ed25519_2020_multibase_works() { + let actual_key = VerificationMethod { + id: "did:example:eve#key-ed25519-1".to_string(), + type_: VerificationMethodType::Ed25519VerificationKey2020, + controller: "did:example:eve#key-ed25519-1".to_string(), + verification_material: (VerificationMaterial::Multibase { + value: "z6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V".to_string(), + }), + } + .as_key_pair() + .unwrap(); + + let expected_key = Ed25519KeyPair::from_jwk_value(&json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": "owBhCbktDjkfS6PdQddT0D3yjSitaSysP3YimJ_YgmA", + })) + .map(KnownKeyPair::Ed25519) + .unwrap(); + assert_eq!(format!("{:?}", actual_key), format!("{:?}", expected_key)); + } #[test] fn did_or_url_works() { @@ -157,4 +658,13 @@ mod tests { let res = did_or_url("#"); assert_eq!(res, ("", Some("#"))); } + + #[test] + fn is_did_works() { + assert_eq!(is_did(""), false); + assert_eq!(is_did("did:example:alice"), true); + assert_eq!(is_did("did::"), true); //TODO is this ok? + assert_eq!(is_did("example:example:alice"), false); + assert_eq!(is_did("example:alice"), false); + } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f1bbf77..e5faaec 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod crypto; pub(crate) mod did; +pub(crate) mod serde; diff --git a/src/utils/serde.rs b/src/utils/serde.rs new file mode 100644 index 0000000..fa661d2 --- /dev/null +++ b/src/utils/serde.rs @@ -0,0 +1,3 @@ +pub(crate) fn _true() -> bool { + true +} diff --git a/uniffi/.gitignore b/uniffi/.gitignore new file mode 100644 index 0000000..a7dc619 --- /dev/null +++ b/uniffi/.gitignore @@ -0,0 +1,9 @@ +/target +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log +uniffi-rs +uniffi-swift +uniffi-kotlin diff --git a/uniffi/Cargo.toml b/uniffi/Cargo.toml new file mode 100644 index 0000000..4663e3b --- /dev/null +++ b/uniffi/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = 'didcomm-uniffi' +version = '0.2.0' +authors = ['Vyacheslav Gudkov '] +edition = '2018' +description = 'FFI wrapper for DIDComm' +license = 'Apache-2.0' +repository = 'https://github.com/sicpa-dlab/didcomm-rust' + +[lib] +crate-type = [ + 'cdylib' +] + +[dependencies.didcomm_core] +path = '..' +features = ['uniffi'] +package = "didcomm" + +[dev-dependencies.didcomm_core] +path = '..' +features = ['testvectors'] +package = "didcomm" + +[dependencies] +uniffi_macros = "0.15.2" +uniffi = { version = "0.15.2", features=["builtin-bindgen"] } +lazy_static = "1.3" +futures = { version = "0.3.17", features = ["thread-pool"] } +num_cpus = "1.8.0" +async-trait = '0.1' +serde_json = '1.0' + +[dev-dependencies.tokio] +version = '1.9' +features = [ + 'rt', + 'macros', +] + +[build-dependencies] +uniffi_build = { version = "0.15.2", features=["builtin-bindgen"] } + diff --git a/uniffi/LICENSE b/uniffi/LICENSE new file mode 100644 index 0000000..1b5ec8b --- /dev/null +++ b/uniffi/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/uniffi/README.md b/uniffi/README.md new file mode 100644 index 0000000..b2f0e6a --- /dev/null +++ b/uniffi/README.md @@ -0,0 +1,31 @@ +## DIDComm FFI +DIDComm bindings based on [uniffi-rs](https://github.com/mozilla/uniffi-rs). + +It's used for [DIDComm Swift](../wrappers/swift) wrapper. + +DIDComm uniffi has the following specific comparing to the core DIDComm crate: +- Callback-based versions of pack/unpack functions and DID/Secret resolver interfaces instead of async onces. +These pack/unpack functions wrap the corresponding async futures and execute them in a thread pool executor. +The result is passed to a callback function. +- The language specific bidnings are generated for the API exposed in [didcomm.udl](src/didcomm.udl) file. + +### Swift Build +1. Install uniffi_bindgen: +``` +cargo install uniffi_bindgen +``` + +2. Build: +``` +cargo build --release +``` + +3. Generate Swift binding: +``` +uniffi-bindgen generate src/didcomm.udl --language swift -o ../wrappers/swift/didcomm +``` + +4. Compile a Swift module: +``` +swiftc -module-name didcomm -emit-library -o ../wrappers/swift/didcomm/libdidcomm.dylib -emit-module -emit-module-path ../wrappers/swift/didcomm -parse-as-library -L ./target/debug/ -ldidcomm_uniffi -Xcc -fmodule-map-file=../wrappers/swift/didcomm/didcommFFI.modulemap ../wrappers/swift/didcomm/didcomm.swift +``` \ No newline at end of file diff --git a/uniffi/build.rs b/uniffi/build.rs new file mode 100644 index 0000000..0834284 --- /dev/null +++ b/uniffi/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi_build::generate_scaffolding("./src/didcomm.udl").unwrap(); +} diff --git a/uniffi/src/common.rs b/uniffi/src/common.rs new file mode 100644 index 0000000..54820c4 --- /dev/null +++ b/uniffi/src/common.rs @@ -0,0 +1,89 @@ +use std::{ + cell::RefCell, + cmp, + sync::{Arc, Mutex}, +}; + +use crate::UniffiCustomTypeWrapper; +use didcomm_core::error::{err_msg, ErrorKind, Result, ResultExt, ResultExtNoContext, ToResult}; +use futures::{channel::oneshot, executor::ThreadPool}; +use lazy_static::lazy_static; + +pub enum ErrorCode { + Success = 0, + Error = 1, +} + +lazy_static! { + // Global (lazy inited) instance of future executor + pub(crate) static ref EXECUTOR: ThreadPool = ThreadPool::builder() + .pool_size(cmp::max(8, num_cpus::get())) + .create() + .unwrap(); +} + +// We use `JsonValue` in our UDL. It moves to and from Uniffi bindings via a string. +pub type JsonValue = serde_json::Value; + +// We must implement the UniffiCustomTypeWrapper trait. +impl UniffiCustomTypeWrapper for JsonValue { + type Wrapped = String; + + fn wrap(val: Self::Wrapped) -> uniffi::Result { + Ok(serde_json::from_str(&val).to_didcomm("Invalid json value")?) + } + + fn unwrap(obj: Self) -> Self::Wrapped { + serde_json::to_string(&obj).expect("unable serialize json value") + } +} + +pub(crate) struct OnResultReceiver(oneshot::Receiver>); + +impl OnResultReceiver { + pub(crate) async fn get(self) -> Result { + self.0 + .await + .kind(ErrorKind::InvalidState, "unable receive callback result")? + } +} + +pub struct OnResult(Mutex>>>>); + +impl OnResult { + pub(crate) fn new() -> (Arc, OnResultReceiver) { + let (sender, receiver) = oneshot::channel::>(); + let on_result = Arc::new(OnResult(Mutex::new(RefCell::new(Some(sender))))); + (on_result, OnResultReceiver(receiver)) + } + + pub fn success(&self, result: T) -> std::result::Result<(), ErrorKind> { + let sender = self + .0 + .lock() + .to_error_kind(ErrorKind::InvalidState)? + .replace(None); + match sender { + Some(sender) => sender + .send(Ok(result)) + .to_error_kind(ErrorKind::InvalidState)?, + None => Err(ErrorKind::InvalidState)?, + }; + Ok(()) + } + + pub fn error(&self, err: ErrorKind, msg: String) -> std::result::Result<(), ErrorKind> { + let sender = self + .0 + .lock() + .to_error_kind(ErrorKind::InvalidState)? + .replace(None); + match sender { + Some(sender) => sender + .send(Err(err_msg(err, msg))) + .to_error_kind(ErrorKind::InvalidState)?, + None => Err(ErrorKind::InvalidState)?, + }; + Ok(()) + } +} diff --git a/uniffi/src/did/did_resolver.rs b/uniffi/src/did/did_resolver.rs new file mode 100644 index 0000000..8d0cfa3 --- /dev/null +++ b/uniffi/src/did/did_resolver.rs @@ -0,0 +1,21 @@ +use std::sync::Arc; + +use didcomm_core::did::DIDDoc; + +use crate::{common::OnResult, ErrorCode}; + +/// Represents DID Doc resolver (https://www.w3.org/TR/did-core/#did-resolution). +pub trait DIDResolver: Sync + Send { + /// Resolves a DID document by the given DID. + /// + /// # Params + /// - `did` a DID to be resolved. + /// - `cb` a callback with a result + /// + /// # Returns + /// A result code + /// + fn resolve(&self, did: String, cb: Arc) -> ErrorCode; +} + +pub type OnDIDResolverResult = OnResult>; diff --git a/uniffi/src/did/did_resolver_adapter.rs b/uniffi/src/did/did_resolver_adapter.rs new file mode 100644 index 0000000..ae23846 --- /dev/null +++ b/uniffi/src/did/did_resolver_adapter.rs @@ -0,0 +1,35 @@ +use std::sync::Arc; + +use didcomm_core::{ + did::{DIDDoc, DIDResolver as _DIDResolver}, + error::{ErrorKind, Result, ResultExt}, +}; + +use async_trait::async_trait; + +use crate::{DIDResolver, OnDIDResolverResult}; + +pub(crate) struct DIDResolverAdapter { + did_resolver: Arc>, +} + +impl DIDResolverAdapter { + pub fn new(did_resolver: Arc>) -> Self { + DIDResolverAdapter { did_resolver } + } +} + +#[async_trait] +impl _DIDResolver for DIDResolverAdapter { + async fn resolve(&self, did: &str) -> Result> { + let (cb, receiver) = OnDIDResolverResult::new(); + + self.did_resolver.resolve(String::from(did), cb); + + let res = receiver + .get() + .await + .kind(ErrorKind::InvalidState, "can not resolve DID Doc")?; + Ok(res) + } +} diff --git a/uniffi/src/did/mod.rs b/uniffi/src/did/mod.rs new file mode 100644 index 0000000..f5de379 --- /dev/null +++ b/uniffi/src/did/mod.rs @@ -0,0 +1,6 @@ +pub mod resolvers; + +pub(crate) mod did_resolver; +pub(crate) mod did_resolver_adapter; + +pub use did_resolver::{DIDResolver, OnDIDResolverResult}; diff --git a/uniffi/src/did/resolvers/example.rs b/uniffi/src/did/resolvers/example.rs new file mode 100644 index 0000000..1e17e03 --- /dev/null +++ b/uniffi/src/did/resolvers/example.rs @@ -0,0 +1,33 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use didcomm_core::did::DIDDoc; + +use crate::{common::ErrorCode, did::DIDResolver, OnDIDResolverResult}; + +/// Allows resolve pre-defined did's for `example` and other methods. +pub struct ExampleDIDResolver { + known_dids: Vec, +} + +impl ExampleDIDResolver { + pub fn new(known_dids: Vec) -> Self { + ExampleDIDResolver { known_dids } + } +} + +#[async_trait] +impl DIDResolver for ExampleDIDResolver { + fn resolve(&self, did: String, cb: Arc) -> ErrorCode { + let diddoc = self + .known_dids + .iter() + .find(|ddoc| ddoc.did == did) + .map(|ddoc| ddoc.clone()); + + match cb.success(diddoc) { + Ok(_) => ErrorCode::Success, + Err(_) => ErrorCode::Error, + } + } +} diff --git a/uniffi/src/did/resolvers/mod.rs b/uniffi/src/did/resolvers/mod.rs new file mode 100644 index 0000000..9b8abc6 --- /dev/null +++ b/uniffi/src/did/resolvers/mod.rs @@ -0,0 +1,3 @@ +mod example; + +pub use example::ExampleDIDResolver; diff --git a/uniffi/src/didcomm.udl b/uniffi/src/didcomm.udl new file mode 100644 index 0000000..a84e84d --- /dev/null +++ b/uniffi/src/didcomm.udl @@ -0,0 +1,356 @@ +[Wrapped] +typedef string JsonValue; + +// 1. MAIN DIDCOMM INTERFACE + +interface DIDComm { + constructor(DIDResolver did_resolver, SecretsResolver secret_resolver); + + ErrorCode pack_plaintext([ByRef] Message msg, OnPackPlaintextResult cb); + ErrorCode pack_signed([ByRef] Message msg, string sign_by, OnPackSignedResult cb); + ErrorCode pack_encrypted([ByRef] Message msg, string to, string? from, string? sign_by, [ByRef] PackEncryptedOptions options, OnPackEncryptedResult cb); + ErrorCode unpack(string msg, [ByRef] UnpackOptions options, OnUnpackResult cb); + + ErrorCode pack_from_prior([ByRef] FromPrior msg, string? issuer_kid, OnFromPriorPackResult cb); + ErrorCode unpack_from_prior(string from_prior_jwt, OnFromPriorUnpackResult cb); + + ErrorCode wrap_in_forward(string msg, [ByRef] record headers, string to, [ByRef] sequence routing_keys, [ByRef] AnonCryptAlg enc_alg_anon, OnWrapInForwardResult cb); + +}; + +// 2. MESSAGE + +dictionary Message { + string id; + string typ; + string type_; + JsonValue body; + string? from; + sequence? to; + string? thid; + string? pthid; + record extra_headers; + u64? created_time; + u64? expires_time; + string? from_prior; + sequence? attachments; +}; + +dictionary Attachment { + AttachmentData data; + string? id; + string? description; + string? filename; + string? media_type; + string? format; + u64? lastmod_time; + u64? byte_count; +}; + +[Enum] +interface AttachmentData { + Base64(Base64AttachmentData value); + Json(JsonAttachmentData value); + Links(LinksAttachmentData value); +}; + + +dictionary Base64AttachmentData { + string base64; + string? jws; +}; + +dictionary JsonAttachmentData { + JsonValue json; + string? jws; +}; + +dictionary LinksAttachmentData { + sequence links; + string hash; + string? jws; +}; + + + +// 3. ERRORS + +[Error] +enum ErrorKind { + "DIDNotResolved", + "DIDUrlNotFound", + "SecretNotFound", + "Malformed", + "IoError", + "InvalidState", + "NoCompatibleCrypto", + "Unsupported", + "IllegalArgument", +}; + +enum ErrorCode { + "Success", + "Error", +}; + + + +// 4. DID RESOLVER + +callback interface DIDResolver { + ErrorCode resolve(string did, OnDIDResolverResult cb); +}; + +interface OnDIDResolverResult { + [Throws=ErrorKind] + void success(DIDDoc? result); + + [Throws=ErrorKind] + void error(ErrorKind err, string msg); +}; + +dictionary DIDDoc { + string did; + sequence key_agreements; + sequence authentications; + sequence verification_methods; + sequence services; +}; + +dictionary VerificationMethod { + string id; + VerificationMethodType type_; + string controller; + VerificationMaterial verification_material; +}; + +[Enum] +interface VerificationMaterial { + JWK(JsonValue value); + Multibase(string value); + Base58(string value); + Hex(string value); + Other(JsonValue value); +}; + +enum VerificationMethodType { + "JsonWebKey2020", + "X25519KeyAgreementKey2019", + "Ed25519VerificationKey2018", + "EcdsaSecp256k1VerificationKey2019", + "X25519KeyAgreementKey2020", + "Ed25519VerificationKey2020", + "Other", +}; + +dictionary Service { + string id; + ServiceKind kind; +}; + +[Enum] +interface ServiceKind { + DIDCommMessaging(DIDCommMessagingService value); + Other(JsonValue value); +}; + +dictionary DIDCommMessagingService { + string service_endpoint; + sequence accept; + sequence routing_keys; +}; + +interface ExampleDIDResolver { + constructor(sequence known_dids); + ErrorCode resolve(string did, OnDIDResolverResult cb); +}; + + + + +// 5. SECRETS RESOLVER + +callback interface SecretsResolver { + ErrorCode get_secret(string secretid, OnGetSecretResult cb); // should be in camel case + ErrorCode find_secrets(sequence secretids, OnFindSecretsResult cb); // should be in camel case +}; + +interface OnGetSecretResult { + [Throws=ErrorKind] + void success(Secret? result); + + [Throws=ErrorKind] + void error(ErrorKind err, string msg); +}; + +interface OnFindSecretsResult { + [Throws=ErrorKind] + void success(sequence result); + + [Throws=ErrorKind] + void error(ErrorKind err, string msg); +}; + +dictionary Secret { + string id; + SecretType type_; + SecretMaterial secret_material; +}; + +[Enum] +interface SecretMaterial { + JWK(JsonValue value); + Multibase(string value); + Base58(string value); + Hex(string value); + Other(JsonValue value); +}; + +enum SecretType { + "JsonWebKey2020", + "X25519KeyAgreementKey2019", + "Ed25519VerificationKey2018", + "EcdsaSecp256k1VerificationKey2019", + "X25519KeyAgreementKey2020", + "Ed25519VerificationKey2020", + "Other", +}; + +interface ExampleSecretsResolver { + constructor(sequence known_secrets); + ErrorCode get_secret(string secret_id, OnGetSecretResult cb); + ErrorCode find_secrets(sequence secret_ids, OnFindSecretsResult cb); +}; + + + + +// 6. PACK SIGNED + +callback interface OnPackSignedResult { + void success(string result, PackSignedMetadata metadata); + void error(ErrorKind err, string msg); +}; + +dictionary PackSignedMetadata { + string sign_by_kid; +}; + + + +// 7. PACK ENCRYPTED + +callback interface OnPackEncryptedResult { + void success(string result, PackEncryptedMetadata metadata); + void error(ErrorKind err, string msg); +}; + +dictionary PackEncryptedMetadata { + MessagingServiceMetadata? messaging_service; + string? from_kid; + string? sign_by_kid; + sequence to_kids; +}; + +dictionary MessagingServiceMetadata { + string id; + string service_endpoint; +}; + +enum AuthCryptAlg { + "A256cbcHs512Ecdh1puA256kw", +}; + +enum AnonCryptAlg { + "A256cbcHs512EcdhEsA256kw", + "Xc20pEcdhEsA256kw", + "A256gcmEcdhEsA256kw", +}; + +dictionary PackEncryptedOptions { + boolean protect_sender; + boolean forward; + record? forward_headers; + string? messaging_service; + AuthCryptAlg enc_alg_auth; + AnonCryptAlg enc_alg_anon; +}; + + + +// 8. PACK PLAINTEXT + +callback interface OnPackPlaintextResult { + void success(string result); + void error(ErrorKind err, string msg); +}; + + + +// 9. UNPACK + +callback interface OnUnpackResult { + void success(Message result, UnpackMetadata metadata); + void error(ErrorKind err, string msg); +}; + +dictionary UnpackMetadata { + boolean encrypted; + boolean authenticated; + boolean non_repudiation; + boolean anonymous_sender; + boolean re_wrapped_in_forward; + string? encrypted_from_kid; + sequence? encrypted_to_kids; + string? sign_from; + string? from_prior_issuer_kid; + AuthCryptAlg? enc_alg_auth; + AnonCryptAlg? enc_alg_anon; + SignAlg? sign_alg; + string? signed_message; + FromPrior? from_prior; +}; + +dictionary UnpackOptions { + boolean expect_decrypt_by_all_keys; + boolean unwrap_re_wrapping_forward; +}; + +enum SignAlg { + "EdDSA", + "ES256", + "ES256K", +}; + +// 10. FROM PRIOR +dictionary FromPrior { + string iss; + string sub; + string? aud; + u64? exp; + u64? nbf; + u64? iat; + string? jti; +}; + +callback interface OnFromPriorPackResult { + void success(string frompriorjwt, string kid); // should be in camel case + void error(ErrorKind err, string msg); +}; + +callback interface OnFromPriorUnpackResult { + void success(FromPrior fromprior, string kid); // should be in camel case + void error(ErrorKind err, string msg); +}; + + +// 11. FORWARD + +callback interface OnWrapInForwardResult { + void success(string result); + void error(ErrorKind err, string msg); +}; + + + +namespace didcomm {}; diff --git a/uniffi/src/didcomm/from_prior.rs b/uniffi/src/didcomm/from_prior.rs new file mode 100644 index 0000000..3f370f7 --- /dev/null +++ b/uniffi/src/didcomm/from_prior.rs @@ -0,0 +1,126 @@ +use didcomm_core::{error::ErrorKind, FromPrior}; + +use crate::DIDComm; + +use crate::common::{ErrorCode, EXECUTOR}; +use crate::did_resolver_adapter::DIDResolverAdapter; +use crate::secrets_resolver_adapter::SecretsResolverAdapter; + +pub trait OnFromPriorPackResult: Sync + Send { + fn success(&self, from_prior_jwt: String, kid: String); + fn error(&self, err: ErrorKind, err_msg: String); +} + +pub trait OnFromPriorUnpackResult: Sync + Send { + fn success(&self, from_prior: FromPrior, kid: String); + fn error(&self, err: ErrorKind, err_msg: String); +} + +impl DIDComm { + pub fn pack_from_prior( + &self, + msg: &FromPrior, + issuer_kid: Option, + cb: Box, + ) -> ErrorCode { + let msg = msg.clone(); + let did_resolver = DIDResolverAdapter::new(self.did_resolver.clone()); + let secret_resolver = SecretsResolverAdapter::new(self.secret_resolver.clone()); + + let future = async move { + msg.pack(issuer_kid.as_deref(), &did_resolver, &secret_resolver) + .await + }; + EXECUTOR.spawn_ok(async move { + match future.await { + Ok((from_prior_jwt, kid)) => cb.success(from_prior_jwt, kid), + Err(err) => cb.error(err.kind(), err.to_string()), + } + }); + + ErrorCode::Success + } + + pub fn unpack_from_prior( + &self, + from_prior_jwt: String, + cb: Box, + ) -> ErrorCode { + let did_resolver = DIDResolverAdapter::new(self.did_resolver.clone()); + + let future = async move { FromPrior::unpack(&from_prior_jwt, &did_resolver).await }; + EXECUTOR.spawn_ok(async move { + match future.await { + Ok((from_prior_jwt, kid)) => cb.success(from_prior_jwt, kid), + Err(err) => cb.error(err.kind(), err.to_string()), + } + }); + + ErrorCode::Success + } +} + +#[cfg(test)] +mod tests { + use didcomm_core::FromPrior; + + use crate::{ + test_helper::{create_did_resolver, get_ok, FromPriorPackResult, FromPriorUnpackResult}, + DIDComm, ExampleSecretsResolver, + }; + use didcomm_core::test_vectors::{ + ALICE_DID, CHARLIE_DID, CHARLIE_ROTATED_TO_ALICE_SECRETS, CHARLIE_SECRET_AUTH_KEY_ED25519, + }; + + #[tokio::test] + async fn pack_from_prior_works() { + let (cb, receiver) = FromPriorPackResult::new(); + + let from_prior = FromPrior::build(CHARLIE_DID.into(), ALICE_DID.into()).finalize(); + + DIDComm::new( + create_did_resolver(), + Box::new(ExampleSecretsResolver::new( + CHARLIE_ROTATED_TO_ALICE_SECRETS.clone(), + )), + ) + .pack_from_prior( + &from_prior, + Some(CHARLIE_SECRET_AUTH_KEY_ED25519.id.clone()), + cb, + ); + + let (_, kid) = get_ok(receiver).await; + assert_eq!(kid, CHARLIE_SECRET_AUTH_KEY_ED25519.id.clone()); + } + + #[tokio::test] + async fn unpack_from_prior_works() { + let (cb, receiver) = FromPriorPackResult::new(); + let did_comm = DIDComm::new( + create_did_resolver(), + Box::new(ExampleSecretsResolver::new( + CHARLIE_ROTATED_TO_ALICE_SECRETS.clone(), + )), + ); + + let from_prior = FromPrior::build(CHARLIE_DID.into(), ALICE_DID.into()) + .exp(1234) + .finalize(); + did_comm.pack_from_prior( + &from_prior, + Some(CHARLIE_SECRET_AUTH_KEY_ED25519.id.clone()), + cb, + ); + let (res, _) = get_ok(receiver).await; + + let (cb, receiver) = FromPriorUnpackResult::new(); + did_comm.unpack_from_prior(res, cb); + let (res, kid) = get_ok(receiver).await; + + assert_eq!(kid, CHARLIE_SECRET_AUTH_KEY_ED25519.id.clone()); + assert_eq!(CHARLIE_DID.clone(), res.iss); + assert_eq!(ALICE_DID.clone(), res.sub); + assert_eq!(Some(1234), res.exp); + } +} diff --git a/uniffi/src/didcomm/mod.rs b/uniffi/src/didcomm/mod.rs new file mode 100644 index 0000000..d6f91e7 --- /dev/null +++ b/uniffi/src/didcomm/mod.rs @@ -0,0 +1,34 @@ +mod from_prior; +mod pack_encrypted; +mod pack_plaintext; +mod pack_signed; +mod protocols; +mod unpack; + +pub use from_prior::{OnFromPriorPackResult, OnFromPriorUnpackResult}; +pub use pack_encrypted::OnPackEncryptedResult; +pub use pack_plaintext::OnPackPlaintextResult; +pub use pack_signed::OnPackSignedResult; +pub use protocols::routing::OnWrapInForwardResult; +pub use unpack::OnUnpackResult; + +use std::sync::Arc; + +use crate::{DIDResolver, SecretsResolver}; + +pub struct DIDComm { + did_resolver: Arc>, + secret_resolver: Arc>, +} + +impl DIDComm { + pub fn new( + did_resolver: Box, + secret_resolver: Box, + ) -> Self { + DIDComm { + did_resolver: Arc::new(did_resolver), + secret_resolver: Arc::new(secret_resolver), + } + } +} diff --git a/uniffi/src/didcomm/pack_encrypted.rs b/uniffi/src/didcomm/pack_encrypted.rs new file mode 100644 index 0000000..2f86b73 --- /dev/null +++ b/uniffi/src/didcomm/pack_encrypted.rs @@ -0,0 +1,158 @@ +use didcomm_core::error::ErrorKind; +use didcomm_core::{Message, PackEncryptedMetadata, PackEncryptedOptions}; + +use crate::common::{ErrorCode, EXECUTOR}; +use crate::did_resolver_adapter::DIDResolverAdapter; +use crate::secrets::secrets_resolver_adapter::SecretsResolverAdapter; +use crate::DIDComm; + +pub trait OnPackEncryptedResult: Sync + Send { + fn success(&self, result: String, metadata: PackEncryptedMetadata); + fn error(&self, err: ErrorKind, err_msg: String); +} + +impl DIDComm { + pub fn pack_encrypted<'a, 'b>( + &self, + msg: &'a Message, + to: String, + from: Option, + sign_by: Option, + options: &'b PackEncryptedOptions, + cb: Box, + ) -> ErrorCode { + let msg = msg.clone(); + let options = options.clone(); + let did_resolver = DIDResolverAdapter::new(self.did_resolver.clone()); + let secret_resolver = SecretsResolverAdapter::new(self.secret_resolver.clone()); + + let future = async move { + msg.pack_encrypted( + &to, + from.as_deref(), + sign_by.as_deref(), + &did_resolver, + &secret_resolver, + &options, + ) + .await + }; + EXECUTOR.spawn_ok(async move { + match future.await { + Ok((result, metadata)) => cb.success(result, metadata), + Err(err) => cb.error(err.kind(), err.to_string()), + } + }); + + ErrorCode::Success + } +} + +#[cfg(test)] +mod tests { + use crate::test_helper::{ + create_did_resolver, create_secrets_resolver, get_error, get_ok, PackResult, + }; + use crate::DIDComm; + use didcomm_core::error::ErrorKind; + use didcomm_core::test_vectors::{ALICE_DID, BOB_DID, MESSAGE_SIMPLE}; + use didcomm_core::{Message, PackEncryptedOptions}; + use serde_json::json; + + #[tokio::test] + async fn pack_encrypted_works() { + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_encrypted( + &MESSAGE_SIMPLE, + String::from(BOB_DID), + Some(String::from(ALICE_DID)), + Some(String::from(ALICE_DID)), + &PackEncryptedOptions::default(), + cb, + ); + + let res = get_ok(receiver).await; + assert!(res.contains("ciphertext")); + } + + #[tokio::test] + async fn pack_encrypted_works_did_not_found() { + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(String::from("did:unknown:bob")) + .from(ALICE_DID.to_owned()) + .finalize(); + + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_encrypted( + &msg, + String::from("did:unknown:bob"), + Some(String::from(ALICE_DID)), + Some(String::from(ALICE_DID)), + &PackEncryptedOptions::default(), + cb, + ); + + let res = get_error(receiver).await; + assert_eq!(res.kind(), ErrorKind::DIDNotResolved); + } + + #[tokio::test] + async fn pack_encrypted_works_did_url_not_found() { + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_encrypted( + &MESSAGE_SIMPLE, + String::from(format!("{}#unknown-fragment", BOB_DID)), + Some(String::from(ALICE_DID)), + Some(String::from(ALICE_DID)), + &PackEncryptedOptions::default(), + cb, + ); + + let res = get_error(receiver).await; + assert_eq!(res.kind(), ErrorKind::DIDUrlNotFound); + } + + #[tokio::test] + async fn pack_encrypted_works_secret_not_found() { + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_encrypted( + &MESSAGE_SIMPLE, + String::from(BOB_DID), + Some(String::from(format!( + "{}#key-x25519-not-in-secrets-1", + ALICE_DID + ))), + Some(String::from(ALICE_DID)), + &PackEncryptedOptions::default(), + cb, + ); + + let res = get_error(receiver).await; + assert_eq!(res.kind(), ErrorKind::SecretNotFound); + } + + #[tokio::test] + async fn pack_encrypted_works_illegal_argument() { + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_encrypted( + &MESSAGE_SIMPLE, + String::from("not-a-did"), + Some(String::from(ALICE_DID)), + Some(String::from(ALICE_DID)), + &PackEncryptedOptions::default(), + cb, + ); + + let res = get_error(receiver).await; + assert_eq!(res.kind(), ErrorKind::IllegalArgument); + } +} diff --git a/uniffi/src/didcomm/pack_plaintext.rs b/uniffi/src/didcomm/pack_plaintext.rs new file mode 100644 index 0000000..8b7126c --- /dev/null +++ b/uniffi/src/didcomm/pack_plaintext.rs @@ -0,0 +1,49 @@ +use didcomm_core::error::ErrorKind; +use didcomm_core::Message; + +use crate::common::{ErrorCode, EXECUTOR}; +use crate::did_resolver_adapter::DIDResolverAdapter; +use crate::DIDComm; + +pub trait OnPackPlaintextResult: Sync + Send { + fn success(&self, result: String); + fn error(&self, err: ErrorKind, err_msg: String); +} + +impl DIDComm { + pub fn pack_plaintext(&self, msg: &Message, cb: Box) -> ErrorCode { + let msg = msg.clone(); + let did_resolver = DIDResolverAdapter::new(self.did_resolver.clone()); + + let future = async move { msg.pack_plaintext(&did_resolver).await }; + + EXECUTOR.spawn_ok(async move { + match future.await { + Ok(result) => cb.success(result), + Err(err) => cb.error(err.kind(), err.to_string()), + } + }); + + ErrorCode::Success + } +} + +#[cfg(test)] +mod tests { + + use crate::DIDComm; + + use crate::test_helper::{create_did_resolver, create_secrets_resolver, get_ok, PackResult}; + use didcomm_core::test_vectors::MESSAGE_SIMPLE; + + #[tokio::test] + async fn pack_plaintext_works() { + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()) + .pack_plaintext(&MESSAGE_SIMPLE, cb); + + let res = get_ok(receiver).await; + assert!(res.contains("body")); + } +} diff --git a/uniffi/src/didcomm/pack_signed.rs b/uniffi/src/didcomm/pack_signed.rs new file mode 100644 index 0000000..9981307 --- /dev/null +++ b/uniffi/src/didcomm/pack_signed.rs @@ -0,0 +1,132 @@ +use didcomm_core::Message; +use didcomm_core::{error::ErrorKind, PackSignedMetadata}; + +use crate::common::{ErrorCode, EXECUTOR}; +use crate::did_resolver_adapter::DIDResolverAdapter; +use crate::secrets::secrets_resolver_adapter::SecretsResolverAdapter; +use crate::DIDComm; + +pub trait OnPackSignedResult: Sync + Send { + fn success(&self, result: String, metadata: PackSignedMetadata); + fn error(&self, err: ErrorKind, err_msg: String); +} + +impl DIDComm { + pub fn pack_signed( + &self, + msg: &Message, + sign_by: String, + cb: Box, + ) -> ErrorCode { + let msg = msg.clone(); + let did_resolver = DIDResolverAdapter::new(self.did_resolver.clone()); + let secret_resolver = SecretsResolverAdapter::new(self.secret_resolver.clone()); + + let future = async move { + msg.pack_signed(&sign_by, &did_resolver, &secret_resolver) + .await + }; + + EXECUTOR.spawn_ok(async move { + match future.await { + Ok((result, metadata)) => cb.success(result, metadata), + Err(err) => cb.error(err.kind(), err.to_string()), + } + }); + + ErrorCode::Success + } +} + +#[cfg(test)] +mod tests { + use didcomm_core::error::ErrorKind; + use didcomm_core::Message; + use serde_json::json; + + use crate::test_helper::{ + create_did_resolver, create_secrets_resolver, get_error, get_ok, PackResult, + }; + use crate::DIDComm; + + use didcomm_core::test_vectors::{ALICE_DID, MESSAGE_SIMPLE}; + + #[tokio::test] + async fn pack_signed_works() { + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_signed( + &MESSAGE_SIMPLE, + String::from(ALICE_DID), + cb, + ); + + let res = get_ok(receiver).await; + assert!(res.contains("payload")); + } + + #[tokio::test] + async fn pack_signed_works_did_not_found() { + let msg = Message::build( + "example-1".to_owned(), + "example/v1".to_owned(), + json!("example-body"), + ) + .to(String::from("did:unknown:bob")) + .from(ALICE_DID.to_owned()) + .finalize(); + + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_signed( + &msg, + String::from("did:unknown:alice"), + cb, + ); + + let res = get_error(receiver).await; + assert_eq!(res.kind(), ErrorKind::DIDNotResolved); + } + + #[tokio::test] + async fn pack_signed_works_did_url_not_found() { + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_signed( + &MESSAGE_SIMPLE, + String::from(format!("{}#unknown-fragment", ALICE_DID)), + cb, + ); + + let res = get_error(receiver).await; + assert_eq!(res.kind(), ErrorKind::DIDUrlNotFound); + } + + #[tokio::test] + async fn pack_signed_works_secret_not_found() { + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_signed( + &MESSAGE_SIMPLE, + String::from(format!("{}#key-not-in-secrets-1", ALICE_DID)), + cb, + ); + + let res = get_error(receiver).await; + assert_eq!(res.kind(), ErrorKind::SecretNotFound); + } + + #[tokio::test] + async fn pack_signed_works_illegal_argument() { + let (cb, receiver) = PackResult::new(); + + DIDComm::new(create_did_resolver(), create_secrets_resolver()).pack_signed( + &MESSAGE_SIMPLE, + String::from("not-a-did"), + cb, + ); + + let res = get_error(receiver).await; + assert_eq!(res.kind(), ErrorKind::IllegalArgument); + } +} diff --git a/uniffi/src/didcomm/protocols/mod.rs b/uniffi/src/didcomm/protocols/mod.rs new file mode 100644 index 0000000..e78cad0 --- /dev/null +++ b/uniffi/src/didcomm/protocols/mod.rs @@ -0,0 +1 @@ +pub mod routing; diff --git a/uniffi/src/didcomm/protocols/routing/mod.rs b/uniffi/src/didcomm/protocols/routing/mod.rs new file mode 100644 index 0000000..4444272 --- /dev/null +++ b/uniffi/src/didcomm/protocols/routing/mod.rs @@ -0,0 +1,264 @@ +use std::collections::HashMap; + +use didcomm_core::{ + algorithms::AnonCryptAlg, error::ErrorKind, protocols::routing::wrap_in_forward, +}; +use serde_json::Value; + +use crate::common::EXECUTOR; +use crate::{did_resolver_adapter::DIDResolverAdapter, DIDComm, ErrorCode}; + +pub trait OnWrapInForwardResult: Sync + Send { + fn success(&self, result: String); + fn error(&self, err: ErrorKind, err_msg: String); +} + +impl DIDComm { + pub fn wrap_in_forward( + &self, + msg: String, + headers: &HashMap, + to: String, + routing_keys: &Vec, + enc_alg_anon: &AnonCryptAlg, + cb: Box, + ) -> ErrorCode { + let did_resolver = DIDResolverAdapter::new(self.did_resolver.clone()); + let headers = headers.clone(); + let routing_keys = routing_keys.clone(); + let enc_alg_anon = enc_alg_anon.clone(); + + let future = async move { + wrap_in_forward( + &msg, + Some(&headers), + &to, + &routing_keys, + &enc_alg_anon, + &did_resolver, + ) + .await + }; + + EXECUTOR.spawn_ok(async move { + match future.await { + Ok(result) => cb.success(result), + Err(err) => cb.error(err.kind(), err.to_string()), + } + }); + + ErrorCode::Success + } +} + +#[cfg(test)] +mod tests { + + use std::collections::HashMap; + use std::iter::FromIterator; + + use crate::test_helper::{ + create_did_resolver, create_secrets_resolver, get_ok, PackResult, UnpackResult, + WrapInForwardResult, + }; + use crate::DIDComm; + use didcomm_core::algorithms::AnonCryptAlg; + use didcomm_core::protocols::routing::try_parse_forward; + use didcomm_core::test_vectors::{ + ALICE_DID, BOB_DID, CHARLIE_DID, MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_X25519_1, + MESSAGE_SIMPLE, + }; + use didcomm_core::{Message, PackEncryptedOptions, UnpackOptions}; + use serde_json::json; + + #[tokio::test] + async fn pack_encrypted_works_single_mediator() { + let didcomm = DIDComm::new(create_did_resolver(), create_secrets_resolver()); + let msg = MESSAGE_SIMPLE.clone(); + + // ALICE + let (cb, receiver) = PackResult::new(); + didcomm.pack_encrypted( + &msg, + String::from(BOB_DID), + Some(String::from(ALICE_DID)), + Some(String::from(ALICE_DID)), + &PackEncryptedOptions::default(), + cb, + ); + let packed = get_ok(receiver).await; + + // MEDIATOR 1 + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack( + packed, + &UnpackOptions { + expect_decrypt_by_all_keys: true, + unwrap_re_wrapping_forward: false, + }, + cb, + ); + let unpacked_mediator1 = get_ok(receiver).await; + let forward = try_parse_forward(&unpacked_mediator1).expect("Message is not Forward"); + let forwarded_msg = serde_json::to_string(&forward.forwarded_msg) + .expect("Unable serialize forwarded message"); + + // BOB + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack(forwarded_msg, &UnpackOptions::default(), cb); + let unpacked_msg = get_ok(receiver).await; + + assert_eq!(unpacked_msg, msg); + } + + #[tokio::test] + async fn pack_encrypted_works_multiple_mediators_alternative_endpoints() { + let didcomm = DIDComm::new(create_did_resolver(), create_secrets_resolver()); + let msg = Message::build( + "1234567890".to_owned(), + "http://example.com/protocols/lets_do_lunch/1.0/proposal".to_owned(), + json!({"messagespecificattribute": "and its value"}), + ) + .from(ALICE_DID.to_owned()) + .to(CHARLIE_DID.to_owned()) + .finalize(); + + // ALICE + let (cb, receiver) = PackResult::new(); + didcomm.pack_encrypted( + &msg, + String::from(CHARLIE_DID), + Some(String::from(ALICE_DID)), + Some(String::from(ALICE_DID)), + &PackEncryptedOptions::default(), + cb, + ); + let packed = get_ok(receiver).await; + + // MEDIATOR 3 + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack( + packed, + &UnpackOptions { + expect_decrypt_by_all_keys: true, + unwrap_re_wrapping_forward: false, + }, + cb, + ); + let unpacked_msg_mediator3 = get_ok(receiver).await; + let forward_at_mediator3 = + try_parse_forward(&unpacked_msg_mediator3).expect("Message is not Forward"); + let forward_msg_at_mediator3 = serde_json::to_string(&forward_at_mediator3.forwarded_msg) + .expect("Unable serialize forwarded message"); + + // MEDIATOR 2 + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack( + forward_msg_at_mediator3, + &UnpackOptions { + expect_decrypt_by_all_keys: true, + unwrap_re_wrapping_forward: false, + }, + cb, + ); + let unpacked_msg_mediator2 = get_ok(receiver).await; + let forward_at_mediator2 = + try_parse_forward(&unpacked_msg_mediator2).expect("Message is not Forward"); + let forward_msg_at_mediator2 = serde_json::to_string(&forward_at_mediator2.forwarded_msg) + .expect("Unable serialize forwarded message"); + + // MEDIATOR 1 + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack( + forward_msg_at_mediator2, + &UnpackOptions { + expect_decrypt_by_all_keys: true, + unwrap_re_wrapping_forward: false, + }, + cb, + ); + let unpacked_msg_mediator1 = get_ok(receiver).await; + let forward_at_mediator1 = + try_parse_forward(&unpacked_msg_mediator1).expect("Message is not Forward"); + let forward_msg_at_mediator1 = serde_json::to_string(&forward_at_mediator1.forwarded_msg) + .expect("Unable serialize forwarded message"); + + // CHARLIE + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack(forward_msg_at_mediator1, &UnpackOptions::default(), cb); + let unpacked_msg = get_ok(receiver).await; + + assert_eq!(unpacked_msg, msg); + } + + #[tokio::test] + async fn wrap_in_forward_works_mediator_unknown_by_sender() { + let didcomm = DIDComm::new(create_did_resolver(), create_secrets_resolver()); + let msg = MESSAGE_SIMPLE.clone(); + + // ALICE + let (cb, receiver) = PackResult::new(); + didcomm.pack_encrypted( + &msg, + String::from(BOB_DID), + Some(String::from(ALICE_DID)), + Some(String::from(ALICE_DID)), + &PackEncryptedOptions::default(), + cb, + ); + let packed = get_ok(receiver).await; + + // MEDIATOR 1 + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack( + packed, + &UnpackOptions { + expect_decrypt_by_all_keys: true, + unwrap_re_wrapping_forward: false, + }, + cb, + ); + let unpacked_mediator1 = get_ok(receiver).await; + let forward_at_mediator1 = + try_parse_forward(&unpacked_mediator1).expect("Message is not Forward"); + let forwarded_msg_at_mediator1 = serde_json::to_string(&forward_at_mediator1.forwarded_msg) + .expect("Unable serialize forwarded message"); + + let (cb, receiver) = WrapInForwardResult::new(); + didcomm.wrap_in_forward( + forwarded_msg_at_mediator1, + &HashMap::from_iter([ + ("example-header-1".into(), json!("example-header-1-value")), + ("example-header-2".into(), json!("example-header-2-value")), + ]), + forward_at_mediator1.next, + &vec![MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_X25519_1.id.clone()], + &AnonCryptAlg::default(), + cb, + ); + let msg_for_mediator2 = get_ok(receiver).await; + + // MEDIATOR 2 + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack( + msg_for_mediator2, + &UnpackOptions { + expect_decrypt_by_all_keys: true, + unwrap_re_wrapping_forward: false, + }, + cb, + ); + let unpacked_msg_mediator2 = get_ok(receiver).await; + let forward_at_mediator2 = + try_parse_forward(&unpacked_msg_mediator2).expect("Message is not Forward"); + let forwarded_msg_at_mediator2 = serde_json::to_string(&forward_at_mediator2.forwarded_msg) + .expect("Unable serialize forwarded message"); + + // BOB + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack(forwarded_msg_at_mediator2, &UnpackOptions::default(), cb); + let unpacked_msg = get_ok(receiver).await; + + assert_eq!(unpacked_msg, msg); + } +} diff --git a/uniffi/src/didcomm/unpack.rs b/uniffi/src/didcomm/unpack.rs new file mode 100644 index 0000000..67c069e --- /dev/null +++ b/uniffi/src/didcomm/unpack.rs @@ -0,0 +1,119 @@ +use didcomm_core::{error::ErrorKind, Message, UnpackMetadata, UnpackOptions}; + +use crate::common::EXECUTOR; +use crate::did_resolver_adapter::DIDResolverAdapter; +use crate::DIDComm; +use crate::{secrets_resolver_adapter::SecretsResolverAdapter, ErrorCode}; + +pub trait OnUnpackResult: Sync + Send { + fn success(&self, result: Message, metadata: UnpackMetadata); + fn error(&self, err: ErrorKind, err_msg: String); +} + +impl DIDComm { + pub fn unpack<'a>( + &self, + msg: String, + options: &'a UnpackOptions, + cb: Box, + ) -> ErrorCode { + let msg = msg.clone(); + let options = options.clone(); + let did_resolver = DIDResolverAdapter::new(self.did_resolver.clone()); + let secret_resolver = SecretsResolverAdapter::new(self.secret_resolver.clone()); + + let future = + async move { Message::unpack(&msg, &did_resolver, &secret_resolver, &options).await }; + EXECUTOR.spawn_ok(async move { + match future.await { + Ok((result, metadata)) => cb.success(result, metadata), + Err(err) => cb.error(err.kind(), err.to_string()), + } + }); + + ErrorCode::Success + } +} + +#[cfg(test)] +mod tests { + use crate::test_helper::{ + create_did_resolver, create_secrets_resolver, get_error, get_ok, PackResult, UnpackResult, + }; + use crate::DIDComm; + use didcomm_core::error::ErrorKind; + use didcomm_core::{PackEncryptedOptions, UnpackOptions}; + + use didcomm_core::test_vectors::{ALICE_DID, BOB_DID, MESSAGE_SIMPLE}; + + #[tokio::test] + async fn unpack_works_plaintext() { + let msg = MESSAGE_SIMPLE.clone(); + let didcomm = DIDComm::new(create_did_resolver(), create_secrets_resolver()); + + let (cb, receiver) = PackResult::new(); + didcomm.pack_plaintext(&MESSAGE_SIMPLE, cb); + let res = get_ok(receiver).await; + + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack(res, &UnpackOptions::default(), cb); + let res = get_ok(receiver).await; + + assert_eq!(res, msg); + } + + #[tokio::test] + async fn unpack_works_signed() { + let msg = MESSAGE_SIMPLE.clone(); + let didcomm = DIDComm::new(create_did_resolver(), create_secrets_resolver()); + + let (cb, receiver) = PackResult::new(); + didcomm.pack_signed(&msg, String::from(ALICE_DID), cb); + let res = get_ok(receiver).await; + + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack(res, &UnpackOptions::default(), cb); + let res = get_ok(receiver).await; + + assert_eq!(res, msg); + } + + #[tokio::test] + async fn unpack_works_encrypted() { + let msg = MESSAGE_SIMPLE.clone(); + let didcomm = DIDComm::new(create_did_resolver(), create_secrets_resolver()); + + let (cb, receiver) = PackResult::new(); + didcomm.pack_encrypted( + &msg, + String::from(BOB_DID), + Some(String::from(ALICE_DID)), + Some(String::from(ALICE_DID)), + &PackEncryptedOptions { + forward: false, + ..PackEncryptedOptions::default() + }, + cb, + ); + let res = get_ok(receiver).await; + + let (cb, receiver) = UnpackResult::new(); + didcomm.unpack(res, &UnpackOptions::default(), cb); + let res = get_ok(receiver).await; + + assert_eq!(res, msg); + } + + #[tokio::test] + async fn unpack_works_malformed() { + let (cb, receiver) = UnpackResult::new(); + DIDComm::new(create_did_resolver(), create_secrets_resolver()).unpack( + String::from("invalid message"), + &UnpackOptions::default(), + cb, + ); + let res = get_error(receiver).await; + + assert_eq!(res.kind(), ErrorKind::Malformed); + } +} diff --git a/uniffi/src/lib.rs b/uniffi/src/lib.rs new file mode 100644 index 0000000..dbebd5a --- /dev/null +++ b/uniffi/src/lib.rs @@ -0,0 +1,25 @@ +mod common; +mod did; +mod didcomm; +mod secrets; + +pub use common::ErrorCode; +pub use common::JsonValue; +pub use did::resolvers::*; +pub use did::*; +pub use didcomm::*; +pub use didcomm_core::algorithms::*; +pub use didcomm_core::did::{ + DIDCommMessagingService, DIDDoc, Service, ServiceKind, VerificationMaterial, + VerificationMethod, VerificationMethodType, +}; +pub use didcomm_core::error::*; +pub use didcomm_core::secrets::{Secret, SecretMaterial, SecretType}; +pub use didcomm_core::*; +pub use secrets::resolvers::*; +pub use secrets::*; + +#[cfg(test)] +mod test_helper; + +uniffi_macros::include_scaffolding!("didcomm"); diff --git a/uniffi/src/secrets/mod.rs b/uniffi/src/secrets/mod.rs new file mode 100644 index 0000000..d97a502 --- /dev/null +++ b/uniffi/src/secrets/mod.rs @@ -0,0 +1,6 @@ +pub mod resolvers; + +pub(crate) mod secrets_resolver; +pub(crate) mod secrets_resolver_adapter; + +pub use secrets_resolver::{OnFindSecretsResult, OnGetSecretResult, SecretsResolver}; diff --git a/uniffi/src/secrets/resolvers/example.rs b/uniffi/src/secrets/resolvers/example.rs new file mode 100644 index 0000000..bd6e862 --- /dev/null +++ b/uniffi/src/secrets/resolvers/example.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use didcomm_core::secrets::Secret; + +use crate::{common::ErrorCode, secrets::SecretsResolver, OnFindSecretsResult, OnGetSecretResult}; + +/// Allows resolve pre-defined did's for `example` and other methods. +pub struct ExampleSecretsResolver { + known_secrets: Vec, +} + +impl ExampleSecretsResolver { + pub fn new(known_secrets: Vec) -> Self { + ExampleSecretsResolver { known_secrets } + } +} + +#[async_trait] +impl SecretsResolver for ExampleSecretsResolver { + fn get_secret(&self, secret_id: String, cb: Arc) -> ErrorCode { + let secret = self + .known_secrets + .iter() + .find(|s| s.id == secret_id) + .map(|s| s.clone()); + + match cb.success(secret) { + Ok(_) => ErrorCode::Success, + Err(_) => ErrorCode::Error, + } + } + + fn find_secrets(&self, secret_ids: Vec, cb: Arc) -> ErrorCode { + let res = secret_ids + .iter() + .filter(|sid| self.known_secrets.iter().find(|s| s.id == **sid).is_some()) + .map(|sid| sid.clone()) + .collect(); + + match cb.success(res) { + Ok(_) => ErrorCode::Success, + Err(_) => ErrorCode::Error, + } + } +} diff --git a/uniffi/src/secrets/resolvers/mod.rs b/uniffi/src/secrets/resolvers/mod.rs new file mode 100644 index 0000000..a3f0d0f --- /dev/null +++ b/uniffi/src/secrets/resolvers/mod.rs @@ -0,0 +1,3 @@ +mod example; + +pub use example::ExampleSecretsResolver; diff --git a/uniffi/src/secrets/secrets_resolver.rs b/uniffi/src/secrets/secrets_resolver.rs new file mode 100644 index 0000000..0da676a --- /dev/null +++ b/uniffi/src/secrets/secrets_resolver.rs @@ -0,0 +1,35 @@ +use std::sync::Arc; + +use didcomm_core::secrets::Secret; + +use crate::common::{ErrorCode, OnResult}; + +/// Interface for secrets resolver. +/// Resolves secrets such as private keys to be used for signing and encryption. +pub trait SecretsResolver: Sync + Send { + /// Finds secret (usually private key) identified by the given key ID. + /// + /// # Parameters + /// - `secret_id` the ID (in form of DID URL) identifying a secret + /// - `cb` a callback with a result + /// + /// # Returns + /// A secret (usually private key) or None of there is no secret for the given ID + /// + fn get_secret(&self, secret_id: String, cb: Arc) -> ErrorCode; + + /// Find all secrets that have one of the given IDs. + /// Return secrets only for key IDs for which a secret is present. + /// + /// # Parameters + /// - `secret_ids` the IDs find secrets for + /// - `cb` a callback with a result + /// + /// # Returns + /// A secret (usually private key) or None of there is no secret for the given ID + /// + fn find_secrets(&self, secret_ids: Vec, cb: Arc) -> ErrorCode; +} + +pub type OnGetSecretResult = OnResult>; +pub type OnFindSecretsResult = OnResult>; diff --git a/uniffi/src/secrets/secrets_resolver_adapter.rs b/uniffi/src/secrets/secrets_resolver_adapter.rs new file mode 100644 index 0000000..03a38c5 --- /dev/null +++ b/uniffi/src/secrets/secrets_resolver_adapter.rs @@ -0,0 +1,53 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use didcomm_core::error::{ErrorKind, Result, ResultExt}; +use didcomm_core::secrets::{Secret, SecretsResolver as _SecretsResolver}; + +use crate::secrets_resolver::{OnFindSecretsResult, OnGetSecretResult}; + +use super::SecretsResolver; + +pub struct SecretsResolverAdapter { + secrets_resolver: Arc>, +} + +impl SecretsResolverAdapter { + pub fn new(secrets_resolver: Arc>) -> Self { + SecretsResolverAdapter { secrets_resolver } + } +} + +#[async_trait] +impl _SecretsResolver for SecretsResolverAdapter { + async fn get_secret(&self, secret_id: &str) -> Result> { + let (cb, receiver) = OnGetSecretResult::new(); + + self.secrets_resolver + .get_secret(String::from(secret_id), cb); + + let res = receiver + .get() + .await + .kind(ErrorKind::InvalidState, "can not get secret")?; + Ok(res) + } + + async fn find_secrets<'a>(&self, secret_ids: &'a [&'a str]) -> Result> { + let (cb, receiver) = OnFindSecretsResult::new(); + + self.secrets_resolver + .find_secrets(secret_ids.iter().map(|&s| String::from(s)).collect(), cb); + + let res = receiver + .get() + .await + .kind(ErrorKind::InvalidState, "can not get secret")?; + + Ok(secret_ids + .iter() + .filter(|&&sid| res.iter().find(|&s| s == sid).is_some()) + .map(|sid| *sid) + .collect()) + } +} diff --git a/uniffi/src/test_helper.rs b/uniffi/src/test_helper.rs new file mode 100644 index 0000000..c8019c9 --- /dev/null +++ b/uniffi/src/test_helper.rs @@ -0,0 +1,180 @@ +use std::cell::RefCell; +use std::fmt; +use std::sync::Mutex; + +use didcomm_core::error::{err_msg, Error, ErrorKind, Result}; +use didcomm_core::{FromPrior, Message, PackEncryptedMetadata, PackSignedMetadata, UnpackMetadata}; +use futures::channel::oneshot::{self, Receiver}; + +use crate::{ + DIDResolver, ExampleDIDResolver, ExampleSecretsResolver, OnFromPriorPackResult, + OnFromPriorUnpackResult, OnPackEncryptedResult, OnPackPlaintextResult, OnPackSignedResult, + OnUnpackResult, OnWrapInForwardResult, SecretsResolver, +}; +use didcomm_core::test_vectors::{ + ALICE_DID_DOC_WITH_NO_SECRETS, ALICE_SECRETS, BOB_DID_DOC, BOB_SECRETS, CHARLIE_DID_DOC, + CHARLIE_SECRETS, MEDIATOR1_DID_DOC, MEDIATOR1_SECRETS, MEDIATOR2_DID_DOC, MEDIATOR2_SECRETS, + MEDIATOR3_DID_DOC, MEDIATOR3_SECRETS, +}; + +pub(crate) async fn get_ok(receiver: Receiver>) -> T { + receiver + .await + .expect("unable receive result") + .expect("result is error") +} + +pub(crate) async fn get_error(receiver: Receiver>) -> Error { + receiver + .await + .expect("unable receive result") + .err() + .expect("result is ok") +} + +pub(crate) fn create_secrets_resolver() -> Box { + Box::new(ExampleSecretsResolver::new( + ALICE_SECRETS + .clone() + .into_iter() + .chain(BOB_SECRETS.clone().into_iter()) + .chain(CHARLIE_SECRETS.clone().into_iter()) + .chain(MEDIATOR1_SECRETS.clone().into_iter()) + .chain(MEDIATOR2_SECRETS.clone().into_iter()) + .chain(MEDIATOR3_SECRETS.clone().into_iter()) + .collect(), + )) +} + +pub(crate) fn create_did_resolver() -> Box { + Box::new(ExampleDIDResolver::new(vec![ + ALICE_DID_DOC_WITH_NO_SECRETS.clone(), + BOB_DID_DOC.clone(), + CHARLIE_DID_DOC.clone(), + MEDIATOR1_DID_DOC.clone(), + MEDIATOR2_DID_DOC.clone(), + MEDIATOR3_DID_DOC.clone(), + ])) +} + +pub(crate) struct TestResult +where + T: fmt::Debug + 'static, +{ + sender: Mutex>>>>, +} + +impl TestResult +where + T: fmt::Debug + 'static, +{ + pub(crate) fn new() -> (Box, Receiver>) { + let (sender, receiver) = oneshot::channel::>(); + ( + Box::new(TestResult { + sender: Mutex::new(RefCell::new(Some(sender))), + }), + receiver, + ) + } + + fn _success(&self, result: T) { + self.sender + .lock() + .expect("Unable lock") + .replace(None) + .expect("Callback has been already called") + .send(Ok(result)) + .expect("Unable send"); + } + + fn _error(&self, err: ErrorKind, msg: String) { + self.sender + .lock() + .expect("Unable lock") + .replace(None) + .expect("Callback has been already called") + .send(Err(err_msg(err, msg))) + .expect("Unable send"); + } +} + +pub(crate) type PackResult = TestResult; + +impl OnPackPlaintextResult for PackResult { + fn success(&self, result: String) { + self._success(result); + } + + fn error(&self, err: ErrorKind, msg: String) { + self._error(err, msg); + } +} + +impl OnPackSignedResult for PackResult { + fn success(&self, result: String, _metadata: PackSignedMetadata) { + self._success(result); + } + + fn error(&self, err: ErrorKind, msg: String) { + self._error(err, msg); + } +} + +impl OnPackEncryptedResult for PackResult { + fn success(&self, result: String, _metadata: PackEncryptedMetadata) { + self._success(result); + } + + fn error(&self, err: ErrorKind, msg: String) { + self._error(err, msg); + } +} + +pub(crate) type UnpackResult = TestResult; + +impl OnUnpackResult for UnpackResult { + fn success(&self, result: Message, _metadata: UnpackMetadata) { + self._success(result); + } + + fn error(&self, err: ErrorKind, msg: String) { + self._error(err, msg); + } +} + +pub(crate) type FromPriorPackResult = TestResult<(String, String)>; + +impl OnFromPriorPackResult for FromPriorPackResult { + fn success(&self, from_prior_jwt: String, kid: String) { + self._success((from_prior_jwt, kid)); + } + + fn error(&self, err: ErrorKind, msg: String) { + self._error(err, msg); + } +} + +pub(crate) type FromPriorUnpackResult = TestResult<(FromPrior, String)>; + +impl OnFromPriorUnpackResult for FromPriorUnpackResult { + fn success(&self, from_prior: FromPrior, kid: String) { + self._success((from_prior, kid)); + } + + fn error(&self, err: ErrorKind, msg: String) { + self._error(err, msg); + } +} + +pub(crate) type WrapInForwardResult = TestResult; + +impl OnWrapInForwardResult for WrapInForwardResult { + fn success(&self, result: String) { + self._success(result); + } + + fn error(&self, err: ErrorKind, msg: String) { + self._error(err, msg); + } +} diff --git a/wasm/.gitignore b/wasm/.gitignore new file mode 100644 index 0000000..4e30131 --- /dev/null +++ b/wasm/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml new file mode 100644 index 0000000..f84c1f4 --- /dev/null +++ b/wasm/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = 'didcomm-js' +version = '0.3.0' +authors = ['Vyacheslav Gudkov '] +edition = '2018' +description = 'WASM based javascript wrapper for DIDComm' +license = 'Apache-2.0' +repository = 'https://github.com/sicpa-dlab/didcomm-rust/tree/main/wasm' +homepage = 'https://github.com/sicpa-dlab/didcomm-rust/tree/main/wasm#readme' +readme = 'README.md' + +[lib] +crate-type = [ + 'cdylib', + 'rlib', +] + +[features] +default = ['console_error_panic_hook'] + +[dependencies] +async-trait = '0.1' +wasm-bindgen-futures = '0.4' +js-sys = '0.3' +serde_json = '1.0' + +[dependencies.didcomm] +path = '..' + +[dev-dependencies.getrandom] +version = '0.2' +features = ['js'] + +[dependencies.wasm-bindgen] +version = '0.2' +features = ['serde-serialize'] + +[dependencies.console_error_panic_hook] +version = '0.1' +optional = true + +[dependencies.serde] +version = '1.0' +features = ['derive'] + +[dependencies.wee_alloc] +version = '0.4' +optional = true + +[dependencies.uuid] +version = "0.8" +features = ["v4", "wasm-bindgen"] + +[dev-dependencies] +wasm-bindgen-test = '0.3' + +[profile.release] +opt-level = 's' +lto = true diff --git a/wasm/LICENSE b/wasm/LICENSE new file mode 100644 index 0000000..1b5ec8b --- /dev/null +++ b/wasm/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/wasm/Makefile b/wasm/Makefile new file mode 100644 index 0000000..7ddd5af --- /dev/null +++ b/wasm/Makefile @@ -0,0 +1,41 @@ +PKG_NAME ?= didcomm +WASM_TARGET ?= bundler +WASM_OPTS_PUBLISH ?= + +ifndef PROJECT_DIR +PROJECT_DIR := $(shell git rev-parse --show-toplevel) +endif + +WASM_DIR_NAME := wasm +WASM_DIR := $(PROJECT_DIR)/$(WASM_DIR_NAME) + +WASM_PKG_DIR_NAME := pkg +WASM_PKG_DIR := $(WASM_DIR)/$(WASM_PKG_DIR_NAME) + +.PHONY: all \ + pkg_clean build install publish clean + +all: build + +pkg_clean: + rm -rf $(WASM_PKG_DIR) + +build: $(WASM_DIR) pkg_clean + cd $< + wasm-pack build --target $(WASM_TARGET) --out-name index + sed -i -r "s~\"name\": \".+\"~\"name\": \"${PKG_NAME}\"~" $(WASM_PKG_DIR_NAME)/package.json + +install: $(WASM_PKG_DIR) + cd $< && npm install . + +pack: $(WASM_PKG_DIR) + cd $(WASM_DIR) + wasm-pack pack + +publish: $(WASM_PKG_DIR) + cd $(WASM_DIR) + echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > $) crate. It compiles +to `wasm32` and exposes Javascript/Typescript API with [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) help. +Also [wasmp-pack](https://github.com/rustwasm/wasm-pack) helps in packaging and publishing. + +## Usage + +To use `didcomm` install it with npm + +```sh +npm install didcomm --save # If you plan use webpack or other bundler + +npm install didcomm-node --save # If you plan use it without bundlers in NodeJS + +``` + +## Run demo + +```sh +WASM_TARGET=nodejs make # builds NodeJS package in pkg directory +cd ./demo +npm install +npm run start +``` + +## Assumptions and Limitations + +- This library requires `wasm32` compatible environment (modern browsers and recent NodeJS are supported). +- In order to use the library, `SecretsResolver` and `DIDResolver` interfaces must be implemented on the application level. + Demo application provides 2 simple implementations `ExampleDIDResolver` + and `ExampleSecretsResolver` that allows resolve locally known DID docs and secrets for tests/demo purposes. + - Verification materials are expected in JWK, Base58 and Multibase (internally Base58 only) formats. + - In Base58 and Multibase formats, keys using only X25519 and Ed25519 curves are supported. + - For private keys in Base58 and Multibase formats, the verification material value contains both private and public parts (concatenated bytes). + - In Multibase format, bytes of the verification material value is prefixed with the corresponding Multicodec code. + - Key IDs (kids) used in `SecretsResolver` must match the corresponding key IDs from DID Doc verification methods. + - Key IDs (kids) in DID Doc verification methods and secrets must be a full [DID Fragment](https://www.w3.org/TR/did-core/#fragment), that is `did#key-id`. + - Verification methods referencing another DID Document are not supported (see [Referring to Verification Methods](https://www.w3.org/TR/did-core/#referring-to-verification-methods)). +- The following curves and algorithms are supported: + - Encryption: + - Curves: X25519, P-256 + - Content encryption algorithms: + - XC20P (to be used with ECDH-ES only, default for anoncrypt), + - A256GCM (to be used with ECDH-ES only), + - A256CBC-HS512 (default for authcrypt) + - Key wrapping algorithms: ECDH-ES+A256KW, ECDH-1PU+A256KW + - Signing: + - Curves: Ed25519, Secp256k1, P-256 + - Algorithms: EdDSA (with crv=Ed25519), ES256, ES256K +- Forward protocol is implemented and used by default. +- DID rotation (`fromPrior` field) is supported. +- DIDComm has been implemented under the following [Assumptions](https://hackmd.io/i3gLqgHQR2ihVFV5euyhqg) + +## Examples + +A general usage of the API is the following: + +- Sender Side: + - Build a `Message` (plaintext, payload). + - Convert a message to a DIDComm Message for further transporting by calling one of the following: + - `Message.pack_encrypted` to build an Encrypted DIDComm message + - `Message.pack_signed` to build a Signed DIDComm message + - `Message.pack_plaintext` to build a Plaintext DIDComm message +- Receiver side: + - Call `Message.unpack` on receiver side that will decrypt the message, verify signature if needed + and return a `Message` for further processing on the application level. + +### 1. Build an Encrypted DIDComm message for the given recipient + +This is the most common DIDComm message to be used in most of the applications. + +A DIDComm encrypted message is an encrypted JWM (JSON Web Messages) that + +- hides its content from all but authorized recipients +- (optionally) discloses and proves the sender to only those recipients +- provides message integrity guarantees + +It is important in privacy-preserving routing. It is what normally moves over network transports in DIDComm +applications, and is the safest format for storing DIDComm data at rest. + +See `Message::pack_encrypted` documentation for more details. + +**Authentication encryption** example (most common case): + +```typescript +// --- Build message from ALICE to BOB --- +const msg = new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, +}); + +// --- Packing encrypted and authenticated message --- + +let didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); +let secretsResolver = new ExampleSecretsResolver(ALICE_SECRETS); + +const [encryptedMsg, encryptMetadata] = await msg.pack_encrypted( + BOB_DID, + ALICE_DID, + null, + didResolver, + secretsResolver, + { + forward: false, // Forward wrapping is unsupported in current version + } +); + +console.log("Encryption metadata is\n", encryptMetadata); + +// --- Send message --- +console.log("Sending message\n", encryptedMsg); + +// --- Unpacking message --- +didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); +secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + +const [unpackedMsg, unpackMetadata] = await Message.unpack( + encrypted_msg, + didResolver, + secretsResolver, + {} +); + +console.log("Receved message is\n", unpackedMsg.as_value()); +console.log("Receved message unpack metadata is\n", unpackMetadata); +``` + +**Anonymous encryption** example: + +```typescript +let [encryptedMsg, encryptMetadata] = await msg.pack_encrypted( + BOB_DID, + null, // Keep sender as None here + null, + didResolver, + secretsResolver, + { + forward: false, // Forward wrapping is unsupported in current version + } +); +``` + +**Encryption with non-repudiation** example: + +```typescript +let [encrypted_msg, encrypt_metadata] = await msg.pack_encrypted( + BOB_DID, + ALICE_DID, + ALICE_DID, // Provide information about signer here + did_resolver, + secrets_resolver, + { + forward: false, // Forward wrapping is unsupported in current version + } +); +``` + +### 2. Build an unencrypted but Signed DIDComm message + +Signed messages are only necessary when + +- the origin of plaintext must be provable to third parties +- or the sender can’t be proven to the recipient by authenticated encryption because the recipient is not known in advance (e.g., in a + broadcast scenario). + +Adding a signature when one is not needed can degrade rather than enhance security because it +relinquishes the sender’s ability to speak off the record. + +See `Message.pack_signed` documentation for more details. + +```typescript +let [signed, metadata] = await msg.pack_signed( + ALICE_DID, + didResolver, + secretsResolver +); +``` + +### 3. Build a Plaintext DIDComm message + +A DIDComm message in its plaintext form that + +- is not packaged into any protective envelope +- lacks confidentiality and integrity guarantees +- repudiable + +They are therefore not normally transported across security boundaries. + +```typescript +let plaintext = msg.pack_plaintext(didResolver).expect("Unable pack_plaintext"); +``` + +## How to build + +Install `wasm-pack` from https://rustwasm.github.io/wasm-pack/installer/ and then + +```bash +make # Will output modules best-suited to be bundled with webpack +WASM_TARGET=nodejs make # Will output modules that can be directly consumed by NodeJS +WASM_TARGET=web make # Will output modules that can be directly consumed in browser without bundler usage +``` + +### How to build with `wasm-pack build` + +```bash +wasm-pack build # Will output modules best-suited to be bundled with webpack +wasm-pack build --target=nodejs # Will output modules that can be directly consumed by NodeJS +wasm-pack build --target=web # Will output modules that can be directly consumed in browser without bundler usage +``` + +## How to test in NodeJS + +```bash +WASM_TARGET=nodejs make +cd ./tests-js +npm install +npm test +``` + +## How to test in Browser + +```bash +WASM_TARGET=nodejs make +cd ./tests-js +npm install +npm run test-puppeteer +``` + +_Note tests will be executed with jest+puppeteer in Chromium installed inside node_modules._ + +## Hot to publish to NPM with `wasm-pack publish` + +``` +wasm-pack publish +``` + +## 🔋 Batteries Included + +- [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating + between WebAssembly and JavaScript. +- [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) + for logging panic messages to the developer console. +- [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized + for small code size. + +## Contribution + +PRs are welcome! + +The following CI checks are run against every PR: + +- No warnings from `cargo check --all-targets` +- No warnings from `npm run check` in `tests-js` directory +- No warnings from `npm run check` in `demo` directory +- All tests must pass with `npm test` in `tests-js` directory +- Rust code must be formatted by `cargo fmt --all` +- Javascript/Typescript code must be formatted by prettier `npx prettier --write .` diff --git a/wasm/demo/.gitignore b/wasm/demo/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/wasm/demo/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/wasm/demo/package-lock.json b/wasm/demo/package-lock.json new file mode 100644 index 0000000..a2d7750 --- /dev/null +++ b/wasm/demo/package-lock.json @@ -0,0 +1,976 @@ +{ + "name": "didcomm-demo", + "version": "0.2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "didcomm-demo", + "version": "0.2.0", + "license": "Apache-2.0", + "dependencies": { + "didcomm": "file:../pkg", + "typescript": "^4.5.2" + }, + "devDependencies": { + "@types/node": "^16.11.10", + "ts-node": "^10.4.0", + "tslint": "^6.1.3" + } + }, + "../pkg": { + "name": "didcomm", + "version": "0.2.0", + "license": "Apache-2.0" + }, + "node_modules/@babel/code-frame": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.11.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.10.tgz", + "integrity": "sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.5.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/didcomm": { + "resolved": "../pkg", + "link": true + }, + "node_modules/diff": { + "version": "4.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ts-node": { + "version": "10.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", + "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", + "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "dev": true + }, + "@types/node": { + "version": "16.11.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.10.tgz", + "integrity": "sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA==", + "dev": true + }, + "acorn": { + "version": "8.5.0", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "arg": { + "version": "4.1.3", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "dev": true + }, + "didcomm": { + "version": "file:../pkg" + }, + "diff": { + "version": "4.0.2", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "ts-node": { + "version": "10.4.0", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yn": { + "version": "3.1.1", + "dev": true + } + } +} diff --git a/wasm/demo/package.json b/wasm/demo/package.json new file mode 100644 index 0000000..47be55d --- /dev/null +++ b/wasm/demo/package.json @@ -0,0 +1,21 @@ +{ + "name": "didcomm-demo", + "version": "0.2.0", + "description": "JS demo for didcomm", + "scripts": { + "start": "ts-node src/main.ts", + "test": "echo \"Error: no test specified\" && exit 1", + "check": "tslint -c tslint.json 'src/**/*.ts'" + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "didcomm": "file:../pkg", + "typescript": "^4.5.2" + }, + "devDependencies": { + "@types/node": "^16.11.10", + "ts-node": "^10.4.0", + "tslint": "^6.1.3" + } +} diff --git a/wasm/demo/src/advanced-params.ts b/wasm/demo/src/advanced-params.ts new file mode 100644 index 0000000..0fef556 --- /dev/null +++ b/wasm/demo/src/advanced-params.ts @@ -0,0 +1,67 @@ +import { Message } from "didcomm"; +import { + ALICE_DID, + ALICE_DID_DOC, + ALICE_SECRETS, + BOB_DID, + BOB_DID_DOC, + BOB_SECRETS, +} from "./test-vectors"; +import { + ExampleDIDResolver, + ExampleSecretsResolver, +} from "../../tests-js/src/test-vectors"; + +async function main() { + // --- Building message from ALICE to BOB --- + const msg = new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, + }); + + // --- Packing encrypted and authenticated message --- + let didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + let secretsResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const [encryptedMsg, encryptMetadata] = await msg.pack_encrypted( + "did:example:bob#key-p256-1", + "did:example:alice#key-p256-1", + "did:example:alice#key-2", + didResolver, + secretsResolver, + { + forward: false, // TODO: should be true by default + protect_sender: true, + enc_alg_anon: "A256cbcHs512EcdhEsA256kw", + messaging_service: "did:example:bob#didcomm-1", + enc_alg_auth: "A256cbcHs512Ecdh1puA256kw", + } + ); + + console.log("Encryption metadata is\n", encryptMetadata); + + // --- Sending message --- + console.log("Sending message\n", encryptedMsg); + + // --- Unpacking message --- + didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpackedMsg, unpackMetadata] = await Message.unpack( + encryptedMsg, + didResolver, + secretsResolver, + {} + ); + + console.log("Reveived message is\n", unpackedMsg.as_value()); + console.log("Reveived message unpack metadata is\n", unpackMetadata); +} + +main().catch((e) => console.log(e)); diff --git a/wasm/demo/src/attachments.ts b/wasm/demo/src/attachments.ts new file mode 100644 index 0000000..734dc3f --- /dev/null +++ b/wasm/demo/src/attachments.ts @@ -0,0 +1,73 @@ +import { Message } from "didcomm"; +import { + ALICE_DID, + ALICE_DID_DOC, + ALICE_SECRETS, + BOB_DID, + BOB_DID_DOC, + BOB_SECRETS, +} from "./test-vectors"; +import { + ExampleDIDResolver, + ExampleSecretsResolver, +} from "../../tests-js/src/test-vectors"; + +async function main() { + // --- Building message from ALICE to BOB --- + const msg = new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + attachments: [ + { + data: { + json: { foo: "bar" }, + }, + id: "123", + description: "example", + media_type: "application/didcomm-encrypted+json", + }, + ], + body: { messagespecificattribute: "and its value" }, + }); + + // --- Packing encrypted and authenticated message --- + let didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + let secretsResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const [encryptedMsg, encryptMetadata] = await msg.pack_encrypted( + BOB_DID, + ALICE_DID, + ALICE_DID, + didResolver, + secretsResolver, + { + forward: false, // TODO: should be true by default + } + ); + + console.log("Encryption metadata is\n", encryptMetadata); + + // --- Sending message --- + console.log("Sending message\n", encryptedMsg); + + // --- Unpacking message --- + didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpackedMsg, unpackMetadata] = await Message.unpack( + encryptedMsg, + didResolver, + secretsResolver, + {} + ); + + console.log("Reveived message is\n", unpackedMsg.as_value()); + console.log("Reveived message unpack metadata is\n", unpackMetadata); +} + +main().catch((e) => console.log(e)); diff --git a/wasm/demo/src/main.ts b/wasm/demo/src/main.ts new file mode 100644 index 0000000..45462a2 --- /dev/null +++ b/wasm/demo/src/main.ts @@ -0,0 +1,386 @@ +import { + ALICE_DID, + ALICE_DID_DOC, + ALICE_SECRETS, + BOB_DID, + BOB_DID_DOC, + BOB_SECRETS, +} from "./test-vectors"; + +import { Message, DIDDoc, DIDResolver, Secret, SecretsResolver } from "didcomm"; +import { + CHARLIE_DID, + CHARLIE_DID_DOC, + CHARLIE_SECRETS, +} from "../../tests-js/src/test-vectors"; + +class ExampleDIDResolver implements DIDResolver { + knownDids: DIDDoc[]; + + constructor(knownDids: DIDDoc[]) { + this.knownDids = knownDids; + } + + async resolve(did: string): Promise { + return this.knownDids.find((ddoc) => ddoc.did === did) || null; + } +} + +class ExampleSecretsResolver implements SecretsResolver { + knownSecrets: Secret[]; + + constructor(knownSecrets: Secret[]) { + this.knownSecrets = knownSecrets; + } + + async get_secret(secretId: string): Promise { + return this.knownSecrets.find((secret) => secret.id === secretId) || null; + } + + async find_secrets(secretIds: string[]): Promise { + return secretIds.filter((id) => + this.knownSecrets.find((secret) => secret.id === id) + ); + } +} + +async function main() { + console.log( + "=================== NON REPUDIABLE ENCRYPTION ===================\n" + ); + await nonRepudiableEncryption(); + console.log("\n=================== MULTI RECIPIENT ===================\n"); + await multiRecipient(); + console.log( + "\n=================== REPUDIABLE AUTHENTICATED ENCRYPTION ===================\n" + ); + await repudiableAuthentcatedEncryption(); + console.log( + "\n=================== REPUDIABLE NON AUTHENTICATED ENCRYPTION ===================\n" + ); + await repudiableNonAuthentcatedEncryption(); + console.log("\n=================== SIGNED UNENCRYPTED ===================\n"); + await signedUnencrypteed(); + console.log("\n=================== PLAINTEXT ==================="); + await plaintext(); +} + +async function nonRepudiableEncryption() { + // --- Building message from ALICE to BOB --- + const msg = new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, + }); + + // --- Packing encrypted and authenticated message --- + let didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + let secretsResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const [encryptedMsg, encryptMetadata] = await msg.pack_encrypted( + BOB_DID, + ALICE_DID, + ALICE_DID, + didResolver, + secretsResolver, + { + forward: false, // TODO: should be true by default + } + ); + + console.log("Encryption metadata is\n", encryptMetadata); + + // --- Sending message --- + console.log("Sending message\n", encryptedMsg); + + // --- Unpacking message --- + didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpackedMsg, unpackMetadata] = await Message.unpack( + encryptedMsg, + didResolver, + secretsResolver, + {} + ); + + console.log("Reveived message is\n", unpackedMsg.as_value()); + console.log("Reveived message unpack metadata is\n", unpackMetadata); +} + +async function multiRecipient() { + // --- Building message from ALICE to BOB and Charlie --- + const msg = new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob", "did:example:charlie"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, + }); + + let didResolver = new ExampleDIDResolver([ + ALICE_DID_DOC, + BOB_DID_DOC, + CHARLIE_DID_DOC, + ]); + let secretsResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + // --- Packing encrypted and authenticated message for Bob --- + const [encryptedMsgBob, encryptMetadataBob] = await msg.pack_encrypted( + BOB_DID, + ALICE_DID, + null, + didResolver, + secretsResolver, + { + forward: false, // TODO: should be true by default + } + ); + + console.log("Encryption metadata for Bob is\n", encryptMetadataBob); + + // --- Sending message --- + console.log("Sending message to Bob\n", encryptedMsgBob); + + // --- Packing encrypted and authenticated message for Charlie --- + const [encryptedMsgCharlie, encryptMetadataCharlie] = + await msg.pack_encrypted( + CHARLIE_DID, + ALICE_DID, + null, + didResolver, + secretsResolver, + { + forward: false, // TODO: should be true by default + } + ); + + console.log("Encryption metadata for Charle is\n", encryptMetadataCharlie); + + // --- Sending message --- + console.log("Sending message to Charle\n", encryptedMsgCharlie); + + // --- Unpacking message for Bob --- + didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpackedMsgBob, unpackMetadataBob] = await Message.unpack( + encryptedMsgBob, + didResolver, + secretsResolver, + {} + ); + + console.log("Reveived message for Bob is\n", unpackedMsgBob.as_value()); + console.log( + "Reveived message unpack metadata for Bob is\n", + unpackMetadataBob + ); + + // --- Unpacking message for Charlie --- + didResolver = new ExampleDIDResolver([ALICE_DID_DOC, CHARLIE_DID_DOC]); + secretsResolver = new ExampleSecretsResolver(CHARLIE_SECRETS); + + const [unpackedMsgCharlie, unpackMetadataCharlie] = await Message.unpack( + encryptedMsgCharlie, + didResolver, + secretsResolver, + {} + ); + + console.log( + "Reveived message for Charlie is\n", + unpackedMsgCharlie.as_value() + ); + console.log( + "Reveived message unpack metadata for Charlie is\n", + unpackMetadataCharlie + ); +} + +async function repudiableAuthentcatedEncryption() { + // --- Building message from ALICE to BOB --- + const msg = new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, + }); + + // --- Packing encrypted and authenticated message --- + let didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + let secretsResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const [encryptedMsg, encryptMetadata] = await msg.pack_encrypted( + BOB_DID, + ALICE_DID, + null, + didResolver, + secretsResolver, + { + forward: false, // TODO: should be true by default + } + ); + + console.log("Encryption metadata is\n", encryptMetadata); + + // --- Sending message --- + console.log("Sending message\n", encryptedMsg); + + // --- Unpacking message --- + didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpackedMsg, unpackMetadata] = await Message.unpack( + encryptedMsg, + didResolver, + secretsResolver, + {} + ); + + console.log("Reveived message is\n", unpackedMsg.as_value()); + console.log("Reveived message unpack metadata is\n", unpackMetadata); +} + +async function repudiableNonAuthentcatedEncryption() { + // --- Building message from ALICE to BOB --- + const msg = new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, + }); + + // --- Packing encrypted and authenticated message --- + let didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + let secretsResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const [encryptedMsg, encryptMetadata] = await msg.pack_encrypted( + BOB_DID, + null, + null, + didResolver, + secretsResolver, + { + forward: false, // TODO: should be true by default + } + ); + + console.log("Encryption metadata is\n", encryptMetadata); + + // --- Sending message --- + console.log("Sending message\n", encryptedMsg); + + // --- Unpacking message --- + didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpackedMsg, unpackMetadata] = await Message.unpack( + encryptedMsg, + didResolver, + secretsResolver, + {} + ); + + console.log("Reveived message is\n", unpackedMsg.as_value()); + console.log("Reveived message unpack metadata is\n", unpackMetadata); +} + +async function signedUnencrypteed() { + // --- Building message from ALICE to BOB --- + const msg = new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, + }); + + // --- Packing encrypted and authenticated message --- + let didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + let secretsResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const [signedMsg, signMetadata] = await msg.pack_signed( + ALICE_DID, + didResolver, + secretsResolver + ); + + console.log("Encryption metadata is\n", signMetadata); + + // --- Sending message --- + console.log("Sending message\n", signedMsg); + + // --- Unpacking message --- + didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpackedMsg, unpackMetadata] = await Message.unpack( + signedMsg, + didResolver, + secretsResolver, + {} + ); + + console.log("Reveived message is\n", unpackedMsg.as_value()); + console.log("Reveived message unpack metadata is\n", unpackMetadata); +} + +async function plaintext() { + // --- Building message from ALICE to BOB --- + const msg = new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, + }); + + // --- Packing encrypted and authenticated message --- + let didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + + const plaintextMsg = await msg.pack_plaintext(didResolver); + + // --- Sending message --- + console.log("Sending message\n", plaintextMsg); + + // --- Unpacking message --- + didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + const secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpackedMsg, unpackMetadata] = await Message.unpack( + plaintextMsg, + didResolver, + secretsResolver, + {} + ); + + console.log("Reveived message is\n", unpackedMsg.as_value()); + console.log("Reveived message unpack metadata is\n", unpackMetadata); +} + +main().catch((e) => console.log(e)); + +// TODO: add examples for Forward (routing) and Mediators diff --git a/wasm/demo/src/test-vectors.ts b/wasm/demo/src/test-vectors.ts new file mode 100644 index 0000000..324c4f7 --- /dev/null +++ b/wasm/demo/src/test-vectors.ts @@ -0,0 +1,438 @@ +export const ALICE_DID = "did:example:alice"; + +export const ALICE_DID_DOC = { + did: "did:example:alice", + key_agreements: [ + "did:example:alice#key-x25519-not-in-secrets-1", + "did:example:alice#key-x25519-1", + "did:example:alice#key-p256-1", + "did:example:alice#key-p521-1", + ], + authentications: [ + "did:example:alice#key-1", + "did:example:alice#key-2", + "did:example:alice#key-3", + ], + verification_methods: [ + { + id: "did:example:alice#key-x25519-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-x25519-1", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", + }, + }, + }, + { + id: "did:example:alice#key-p256-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-p256-1", + verification_material: { + JWK: { + crv: "P-256", + kty: "EC", + x: "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", + y: "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", + }, + }, + }, + { + id: "did:example:alice#key-p521-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-p521-1", + verification_material: { + JWK: { + crv: "P-521", + kty: "EC", + x: "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", + y: "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", + }, + }, + }, + { + id: "did:example:alice#key-not-in-secrets-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-not-in-secrets-1", + verification_material: { + JWK: { + crv: "Ed25519", + kty: "OKP", + x: "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", + }, + }, + }, + { + id: "did:example:alice#key-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-1", + verification_material: { + JWK: { + crv: "Ed25519", + kty: "OKP", + x: "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", + }, + }, + }, + { + id: "did:example:alice#key-2", + type: "JsonWebKey2020", + controller: "did:example:alice#key-2", + verification_material: { + JWK: { + crv: "P-256", + kty: "EC", + x: "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", + y: "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", + }, + }, + }, + { + id: "did:example:alice#key-3", + type: "JsonWebKey2020", + controller: "did:example:alice#key-3", + verification_material: { + JWK: { + crv: "secp256k1", + kty: "EC", + x: "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", + y: "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", + }, + }, + }, + ], + services: [], +}; + +export const ALICE_SECRETS = [ + { + id: "did:example:alice#key-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "Ed25519", + d: "pFRUKkyzx4kHdJtFSnlPA9WzqkDT1HWV0xZ5OYZd2SY", + kty: "OKP", + x: "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", + }, + }, + }, + { + id: "did:example:alice#key-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "7TCIdt1rhThFtWcEiLnk_COEjh1ZfQhM4bW2wz-dp4A", + kty: "EC", + x: "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", + y: "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", + }, + }, + }, + { + id: "did:example:alice#key-3", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "secp256k1", + d: "N3Hm1LXA210YVGGsXw_GklMwcLu_bMgnzDese6YQIyA", + kty: "EC", + x: "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", + y: "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", + }, + }, + }, + { + id: "did:example:alice#key-x25519-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "r-jK2cO3taR8LQnJB1_ikLBTAnOtShJOsHXRUWT-aZA", + kty: "OKP", + x: "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", + }, + }, + }, + { + id: "did:example:alice#key-p256-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "sB0bYtpaXyp-h17dDpMx91N3Du1AdN4z1FUq02GbmLw", + kty: "EC", + x: "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", + y: "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", + }, + }, + }, + { + id: "did:example:alice#key-p521-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-521", + d: "AQCQKE7rZpxPnX9RgjXxeywrAMp1fJsyFe4cir1gWj-8t8xWaM_E2qBkTTzyjbRBu-JPXHe_auT850iYmE34SkWi", + kty: "EC", + x: "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", + y: "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", + }, + }, + }, +]; + +export const BOB_DID = "did:example:bob"; + +export const BOB_DID_DOC = { + did: "did:example:bob", + key_agreements: [ + "did:example:bob#key-x25519-1", + "did:example:bob#key-x25519-2", + "did:example:bob#key-x25519-3", + "did:example:bob#key-p256-1", + "did:example:bob#key-p256-2", + "did:example:bob#key-p384-1", + "did:example:bob#key-p384-2", + "did:example:bob#key-p521-1", + "did:example:bob#key-p521-2", + ], + authentications: [], + verification_methods: [ + { + id: "did:example:bob#key-x25519-1", + type: "JsonWebKey2020", + controller: "did:example:bob#key-x25519-1", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }, + }, + }, + { + id: "did:example:bob#key-x25519-2", + type: "JsonWebKey2020", + controller: "did:example:bob#key-x25519-2", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", + }, + }, + }, + { + id: "did:example:bob#key-x25519-3", + type: "JsonWebKey2020", + controller: "did:example:bob#key-x25519-3", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", + }, + }, + }, + { + id: "did:example:bob#key-p256-1", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p256-1", + verification_material: { + JWK: { + crv: "P-256", + kty: "EC", + x: "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + y: "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }, + }, + }, + { + id: "did:example:bob#key-p256-2", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p256-2", + verification_material: { + JWK: { + crv: "P-256", + kty: "EC", + x: "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", + y: "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", + }, + }, + }, + { + id: "did:example:bob#key-p384-1", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p384-1", + verification_material: { + JWK: { + crv: "P-384", + kty: "EC", + x: "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + y: "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }, + }, + }, + { + id: "did:example:bob#key-p384-2", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p384-2", + verification_material: { + JWK: { + crv: "P-384", + kty: "EC", + x: "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", + y: "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", + }, + }, + }, + { + id: "did:example:bob#key-p521-1", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p521-1", + verification_material: { + JWK: { + crv: "P-521", + kty: "EC", + x: "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + y: "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }, + }, + }, + { + id: "did:example:bob#key-p521-2", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p521-2", + verification_material: { + JWK: { + crv: "P-521", + kty: "EC", + x: "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", + y: "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", + }, + }, + }, + ], + services: [], +}; + +export const BOB_SECRETS = [ + { + id: "did:example:bob#key-x25519-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "b9NnuOCB0hm7YGNvaE9DMhwH_wjZA1-gWD6dA0JWdL0", + kty: "OKP", + x: "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }, + }, + }, + { + id: "did:example:bob#key-x25519-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "p-vteoF1gopny1HXywt76xz_uC83UUmrgszsI-ThBKk", + kty: "OKP", + x: "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", + }, + }, + }, + { + id: "did:example:bob#key-x25519-3", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "f9WJeuQXEItkGM8shN4dqFr5fLQLBasHnWZ-8dPaSo0", + kty: "OKP", + x: "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", + }, + }, + }, + { + id: "did:example:bob#key-p256-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "PgwHnlXxt8pwR6OCTUwwWx-P51BiLkFZyqHzquKddXQ", + kty: "EC", + x: "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + y: "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }, + }, + }, + { + id: "did:example:bob#key-p256-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "agKz7HS8mIwqO40Q2dwm_Zi70IdYFtonN5sZecQoxYU", + kty: "EC", + x: "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", + y: "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", + }, + }, + }, + { + id: "did:example:bob#key-p384-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-384", + d: "ajqcWbYA0UDBKfAhkSkeiVjMMt8l-5rcknvEv9t_Os6M8s-HisdywvNCX4CGd_xY", + kty: "EC", + x: "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + y: "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }, + }, + }, + { + id: "did:example:bob#key-p384-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-384", + d: "OiwhRotK188BtbQy0XBO8PljSKYI6CCD-nE_ZUzK7o81tk3imDOuQ-jrSWaIkI-T", + kty: "EC", + x: "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", + y: "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", + }, + }, + }, + { + id: "did:example:bob#key-p521-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-521", + d: "AV5ocjvy7PkPgNrSuvCxtG70NMj6iTabvvjSLbsdd8OdI9HlXYlFR7RdBbgLUTruvaIRhjEAE9gNTH6rWUIdfuj6", + kty: "EC", + x: "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + y: "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }, + }, + }, + { + id: "did:example:bob#key-p521-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-521", + d: "ABixMEZHsyT7SRw-lY5HxdNOofTZLlwBHwPEJ3spEMC2sWN1RZQylZuvoyOBGJnPxg4-H_iVhNWf_OtgYODrYhCk", + kty: "EC", + x: "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", + y: "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", + }, + }, + }, +]; diff --git a/wasm/demo/tslint.json b/wasm/demo/tslint.json new file mode 100644 index 0000000..2562f1a --- /dev/null +++ b/wasm/demo/tslint.json @@ -0,0 +1,12 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "no-console": false, + "max-classes-per-file": false + }, + "rulesDirectory": [] +} \ No newline at end of file diff --git a/wasm/src/did/did_doc.rs b/wasm/src/did/did_doc.rs new file mode 100644 index 0000000..a2c8833 --- /dev/null +++ b/wasm/src/did/did_doc.rs @@ -0,0 +1,112 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const DID_DOC_TS: &'static str = r#" +/** + * Represents DID Document (https://www.w3.org/TR/did-core/) + */ +type DIDDoc = { + /** + * DID for the given DID Doc + */ + did: string, + + /** + * DID URLs of verification methods used for key agreement. + * See https://www.w3.org/TR/did-core/#verification-methods. + */ + key_agreements: Array, + + /** + * Returns DID URLs of verification methods used for authentication. + * See https://www.w3.org/TR/did-core/#authentication + */ + authentications: Array, + + /** + * All local verification methods including embedded to + * key agreement and authentication sections. + * See https://www.w3.org/TR/did-core/#verification-methods. + */ + verification_methods: Array, + + /** + * All services (https://www.w3.org/TR/did-core/#services) + */ + services: Array, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const VERIFICATION_METHOD_TS: &'static str = r#" +/** + * Represents verification method record in DID Document + * (https://www.w3.org/TR/did-core/#verification-methods). + */ +type VerificationMethod = { + id: string, + type: VerificationMethodType, + controller: string, + verification_material: VerificationMaterial, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const VERIFICATION_METHOD_TYPE_TS: &'static str = r#" +type VerificationMethodType = "JsonWebKey2020" | "X25519KeyAgreementKey2019" + | "Ed25519VerificationKey2018" | "EcdsaSecp256k1VerificationKey2019" | string +"#; + +#[wasm_bindgen(typescript_custom_section)] +const VERIFICATION_MATERIAL_TYPE_TS: &'static str = r#" +/** + * Represents verification material (https://www.w3.org/TR/did-core/#verification-material) + */ +type VerificationMaterial = { + "JWK": any, +} | { + "Multibase": string, +} | { + "Base58": string, +} | { + "Hex": string, +} | { + "Other": any, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const SERVICE_TS: &'static str = r#" +/** + * Represents service record in DID Document (https://www.w3.org/TR/did-core/#services). + */ +type Service = { + id: string, + kind: ServiceKind, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const SERVICE_KIND_TS: &'static str = r#" +/** + * Represents additional service properties defined for specific Service type. + */ +type ServiceKind = { + "DIDCommMessaging": DIDCommMessagingService, +} | { + "Other": any, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const DIDCOMM_MESSAGING_SERVICE_TS: &'static str = r#" +/** + * Properties for DIDCommMessagingService + * (https://identity.foundation/didcomm-messaging/spec/#did-document-service-endpoint). + */ +type DIDCommMessagingService = { + service_endpoint: string, + accept: Array, + route_keys: Array, +} +"#; diff --git a/wasm/src/did/did_resolver.rs b/wasm/src/did/did_resolver.rs new file mode 100644 index 0000000..282aa99 --- /dev/null +++ b/wasm/src/did/did_resolver.rs @@ -0,0 +1,66 @@ +use async_trait::async_trait; +use didcomm::{ + did::{DIDDoc, DIDResolver as _DIDResolver}, + error::{ErrorKind, Result as _Result, ResultContext, ResultExt}, +}; +use wasm_bindgen::prelude::*; + +use crate::error::FromJsResult; + +#[wasm_bindgen] +extern "C" { + pub type DIDResolver; + + #[wasm_bindgen(structural, method, catch)] + pub async fn resolve(this: &DIDResolver, did: &str) -> Result; +} + +#[wasm_bindgen(typescript_custom_section)] +const DID_RESOLVER_TS: &'static str = r#" +/** + * Represents DID Doc resolver (https://www.w3.org/TR/did-core/#did-resolution). + */ +interface DIDResolver { + /** + * Resolves a DID document by the given DID. + * + * @param `did` a DID to be resolved. + * + * @returns An instance of resolved DID DOC or null if DID is not found. + * + * @throws DIDCommMalformed - Resolved DID Doc looks malformed + * @throws DIDCommIoError - IO error in resolving process + * @throws DIDCommInvalidState - Code error or unexpected state was detected + * + * Note to throw compatible error use code like this + * + * ``` + * let e = Error("Unble perform io operation"); + * e.name = "DIDCommIoError" + * throw e + * ``` + */ + resolve(did: string): Promise; +} +"#; + +pub(crate) struct JsDIDResolver(pub(crate) DIDResolver); + +#[async_trait(?Send)] +impl _DIDResolver for JsDIDResolver { + async fn resolve(&self, did: &str) -> _Result> { + let ddoc = self + .0 + .resolve(did) + .await + .from_js() + .context("Unable resolve did")?; + + let ddoc: Option = ddoc.into_serde().kind( + ErrorKind::InvalidState, + "Unable deserialize DIDDoc from JsValue", + )?; + + Ok(ddoc) + } +} diff --git a/wasm/src/did/mod.rs b/wasm/src/did/mod.rs new file mode 100644 index 0000000..74ecd2b --- /dev/null +++ b/wasm/src/did/mod.rs @@ -0,0 +1,5 @@ +mod did_doc; +mod did_resolver; + +pub use did_resolver::DIDResolver; +pub(crate) use did_resolver::JsDIDResolver; diff --git a/wasm/src/error.rs b/wasm/src/error.rs new file mode 100644 index 0000000..6453dcf --- /dev/null +++ b/wasm/src/error.rs @@ -0,0 +1,71 @@ +use didcomm::error::{err_msg as _err_msg, ErrorKind as _ErrorKind, Result as _Result}; +use js_sys::{Error as JsError, JsString}; +use wasm_bindgen::{prelude::*, JsCast}; + +// Alows convertion of didcomm error to javascript error +pub(crate) trait JsResult { + fn as_js(self) -> Result; +} + +impl JsResult for _Result { + fn as_js(self) -> Result { + self.map_err(|e| { + let name = match e.kind() { + _ErrorKind::DIDNotResolved => "DIDCommDIDNotResolved", + _ErrorKind::DIDUrlNotFound => "DIDCommDIDUrlNotFound", + _ErrorKind::Malformed => "DIDCommMalformed", + _ErrorKind::IoError => "DIDCommIoError", + _ErrorKind::InvalidState => "DIDCommInvalidState", + _ErrorKind::NoCompatibleCrypto => "DIDCommNoCompatibleCrypto", + _ErrorKind::Unsupported => "DIDCommUnsupported", + _ErrorKind::IllegalArgument => "DIDCommIllegalArgument", + _ErrorKind::SecretNotFound => "DIDCommSecretNotFound", + }; + + let e = JsError::new(&format!("{}", e)); + e.set_name(name); + e + }) + } +} + +// Alows convertion of javascript error to didcomm error +pub(crate) trait FromJsResult { + fn from_js(self) -> _Result; +} + +impl FromJsResult for Result { + fn from_js(self) -> _Result { + self.map_err(|e| { + // String was thrown + if let Some(e) = e.dyn_ref::() { + return _err_msg( + _ErrorKind::InvalidState, + e.as_string().unwrap_or(format!("{:?}", e)), + ); + } + + // Error instance was thrown + if let Some(e) = e.dyn_ref::() { + let kind = match e.name().as_string().as_deref() { + Some("DIDCommDIDNotResolved") => _ErrorKind::DIDNotResolved, + Some("DIDCommDIDUrlNotFound") => _ErrorKind::DIDUrlNotFound, + Some("DIDCommMalformed") => _ErrorKind::Malformed, + Some("DIDCommIoError") => _ErrorKind::IoError, + Some("DIDCommInvalidState") => _ErrorKind::InvalidState, + Some("DIDCommNoCompatibleCrypto") => _ErrorKind::NoCompatibleCrypto, + Some("DIDCommUnsupported") => _ErrorKind::Unsupported, + Some("DIDCommIllegalArgument") => _ErrorKind::IllegalArgument, + _ => _ErrorKind::InvalidState, + }; + + let message = e.message().as_string().unwrap_or(format!("{:?}", e)); + + return _err_msg(kind, message); + } + + // Something unusual was thrown + _err_msg(_ErrorKind::InvalidState, format!("{:?}", e)) + }) + } +} diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs new file mode 100644 index 0000000..fe3e5ff --- /dev/null +++ b/wasm/src/lib.rs @@ -0,0 +1,18 @@ +mod did; +mod error; +mod message; +mod secrets; +mod utils; + +pub use crate::{ + did::DIDResolver, + message::{FromPrior, Message}, + secrets::SecretsResolver, +}; +use crate::{did::JsDIDResolver, secrets::JsSecretsResolver}; + +// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global +// allocator. +#[cfg(feature = "wee_alloc")] +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; diff --git a/wasm/src/message/from_prior/mod.rs b/wasm/src/message/from_prior/mod.rs new file mode 100644 index 0000000..802370b --- /dev/null +++ b/wasm/src/message/from_prior/mod.rs @@ -0,0 +1,76 @@ +mod pack; +mod unpack; + +use didcomm::error::{ErrorKind, ResultExt}; +use std::rc::Rc; +use wasm_bindgen::prelude::*; + +use crate::{error::JsResult, utils::set_panic_hook}; + +#[wasm_bindgen] +/// Allows building of `from_prior` message header according +/// to DIDComm DID Rotation procedure +/// https://identity.foundation/didcomm-messaging/spec/#did-rotation. +pub struct FromPrior(pub(crate) Rc); + +#[wasm_bindgen] +impl FromPrior { + #[wasm_bindgen(constructor)] + /// Instantiates FromPrior from plain object + pub fn new(value: IFromPrior) -> Result { + // TODO: Better place? + set_panic_hook(); + + let msg = value + .into_serde() + .kind(ErrorKind::Malformed, "Unable deserialize FromPrior") + .as_js()?; + + Ok(FromPrior(Rc::new(msg))) + } + + #[wasm_bindgen(skip_typescript)] + pub fn as_value(&self) -> Result { + let msg = JsValue::from_serde(&*self.0) + .kind(ErrorKind::Malformed, "Unable serialize FromPrior") + .as_js()?; + + Ok(msg) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const MESSAGE_AS_VALUE_TS: &'static str = r#" +interface FromPrior { + /** + * @returns FromPrior representation as plain object + */ + as_value(): IFromPrior; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "IFromPrior")] + pub type IFromPrior; +} + +#[wasm_bindgen(typescript_custom_section)] +const IMESSAGE_TS: &'static str = r#" +type IFromPrior = { + /** + * new DID after rotation + */ + iss: string, + + /** + * prior DID + */ + sub: string, + + /** + * Datetime of the DID rotation + */ + iat?: number, +} +"#; diff --git a/wasm/src/message/from_prior/pack.rs b/wasm/src/message/from_prior/pack.rs new file mode 100644 index 0000000..5b80cc5 --- /dev/null +++ b/wasm/src/message/from_prior/pack.rs @@ -0,0 +1,68 @@ +use js_sys::{Array, Promise}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +use crate::{ + error::JsResult, DIDResolver, FromPrior, JsDIDResolver, JsSecretsResolver, SecretsResolver, +}; + +#[wasm_bindgen] +impl FromPrior { + #[wasm_bindgen(skip_typescript)] + pub fn pack( + &self, + issuer_kid: Option, + did_resolver: DIDResolver, + secrets_resolver: SecretsResolver, + ) -> Promise { + let from_prior = self.0.clone(); + let did_resolver = JsDIDResolver(did_resolver); + let secrets_resolver = JsSecretsResolver(secrets_resolver); + + future_to_promise(async move { + let (msg, metadata) = from_prior + .pack(issuer_kid.as_deref(), &did_resolver, &secrets_resolver) + .await + .as_js()?; + + let res = { + let res = Array::new_with_length(2); + res.set(0, msg.into()); + res.set(1, metadata.into()); + res + }; + + Ok(res.into()) + }) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const FROM_PRIOR_PACK_TS: &'static str = r#" +interface FromPrior { + /** + * Packs a plaintext `from_prior` value into a signed JWT. + * https://identity.foundation/didcomm-messaging/spec/#did-rotation + * + * @param issuer_kid (optional) identifier of the issuer key being used to sign `from_prior` JWT value + * @param did_resolver instance of `DIDResolver` to resolve DIDs + * @param secrets_resolver instance of `SecretsResolver` to resolve issuer DID keys secrets + * + * @returns promise resolving to a tuple of the signed `from_prior` JWT and the identifier of the issuer key + * actually used to sign `from_prior` + * + * @throws DIDCommMalformed `from_prior` plaintext value has invalid format. + * @throws DIDCommIllegalArgument `issuer_kid` is invalid or does not consist with `from_prior` plaintext value. + * @throws DIDCommDIDNotResolved Issuer DID not found. + * @throws DIDCommDIDUrlNotFound Issuer authentication verification method is not found. + * @throws DIDCommSecretNotFound Issuer secret is not found. + * @throws DIDCommUnsupported Used crypto or method is unsupported. + * @throws DIDCommInvalidState Indicates a library error. + */ + pack( + issuer_kid: string | null, + did_resolver: DIDResolver, + secrets_resolver: SecretsResolver, + ): Promise<[string, string]>; +} +"#; diff --git a/wasm/src/message/from_prior/unpack.rs b/wasm/src/message/from_prior/unpack.rs new file mode 100644 index 0000000..76c419c --- /dev/null +++ b/wasm/src/message/from_prior/unpack.rs @@ -0,0 +1,57 @@ +use js_sys::{Array, Promise}; +use std::rc::Rc; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +use crate::{error::JsResult, utils::set_panic_hook, DIDResolver, FromPrior, JsDIDResolver}; + +#[wasm_bindgen(skip_typescript)] +impl FromPrior { + #[wasm_bindgen(skip_typescript)] + pub fn unpack(from_prior: String, did_resolver: DIDResolver) -> Promise { + // TODO: Better place? + set_panic_hook(); + + let did_resolver = JsDIDResolver(did_resolver); + + future_to_promise(async move { + let (msg, metadata) = didcomm::FromPrior::unpack(&from_prior, &did_resolver) + .await + .as_js()?; + + let res = { + let res = Array::new_with_length(2); + res.set(0, FromPrior(Rc::new(msg)).into()); + res.set(1, metadata.into()); + res + }; + + Ok(res.into()) + }) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const FROM_PRIOR_UNPACK_TS: &'static str = r#" +export namespace FromPrior { + /** + * Unpacks a plaintext value from a signed `from_prior` JWT. + * https://identity.foundation/didcomm-messaging/spec/#did-rotation + * + * @param from_prior_jwt signed `from_prior` JWT + * @param did_resolver instance of `DIDResolver` to resolve DIDs + * + * @returns promise resolving to a tuple of the plaintext `from_prior` value and the identifier + * of the issuer key used to sign `from_prior` + * + * @throws DIDCommMalformed Signed `from_prior` JWT is malformed. + * @throws DIDCommDIDNotResolved Issuer DID not found. + * @throws DIDCommDIDUrlNotFound Issuer authentication verification method is not found. + * @throws DIDCommUnsupported Used crypto or method is unsupported. + */ + function unpack( + from_prior: string, + did_resolver: DIDResolver, + ): Promise<[FromPrior, string]>; +} +"#; diff --git a/wasm/src/message/mod.rs b/wasm/src/message/mod.rs new file mode 100644 index 0000000..b02ddb2 --- /dev/null +++ b/wasm/src/message/mod.rs @@ -0,0 +1,254 @@ +mod from_prior; +mod pack_encrypted; +mod pack_plaintext; +mod pack_signed; +mod protocols; +mod unpack; + +use didcomm::error::{ErrorKind, ResultExt}; +use std::rc::Rc; +use wasm_bindgen::prelude::*; + +use crate::{error::JsResult, utils::set_panic_hook}; +pub use from_prior::FromPrior; + +#[wasm_bindgen] +/// Wrapper for plain message. Provides helpers for message building and packing/unpacking. +pub struct Message(pub(crate) Rc); + +#[wasm_bindgen] +impl Message { + #[wasm_bindgen(constructor)] + /// Instantiates message from plain object + pub fn new(value: IMessage) -> Result { + // TODO: Better place? + set_panic_hook(); + + let msg = value + .into_serde() + .kind(ErrorKind::Malformed, "Unable deserialize Message") + .as_js()?; + + Ok(Message(Rc::new(msg))) + } + + #[wasm_bindgen(skip_typescript)] + pub fn as_value(&self) -> Result { + let msg = JsValue::from_serde(&*self.0) + .kind(ErrorKind::Malformed, "Unable serialize Message") + .as_js()?; + + Ok(msg) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const MESSAGE_AS_VALUE_TS: &'static str = r#" +interface Message { + /** + * @returns message representation as plain object + */ + as_value(): IMessage; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "IMessage")] + pub type IMessage; +} + +#[wasm_bindgen(typescript_custom_section)] +const IMESSAGE_TS: &'static str = r#" +type IMessage = { + /** + * Message id. Must be unique to the sender. + */ + id: string, + + /** + * Must be "application/didcomm-plain+json" + */ + typ: string, + + /** + * Message type attribute value MUST be a valid Message Type URI, + * that when resolved gives human readable information about the message. + * The attribute’s value also informs the content of the message, + * or example the presence of other attributes and how they should be processed. + */ + type: string, + + /** + * Message body. + */ + body: any, + + /** + * Sender identifier. The from attribute MUST be a string that is a valid DID + * or DID URL (without the fragment component) which identifies the sender of the message. + */ + from?: string, + + /** + * Identifier(s) for recipients. MUST be an array of strings where each element + * is a valid DID or DID URL (without the fragment component) that identifies a member + * of the message’s intended audience. + */ + to?: Array, + + /** + * Uniquely identifies the thread that the message belongs to. + * If not included the id property of the message MUST be treated as the value of the `thid`. + */ + thid?: string, + + /** + * If the message is a child of a thread the `pthid` + * will uniquely identify which thread is the parent. + */ + pthid?: string, + + /** + * Custom message headers. + */ + [extra_header: string]: any + + /** + * The attribute is used for the sender + * to express when they created the message, expressed in + * UTC Epoch Seconds (seconds since 1970-01-01T00:00:00Z UTC). + * This attribute is informative to the recipient, and may be relied on by protocols. + */ + created_time?: number, + + /** + * The expires_time attribute is used for the sender to express when they consider + * the message to be expired, expressed in UTC Epoch Seconds (seconds since 1970-01-01T00:00:00Z UTC). + * This attribute signals when the message is considered no longer valid by the sender. + * When omitted, the message is considered to have no expiration by the sender. + */ + expires_time?: number, + + /** + * from_prior is a compactly serialized signed JWT containing FromPrior value + */ + from_prior?: string, + + /** + * Message attachments + */ + attachments?: Array, +}; +"#; + +#[wasm_bindgen(typescript_custom_section)] +const ATTACHMENT_TS: &'static str = r#" +type Attachment = { + /** + * A JSON object that gives access to the actual content of the attachment. + * Can be based on base64, json or external links. + */ + data: AttachmentData, + + /** + * Identifies attached content within the scope of a given message. + * Recommended on appended attachment descriptors. Possible but generally unused + * on embedded attachment descriptors. Never required if no references to the attachment + * exist; if omitted, then there is no way to refer to the attachment later in the thread, + * in error messages, and so forth. Because id is used to compose URIs, it is recommended + * that this name be brief and avoid spaces and other characters that require URI escaping. + */ + id?: string, + + /** + * A human-readable description of the content. + */ + description?: string, + + /** + * A hint about the name that might be used if this attachment is persisted as a file. + * It is not required, and need not be unique. If this field is present and mime-type is not, + * the extension on the filename may be used to infer a MIME type. + */ + filename?: string, + + /** + * Describes the MIME type of the attached content. + */ + media_type?: string, + + /** + * Describes the format of the attachment if the mime_type is not sufficient. + */ + format?: string, + + /** + * A hint about when the content in this attachment was last modified + * in UTC Epoch Seconds (seconds since 1970-01-01T00:00:00Z UTC). + */ + lastmod_time?: number, + + /** + * Mostly relevant when content is included by reference instead of by value. + * Lets the receiver guess how expensive it will be, in time, bandwidth, and storage, + * to fully fetch the attachment. + */ + byte_count?: number, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const ATTACHMENT_DATA_TS: &'static str = r#" +type AttachmentData = Base64AttachmentData | JsonAttachmentData | LinksAttachmentData +"#; + +#[wasm_bindgen(typescript_custom_section)] +const BASE64_ATTACHMENT_DATA_TS: &'static str = r#" +type Base64AttachmentData = { + /** + * Base64-encoded data, when representing arbitrary content inline. + */ + base64: string, + + /** + * A JSON Web Signature over the content of the attachment. + */ + jws?: string, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const JSON_ATTACHMENT_DATA_TS: &'static str = r#" +type JsonAttachmentData = { + /** + * Directly embedded JSON data. + */ + json: any, + + /** + * A JSON Web Signature over the content of the attachment. + */ + jws?: string, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const LINKS_ATTACHMENT_DATA_TS: &'static str = r#" +type LinksAttachmentData = { + /** + * A list of one or more locations at which the content may be fetched. + */ + links: Array, + + /** + * The hash of the content encoded in multi-hash format. Used as an integrity check for the attachment. + */ + hash: string, + + /** + * A JSON Web Signature over the content of the attachment. + */ + jws?: string, +} +"#; diff --git a/wasm/src/message/pack_encrypted.rs b/wasm/src/message/pack_encrypted.rs new file mode 100644 index 0000000..5bfde02 --- /dev/null +++ b/wasm/src/message/pack_encrypted.rs @@ -0,0 +1,226 @@ +use didcomm::{ + error::{ErrorKind, ResultExt}, + PackEncryptedOptions, +}; +use js_sys::{Array, Promise}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +use crate::{ + error::JsResult, DIDResolver, JsDIDResolver, JsSecretsResolver, Message, SecretsResolver, +}; + +#[wasm_bindgen] +impl Message { + #[wasm_bindgen(skip_typescript)] + pub fn pack_encrypted( + &self, + to: String, + from: Option, + sign_by: Option, + did_resolver: DIDResolver, + secrets_resolver: SecretsResolver, + options: JsValue, + ) -> Promise { + let msg = self.0.clone(); + let did_resolver = JsDIDResolver(did_resolver); + let secrets_resolver = JsSecretsResolver(secrets_resolver); + + future_to_promise(async move { + let options: PackEncryptedOptions = options + .into_serde() + .kind(ErrorKind::Malformed, "Options param is malformed") + .as_js()?; + + let (msg, metadata) = msg + .pack_encrypted( + &to, + from.as_deref(), + sign_by.as_deref(), + &did_resolver, + &secrets_resolver, + &options, + ) + .await + .as_js()?; + + let metadata = JsValue::from_serde(&metadata) + .kind( + ErrorKind::InvalidState, + "Unable serialize PackEncryptedMetadata", + ) + .as_js()?; + + let res = { + let res = Array::new_with_length(2); + res.set(0, msg.into()); + res.set(1, metadata); + res + }; + + Ok(res.into()) + }) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const MESSAGE_PACK_ENCRYPTED_TS: &'static str = r#" +interface Message { + /** + * Produces `DIDComm Encrypted Message` + * https://identity.foundation/didcomm-messaging/spec/#didcomm-encrypted-message. + * + * A DIDComm encrypted message is an encrypted JWM (JSON Web Messages) and + * hides its content from all but authorized recipients, discloses (optionally) and proves + * the sender to exactly and only those recipients, and provides integrity guarantees. + * It is important in privacy-preserving routing. It is what normally moves over network + * transports in DIDComm applications, and is the safest format for storing DIDComm data at rest. + * + * Encryption is done as following: + * - Encryption is done via the keys from the `keyAgreement` verification relationship in the DID Doc + * - if `to` is a DID, then multiplex encryption is done for all keys from the + * receiver's `keyAgreement` verification relationship + * which are compatible the sender's key. + * - if `to` is a key ID, then encryption is done for the receiver's `keyAgreement` + * verification method identified by the given key ID. + * - if `from` is a DID, then sender `keyAgreement` will be negotiated based on recipient preference and + * sender-recipient crypto compatibility. + * - if `from` is a key ID, then the sender's `keyAgreement` verification method + * identified by the given key ID is used. + * - if `from` is None, then anonymous encryption is done and there will be no sender authentication property. + * + * It's possible to add non-repudiation by providing `sign_by` parameter. + * + * @param `to` recipient DID or key ID the sender uses encryption. + * @param `from` a sender DID or key ID. If set message will be repudiable authenticated or anonymous otherwise. + * Must match `from` header in Plaintext if the header is set. + * @param `sign_by` if `Some` message will be additionally signed to provide additional non-repudiable authentication + * by provided DID/Key. Signed messages are only necessary when the origin of plaintext must be provable + * to third parties, or when the sender can’t be proven to the recipient by authenticated encryption because + * the recipient is not known in advance (e.g., in a broadcast scenario). + * Adding a signature when one is not needed can degrade rather than enhance security because + * it relinquishes the sender’s ability to speak off the record. + * @param `did_resolver` instance of `DIDResolver` to resolve DIDs. + * @param `secrets_resolver` instance of SecretsResolver` to resolve sender DID keys secrets. + * @param `options` allow fine configuration of packing process. + * + * @returns Tuple `[encrypted_message, metadata]`. + * - `encrypted_message` A DIDComm encrypted message as a JSON string. + * - `metadata` additional metadata about this `pack` execution like used keys identifiers, + * used messaging service. + * + * @throws DIDCommDIDNotResolved + * @throws DIDCommDIDUrlNotFound + * @throws DIDCommMalformed + * @throws DIDCommIoError + * @throws DIDCommInvalidState + * @throws DIDCommNoCompatibleCrypto + * @throws DIDCommUnsupported + * @throws DIDCommIllegalArgument + */ + pack_encrypted( + to: string, + from: string | null, + sign_by: string | null, + did_resolver: DIDResolver, + secrets_resolver: SecretsResolver, + options: PackEncryptedOptions, + ): Promise<[string, PackEncryptedMetadata]>; +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const PACK_ENCRYPTED_OPTIONS_TS: &'static str = r#" +/** + * Allow fine configuration of packing process. + */ +type PackEncryptedOptions = { + /** + * If `true` and message is authenticated than information about sender will be protected from mediators, but + * additional re-encryption will be required. For anonymous messages this property will be ignored. + * Default false. + */ + protect_sender?: boolean, + + /** + * Whether the encrypted messages need to be wrapped into `Forward` messages to be sent to Mediators + * as defined by the `Forward` protocol. + * Default true. + */ + forward?: boolean, + + /** + * if forward is enabled these optional headers can be passed to the wrapping `Forward` messages. + * If forward is disabled this property will be ignored. + */ + forward_headers?: Array<[string, string]>, + + /** + * Identifier (DID URL) of messaging service (https://identity.foundation/didcomm-messaging/spec/#did-document-service-endpoint). + * If DID contains multiple messaging services it allows specify what service to use. + * If not present first service will be used. + */ + messaging_service?: string, + + /** + * Algorithm used for authenticated encryption. + * Default "A256cbcHs512Ecdh1puA256kw" + */ + enc_alg_auth?: "A256cbcHs512Ecdh1puA256kw", + + /** + * Algorithm used for anonymous encryption. + * Default "Xc20pEcdhEsA256kw" + */ + enc_alg_anon?: "A256cbcHs512EcdhEsA256kw" | "Xc20pEcdhEsA256kw" | "A256gcmEcdhEsA256kw", +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const PACK_ENCRYPTED_METADATA_TS: &'static str = r#" +/** + * Additional metadata about this `encrypt` method execution like used keys identifiers, + * used messaging service. + */ +type PackEncryptedMetadata = { + /** + * Information about messaging service used for message preparation. + * Practically `service_endpoint` field can be used to transport the message. + */ + messaging_service?: MessagingServiceMetadata, + + /** + * Identifier (DID URL) of sender key used for message encryption. + */ + from_kid?: string, + + /** + * Identifier (DID URL) of sender key used for message sign. + */ + sign_by_kid?: string, + + /** + * Identifiers (DID URLs) of recipient keys used for message encryption. + */ + to_kids: Array, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const MESSAGING_SERVICE_METADATA_TS: &'static str = r#" +/** + * Information about messaging service used for message preparation. + * Practically `service_endpoint` field can be used to transport the message. + */ +type MessagingServiceMetadata = { + /** + * Identifier (DID URL) of used messaging service. + */ + id: string, + + /** + * Service endpoint of used messaging service. + */ + service_endpoint: string, +} +"#; diff --git a/wasm/src/message/pack_plaintext.rs b/wasm/src/message/pack_plaintext.rs new file mode 100644 index 0000000..8d35c43 --- /dev/null +++ b/wasm/src/message/pack_plaintext.rs @@ -0,0 +1,48 @@ +use js_sys::Promise; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +use crate::{error::JsResult, DIDResolver, JsDIDResolver, Message}; + +#[wasm_bindgen(skip_typescript)] +impl Message { + #[wasm_bindgen(skip_typescript)] + pub fn pack_plaintext(&self, did_resolver: DIDResolver) -> Promise { + let msg = self.0.clone(); + let did_resolver = JsDIDResolver(did_resolver); + + future_to_promise(async move { + let msg = msg.pack_plaintext(&did_resolver).await.as_js()?; + + Ok(msg.into()) + }) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const MESSAGE_PACK_PLAYNTEXT_TS: &'static str = r#" +interface Message { + /** + * Produces `DIDComm Plaintext Messages` + * https://identity.foundation/didcomm-messaging/spec/#didcomm-plaintext-messages. + * + * A DIDComm message in its plaintext form, not packaged into any protective envelope, + * is known as a DIDComm plaintext message. Plaintext messages lack confidentiality and integrity + * guarantees, and are repudiable. They are therefore not normally transported across security boundaries. + * However, this may be a helpful format to inspect in debuggers, since it exposes underlying semantics, + * and it is the format used in this spec to give examples of headers and other internals. + * Depending on ambient security, plaintext may or may not be an appropriate format for DIDComm data at rest. + * + * @param `did_resolver` instance of `DIDResolver` to resolve DIDs. + * + * @returns a DIDComm plaintext message s JSON string + * + * @throws DIDCommDIDNotResolved + * @throws DIDCommDIDUrlNotFound + * @throws DIDCommIoError + * @throws DIDCommInvalidState + * @throws DIDCommIllegalArgument + */ + pack_plaintext(did_resolver: DIDResolver): Promise; +} +"#; diff --git a/wasm/src/message/pack_signed.rs b/wasm/src/message/pack_signed.rs new file mode 100644 index 0000000..ff4577f --- /dev/null +++ b/wasm/src/message/pack_signed.rs @@ -0,0 +1,99 @@ +use didcomm::error::{ErrorKind, ResultExt}; +use js_sys::{Array, Promise}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +use crate::{ + error::JsResult, DIDResolver, JsDIDResolver, JsSecretsResolver, Message, SecretsResolver, +}; + +#[wasm_bindgen(skip_typescript)] +impl Message { + #[wasm_bindgen(skip_typescript)] + pub fn pack_signed( + &self, + sign_by: String, + did_resolver: DIDResolver, + secrets_resolver: SecretsResolver, + ) -> Promise { + let msg = self.0.clone(); + let did_resolver = JsDIDResolver(did_resolver); + let secrets_resolver = JsSecretsResolver(secrets_resolver); + + future_to_promise(async move { + let (msg, metadata) = msg + .pack_signed(&sign_by, &did_resolver, &secrets_resolver) + .await + .as_js()?; + + let metadata = JsValue::from_serde(&metadata) + .kind( + ErrorKind::InvalidState, + "Unable serialize PackSignedMetadata", + ) + .as_js()?; + + let res = { + let res = Array::new_with_length(2); + res.set(0, msg.into()); + res.set(1, metadata); + res + }; + + Ok(res.into()) + }) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const MESSAGE_PACK_ENCRYPTED_TS: &'static str = r#" +interface Message { + /** + * Produces `DIDComm Signed Message` + * https://identity.foundation/didcomm-messaging/spec/#didcomm-signed-message. + * + * Signed messages are not necessary to provide message integrity (tamper evidence), + * or to prove the sender to the recipient. Both of these guarantees automatically occur + * with the authenticated encryption in DIDComm encrypted messages. Signed messages are only + * necessary when the origin of plaintext must be provable to third parties, + * or when the sender can’t be proven to the recipient by authenticated encryption because + * the recipient is not known in advance (e.g., in a broadcast scenario). + * We therefore expect signed messages to be used in a few cases, but not as a matter of course. + * + * @param `sign_by` a DID or key ID the sender uses for signing + * @param `did_resolver` instance of `DIDResolver` to resolve DIDs. + * @param `secrets_resolver` instance of SecretsResolver` to resolve sender DID keys secrets + * + * @returns Tuple (signed_message, metadata) + * - `signed_message` a DIDComm signed message as JSON string + * - `metadata` additional metadata about this `encrypt` execution like used keys identifiers and algorithms. + * + * @throws DIDCommDIDNotResolved + * @throws DIDCommDIDUrlNotFound + * @throws DIDCommMalformed + * @throws DIDCommIoError + * @throws DIDCommInvalidState + * @throws DIDCommNoCompatibleCrypto + * @throws DIDCommUnsupported + * @throws DIDCommIllegalArgument + */ + pack_signed( + sign_by: string, + did_resolver: DIDResolver, + secrets_resolver: SecretsResolver, + ): Promise<[string, PackSignedMetadata]>; +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const PACK_SIGNED_METADATA_TS: &'static str = r#" +/** + * Additional metadata about this `pack` method execution like used key identifiers. + */ +type PackSignedMetadata = { + /** + * Identifier (DID URL) of sign key. + */ + sign_by_kid: String, +} +"#; diff --git a/wasm/src/message/protocols/mod.rs b/wasm/src/message/protocols/mod.rs new file mode 100644 index 0000000..e78cad0 --- /dev/null +++ b/wasm/src/message/protocols/mod.rs @@ -0,0 +1 @@ +pub mod routing; diff --git a/wasm/src/message/protocols/routing/mod.rs b/wasm/src/message/protocols/routing/mod.rs new file mode 100644 index 0000000..f7823a7 --- /dev/null +++ b/wasm/src/message/protocols/routing/mod.rs @@ -0,0 +1,155 @@ +use std::{collections::HashMap, rc::Rc}; + +use crate::{did::JsDIDResolver, error::JsResult, DIDResolver}; +use crate::{utils::set_panic_hook, Message}; +use didcomm::{ + algorithms::AnonCryptAlg, + error::{ErrorKind, ResultExt}, + protocols::routing::{try_parse_forward, wrap_in_forward}, +}; +use js_sys::Promise; +use serde_json::Value; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +#[wasm_bindgen] +pub struct ParsedForward(pub(crate) Rc>); + +#[wasm_bindgen] +impl ParsedForward { + #[wasm_bindgen(skip_typescript)] + pub fn as_value(&self) -> Result { + let msg = JsValue::from_serde(&*self.0) + .kind(ErrorKind::Malformed, "Unable serialize ParsedForward") + .as_js()?; + + Ok(msg) + } +} + +#[wasm_bindgen] +impl Message { + #[wasm_bindgen(skip_typescript)] + pub fn wrap_in_forward( + msg: String, + headers: JsValue, + to: String, + routing_keys: JsValue, + enc_alg_anon: JsValue, + did_resolver: DIDResolver, + ) -> Promise { + // TODO: Better place? + set_panic_hook(); + + let did_resolver = JsDIDResolver(did_resolver); + future_to_promise(async move { + let headers: HashMap = headers + .into_serde() + .kind(ErrorKind::Malformed, "headers param is malformed") + .as_js()?; + + let routing_keys: Vec = routing_keys + .into_serde() + .kind(ErrorKind::Malformed, "routing_keys param is malformed") + .as_js()?; + + let enc_alg_anon: AnonCryptAlg = enc_alg_anon + .into_serde() + .kind(ErrorKind::Malformed, "enc_alg_anon param is malformed") + .as_js()?; + + let res = wrap_in_forward( + &msg, + Some(&headers), + &to, + &routing_keys, + &enc_alg_anon, + &did_resolver, + ) + .await + .as_js()?; + + Ok(res.into()) + }) + } + + #[wasm_bindgen(skip_typescript)] + pub fn try_parse_forward(&self) -> Result { + let msg = self.0.clone(); + let parsed_message = try_parse_forward(&msg); + Ok(JsValue::from_serde(&parsed_message) + .kind( + ErrorKind::Malformed, + "Unable serialize parsed forward message", + ) + .as_js()?) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const PARSED_FORWARD_AS_VALUE_TS: &'static str = r#" +interface ParsedForward { + as_value(): IParsedForward; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "IParsedForward")] + pub type IParsedForward; +} + +#[wasm_bindgen(typescript_custom_section)] +const IPARSED_FORWARD_TS: &'static str = r#" +type IParsedForward = { + msg: Message, + next: string, + forwarded_msg: any +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const MESSAGE_WRAP_IN_FORWARD_TS: &'static str = r#" +export namespace Message { + /** + * Resolves recipient DID DOC Service and Builds Forward envelops if needed. + * + * Wraps the given packed DIDComm message in Forward messages for every routing key. + * + * @param msg the message to be wrapped in Forward messages + * @param headers optional headers for Forward message + * @param to recipient's DID (DID URL) + * @param routing_keys list of routing keys + * @param enc_alg_anon The encryption algorithm to be used for anonymous encryption (anon_crypt) + * @param did_resolver instance of `DIDResolver` to resolve DIDs. + * + * @returns a top-level packed Forward message as JSON string + * + * @throws DIDCommDIDNotResolved + * @throws DIDCommDIDUrlNotFound + * @throws DIDCommIoError + * @throws DIDCommInvalidState + * @throws DIDCommIllegalArgument + */ + function wrap_in_forward( + msg: string, + headers: Record, + to: string, + routing_keys: Array, + enc_alg_anon: string, + did_resolver: DIDResolver, + ): Promise; +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const MESSAGE_TRY_PARSE_FORWARD_TS: &'static str = r#" +interface Message { + /** + * Tries to parse the Message to a Forward message + * + * @returns a parsed message or null + */ + try_parse_forward(): ParsedForward; +} +"#; diff --git a/wasm/src/message/unpack.rs b/wasm/src/message/unpack.rs new file mode 100644 index 0000000..dcd5331 --- /dev/null +++ b/wasm/src/message/unpack.rs @@ -0,0 +1,200 @@ +use std::rc::Rc; + +use didcomm::{ + error::{ErrorKind, ResultExt}, + UnpackOptions, +}; +use js_sys::{Array, Promise}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::future_to_promise; + +use crate::{ + error::JsResult, utils::set_panic_hook, DIDResolver, JsDIDResolver, JsSecretsResolver, Message, + SecretsResolver, +}; + +#[wasm_bindgen(skip_typescript)] +impl Message { + #[wasm_bindgen(skip_typescript)] + pub fn unpack( + msg: String, + did_resolver: DIDResolver, + secrets_resolver: SecretsResolver, + options: JsValue, + ) -> Promise { + // TODO: Better place? + set_panic_hook(); + + let did_resolver = JsDIDResolver(did_resolver); + let secrets_resolver = JsSecretsResolver(secrets_resolver); + + future_to_promise(async move { + let options: UnpackOptions = options + .into_serde() + .kind(ErrorKind::Malformed, "Options param is malformed") + .as_js()?; + + let (msg, metadata) = + didcomm::Message::unpack(&msg, &did_resolver, &secrets_resolver, &options) + .await + .as_js()?; + + let metadata = JsValue::from_serde(&metadata) + .kind(ErrorKind::InvalidState, "Unable serialize UnpackMetadata") + .as_js()?; + + let res = { + let res = Array::new_with_length(2); + res.set(0, Message(Rc::new(msg)).into()); + res.set(1, metadata); + res + }; + + Ok(res.into()) + }) + } +} + +#[wasm_bindgen(typescript_custom_section)] +const MESSAGE_UNPACK_TS: &'static str = r#" +export namespace Message { + /** + * Unpacks the packed message by doing decryption and verifying the signatures. + * This method supports all DID Comm message types (encrypted, signed, plaintext). + * + * If unpack options expect a particular property (for example that a message is encrypted) + * and the packed message doesn't meet the criteria (it's not encrypted), then a MessageUntrusted + * error will be returned. + * + * @param `packed_msg` the message as JSON string to be unpacked + * @param `did_resolver` instance of `DIDResolver` to resolve DIDs + * @param `secrets_resolver` instance of SecretsResolver` to resolve sender DID keys secrets + * @param `options` allow fine configuration of unpacking process and imposing additional restrictions + * to message to be trusted. + * + * @returns Tuple `[message, metadata]`. + * - `message` plain message instance + * - `metadata` additional metadata about this `unpack` execution like used keys identifiers, + * trust context, algorithms and etc. + * + * @throws DIDCommDIDNotResolved + * @throws DIDCommDIDUrlNotFound + * @throws DIDCommMalformed + * @throws DIDCommIoError + * @throws DIDCommInvalidState + * @throws DIDCommNoCompatibleCrypto + * @throws DIDCommUnsupported + * @throws DIDCommIllegalArgument + */ + function unpack( + msg: string, + did_resolver: DIDResolver, + secrets_resolver: SecretsResolver, + options: UnpackOptions, + ): Promise<[Message, UnpackMetadata]>; +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const PACK_UNPACK_OPTIONS_TS: &'static str = r#" +/** + * Allows fine customization of unpacking process + */ +type UnpackOptions = { + /** + * Whether the plaintext must be decryptable by all keys resolved by the secrets resolver. + * False by default. + */ + expect_decrypt_by_all_keys?: boolean, + + /** + * If `true` and the packed message is a `Forward` + * wrapping a plaintext packed for the given recipient, then both Forward and packed plaintext are unpacked automatically, + * and the unpacked plaintext will be returned instead of unpacked Forward. + * False by default. + */ + unwrap_re_wrapping_forward?: boolean, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const UNPACK_METADATA_TS: &'static str = r#" +/** + * Additional metadata about this `unpack` method execution like trust predicates + * and used keys identifiers. + */ +type UnpackMetadata = { + /** + * Whether the plaintext has been encrypted. + */ + encrypted: boolean, + + /** + * Whether the plaintext has been authenticated. + */ + authenticated: boolean, + + /** + * Whether the plaintext has been signed. + */ + non_repudiation: boolean, + + /** + * Whether the sender ID was hidden or protected. + */ + anonymous_sender: boolean, + + /** + * Whether the plaintext was re-wrapped in a forward message by a mediator. + */ + re_wrapped_in_forward: boolean, + + /** + * Key ID of the sender used for authentication encryption + * if the plaintext has been authenticated and encrypted. + */ + encrypted_from_kid?: string, + + /** + * Target key IDS for encryption if the plaintext has been encrypted. + */ + encrypted_to_kids?: Array, + + /** + * Key ID used for signature if the plaintext has been signed. + */ + sign_from: string, + + /** + * Key ID used for from_prior header signature if from_prior header is present + */ + from_prior_issuer_kid?: string, + + /** + * Algorithm used for authenticated encryption. + * Default "A256cbcHs512Ecdh1puA256kw" + */ + enc_alg_auth?: "A256cbcHs512Ecdh1puA256kw", + + /** + * Algorithm used for anonymous encryption. + * Default "Xc20pEcdhEsA256kw" + */ + enc_alg_anon?: "A256cbcHs512EcdhEsA256kw" | "Xc20pEcdhEsA256kw" | "A256gcmEcdhEsA256kw", + + /** + * Algorithm used for message signing. + */ + sign_alg?: "EdDSA" | "ES256" | "ES256K", + + /** + * If the plaintext has been signed, the JWS is returned for non-repudiation purposes. + */ + signed_message?: string, + + /** + * If plaintext contains from_prior header, its unpacked value is returned + */ + from_prior?: IFromPrior, +} +"#; diff --git a/wasm/src/secrets/mod.rs b/wasm/src/secrets/mod.rs new file mode 100644 index 0000000..fca5d2c --- /dev/null +++ b/wasm/src/secrets/mod.rs @@ -0,0 +1,5 @@ +mod secret; +mod secrets_resolver; + +pub(crate) use secrets_resolver::JsSecretsResolver; +pub use secrets_resolver::SecretsResolver; diff --git a/wasm/src/secrets/secret.rs b/wasm/src/secrets/secret.rs new file mode 100644 index 0000000..7b8ea50 --- /dev/null +++ b/wasm/src/secrets/secret.rs @@ -0,0 +1,52 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(typescript_custom_section)] +const SECRET_TS: &'static str = r#" +/** + * Represents secret. + */ +type Secret = { + /** + * A key ID identifying a secret (private key). + */ + id: string, + + /** + * Must have the same semantics as type ('type' field) of the corresponding method in DID Doc containing a public key. + */ + type: SecretType, + + /** + * Value of the secret (private key) + */ + secret_material: SecretMaterial, +} +"#; + +#[wasm_bindgen(typescript_custom_section)] +const SECRET_TYPE_TS: &'static str = r#" +/** + * Must have the same semantics as type ('type' field) of the corresponding method in DID Doc containing a public key. + */ +type SecretType = + "JsonWebKey2020" | "X25519KeyAgreementKey2019" + | "Ed25519VerificationKey2018" | "EcdsaSecp256k1VerificationKey2019" | string +"#; + +#[wasm_bindgen(typescript_custom_section)] +const SECRET_MATERIAL_TS: &'static str = r#" +/** + * Represents secret crypto material. + */ +type SecretMaterial = { + "JWK": any, +} | { + "Multibase": string, +} | { + "Base58": string, +} | { + "Hex": string, +} | { + "Other": any, +} +"#; diff --git a/wasm/src/secrets/secrets_resolver.rs b/wasm/src/secrets/secrets_resolver.rs new file mode 100644 index 0000000..f4b1be2 --- /dev/null +++ b/wasm/src/secrets/secrets_resolver.rs @@ -0,0 +1,132 @@ +use async_trait::async_trait; +use didcomm::{ + error::{err_msg, ErrorKind, Result as _Result, ResultContext, ResultExt}, + secrets::{Secret, SecretsResolver as _SecretsResolver}, +}; +use js_sys::Array; +use wasm_bindgen::{prelude::*, JsCast}; + +use crate::error::FromJsResult; + +#[wasm_bindgen] +extern "C" { + pub type SecretsResolver; + + // Promise resolves to JsValue(object) that can be deserialized to Secret + #[wasm_bindgen(structural, method, catch)] + pub async fn get_secret(this: &SecretsResolver, secret_id: &str) -> Result; + + // Promise resolves to JsValue(object) that can be casted to Array + #[wasm_bindgen(structural, method, catch)] + pub async fn find_secrets( + this: &SecretsResolver, + secret_ids: Array, + ) -> Result; +} + +#[wasm_bindgen(typescript_custom_section)] +const SECRET_RESOLVER_TS: &'static str = r#" +/** + * Interface for secrets resolver. + * Resolves secrets such as private keys to be used for signing and encryption. + */ +interface SecretsResolver { + /** + * Finds secret (usually private key) identified by the given key ID. + * + * @param `secret_id` the ID (in form of DID URL) identifying a secret + * + * @returns A secret (usually private key) or None of there is no secret for the given ID + * + * @throws DIDCommIoError - IO error in resolving process + * @throws DIDCommInvalidState - Code error or unexpected state was detected + * + * ``` + * let e = Error("Unble perform io operation"); + * e.name = "DIDCommIoError" + * throw e + * ``` + */ + get_secret(secret_id: string): Promise; + + /** + * Find all secrets that have one of the given IDs. + * Return secrets only for key IDs for which a secret is present. + * + * @param `secret_ids` the IDs find secrets for + * + * @returns possible empty list of all secrets that have one of the given IDs. + * + * @throws DIDCommIoError - IO error in resolving process + * @throws DIDCommInvalidState - Code error or unexpected state was detected + * + * Note to throw compatible error use code like this + * + * ``` + * let e = Error("Unble perform io operation"); + * e.name = "DIDCommIoError" + * throw e + * ``` + */ + find_secrets(secret_ids: Array): Promise>; +} +"#; + +// TODO: think is it possible to avoid ownership on DIDResolver +pub(crate) struct JsSecretsResolver(pub(crate) SecretsResolver); + +#[async_trait(?Send)] +impl _SecretsResolver for JsSecretsResolver { + async fn get_secret(&self, secret_id: &str) -> _Result> { + // TODO: better error conversion + let secret = self + .0 + .get_secret(secret_id) + .await + .from_js() + .context("Unable get secret")?; + + let secret: Option = secret.into_serde().kind( + ErrorKind::InvalidState, + "Unable deserialize Secret from JsValue", + )?; + + Ok(secret) + } + + async fn find_secrets<'a>(&self, secret_ids: &'a [&'a str]) -> _Result> { + let _secret_ids = secret_ids + .into_iter() + .map(|s| JsValue::from_str(s)) + .collect::(); + + // TODO: better error conversion + let found = self + .0 + .find_secrets(_secret_ids) + .await + .from_js() + .context("Unable find secrets")?; + + let found: Vec<_> = found + .dyn_into::() + .map_err(|_| { + err_msg( + ErrorKind::InvalidState, + "Unable covert secret ids JsValue to Array", + ) + })? + .iter() + .map(|v| v.as_string()) + .flatten() + .collect(); + + let found: Vec<_> = secret_ids + .iter() + .filter(|&s| found.iter().find(|_s| _s == s).is_some()) + .map(|&s| s) + .collect(); + + Ok(found) + } +} diff --git a/wasm/src/utils.rs b/wasm/src/utils.rs new file mode 100644 index 0000000..b1d7929 --- /dev/null +++ b/wasm/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} diff --git a/wasm/tests-js/.gitignore b/wasm/tests-js/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/wasm/tests-js/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/wasm/tests-js/.prettierrc.json b/wasm/tests-js/.prettierrc.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/wasm/tests-js/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/wasm/tests-js/jest-puppeteer.config.js b/wasm/tests-js/jest-puppeteer.config.js new file mode 100644 index 0000000..23cf1a6 --- /dev/null +++ b/wasm/tests-js/jest-puppeteer.config.js @@ -0,0 +1,8 @@ +module.exports = { + launch: { + dumpio: true, + headless: true, + args: ["--disable-infobars"], + }, + browserContext: "default", +}; diff --git a/wasm/tests-js/jest.config.js b/wasm/tests-js/jest.config.js new file mode 100644 index 0000000..06a3def --- /dev/null +++ b/wasm/tests-js/jest.config.js @@ -0,0 +1,7 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + testEnvironment: "node", + transform: { + "^.+\\.ts?$": "ts-jest", + }, +}; diff --git a/wasm/tests-js/jest.config.puppeteer.js b/wasm/tests-js/jest.config.puppeteer.js new file mode 100644 index 0000000..062411d --- /dev/null +++ b/wasm/tests-js/jest.config.puppeteer.js @@ -0,0 +1,7 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: "jest-puppeteer", + transform: { + "^.+\\.ts?$": "ts-jest", + }, +}; diff --git a/wasm/tests-js/package-lock.json b/wasm/tests-js/package-lock.json new file mode 100644 index 0000000..2737407 --- /dev/null +++ b/wasm/tests-js/package-lock.json @@ -0,0 +1,8382 @@ +{ + "name": "didcomm-tests", + "version": "0.2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "didcomm-tests", + "version": "0.2.0", + "license": "Apache-2.0", + "dependencies": { + "didcomm": "file:../pkg", + "typescript": "^4.5.2" + }, + "devDependencies": { + "@types/jest": "^27.0.2", + "@types/jest-environment-puppeteer": "^4.4.1", + "@types/puppeteer": "^5.4.4", + "jest": "^27.3.1", + "jest-puppeteer": "^6.0.0", + "prettier": "2.4.1", + "puppeteer": "^11.0.0", + "ts-jest": "^27.0.7", + "ts-node": "^10.4.0", + "tslint": "^6.1.3" + } + }, + "../pkg": { + "name": "didcomm", + "version": "0.2.0", + "license": "Apache-2.0" + }, + "node_modules/@babel/code-frame": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.0", + "@babel/helper-compilation-targets": "^7.16.0", + "@babel/helper-module-transforms": "^7.16.0", + "@babel/helpers": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/source-map": { + "version": "0.5.7", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.16.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/source-map": { + "version": "0.5.7", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.16.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.16.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-get-function-arity": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-replace-supers": "^7.16.0", + "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.16.0", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.15.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.16.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.3", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.16.3", + "dev": true, + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/types": "^7.16.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.16.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.0", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/parser": "^7.16.3", + "@babel/types": "^7.16.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.16.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.15.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.2.1", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.3.1", + "jest-util": "^27.3.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/core": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^27.3.1", + "@jest/reporters": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^27.3.0", + "jest-config": "^27.3.1", + "jest-haste-map": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.3.1", + "jest-resolve-dependencies": "^27.3.1", + "jest-runner": "^27.3.1", + "jest-runtime": "^27.3.1", + "jest-snapshot": "^27.3.1", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "jest-watcher": "^27.3.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "jest-mock": "^27.3.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.3.1", + "jest-mock": "^27.3.0", + "jest-util": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.3.1", + "@jest/types": "^27.2.5", + "expect": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^27.3.1", + "jest-resolve": "^27.3.1", + "jest-util": "^27.3.1", + "jest-worker": "^27.3.1", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/source-map": { + "version": "27.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^27.3.1", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.3.1", + "jest-runtime": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.2.5", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.3.1", + "jest-regex-util": "^27.0.6", + "jest-util": "^27.3.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/types": { + "version": "27.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.1.16", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.14.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.3.0" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "27.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/jest-environment-puppeteer": { + "version": "4.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": ">=24 <=26", + "@types/puppeteer": "*", + "jest-environment-node": ">=24 <=26" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/@jest/environment": { + "version": "26.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/@jest/fake-timers": { + "version": "26.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^26.6.2", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/@jest/types": { + "version": "26.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/@sinonjs/fake-timers": { + "version": "6.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/@types/yargs": { + "version": "15.0.14", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/jest-environment-node": { + "version": "26.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/jest-message-util": { + "version": "26.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/jest-mock": { + "version": "26.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^26.6.2", + "@types/node": "*" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/jest-util": { + "version": "26.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@types/jest-environment-puppeteer/node_modules/pretty-format": { + "version": "26.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/node": { + "version": "16.11.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prettier": { + "version": "2.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/puppeteer": { + "version": "5.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "16.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "20.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.9.2", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abab": { + "version": "2.0.5", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.5.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/axios": { + "version": "0.21.4", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/babel-jest": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^27.2.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.1.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "27.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^27.2.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/browserslist": { + "version": "4.17.6", + "dev": true, + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001274", + "electron-to-chromium": "^1.3.886", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001279", + "dev": true, + "license": "CC-BY-4.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "dev": true, + "license": "ISC" + }, + "node_modules/ci-info": { + "version": "3.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "7.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone-deep": { + "version": "0.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "dev": true, + "license": "MIT" + }, + "node_modules/cwd": { + "version": "0.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/data-urls": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/debug": { + "version": "4.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "0.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.901419", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/didcomm": { + "resolved": "../pkg", + "link": true + }, + "node_modules/diff": { + "version": "4.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "27.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/domexception": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.894", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.8.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "2.0.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "os-homedir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.3.1", + "jest-matcher-utils": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-regex-util": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/expect-puppeteer": { + "version": "6.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/expect/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-file-up": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-pkg": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "find-file-up": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-process": { + "version": "1.4.5", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "debug": "^4.1.1" + }, + "bin": { + "find-process": "bin/find-process.js" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.14.5", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/form-data": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fs-exists-sync": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-modules": { + "version": "0.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "0.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.8", + "dev": true, + "license": "ISC" + }, + "node_modules/has": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/import-local": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "dev": true, + "license": "ISC" + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "dev": true, + "license": "MIT" + }, + "node_modules/is-ci": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-ci/node_modules/ci-info": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-windows": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.5", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^27.3.1", + "import-local": "^3.0.2", + "jest-cli": "^27.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.3.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.3.1", + "jest-matcher-utils": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-runtime": "^27.3.1", + "jest-snapshot": "^27.3.1", + "jest-util": "^27.3.1", + "pretty-format": "^27.3.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-cli": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/types": "^27.2.5", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "jest-config": "^27.3.1", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^27.3.1", + "@jest/types": "^27.2.5", + "babel-jest": "^27.3.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-circus": "^27.3.1", + "jest-environment-jsdom": "^27.3.1", + "jest-environment-node": "^27.3.1", + "jest-get-type": "^27.3.1", + "jest-jasmine2": "^27.3.1", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.3.1", + "jest-runner": "^27.3.1", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "micromatch": "^4.0.4", + "pretty-format": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-dev-server": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "cwd": "^0.10.0", + "find-process": "^1.4.5", + "prompts": "^2.4.1", + "spawnd": "^6.0.0", + "tree-kill": "^1.2.2", + "wait-on": "^6.0.0" + } + }, + "node_modules/jest-diff": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.3.1", + "pretty-format": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "27.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "chalk": "^4.0.0", + "jest-get-type": "^27.3.1", + "jest-util": "^27.3.1", + "pretty-format": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.3.1", + "@jest/fake-timers": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "jest-mock": "^27.3.0", + "jest-util": "^27.3.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^27.3.1", + "@jest/fake-timers": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "jest-mock": "^27.3.0", + "jest-util": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-puppeteer": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "cwd": "^0.10.0", + "jest-dev-server": "^6.0.0", + "jest-environment-node": "^27.0.1", + "merge-deep": "^3.0.3" + } + }, + "node_modules/jest-get-type": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^27.0.6", + "jest-serializer": "^27.0.6", + "jest-util": "^27.3.1", + "jest-worker": "^27.3.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^27.3.1", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.3.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.3.1", + "jest-matcher-utils": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-runtime": "^27.3.1", + "jest-snapshot": "^27.3.1", + "jest-util": "^27.3.1", + "pretty-format": "^27.3.1", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^27.3.1", + "pretty-format": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.3.1", + "jest-get-type": "^27.3.1", + "pretty-format": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.2.5", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.3.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock": { + "version": "27.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-puppeteer": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "expect-puppeteer": "^6.0.0", + "jest-environment-puppeteer": "^6.0.0" + }, + "peerDependencies": { + "puppeteer": ">= 1.5.0" + } + }, + "node_modules/jest-regex-util": { + "version": "27.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.3.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "jest-regex-util": "^27.0.6", + "jest-snapshot": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^27.3.1", + "@jest/environment": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-docblock": "^27.0.6", + "jest-environment-jsdom": "^27.3.1", + "jest-environment-node": "^27.3.1", + "jest-haste-map": "^27.3.1", + "jest-leak-detector": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-resolve": "^27.3.1", + "jest-runtime": "^27.3.1", + "jest-util": "^27.3.1", + "jest-worker": "^27.3.1", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^27.3.1", + "@jest/environment": "^27.3.1", + "@jest/globals": "^27.3.1", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.3.1", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-mock": "^27.3.0", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.3.1", + "jest-snapshot": "^27.3.1", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^16.2.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-serializer": { + "version": "27.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/parser": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.3.1", + "graceful-fs": "^4.2.4", + "jest-diff": "^27.3.1", + "jest-get-type": "^27.3.1", + "jest-haste-map": "^27.3.1", + "jest-matcher-utils": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-resolve": "^27.3.1", + "jest-util": "^27.3.1", + "natural-compare": "^1.4.0", + "pretty-format": "^27.3.1", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.3.5", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.4", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.3.1", + "leven": "^3.1.0", + "pretty-format": "^27.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.3.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-worker": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.4.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.0", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "16.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-deep": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "dev": true, + "license": "MIT" + }, + "node_modules/mixin-object": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-object/node_modules/for-in": { + "version": "0.1.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.6.5", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-modules-regexp": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/pend": { + "version": "1.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "node-modules-regexp": "^1.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/pretty-format": { + "version": "27.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^27.2.5", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.8.0", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-11.0.0.tgz", + "integrity": "sha512-6rPFqN1ABjn4shgOICGDBITTRV09EjXVqhDERBDKwCLz0UyBxeeBH6Ay0vQUJ84VACmlxwzOIzVEJXThcF3aNg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "debug": "4.3.2", + "devtools-protocol": "0.0.901419", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.5", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer/node_modules/ws": { + "version": "8.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rxjs": { + "version": "7.4.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "~2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "5.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shallow-clone": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/kind-of": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shallow-clone/node_modules/lazy-cache": { + "version": "0.2.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.5", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.20", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spawnd": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "exit": "^0.1.2", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/string-length": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/throat": { + "version": "6.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/through": { + "version": "2.3.8", + "dev": true, + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-jest": { + "version": "27.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^27.0.0", + "json5": "2.x", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@types/jest": "^27.0.0", + "babel-jest": ">=27.0.0 <28", + "jest": "^27.0.0", + "typescript": ">=3.8 <5.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/jest": { + "optional": true + }, + "babel-jest": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.3.5", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-node": { + "version": "10.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/tslib": { + "version": "2.1.0", + "dev": true, + "license": "0BSD" + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/tslint/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/tslint/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/tslint/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tslint/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tslint/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/source-map": { + "version": "0.7.3", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wait-on": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^0.21.1", + "joi": "^17.4.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^7.1.0" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10.4" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "8.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "16.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.0" + } + }, + "@babel/compat-data": { + "version": "7.16.0", + "dev": true + }, + "@babel/core": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.0", + "@babel/helper-compilation-targets": "^7.16.0", + "@babel/helper-module-transforms": "^7.16.0", + "@babel/helpers": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/types": "^7.16.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "dev": true + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.16.3", + "dev": true, + "requires": { + "@babel/compat-data": "^7.16.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.17.5", + "semver": "^6.3.0" + } + }, + "@babel/helper-function-name": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.16.0", + "@babel/template": "^7.16.0", + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-module-transforms": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.16.0", + "@babel/helper-replace-supers": "^7.16.0", + "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.16.0", + "@babel/helper-optimise-call-expression": "^7.16.0", + "@babel/traverse": "^7.16.0", + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-simple-access": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/types": "^7.16.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.14.5", + "dev": true + }, + "@babel/helpers": { + "version": "7.16.3", + "dev": true, + "requires": { + "@babel/template": "^7.16.0", + "@babel/traverse": "^7.16.3", + "@babel/types": "^7.16.0" + } + }, + "@babel/highlight": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.16.3", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/template": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.0", + "@babel/parser": "^7.16.0", + "@babel/types": "^7.16.0" + } + }, + "@babel/traverse": { + "version": "7.16.3", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.0", + "@babel/generator": "^7.16.0", + "@babel/helper-function-name": "^7.16.0", + "@babel/helper-hoist-variables": "^7.16.0", + "@babel/helper-split-export-declaration": "^7.16.0", + "@babel/parser": "^7.16.3", + "@babel/types": "^7.16.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.16.0", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.15.7", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "dev": true + }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@hapi/hoek": { + "version": "9.2.1", + "dev": true + }, + "@hapi/topo": { + "version": "5.1.0", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "dev": true + }, + "@jest/console": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.3.1", + "jest-util": "^27.3.1", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/console": "^27.3.1", + "@jest/reporters": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^27.3.0", + "jest-config": "^27.3.1", + "jest-haste-map": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.3.1", + "jest-resolve-dependencies": "^27.3.1", + "jest-runner": "^27.3.1", + "jest-runtime": "^27.3.1", + "jest-snapshot": "^27.3.1", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "jest-watcher": "^27.3.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/fake-timers": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "jest-mock": "^27.3.0" + } + }, + "@jest/fake-timers": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.3.1", + "jest-mock": "^27.3.0", + "jest-util": "^27.3.1" + } + }, + "@jest/globals": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/environment": "^27.3.1", + "@jest/types": "^27.2.5", + "expect": "^27.3.1" + } + }, + "@jest/reporters": { + "version": "27.3.1", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^27.3.1", + "jest-resolve": "^27.3.1", + "jest-util": "^27.3.1", + "jest-worker": "^27.3.1", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + } + }, + "@jest/source-map": { + "version": "27.0.6", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/console": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/test-result": "^27.3.1", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.3.1", + "jest-runtime": "^27.3.1" + } + }, + "@jest/transform": { + "version": "27.3.1", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.2.5", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.3.1", + "jest-regex-util": "^27.0.6", + "jest-util": "^27.3.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, + "@jest/types": { + "version": "27.2.5", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@sideway/address": { + "version": "4.1.2", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.0", + "dev": true + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.8.3", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "8.1.0", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "dev": true + }, + "@tsconfig/node10": { + "version": "1.0.8", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.16", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.3", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.14.2", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/graceful-fs": { + "version": "4.1.5", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "27.0.2", + "dev": true, + "requires": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "@types/jest-environment-puppeteer": { + "version": "4.4.1", + "dev": true, + "requires": { + "@jest/types": ">=24 <=26", + "@types/puppeteer": "*", + "jest-environment-node": ">=24 <=26" + }, + "dependencies": { + "@jest/environment": { + "version": "26.6.2", + "dev": true, + "requires": { + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2" + } + }, + "@jest/fake-timers": { + "version": "26.6.2", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.6.2", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "@jest/types": { + "version": "26.6.2", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@types/yargs": { + "version": "15.0.14", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "jest-environment-node": { + "version": "26.6.2", + "dev": true, + "requires": { + "@jest/environment": "^26.6.2", + "@jest/fake-timers": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/node": "*", + "jest-mock": "^26.6.2", + "jest-util": "^26.6.2" + } + }, + "jest-message-util": { + "version": "26.6.2", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + } + }, + "jest-mock": { + "version": "26.6.2", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*" + } + }, + "jest-util": { + "version": "26.6.2", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, + "pretty-format": { + "version": "26.6.2", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + } + } + }, + "@types/node": { + "version": "16.11.7", + "dev": true + }, + "@types/prettier": { + "version": "2.4.1", + "dev": true + }, + "@types/puppeteer": { + "version": "5.4.4", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.1", + "dev": true + }, + "@types/yargs": { + "version": "16.0.4", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.1", + "dev": true + }, + "@types/yauzl": { + "version": "2.9.2", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "abab": { + "version": "2.0.5", + "dev": true + }, + "acorn": { + "version": "8.5.0", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-union": { + "version": "3.1.0", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "dev": true + }, + "axios": { + "version": "0.21.4", + "dev": true, + "requires": { + "follow-redirects": "^1.14.0" + } + }, + "babel-jest": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^27.2.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "5.1.0", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + } + } + }, + "babel-plugin-jest-hoist": { + "version": "27.2.0", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "27.2.0", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^27.2.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "dev": true + }, + "bl": { + "version": "4.1.0", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "dev": true + }, + "browserslist": { + "version": "4.17.6", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001274", + "electron-to-chromium": "^1.3.886", + "escalade": "^3.1.1", + "node-releases": "^2.0.1", + "picocolors": "^1.0.0" + } + }, + "bs-logger": { + "version": "0.2.6", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "dev": true + }, + "buffer-from": { + "version": "1.1.2", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001279", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "dev": true + }, + "chownr": { + "version": "1.1.4", + "dev": true + }, + "ci-info": { + "version": "3.2.0", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone-deep": { + "version": "0.2.4", + "dev": true, + "requires": { + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" + } + }, + "co": { + "version": "4.6.0", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "5.1.0", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "dev": true + }, + "convert-source-map": { + "version": "1.8.0", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "create-require": { + "version": "1.1.1", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "cssom": { + "version": "0.4.4", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "dev": true + } + } + }, + "cwd": { + "version": "0.10.0", + "dev": true, + "requires": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + } + }, + "data-urls": { + "version": "2.0.0", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "debug": { + "version": "4.3.2", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.3.1", + "dev": true + }, + "dedent": { + "version": "0.7.0", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "dev": true + }, + "devtools-protocol": { + "version": "0.0.901419", + "dev": true + }, + "didcomm": { + "version": "file:../pkg" + }, + "diff": { + "version": "4.0.2", + "dev": true + }, + "diff-sequences": { + "version": "27.0.6", + "dev": true + }, + "domexception": { + "version": "2.0.1", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "dev": true + } + } + }, + "electron-to-chromium": { + "version": "1.3.894", + "dev": true + }, + "emittery": { + "version": "0.8.1", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "escalade": { + "version": "3.1.1", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "dev": true + }, + "escodegen": { + "version": "2.0.0", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "dev": true + }, + "execa": { + "version": "5.1.1", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "dev": true + }, + "expand-tilde": { + "version": "1.2.2", + "dev": true, + "requires": { + "os-homedir": "^1.0.1" + } + }, + "expect": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "ansi-styles": "^5.0.0", + "jest-get-type": "^27.3.1", + "jest-matcher-utils": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-regex-util": "^27.0.6" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "dev": true + } + } + }, + "expect-puppeteer": { + "version": "6.0.0", + "dev": true + }, + "extract-zip": { + "version": "2.0.1", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "dev": true + }, + "fb-watchman": { + "version": "2.0.1", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fd-slicer": { + "version": "1.1.0", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "fill-range": { + "version": "7.0.1", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-file-up": { + "version": "0.1.3", + "dev": true, + "requires": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + } + }, + "find-pkg": { + "version": "0.1.2", + "dev": true, + "requires": { + "find-file-up": "^0.1.2" + } + }, + "find-process": { + "version": "1.4.5", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "commander": "^5.1.0", + "debug": "^4.1.1" + } + }, + "find-up": { + "version": "4.1.0", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "follow-redirects": { + "version": "1.14.5", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "form-data": { + "version": "3.0.1", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fs-constants": { + "version": "1.0.0", + "dev": true + }, + "fs-exists-sync": { + "version": "0.1.0", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "dev": true + }, + "glob": { + "version": "7.2.0", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-modules": { + "version": "0.2.3", + "dev": true, + "requires": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + } + }, + "global-prefix": { + "version": "0.1.5", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "globals": { + "version": "11.12.0", + "dev": true + }, + "graceful-fs": { + "version": "4.2.8", + "dev": true + }, + "has": { + "version": "1.0.3", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "dev": true + }, + "homedir-polyfill": { + "version": "1.0.3", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "html-escaper": { + "version": "2.0.2", + "dev": true + }, + "http-proxy-agent": { + "version": "4.0.1", + "dev": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "2.1.0", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "dev": true + }, + "import-local": { + "version": "3.0.3", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "dev": true + }, + "ini": { + "version": "1.3.8", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + }, + "dependencies": { + "ci-info": { + "version": "2.0.0", + "dev": true + } + } + }, + "is-core-module": { + "version": "2.8.0", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extendable": { + "version": "0.1.1", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "dev": true + }, + "is-windows": { + "version": "0.2.0", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.0.5", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/core": "^27.3.1", + "import-local": "^3.0.2", + "jest-cli": "^27.3.1" + } + }, + "jest-changed-files": { + "version": "27.3.0", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "execa": "^5.0.0", + "throat": "^6.0.1" + } + }, + "jest-circus": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/environment": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.3.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.3.1", + "jest-matcher-utils": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-runtime": "^27.3.1", + "jest-snapshot": "^27.3.1", + "jest-util": "^27.3.1", + "pretty-format": "^27.3.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + } + }, + "jest-cli": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/core": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/types": "^27.2.5", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "jest-config": "^27.3.1", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + } + }, + "jest-config": { + "version": "27.3.1", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^27.3.1", + "@jest/types": "^27.2.5", + "babel-jest": "^27.3.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-circus": "^27.3.1", + "jest-environment-jsdom": "^27.3.1", + "jest-environment-node": "^27.3.1", + "jest-get-type": "^27.3.1", + "jest-jasmine2": "^27.3.1", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.3.1", + "jest-runner": "^27.3.1", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "micromatch": "^4.0.4", + "pretty-format": "^27.3.1" + } + }, + "jest-dev-server": { + "version": "6.0.0", + "dev": true, + "requires": { + "chalk": "^4.1.2", + "cwd": "^0.10.0", + "find-process": "^1.4.5", + "prompts": "^2.4.1", + "spawnd": "^6.0.0", + "tree-kill": "^1.2.2", + "wait-on": "^6.0.0" + } + }, + "jest-diff": { + "version": "27.3.1", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.3.1", + "pretty-format": "^27.3.1" + } + }, + "jest-docblock": { + "version": "27.0.6", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "chalk": "^4.0.0", + "jest-get-type": "^27.3.1", + "jest-util": "^27.3.1", + "pretty-format": "^27.3.1" + } + }, + "jest-environment-jsdom": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/environment": "^27.3.1", + "@jest/fake-timers": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "jest-mock": "^27.3.0", + "jest-util": "^27.3.1", + "jsdom": "^16.6.0" + } + }, + "jest-environment-node": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/environment": "^27.3.1", + "@jest/fake-timers": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "jest-mock": "^27.3.0", + "jest-util": "^27.3.1" + } + }, + "jest-environment-puppeteer": { + "version": "6.0.0", + "dev": true, + "requires": { + "chalk": "^4.1.1", + "cwd": "^0.10.0", + "jest-dev-server": "^6.0.0", + "jest-environment-node": "^27.0.1", + "merge-deep": "^3.0.3" + } + }, + "jest-get-type": { + "version": "27.3.1", + "dev": true + }, + "jest-haste-map": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^27.0.6", + "jest-serializer": "^27.0.6", + "jest-util": "^27.3.1", + "jest-worker": "^27.3.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "27.3.1", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^27.3.1", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.3.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.3.1", + "jest-matcher-utils": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-runtime": "^27.3.1", + "jest-snapshot": "^27.3.1", + "jest-util": "^27.3.1", + "pretty-format": "^27.3.1", + "throat": "^6.0.1" + } + }, + "jest-leak-detector": { + "version": "27.3.1", + "dev": true, + "requires": { + "jest-get-type": "^27.3.1", + "pretty-format": "^27.3.1" + } + }, + "jest-matcher-utils": { + "version": "27.3.1", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^27.3.1", + "jest-get-type": "^27.3.1", + "pretty-format": "^27.3.1" + } + }, + "jest-message-util": { + "version": "27.3.1", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.2.5", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "pretty-format": "^27.3.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "27.3.0", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@types/node": "*" + } + }, + "jest-pnp-resolver": { + "version": "1.2.2", + "dev": true, + "requires": {} + }, + "jest-puppeteer": { + "version": "6.0.0", + "dev": true, + "requires": { + "expect-puppeteer": "^6.0.0", + "jest-environment-puppeteer": "^6.0.0" + } + }, + "jest-regex-util": { + "version": "27.0.6", + "dev": true + }, + "jest-resolve": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.3.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "jest-regex-util": "^27.0.6", + "jest-snapshot": "^27.3.1" + } + }, + "jest-runner": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/console": "^27.3.1", + "@jest/environment": "^27.3.1", + "@jest/test-result": "^27.3.1", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-docblock": "^27.0.6", + "jest-environment-jsdom": "^27.3.1", + "jest-environment-node": "^27.3.1", + "jest-haste-map": "^27.3.1", + "jest-leak-detector": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-resolve": "^27.3.1", + "jest-runtime": "^27.3.1", + "jest-util": "^27.3.1", + "jest-worker": "^27.3.1", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + } + }, + "jest-runtime": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/console": "^27.3.1", + "@jest/environment": "^27.3.1", + "@jest/globals": "^27.3.1", + "@jest/source-map": "^27.0.6", + "@jest/test-result": "^27.3.1", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-mock": "^27.3.0", + "jest-regex-util": "^27.0.6", + "jest-resolve": "^27.3.1", + "jest-snapshot": "^27.3.1", + "jest-util": "^27.3.1", + "jest-validate": "^27.3.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^16.2.0" + } + }, + "jest-serializer": { + "version": "27.0.6", + "dev": true, + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "27.3.1", + "dev": true, + "requires": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/parser": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.3.1", + "graceful-fs": "^4.2.4", + "jest-diff": "^27.3.1", + "jest-get-type": "^27.3.1", + "jest-haste-map": "^27.3.1", + "jest-matcher-utils": "^27.3.1", + "jest-message-util": "^27.3.1", + "jest-resolve": "^27.3.1", + "jest-util": "^27.3.1", + "natural-compare": "^1.4.0", + "pretty-format": "^27.3.1", + "semver": "^7.3.2" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jest-util": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.4", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.3.1", + "leven": "^3.1.0", + "pretty-format": "^27.3.1" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "dev": true + } + } + }, + "jest-watcher": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/test-result": "^27.3.1", + "@jest/types": "^27.2.5", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.3.1", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "27.3.1", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "joi": { + "version": "17.4.2", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.0", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsdom": { + "version": "16.7.0", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "dev": true + }, + "json5": { + "version": "2.2.0", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "kind-of": { + "version": "3.2.2", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "kleur": { + "version": "3.0.3", + "dev": true + }, + "lazy-cache": { + "version": "1.0.4", + "dev": true + }, + "leven": { + "version": "3.1.0", + "dev": true + }, + "levn": { + "version": "0.3.0", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "locate-path": { + "version": "5.0.0", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-deep": { + "version": "3.0.3", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + } + }, + "merge-stream": { + "version": "2.0.0", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "mime-db": { + "version": "1.51.0", + "dev": true + }, + "mime-types": { + "version": "2.1.34", + "dev": true, + "requires": { + "mime-db": "1.51.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "dev": true + }, + "mixin-object": { + "version": "2.0.1", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mkdirp-classic": { + "version": "0.5.3", + "dev": true + }, + "ms": { + "version": "2.1.2", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "dev": true + }, + "node-fetch": { + "version": "2.6.5", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "node-int64": { + "version": "0.4.0", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "dev": true + }, + "node-releases": { + "version": "2.0.1", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "nwsapi": { + "version": "2.2.0", + "dev": true + }, + "once": { + "version": "1.4.0", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "dev": true + }, + "parse-passwd": { + "version": "1.0.0", + "dev": true + }, + "parse5": { + "version": "6.0.1", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "dev": true + }, + "pend": { + "version": "1.2.0", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "dev": true + }, + "prettier": { + "version": "2.4.1", + "dev": true + }, + "pretty-format": { + "version": "27.3.1", + "dev": true, + "requires": { + "@jest/types": "^27.2.5", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "dev": true + } + } + }, + "progress": { + "version": "2.0.3", + "dev": true + }, + "prompts": { + "version": "2.4.2", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "dev": true + }, + "psl": { + "version": "1.8.0", + "dev": true + }, + "pump": { + "version": "3.0.0", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "dev": true + }, + "puppeteer": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-11.0.0.tgz", + "integrity": "sha512-6rPFqN1ABjn4shgOICGDBITTRV09EjXVqhDERBDKwCLz0UyBxeeBH6Ay0vQUJ84VACmlxwzOIzVEJXThcF3aNg==", + "dev": true, + "requires": { + "debug": "4.3.2", + "devtools-protocol": "0.0.901419", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "node-fetch": "2.6.5", + "pkg-dir": "4.2.0", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "ws": "8.2.3" + }, + "dependencies": { + "ws": { + "version": "8.2.3", + "dev": true, + "requires": {} + } + } + }, + "react-is": { + "version": "17.0.2", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "dev": true + }, + "resolve": { + "version": "1.20.0", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-dir": { + "version": "0.1.1", + "dev": true, + "requires": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + } + }, + "resolve-from": { + "version": "5.0.0", + "dev": true + }, + "resolve.exports": { + "version": "1.1.0", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rxjs": { + "version": "7.4.0", + "dev": true, + "requires": { + "tslib": "~2.1.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "dev": true + }, + "saxes": { + "version": "5.0.1", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "6.3.0", + "dev": true + }, + "shallow-clone": { + "version": "0.1.2", + "dev": true, + "requires": { + "is-extendable": "^0.1.1", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", + "mixin-object": "^2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "2.0.1", + "dev": true, + "requires": { + "is-buffer": "^1.0.2" + } + }, + "lazy-cache": { + "version": "0.2.7", + "dev": true + } + } + }, + "shebang-command": { + "version": "2.0.0", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "dev": true + }, + "signal-exit": { + "version": "3.0.5", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "dev": true + }, + "slash": { + "version": "3.0.0", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "dev": true + }, + "source-map-support": { + "version": "0.5.20", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spawnd": { + "version": "6.0.0", + "dev": true, + "requires": { + "exit": "^0.1.2", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "dev": true + }, + "stack-utils": { + "version": "2.0.5", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "dev": true + } + } + }, + "string-length": { + "version": "4.0.2", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.2.0", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "dev": true + }, + "tar-fs": { + "version": "2.1.1", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "terminal-link": { + "version": "2.1.1", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "throat": { + "version": "6.0.1", + "dev": true + }, + "through": { + "version": "2.3.8", + "dev": true + }, + "tmpl": { + "version": "1.0.5", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "4.0.0", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tr46": { + "version": "2.1.0", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "tree-kill": { + "version": "1.2.2", + "dev": true + }, + "ts-jest": { + "version": "27.0.7", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^27.0.0", + "json5": "2.x", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "ts-node": { + "version": "10.4.0", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn-walk": { + "version": "8.2.0", + "dev": true + } + } + }, + "tslib": { + "version": "2.1.0", + "dev": true + }, + "tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "type-check": { + "version": "0.3.2", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==" + }, + "unbzip2-stream": { + "version": "1.4.3", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "universalify": { + "version": "0.1.2", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "dev": true + }, + "v8-to-istanbul": { + "version": "8.1.0", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "dev": true + } + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "wait-on": { + "version": "6.0.0", + "dev": true, + "requires": { + "axios": "^0.21.1", + "joi": "^17.4.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^7.1.0" + } + }, + "walker": { + "version": "1.0.8", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "dev": true + }, + "whatwg-url": { + "version": "8.7.0", + "dev": true, + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + } + }, + "which": { + "version": "2.0.2", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.5.5", + "dev": true, + "requires": {} + }, + "xml-name-validator": { + "version": "3.0.0", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "yn": { + "version": "3.1.1", + "dev": true + } + } +} diff --git a/wasm/tests-js/package.json b/wasm/tests-js/package.json new file mode 100644 index 0000000..d623942 --- /dev/null +++ b/wasm/tests-js/package.json @@ -0,0 +1,29 @@ +{ + "name": "didcomm-tests", + "version": "0.2.0", + "description": "JS tests for didcomm", + "scripts": { + "test": "jest", + "test-puppeteer": "jest --config=jest.config.puppeteer.js", + "check": "tslint -c tslint.json 'src/**/*.ts'", + "format": "prettier --write ." + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "didcomm": "file:../pkg", + "typescript": "^4.5.2" + }, + "devDependencies": { + "@types/jest": "^27.0.2", + "@types/jest-environment-puppeteer": "^4.4.1", + "@types/puppeteer": "^5.4.4", + "jest": "^27.3.1", + "jest-puppeteer": "^6.0.0", + "prettier": "2.4.1", + "puppeteer": "^11.0.0", + "ts-jest": "^27.0.7", + "ts-node": "^10.4.0", + "tslint": "^6.1.3" + } +} diff --git a/wasm/tests-js/src/forward/try-parse-forward.test.ts b/wasm/tests-js/src/forward/try-parse-forward.test.ts new file mode 100644 index 0000000..831cae6 --- /dev/null +++ b/wasm/tests-js/src/forward/try-parse-forward.test.ts @@ -0,0 +1,23 @@ +import { Message, ParsedForward } from "didcomm"; +import { MESSAGE_SIMPLE, FORWARD_MESSAGE } from "../test-vectors"; + +// TODO: more tests +test.each([ + { + case: "Not Forward", + message: MESSAGE_SIMPLE, + }, +])("Message.try-parse-forward handles $case", async ({ message }) => { + const res = message.try_parse_forward(); + expect(res).toBeNull(); +}); + +test.each([ + { + case: "Forward", + message: FORWARD_MESSAGE, + }, +])("Message.try-parse-forward handles $case", async ({ message }) => { + const res = message.try_parse_forward(); + expect(res).toEqual(expect.anything()); +}); diff --git a/wasm/tests-js/src/forward/wrap-in-forward.test.ts b/wasm/tests-js/src/forward/wrap-in-forward.test.ts new file mode 100644 index 0000000..c140be3 --- /dev/null +++ b/wasm/tests-js/src/forward/wrap-in-forward.test.ts @@ -0,0 +1,42 @@ +import { Message } from "didcomm"; +import { + IMESSAGE_SIMPLE, + ExampleDIDResolver, + ALICE_DID_DOC, + BOB_DID_DOC, + ALICE_DID, +} from "../test-vectors"; + +// TODO: more tests +test.each([ + { + case: "Simple", + message: IMESSAGE_SIMPLE, + headers: { header1: "aaa", header2: "bbb" }, + to: ALICE_DID, + routing_keys: ["did:example:bob#key-x25519-1"], + enc_alg_anon: "A256cbcHs512EcdhEsA256kw", + did_resolver: new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]), + }, +])( + "Message.wrap-in-forward handles $case", + async ({ + message, + headers, + to, + routing_keys, + enc_alg_anon, + did_resolver, + }) => { + const res = await Message.wrap_in_forward( + JSON.stringify(message), + headers, + to, + routing_keys, + enc_alg_anon, + did_resolver + ); + expect(typeof res).toStrictEqual("string"); + expect(res).toContain("ciphertext"); + } +); diff --git a/wasm/tests-js/src/from_prior/new.test.ts b/wasm/tests-js/src/from_prior/new.test.ts new file mode 100644 index 0000000..38afee5 --- /dev/null +++ b/wasm/tests-js/src/from_prior/new.test.ts @@ -0,0 +1,10 @@ +import { FromPrior } from "didcomm"; +import { IFROM_PRIOR_FULL, IFROM_PRIOR_MINIMAL } from "../test-vectors"; + +test.each([ + { val: IFROM_PRIOR_MINIMAL, case: "Minimal" }, + { val: IFROM_PRIOR_FULL, case: "Full" }, +])("FromPrior.new works for $case", ({ val }) => { + const fromPrior = new FromPrior(val); + expect(fromPrior.as_value()).toStrictEqual(val); +}); diff --git a/wasm/tests-js/src/from_prior/pack.test.ts b/wasm/tests-js/src/from_prior/pack.test.ts new file mode 100644 index 0000000..9d6604c --- /dev/null +++ b/wasm/tests-js/src/from_prior/pack.test.ts @@ -0,0 +1,54 @@ +import { FromPrior } from "didcomm"; +import { + ALICE_DID_DOC, + CHARLIE_DID_DOC, + CHARLIE_SECRETS, + CHARLIE_SECRET_AUTH_KEY_ED25519, + ExampleDIDResolver, + ExampleSecretsResolver, + FROM_PRIOR_FULL, + FROM_PRIOR_MINIMAL, +} from "../test-vectors"; + +test.each([ + { + fromPrior: FROM_PRIOR_MINIMAL, + issuerKid: null, + expKid: CHARLIE_SECRET_AUTH_KEY_ED25519.id, + case: "Minimal", + }, + { + fromPrior: FROM_PRIOR_FULL, + issuerKid: null, + expKid: CHARLIE_SECRET_AUTH_KEY_ED25519.id, + case: "Full", + }, + { + fromPrior: FROM_PRIOR_FULL, + issuerKid: CHARLIE_SECRET_AUTH_KEY_ED25519.id, + expKid: CHARLIE_SECRET_AUTH_KEY_ED25519.id, + case: "Explicit key", + }, +])( + "FromPrior.pack works for $case", + async ({ fromPrior, issuerKid, expKid }) => { + const didResolver = new ExampleDIDResolver([ + ALICE_DID_DOC, + CHARLIE_DID_DOC, + ]); + + const secretsResolver = new ExampleSecretsResolver(CHARLIE_SECRETS); + + const [packed, kid] = await fromPrior.pack( + issuerKid, + didResolver, + secretsResolver + ); + + expect(typeof packed).toStrictEqual("string"); + expect(kid).toStrictEqual(expKid); + + const [unpacked, _] = await FromPrior.unpack(packed, didResolver); + expect(unpacked.as_value()).toStrictEqual(fromPrior.as_value()); + } +); diff --git a/wasm/tests-js/src/from_prior/unpack.test.ts b/wasm/tests-js/src/from_prior/unpack.test.ts new file mode 100644 index 0000000..5b1ab4a --- /dev/null +++ b/wasm/tests-js/src/from_prior/unpack.test.ts @@ -0,0 +1,43 @@ +import { FromPrior } from "didcomm"; +import { + ALICE_DID_DOC, + CHARLIE_DID_DOC, + CHARLIE_SECRET_AUTH_KEY_ED25519, + ExampleDIDResolver, + FROM_PRIOR_JWT_FULL, + FROM_PRIOR_JWT_INVALID, + FROM_PRIOR_JWT_INVALID_SIGNATURE, + IFROM_PRIOR_FULL, +} from "../test-vectors"; + +test("FromPrior.unpack works", async () => { + const didResolver = new ExampleDIDResolver([ALICE_DID_DOC, CHARLIE_DID_DOC]); + + const [fromPrior, issuerKid] = await FromPrior.unpack( + FROM_PRIOR_JWT_FULL, + didResolver + ); + + // TODO: Use toStrictEq check after reduction of FromPrior fields + expect(fromPrior.as_value()).toMatchObject(IFROM_PRIOR_FULL); + + expect(issuerKid).toStrictEqual(CHARLIE_SECRET_AUTH_KEY_ED25519.id); +}); + +test.each([ + { + jwt: FROM_PRIOR_JWT_INVALID, + exp_err: "Malformed: Unable to parse compactly serialized JWS", + case: "Malformed", + }, + { + jwt: FROM_PRIOR_JWT_INVALID_SIGNATURE, + exp_err: + "Malformed: Unable to verify from_prior signature: Unable decode signature: Invalid last symbol 66, offset 85.", + case: "Invalid signature", + }, +])("FromPrior.unpack handles $case", async ({ jwt, exp_err }) => { + const didResolver = new ExampleDIDResolver([ALICE_DID_DOC, CHARLIE_DID_DOC]); + const res = FromPrior.unpack(jwt, didResolver); + await expect(res).rejects.toThrowError(exp_err); +}); diff --git a/wasm/tests-js/src/message/errors.test.ts b/wasm/tests-js/src/message/errors.test.ts new file mode 100644 index 0000000..0224566 --- /dev/null +++ b/wasm/tests-js/src/message/errors.test.ts @@ -0,0 +1,266 @@ +import { + ALICE_DID, + ALICE_DID_DOC, + ALICE_SECRETS, + BOB_DID, + BOB_DID_DOC, + ExampleDIDResolver, + ExampleSecretsResolver, + MESSAGE_SIMPLE, + MockDIDResolver, + MockSecretsResolver, +} from "../test-vectors"; + +test.each([ + { + err: (() => { + const e = Error("Some Malformed error"); + e.name = "DIDCommMalformed"; + return e; + })(), + exp_err: + "Malformed: Unable resolve recipient did: Unable resolve did: Some Malformed error", + case: "Malformed", + }, + { + err: (() => { + const e = Error("Some IoError error"); + e.name = "DIDCommIoError"; + return e; + })(), + exp_err: + "IO error: Unable resolve recipient did: Unable resolve did: Some IoError error", + case: "IoError", + }, + { + err: (() => { + const e = Error("Some InvalidState error"); + e.name = "DIDCommInvalidState"; + return e; + })(), + exp_err: + "Invalid state: Unable resolve recipient did: Unable resolve did: Some InvalidState error", + case: "InvalidState", + }, + { + err: (() => { + return Error("Unknown error"); + })(), + exp_err: + "Invalid state: Unable resolve recipient did: Unable resolve did: Unknown error", + case: "Error", + }, + { + err: (() => { + return "String error"; + })(), + exp_err: + "Invalid state: Unable resolve recipient did: Unable resolve did: String error", + case: "String", + }, + { + err: (() => { + return 123; + })(), + exp_err: + "Invalid state: Unable resolve recipient did: Unable resolve did: JsValue(123)", + case: "Unusual", + }, +])( + "DIDReslver.resolve exception is propogated for $case", + async ({ err, exp_err }) => { + const didResolver = new MockDIDResolver( + [ + () => { + throw err; + }, + ], + new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]) + ); + + const secretsResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const res = MESSAGE_SIMPLE.pack_encrypted( + BOB_DID, + ALICE_DID, + null, + didResolver, + secretsResolver, + { + forward: false, + } + ); + + await expect(res).rejects.toThrowError(exp_err); + } +); + +test.each([ + { + err: (() => { + const e = Error("Some Malformed error"); + e.name = "DIDCommMalformed"; + return e; + })(), + exp_err: + "Malformed: Unable resolve sender secret: Unable get secret: Some Malformed error", + case: "Malformed", + }, + { + err: (() => { + const e = Error("Some IoError error"); + e.name = "DIDCommIoError"; + return e; + })(), + exp_err: + "IO error: Unable resolve sender secret: Unable get secret: Some IoError error", + case: "IoError", + }, + { + err: (() => { + const e = Error("Some InvalidState error"); + e.name = "DIDCommInvalidState"; + return e; + })(), + exp_err: + "Invalid state: Unable resolve sender secret: Unable get secret: Some InvalidState error", + case: "InvalidState", + }, + { + err: (() => { + return Error("Unknown error"); + })(), + exp_err: + "Invalid state: Unable resolve sender secret: Unable get secret: Unknown error", + case: "Error", + }, + { + err: (() => { + return "String error"; + })(), + exp_err: + "Invalid state: Unable resolve sender secret: Unable get secret: String error", + case: "String", + }, + { + err: (() => { + return 123; + })(), + exp_err: + "Invalid state: Unable resolve sender secret: Unable get secret: JsValue(123)", + case: "Unusual", + }, +])( + "Secrets.get_secret exception is propogated for $case", + async ({ err, exp_err }) => { + const didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + + const secretsResolver = new MockSecretsResolver( + [ + () => { + throw err; + }, + ], + [], + new ExampleSecretsResolver(ALICE_SECRETS) + ); + + const res = MESSAGE_SIMPLE.pack_encrypted( + BOB_DID, + ALICE_DID, + null, + didResolver, + secretsResolver, + { + forward: false, + } + ); + + await expect(res).rejects.toThrowError(exp_err); + } +); + +test.each([ + { + err: (() => { + const e = Error("Some Malformed error"); + e.name = "DIDCommMalformed"; + return e; + })(), + exp_err: + "Malformed: Unable find secrets: Unable find secrets: Some Malformed error", + case: "Malformed", + }, + { + err: (() => { + const e = Error("Some IoError error"); + e.name = "DIDCommIoError"; + return e; + })(), + exp_err: + "IO error: Unable find secrets: Unable find secrets: Some IoError error", + case: "IoError", + }, + { + err: (() => { + const e = Error("Some InvalidState error"); + e.name = "DIDCommInvalidState"; + return e; + })(), + exp_err: + "Invalid state: Unable find secrets: Unable find secrets: Some InvalidState error", + case: "InvalidState", + }, + { + err: (() => { + return Error("Unknown error"); + })(), + exp_err: + "Invalid state: Unable find secrets: Unable find secrets: Unknown error", + case: "Error", + }, + { + err: (() => { + return "String error"; + })(), + exp_err: + "Invalid state: Unable find secrets: Unable find secrets: String error", + case: "String", + }, + { + err: (() => { + return 123; + })(), + exp_err: + "Invalid state: Unable find secrets: Unable find secrets: JsValue(123)", + case: "Unusual", + }, +])( + "Secrets.find_secrets exception is propogated for $case", + async ({ err, exp_err }) => { + const didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + + const secretsResolver = new MockSecretsResolver( + [], + [ + () => { + throw err; + }, + ], + new ExampleSecretsResolver(ALICE_SECRETS) + ); + + const res = MESSAGE_SIMPLE.pack_encrypted( + BOB_DID, + ALICE_DID, + null, + didResolver, + secretsResolver, + { + forward: false, + } + ); + + await expect(res).rejects.toThrowError(exp_err); + } +); diff --git a/wasm/tests-js/src/message/new.test.ts b/wasm/tests-js/src/message/new.test.ts new file mode 100644 index 0000000..1e1976c --- /dev/null +++ b/wasm/tests-js/src/message/new.test.ts @@ -0,0 +1,41 @@ +import { Message, IMessage } from "didcomm"; + +test("Message.new works", () => { + const val: IMessage = { + id: "example-1", + typ: "application/didcomm-plain+json", + type: "example/v1", + body: "example-body", + from: "did:example:4", + to: ["did:example:1", "did:example:2", "did:example:3"], + thid: "example-thread-1", + pthid: "example-parent-thread-1", + "example-header-1": "example-header-1-value", + "example-header-2": "example-header-2-value", + created_time: 10000, + expires_time: 20000, + attachments: [ + { + data: { + base64: "ZXhhbXBsZQ==", + }, + id: "attachment1", + }, + { + data: { + json: "example", + }, + id: "attachment2", + }, + { + data: { + json: "example", + }, + id: "attachment3", + }, + ], + }; + + const msg = new Message(val); + expect(msg.as_value()).toStrictEqual(val); +}); diff --git a/wasm/tests-js/src/message/pack-encrypted.anoncrypt.test.ts b/wasm/tests-js/src/message/pack-encrypted.anoncrypt.test.ts new file mode 100644 index 0000000..1583c65 --- /dev/null +++ b/wasm/tests-js/src/message/pack-encrypted.anoncrypt.test.ts @@ -0,0 +1,124 @@ +import { + ALICE_AUTH_METHOD_25519, + ALICE_AUTH_METHOD_P256, + ALICE_AUTH_METHOD_SECP256K1, + ALICE_DID, + ALICE_DID_DOC, + ALICE_SECRETS, + BOB_DID, + BOB_DID_DOC, + BOB_SECRET_KEY_AGREEMENT_KEY_P256_1, + BOB_SECRET_KEY_AGREEMENT_KEY_P384_1, + BOB_SECRET_KEY_AGREEMENT_KEY_P521_1, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3, + BOB_SECRETS, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_3, + ExampleDIDResolver, + ExampleSecretsResolver, + MESSAGE_SIMPLE, +} from "../test-vectors"; +import { Message } from "didcomm"; + +test.each([ + { + message: MESSAGE_SIMPLE, + signBy: null, + to: BOB_DID, + expMetadata: { + messaging_service: null, + from_kid: null, + sign_by_kid: null, + to_kids: [ + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1.id, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3.id, + ], + }, + case: "Simple message X25519", + }, + { + message: MESSAGE_SIMPLE, + signBy: null, + to: BOB_SECRET_KEY_AGREEMENT_KEY_P256_1.id, + expMetadata: { + messaging_service: null, + from_kid: null, + sign_by_kid: null, + to_kids: [BOB_SECRET_KEY_AGREEMENT_KEY_P256_1.id], + }, + case: "Simple message P256", + }, + { + message: MESSAGE_SIMPLE, + signBy: ALICE_AUTH_METHOD_25519.id, + to: BOB_DID, + expMetadata: { + messaging_service: null, + from_kid: null, + sign_by_kid: ALICE_AUTH_METHOD_25519.id, + to_kids: [ + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1.id, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3.id, + ], + }, + case: "Simple message X25519 signed", + }, + { + message: MESSAGE_SIMPLE, + signBy: ALICE_AUTH_METHOD_P256.id, + to: BOB_SECRET_KEY_AGREEMENT_KEY_P256_1.id, + expMetadata: { + messaging_service: null, + from_kid: null, + sign_by_kid: ALICE_AUTH_METHOD_P256.id, + to_kids: [BOB_SECRET_KEY_AGREEMENT_KEY_P256_1.id], + }, + case: "Simple message P256 signed", + }, +])( + "Message.pack-encrypted anoncrypt works for $case", + async ({ message, signBy, to, expMetadata }) => { + const didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + let secretResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const [encrypted, metadata] = await message.pack_encrypted( + to, + null, + signBy, + didResolver, + secretResolver, + { + protect_sender: false, + forward: false, + forward_headers: null, + messaging_service: null, + enc_alg_anon: "A256cbcHs512EcdhEsA256kw", + } + ); + + expect(typeof encrypted).toStrictEqual("string"); + expect(metadata).toStrictEqual(expMetadata); + + secretResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpacked, _] = await Message.unpack( + encrypted, + didResolver, + secretResolver, + {} + ); + + expect(unpacked.as_value()).toStrictEqual(message.as_value()); + } +); diff --git a/wasm/tests-js/src/message/pack-encrypted.authcrypt.test.ts b/wasm/tests-js/src/message/pack-encrypted.authcrypt.test.ts new file mode 100644 index 0000000..9e91f47 --- /dev/null +++ b/wasm/tests-js/src/message/pack-encrypted.authcrypt.test.ts @@ -0,0 +1,219 @@ +import { + ALICE_AUTH_METHOD_25519, + ALICE_AUTH_METHOD_P256, + ALICE_AUTH_METHOD_SECP256K1, + ALICE_DID, + ALICE_DID_DOC, + ALICE_SECRETS, + ALICE_VERIFICATION_METHOD_KEY_AGREEM_P256, + ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519, + ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519_NOT_IN_SECRET, + BOB_DID, + BOB_DID_DOC, + BOB_SECRET_KEY_AGREEMENT_KEY_P256_1, + BOB_SECRET_KEY_AGREEMENT_KEY_P384_1, + BOB_SECRET_KEY_AGREEMENT_KEY_P521_1, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3, + BOB_SECRETS, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_3, + CHARLIE_DID, + CHARLIE_DID_DOC, + ExampleDIDResolver, + ExampleSecretsResolver, + MESSAGE_SIMPLE, + MockDIDResolver, + MockSecretsResolver, +} from "../test-vectors"; +import { Message } from "didcomm"; + +test.each([ + { + message: MESSAGE_SIMPLE, + from: ALICE_DID, + to: BOB_DID, + expMetadata: { + messaging_service: null, + from_kid: ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519.id, + sign_by_kid: null, + to_kids: [ + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1.id, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2.id, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3.id, + ], + }, + case: "Simple message X25519", + }, + { + message: MESSAGE_SIMPLE, + from: ALICE_DID, + to: BOB_SECRET_KEY_AGREEMENT_KEY_P256_1.id, + expMetadata: { + messaging_service: null, + from_kid: ALICE_VERIFICATION_METHOD_KEY_AGREEM_P256.id, + sign_by_kid: null, + to_kids: [BOB_SECRET_KEY_AGREEMENT_KEY_P256_1.id], + }, + case: "Simple message P256", + }, +])( + "Message.pack-encrypted authcrypt works for $case", + async ({ message, from, to, expMetadata }) => { + const didResolver = new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]); + let secretResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const [encrypted, metadata] = await message.pack_encrypted( + to, + from, + null, + didResolver, + secretResolver, + { + protect_sender: false, + forward: false, + forward_headers: null, + messaging_service: null, + enc_alg_anon: "A256cbcHs512EcdhEsA256kw", + } + ); + + expect(typeof encrypted).toStrictEqual("string"); + expect(metadata).toStrictEqual(expMetadata); + + secretResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpacked, _] = await Message.unpack( + encrypted, + didResolver, + secretResolver, + {} + ); + + expect(unpacked.as_value()).toStrictEqual(message.as_value()); + } +); + +test.each([ + { + case: "from is not a did or did url", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + from: "not-a-did", + to: BOB_DID, + signBy: ALICE_DID, + expError: "Illegal argument: `from` value is not a valid DID or DID URL", + }, + { + case: "Signer DID not a did", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + from: ALICE_DID, + to: "not-a-did", + signBy: ALICE_DID, + expError: "Illegal argument: `to` value is not a valid DID or DID URL", + }, + { + case: "Signer DID URL not found", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + from: ALICE_DID, + to: BOB_DID, + signBy: "not-a-did", + expError: + "Illegal argument: `sign_from` value is not a valid DID or DID URL", + }, + { + case: "from differs message from", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + from: BOB_DID, + to: BOB_DID, + signBy: ALICE_DID, + expError: + "Illegal argument: `message.from` value is not equal to `from` value's DID", + }, + { + case: "to differs message to", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC, CHARLIE_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + from: ALICE_DID, + to: CHARLIE_DID, + signBy: ALICE_DID, + expError: + "Illegal argument: `message.to` value does not contain `to` value's DID", + }, + { + case: "from unknown did", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: new Message({ + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:unknown", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, + }), + from: "did:example:unknown", + to: BOB_DID, + signBy: ALICE_DID, + expError: "DID not resolved: Sender did not found", + }, + { + case: "from unknown did url", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + from: ALICE_DID + "#unknown-key", + to: BOB_DID, + signBy: ALICE_DID, + expError: "DID URL not found: No sender key agreements found", + }, + { + case: "from unknown did url", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC, BOB_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + from: "did:example:alice#key-x25519-not-in-secrets-1", + to: BOB_DID, + signBy: null, + expError: "Secret not found: No sender secrets found", + }, +])( + "Message.pack-encrypted handles $case", + async ({ + didResolver, + secretsResolver, + message, + from, + to, + signBy, + expError, + }) => { + const res = message.pack_encrypted( + to, + from, + signBy, + didResolver, + secretsResolver, + {} + ); + await expect(res).rejects.toThrowError(expError); + } +); diff --git a/wasm/tests-js/src/message/pack-plaintext.test.ts b/wasm/tests-js/src/message/pack-plaintext.test.ts new file mode 100644 index 0000000..e4e46b6 --- /dev/null +++ b/wasm/tests-js/src/message/pack-plaintext.test.ts @@ -0,0 +1,43 @@ +import { + MESSAGE_SIMPLE, + ExampleDIDResolver, + ALICE_DID_DOC, + PLAINTEXT_MSG_SIMPLE, + MESSAGE_MINIMAL, + PLAINTEXT_MSG_MINIMAL, + MESSAGE_FROM_PRIOR, + PLAINTEXT_FROM_PRIOR, + BOB_DID_DOC, + CHARLIE_DID_DOC, +} from "../test-vectors"; + +test.each([ + { + message: MESSAGE_SIMPLE, + expPlaintext: PLAINTEXT_MSG_SIMPLE, + case: "Simple", + }, + { + message: MESSAGE_MINIMAL, + expPlaintext: PLAINTEXT_MSG_MINIMAL, + case: "Minimal", + }, + { + message: MESSAGE_FROM_PRIOR, + expPlaintext: PLAINTEXT_FROM_PRIOR, + case: "FromPrior", + }, +])( + "Message.pack-plaintext works for $case", + async ({ message, expPlaintext }) => { + const didResolver = new ExampleDIDResolver([ + ALICE_DID_DOC, + BOB_DID_DOC, + CHARLIE_DID_DOC, + ]); + + const plaintext = await message.pack_plaintext(didResolver); + + expect(JSON.parse(plaintext)).toStrictEqual(JSON.parse(expPlaintext)); + } +); diff --git a/wasm/tests-js/src/message/pack-signed.test.ts b/wasm/tests-js/src/message/pack-signed.test.ts new file mode 100644 index 0000000..f219795 --- /dev/null +++ b/wasm/tests-js/src/message/pack-signed.test.ts @@ -0,0 +1,152 @@ +import { + MESSAGE_SIMPLE, + ExampleDIDResolver, + ALICE_DID_DOC, + PLAINTEXT_MSG_SIMPLE, + MESSAGE_MINIMAL, + MockDIDResolver, + MockSecretsResolver, + PLAINTEXT_MSG_MINIMAL, + ExampleSecretsResolver, + ALICE_SECRETS, + ALICE_DID, + ALICE_AUTH_METHOD_25519, + ALICE_AUTH_METHOD_P256, + ALICE_AUTH_METHOD_SECP256K1, +} from "../test-vectors"; + +import { FromPrior, Message, PackSignedMetadata } from "didcomm"; + +test.each([ + { + message: MESSAGE_SIMPLE, + signBy: ALICE_DID, + expMetadata: { sign_by_kid: ALICE_AUTH_METHOD_25519.id }, + case: "Simple message ED25519", + }, + { + message: MESSAGE_SIMPLE, + signBy: ALICE_AUTH_METHOD_25519.id, + expMetadata: { sign_by_kid: ALICE_AUTH_METHOD_25519.id }, + case: "Simple message ED25519", + }, + { + message: MESSAGE_SIMPLE, + signBy: ALICE_AUTH_METHOD_P256.id, + expMetadata: { sign_by_kid: ALICE_AUTH_METHOD_P256.id }, + case: "Simple message P256", + }, + { + message: MESSAGE_SIMPLE, + signBy: ALICE_AUTH_METHOD_SECP256K1.id, + expMetadata: { sign_by_kid: ALICE_AUTH_METHOD_SECP256K1.id }, + case: "Simple message K256", + }, +])( + "Message.pack-signed works for $case", + async ({ message, signBy, expMetadata }) => { + const didResolver = new ExampleDIDResolver([ALICE_DID_DOC]); + const secretResolver = new ExampleSecretsResolver(ALICE_SECRETS); + + const [signed, metadata] = await message.pack_signed( + signBy, + didResolver, + secretResolver + ); + + expect(typeof signed).toStrictEqual("string"); + expect(metadata).toStrictEqual(expMetadata); + + const [unpacked, _] = await Message.unpack( + signed, + didResolver, + secretResolver, + {} + ); + expect(unpacked.as_value()).toStrictEqual(message.as_value()); + } +); + +test.each([ + { + case: "Signer DID not found", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + sign_by: "did:example:unknown", + expError: "DID not resolved: Signer did not found", + }, + { + case: "Signer DID not a did", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + sign_by: "not-a-did", + expError: + "Illegal argument: `sign_from` value is not a valid DID or DID URL", + }, + { + case: "Signer DID URL not found", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC]), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + sign_by: `${ALICE_DID}#unknown`, + expError: "DID URL not found: Signer key id not found in did doc", + }, + { + case: "DIDResolver error", + didResolver: new MockDIDResolver( + [ + () => { + throw Error("Unknown error"); + }, + ], + new ExampleDIDResolver([ALICE_DID_DOC]) + ), + secretsResolver: new ExampleSecretsResolver(ALICE_SECRETS), + message: MESSAGE_SIMPLE, + sign_by: ALICE_DID, + expError: + "Invalid state: Unable resolve signer did: Unable resolve did: Unknown error", + }, + { + case: "SecretsResolver::get_secrets error", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC]), + secretsResolver: new MockSecretsResolver( + [ + () => { + throw Error("Unknown error"); + }, + ], + [], + new ExampleSecretsResolver(ALICE_SECRETS) + ), + message: MESSAGE_SIMPLE, + sign_by: ALICE_DID, + expError: + "Invalid state: Unable get secret: Unable get secret: Unknown error", + }, + { + case: "SecretsResolver::find_secrets error", + didResolver: new ExampleDIDResolver([ALICE_DID_DOC]), + secretsResolver: new MockSecretsResolver( + [], + [ + () => { + throw Error("Unknown error"); + }, + ], + new ExampleSecretsResolver(ALICE_SECRETS) + ), + message: MESSAGE_SIMPLE, + sign_by: ALICE_DID, + expError: + "Invalid state: Unable find secrets: Unable find secrets: Unknown error", + }, +])( + "Message.pack-signed handles $case", + async ({ didResolver, secretsResolver, message, sign_by, expError }) => { + const res = message.pack_signed(sign_by, didResolver, secretsResolver); + await expect(res).rejects.toThrowError(expError); + } +); diff --git a/wasm/tests-js/src/message/unpack.test.ts b/wasm/tests-js/src/message/unpack.test.ts new file mode 100644 index 0000000..99fdc9e --- /dev/null +++ b/wasm/tests-js/src/message/unpack.test.ts @@ -0,0 +1,113 @@ +import { Message } from "didcomm"; +import { + ALICE_DID_DOC, + BOB_DID_DOC, + BOB_SECRETS, + CHARLIE_DID_DOC, + ExampleDIDResolver, + ExampleSecretsResolver, + IMESSAGE_FROM_PRIOR, + IMESSAGE_MINIMAL, + IMESSAGE_SIMPLE, + PLAINTEXT_FROM_PRIOR, + PLAINTEXT_MSG_MINIMAL, + PLAINTEXT_MSG_SIMPLE, +} from "../test-vectors"; + +test.each([ + { + case: "Minimal", + msg: PLAINTEXT_MSG_MINIMAL, + options: {}, + expMsg: IMESSAGE_MINIMAL, + expMetadata: { + anonymous_sender: false, + authenticated: false, + enc_alg_anon: null, + enc_alg_auth: null, + encrypted: false, + encrypted_from_kid: null, + encrypted_to_kids: null, + from_prior: null, + from_prior_issuer_kid: null, + non_repudiation: false, + re_wrapped_in_forward: false, + sign_alg: null, + sign_from: null, + signed_message: null, + }, + }, + { + case: "Simple", + msg: PLAINTEXT_MSG_SIMPLE, + options: {}, + expMsg: IMESSAGE_SIMPLE, + expMetadata: { + anonymous_sender: false, + authenticated: false, + enc_alg_anon: null, + enc_alg_auth: null, + encrypted: false, + encrypted_from_kid: null, + encrypted_to_kids: null, + from_prior: null, + from_prior_issuer_kid: null, + non_repudiation: false, + re_wrapped_in_forward: false, + sign_alg: null, + sign_from: null, + signed_message: null, + }, + }, + { + case: "FromPrior", + msg: PLAINTEXT_FROM_PRIOR, + options: {}, + expMsg: IMESSAGE_FROM_PRIOR, + expMetadata: { + anonymous_sender: false, + authenticated: false, + enc_alg_anon: null, + enc_alg_auth: null, + encrypted: false, + encrypted_from_kid: null, + encrypted_to_kids: null, + from_prior: { + aud: "123", + exp: 1234, + iat: 123456, + iss: "did:example:charlie", + jti: "dfg", + nbf: 12345, + sub: "did:example:alice", + }, + from_prior_issuer_kid: "did:example:charlie#key-1", + non_repudiation: false, + re_wrapped_in_forward: false, + sign_alg: null, + sign_from: null, + signed_message: null, + }, + }, +])( + "Message.unpack works for $case", + async ({ msg, options, expMsg, expMetadata }) => { + const didResolver = new ExampleDIDResolver([ + ALICE_DID_DOC, + BOB_DID_DOC, + CHARLIE_DID_DOC, + ]); + + const secretsResolver = new ExampleSecretsResolver(BOB_SECRETS); + + const [unpacked, metadata] = await Message.unpack( + msg, + didResolver, + secretsResolver, + options + ); + + expect(unpacked.as_value()).toStrictEqual(expMsg); + expect(metadata).toStrictEqual(expMetadata); + } +); diff --git a/wasm/tests-js/src/test-vectors/common.ts b/wasm/tests-js/src/test-vectors/common.ts new file mode 100644 index 0000000..608e6a4 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/common.ts @@ -0,0 +1,3 @@ +export const ALICE_DID = "did:example:alice"; +export const BOB_DID = "did:example:bob"; +export const CHARLIE_DID = "did:example:charlie"; diff --git a/wasm/tests-js/src/test-vectors/did_doc/alice.ts b/wasm/tests-js/src/test-vectors/did_doc/alice.ts new file mode 100644 index 0000000..1e5f5c7 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/did_doc/alice.ts @@ -0,0 +1,122 @@ +import { DIDDoc, VerificationMethod } from "didcomm"; + +export const ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519_NOT_IN_SECRET: VerificationMethod = + { + id: "did:example:alice#key-x25519-not-in-secrets-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-x25519-not-in-secrets-1", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", + }, + }, + }; + +export const ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519: VerificationMethod = { + id: "did:example:alice#key-x25519-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-x25519-1", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", + }, + }, +}; + +export const ALICE_VERIFICATION_METHOD_KEY_AGREEM_P256: VerificationMethod = { + id: "did:example:alice#key-p256-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-p256-1", + verification_material: { + JWK: { + crv: "P-256", + kty: "EC", + x: "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", + y: "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", + }, + }, +}; + +export const ALICE_VERIFICATION_METHOD_KEY_AGREEM_P521: VerificationMethod = { + id: "did:example:alice#key-p521-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-p521-1", + verification_material: { + JWK: { + crv: "P-521", + kty: "EC", + x: "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", + y: "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", + }, + }, +}; + +export const ALICE_AUTH_METHOD_25519: VerificationMethod = { + id: "did:example:alice#key-1", + type: "JsonWebKey2020", + controller: "did:example:alice#key-1", + verification_material: { + JWK: { + crv: "Ed25519", + kty: "OKP", + x: "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", + }, + }, +}; + +export const ALICE_AUTH_METHOD_P256: VerificationMethod = { + id: "did:example:alice#key-2", + type: "JsonWebKey2020", + controller: "did:example:alice#key-2", + verification_material: { + JWK: { + crv: "P-256", + kty: "EC", + x: "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", + y: "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", + }, + }, +}; + +export const ALICE_AUTH_METHOD_SECP256K1: VerificationMethod = { + id: "did:example:alice#key-3", + type: "JsonWebKey2020", + controller: "did:example:alice#key-3", + verification_material: { + JWK: { + crv: "secp256k1", + kty: "EC", + x: "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", + y: "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", + }, + }, +}; + +export const ALICE_DID_DOC: DIDDoc = { + did: "did:example:alice", + key_agreements: [ + "did:example:alice#key-x25519-not-in-secrets-1", + "did:example:alice#key-x25519-1", + "did:example:alice#key-p256-1", + "did:example:alice#key-p521-1", + ], + authentications: [ + "did:example:alice#key-1", + "did:example:alice#key-2", + "did:example:alice#key-3", + ], + verification_methods: [ + ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519_NOT_IN_SECRET, + ALICE_VERIFICATION_METHOD_KEY_AGREEM_X25519, + ALICE_VERIFICATION_METHOD_KEY_AGREEM_P256, + ALICE_VERIFICATION_METHOD_KEY_AGREEM_P521, + ALICE_AUTH_METHOD_25519, + ALICE_AUTH_METHOD_P256, + ALICE_AUTH_METHOD_SECP256K1, + ], + services: [], +}; diff --git a/wasm/tests-js/src/test-vectors/did_doc/bob.ts b/wasm/tests-js/src/test-vectors/did_doc/bob.ts new file mode 100644 index 0000000..8810ba0 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/did_doc/bob.ts @@ -0,0 +1,147 @@ +import { DIDDoc, VerificationMethod } from "didcomm"; + +export const BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_1: VerificationMethod = { + id: "did:example:bob#key-x25519-1", + type: "JsonWebKey2020", + controller: "did:example:bob#key-x25519-1", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }, + }, +}; + +export const BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_2: VerificationMethod = { + id: "did:example:bob#key-x25519-2", + type: "JsonWebKey2020", + controller: "did:example:bob#key-x25519-2", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", + }, + }, +}; + +export const BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_3: VerificationMethod = { + id: "did:example:bob#key-x25519-3", + type: "JsonWebKey2020", + controller: "did:example:bob#key-x25519-3", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", + }, + }, +}; + +export const BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_1: VerificationMethod = { + id: "did:example:bob#key-p256-1", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p256-1", + verification_material: { + JWK: { + crv: "P-256", + kty: "EC", + x: "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + y: "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }, + }, +}; +export const BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_2: VerificationMethod = { + id: "did:example:bob#key-p256-2", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p256-2", + verification_material: { + JWK: { + crv: "P-256", + kty: "EC", + x: "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", + y: "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", + }, + }, +}; +export const BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_1: VerificationMethod = { + id: "did:example:bob#key-p384-1", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p384-1", + verification_material: { + JWK: { + crv: "P-384", + kty: "EC", + x: "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + y: "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }, + }, +}; +export const BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_2: VerificationMethod = { + id: "did:example:bob#key-p384-2", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p384-2", + verification_material: { + JWK: { + crv: "P-384", + kty: "EC", + x: "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", + y: "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", + }, + }, +}; +export const BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_1: VerificationMethod = { + id: "did:example:bob#key-p521-1", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p521-1", + verification_material: { + JWK: { + crv: "P-521", + kty: "EC", + x: "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + y: "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }, + }, +}; +export const BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_2: VerificationMethod = { + id: "did:example:bob#key-p521-2", + type: "JsonWebKey2020", + controller: "did:example:bob#key-p521-2", + verification_material: { + JWK: { + crv: "P-521", + kty: "EC", + x: "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", + y: "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", + }, + }, +}; + +export const BOB_DID_DOC: DIDDoc = { + did: "did:example:bob", + key_agreements: [ + "did:example:bob#key-x25519-1", + "did:example:bob#key-x25519-2", + "did:example:bob#key-x25519-3", + "did:example:bob#key-p256-1", + "did:example:bob#key-p256-2", + "did:example:bob#key-p384-1", + "did:example:bob#key-p384-2", + "did:example:bob#key-p521-1", + "did:example:bob#key-p521-2", + ], + authentications: [], + verification_methods: [ + BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_X25519_3, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P256_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P384_2, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_1, + BOB_VERIFICATION_METHOD_KEY_AGREEM_P521_2, + ], + services: [], +}; diff --git a/wasm/tests-js/src/test-vectors/did_doc/charlie.ts b/wasm/tests-js/src/test-vectors/did_doc/charlie.ts new file mode 100644 index 0000000..4e9ba4e --- /dev/null +++ b/wasm/tests-js/src/test-vectors/did_doc/charlie.ts @@ -0,0 +1,34 @@ +import { DIDDoc } from "didcomm"; + +export const CHARLIE_DID_DOC: DIDDoc = { + did: "did:example:charlie", + key_agreements: ["did:example:charlie#key-x25519-1"], + authentications: ["did:example:charlie#key-1"], + verification_methods: [ + { + id: "did:example:charlie#key-x25519-1", + type: "JsonWebKey2020", + controller: "did:example:charlie#key-x25519-1", + verification_material: { + JWK: { + crv: "X25519", + kty: "OKP", + x: "nTiVFj7DChMsETDdxd5dIzLAJbSQ4j4UG6ZU1ogLNlw", + }, + }, + }, + { + id: "did:example:charlie#key-1", + type: "JsonWebKey2020", + controller: "did:example:charlie#key-1", + verification_material: { + JWK: { + crv: "Ed25519", + kty: "OKP", + x: "VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE", + }, + }, + }, + ], + services: [], +}; diff --git a/wasm/tests-js/src/test-vectors/did_doc/index.ts b/wasm/tests-js/src/test-vectors/did_doc/index.ts new file mode 100644 index 0000000..2de041a --- /dev/null +++ b/wasm/tests-js/src/test-vectors/did_doc/index.ts @@ -0,0 +1,3 @@ +export * from "./alice"; +export * from "./bob"; +export * from "./charlie"; diff --git a/wasm/tests-js/src/test-vectors/did_resolver.ts b/wasm/tests-js/src/test-vectors/did_resolver.ts new file mode 100644 index 0000000..f317889 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/did_resolver.ts @@ -0,0 +1,32 @@ +import { DIDResolver, DIDDoc } from "didcomm"; + +export class ExampleDIDResolver implements DIDResolver { + knownDids: DIDDoc[]; + + constructor(knownDids: DIDDoc[]) { + this.knownDids = knownDids; + } + + async resolve(did: string): Promise { + const res = this.knownDids.find((ddoc) => ddoc.did === did); + return res ? res : null; + } +} + +type MockResolve = (did: string) => DIDDoc | null; + +/* tslint:disable:max-classes-per-file */ +export class MockDIDResolver implements DIDResolver { + handlers: MockResolve[]; + fallback: DIDResolver; + + constructor(handlers: MockResolve[], fallback: DIDResolver) { + this.handlers = handlers; + this.fallback = fallback; + } + + async resolve(did: string): Promise { + const handler = this.handlers.pop(); + return handler ? handler(did) : await this.fallback.resolve(did); + } +} diff --git a/wasm/tests-js/src/test-vectors/from_prior.ts b/wasm/tests-js/src/test-vectors/from_prior.ts new file mode 100644 index 0000000..424882f --- /dev/null +++ b/wasm/tests-js/src/test-vectors/from_prior.ts @@ -0,0 +1,17 @@ +import { FromPrior, IFromPrior } from "didcomm"; +import { ALICE_DID, BOB_DID, CHARLIE_DID } from "."; + +export const IFROM_PRIOR_MINIMAL: IFromPrior = { + iss: CHARLIE_DID, + sub: ALICE_DID, +}; + +export const FROM_PRIOR_MINIMAL = new FromPrior(IFROM_PRIOR_MINIMAL); + +export const IFROM_PRIOR_FULL = { + iss: CHARLIE_DID, + sub: ALICE_DID, + iat: 123456, +}; + +export const FROM_PRIOR_FULL = new FromPrior(IFROM_PRIOR_FULL); diff --git a/wasm/tests-js/src/test-vectors/from_prior_jwt.ts b/wasm/tests-js/src/test-vectors/from_prior_jwt.ts new file mode 100644 index 0000000..02361a3 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/from_prior_jwt.ts @@ -0,0 +1,7 @@ +export const FROM_PRIOR_JWT_FULL = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDA"; + +export const FROM_PRIOR_JWT_INVALID = "invalid"; + +export const FROM_PRIOR_JWT_INVALID_SIGNATURE = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDB"; diff --git a/wasm/tests-js/src/test-vectors/index.ts b/wasm/tests-js/src/test-vectors/index.ts new file mode 100644 index 0000000..128c18c --- /dev/null +++ b/wasm/tests-js/src/test-vectors/index.ts @@ -0,0 +1,9 @@ +export * from "./common"; +export * from "./did_doc"; +export * from "./did_resolver"; +export * from "./from_prior"; +export * from "./from_prior_jwt"; +export * from "./message"; +export * from "./plaintext"; +export * from "./secrets"; +export * from "./secrets_resolver"; diff --git a/wasm/tests-js/src/test-vectors/message.ts b/wasm/tests-js/src/test-vectors/message.ts new file mode 100644 index 0000000..c3c7e52 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/message.ts @@ -0,0 +1,80 @@ +import { Message } from "didcomm"; + +export const IMESSAGE_SIMPLE = { + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + body: { messagespecificattribute: "and its value" }, +}; + +export const MESSAGE_SIMPLE = new Message(IMESSAGE_SIMPLE); + +export const IMESSAGE_MINIMAL = { + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + body: {}, +}; + +export const MESSAGE_MINIMAL = new Message(IMESSAGE_MINIMAL); + +export const IMESSAGE_FROM_PRIOR = { + id: "1234567890", + typ: "application/didcomm-plain+json", + type: "http://example.com/protocols/lets_do_lunch/1.0/proposal", + from: "did:example:alice", + to: ["did:example:bob"], + created_time: 1516269022, + expires_time: 1516385931, + from_prior: + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDA", + body: { messagespecificattribute: "and its value" }, +}; + +export const MESSAGE_FROM_PRIOR = new Message(IMESSAGE_FROM_PRIOR); + +export const IFORWARD_MESSAGE = { + id: "8404000a-1c6d-4c8c-8c60-e383128d9677", + typ: "application/didcomm-plain+json", + type: "https://didcomm.org/routing/2.0/forward", + body: { + next: "did:example:bob", + }, + attachments: [ + { + data: { + json: { + ciphertext: + "ajYDBYyuuftb0f-pCj9iz7uhSJFK95F_WsXcXSKN2HrfPdojdRb9Ss_xI0zJnTC97yRmO9vmfyR8-MkQ_1gh-KyEHZe6UTM7JWpSWC9onReNLTOLaMoM09W8Fb45ZFbqaqZ1Kt3qvKIXEu2BwrZ2jLRu7r2Lo-cDJhDwzhHux27gd-j9Dhvtct3B2AMzXdu2J4fLqIdz9h0XkiI3PB4tLYsgY6KwDMtLyePDbb747bqViqWoBFgDLX2zgL3R9Okxt7RG4-1vqRHfURgcONofWMpFHEFq3WaplipogvuwouP3hJv3OMppBz2KTo1ULg3WWAdrac7laa2XQ6UE1PUo6Cq7IH7mdVoZwRc2v__swib6_WLTZMTW", + iv: "hC-Frpywx0Pix6Lak-Rwlpw0IbG28rGo", + protected: + "eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLWVuY3J5cHRlZCtqc29uIiwiYWxnIjoiRUNESC1FUytBMjU2S1ciLCJlbmMiOiJYQzIwUCIsImFwdiI6Ik5jc3VBbnJSZlBLNjlBLXJrWjBMOVhXVUc0ak12TkMzWmc3NEJQejUzUEEiLCJlcGsiOnsiY3J2IjoiWDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6Ikg0QURobjA0S1VnX1dXQWpiR0s3eTJ3QkQtTmtsbUhXa0lHUl9jeGtKMXcifX0", + recipients: [ + { + encrypted_key: + "Bjk-DOK_2omU_LN13TEGs3WBAwWaimaAQVvtIdE4mmCW83M8kOWKfw", + header: { kid: "did:example:bob#key-x25519-1" }, + }, + { + encrypted_key: + "SuPR0JolzyGPeNiaj9EoD822TsHXRLJbkyQgOnF_MG-DfPdQ5y2Eeg", + header: { kid: "did:example:bob#key-x25519-2" }, + }, + { + encrypted_key: + "6H5qA6Hic0L2B_lzg6q37VbkmHoi8d82seRxswtXp9c1FpTg8cG76w", + header: { kid: "did:example:bob#key-x25519-3" }, + }, + ], + tag: "j4VLGYCa70LhHyDDLUDzKw", + }, + }, + }, + ], +}; + +export const FORWARD_MESSAGE = new Message(IFORWARD_MESSAGE); diff --git a/wasm/tests-js/src/test-vectors/plaintext.ts b/wasm/tests-js/src/test-vectors/plaintext.ts new file mode 100644 index 0000000..0113c79 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/plaintext.ts @@ -0,0 +1,35 @@ +export const PLAINTEXT_MSG_SIMPLE = ` +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "from": "did:example:alice", + "to": ["did:example:bob"], + "created_time": 1516269022, + "expires_time": 1516385931, + "body": {"messagespecificattribute": "and its value"} +} +`; + +export const PLAINTEXT_MSG_MINIMAL = ` +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "body": {} +} +`; + +export const PLAINTEXT_FROM_PRIOR = ` +{ + "id": "1234567890", + "typ": "application/didcomm-plain+json", + "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", + "from": "did:example:alice", + "to": ["did:example:bob"], + "created_time": 1516269022, + "expires_time": 1516385931, + "from_prior": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.ir0tegXiGJIZIMagO5P853KwhzGTEw0OpFFAyarUV-nQrtbI_ELbxT9l7jPBoPve_-60ifGJ9v3ArmFjELFlDA", + "body": {"messagespecificattribute": "and its value"} +} +`; diff --git a/wasm/tests-js/src/test-vectors/secrets/alice.ts b/wasm/tests-js/src/test-vectors/secrets/alice.ts new file mode 100644 index 0000000..f332525 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/secrets/alice.ts @@ -0,0 +1,80 @@ +import { Secret } from "didcomm"; + +export const ALICE_SECRETS: Secret[] = [ + { + id: "did:example:alice#key-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "Ed25519", + d: "pFRUKkyzx4kHdJtFSnlPA9WzqkDT1HWV0xZ5OYZd2SY", + kty: "OKP", + x: "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", + }, + }, + }, + { + id: "did:example:alice#key-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "7TCIdt1rhThFtWcEiLnk_COEjh1ZfQhM4bW2wz-dp4A", + kty: "EC", + x: "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", + y: "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", + }, + }, + }, + { + id: "did:example:alice#key-3", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "secp256k1", + d: "N3Hm1LXA210YVGGsXw_GklMwcLu_bMgnzDese6YQIyA", + kty: "EC", + x: "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", + y: "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", + }, + }, + }, + { + id: "did:example:alice#key-x25519-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "r-jK2cO3taR8LQnJB1_ikLBTAnOtShJOsHXRUWT-aZA", + kty: "OKP", + x: "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", + }, + }, + }, + { + id: "did:example:alice#key-p256-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "sB0bYtpaXyp-h17dDpMx91N3Du1AdN4z1FUq02GbmLw", + kty: "EC", + x: "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", + y: "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", + }, + }, + }, + { + id: "did:example:alice#key-p521-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-521", + d: "AQCQKE7rZpxPnX9RgjXxeywrAMp1fJsyFe4cir1gWj-8t8xWaM_E2qBkTTzyjbRBu-JPXHe_auT850iYmE34SkWi", + kty: "EC", + x: "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", + y: "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", + }, + }, + }, +]; diff --git a/wasm/tests-js/src/test-vectors/secrets/bob.ts b/wasm/tests-js/src/test-vectors/secrets/bob.ts new file mode 100644 index 0000000..9806535 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/secrets/bob.ts @@ -0,0 +1,128 @@ +import { Secret } from "didcomm"; + +export const BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1: Secret = { + id: "did:example:bob#key-x25519-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "b9NnuOCB0hm7YGNvaE9DMhwH_wjZA1-gWD6dA0JWdL0", + kty: "OKP", + x: "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", + }, + }, +}; +export const BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2: Secret = { + id: "did:example:bob#key-x25519-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "p-vteoF1gopny1HXywt76xz_uC83UUmrgszsI-ThBKk", + kty: "OKP", + x: "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", + }, + }, +}; +export const BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3: Secret = { + id: "did:example:bob#key-x25519-3", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "f9WJeuQXEItkGM8shN4dqFr5fLQLBasHnWZ-8dPaSo0", + kty: "OKP", + x: "82k2BTUiywKv49fKLZa-WwDi8RBf0tB0M8bvSAUQ3yY", + }, + }, +}; +export const BOB_SECRET_KEY_AGREEMENT_KEY_P256_1: Secret = { + id: "did:example:bob#key-p256-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "PgwHnlXxt8pwR6OCTUwwWx-P51BiLkFZyqHzquKddXQ", + kty: "EC", + x: "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", + y: "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", + }, + }, +}; +export const BOB_SECRET_KEY_AGREEMENT_KEY_P256_2: Secret = { + id: "did:example:bob#key-p256-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "agKz7HS8mIwqO40Q2dwm_Zi70IdYFtonN5sZecQoxYU", + kty: "EC", + x: "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", + y: "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", + }, + }, +}; +export const BOB_SECRET_KEY_AGREEMENT_KEY_P384_1: Secret = { + id: "did:example:bob#key-p384-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-384", + d: "ajqcWbYA0UDBKfAhkSkeiVjMMt8l-5rcknvEv9t_Os6M8s-HisdywvNCX4CGd_xY", + kty: "EC", + x: "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", + y: "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", + }, + }, +}; +export const BOB_SECRET_KEY_AGREEMENT_KEY_P384_2: Secret = { + id: "did:example:bob#key-p384-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-384", + d: "OiwhRotK188BtbQy0XBO8PljSKYI6CCD-nE_ZUzK7o81tk3imDOuQ-jrSWaIkI-T", + kty: "EC", + x: "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", + y: "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", + }, + }, +}; +export const BOB_SECRET_KEY_AGREEMENT_KEY_P521_1: Secret = { + id: "did:example:bob#key-p521-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-521", + d: "AV5ocjvy7PkPgNrSuvCxtG70NMj6iTabvvjSLbsdd8OdI9HlXYlFR7RdBbgLUTruvaIRhjEAE9gNTH6rWUIdfuj6", + kty: "EC", + x: "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", + y: "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", + }, + }, +}; +export const BOB_SECRET_KEY_AGREEMENT_KEY_P521_2: Secret = { + id: "did:example:bob#key-p521-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-521", + d: "ABixMEZHsyT7SRw-lY5HxdNOofTZLlwBHwPEJ3spEMC2sWN1RZQylZuvoyOBGJnPxg4-H_iVhNWf_OtgYODrYhCk", + kty: "EC", + x: "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", + y: "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", + }, + }, +}; + +export const BOB_SECRETS: Secret[] = [ + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_1, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_2, + BOB_SECRET_KEY_AGREEMENT_KEY_X25519_3, + BOB_SECRET_KEY_AGREEMENT_KEY_P256_1, + BOB_SECRET_KEY_AGREEMENT_KEY_P256_2, + BOB_SECRET_KEY_AGREEMENT_KEY_P521_1, + BOB_SECRET_KEY_AGREEMENT_KEY_P521_2, + BOB_SECRET_KEY_AGREEMENT_KEY_P384_1, + BOB_SECRET_KEY_AGREEMENT_KEY_P384_2, +]; diff --git a/wasm/tests-js/src/test-vectors/secrets/charlie.ts b/wasm/tests-js/src/test-vectors/secrets/charlie.ts new file mode 100644 index 0000000..3cc9656 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/secrets/charlie.ts @@ -0,0 +1,32 @@ +import { Secret } from "didcomm"; + +export const CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519: Secret = { + id: "did:example:charlie#key-x25519-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "Z-BsgFe-eCvhuZlCBX5BV2XiDE2M92gkaORCe68YdZI", + kty: "OKP", + x: "nTiVFj7DChMsETDdxd5dIzLAJbSQ4j4UG6ZU1ogLNlw", + }, + }, +}; + +export const CHARLIE_SECRET_AUTH_KEY_ED25519: Secret = { + id: "did:example:charlie#key-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "Ed25519", + d: "T2azVap7CYD_kB8ilbnFYqwwYb5N-GcD6yjGEvquZXg", + kty: "OKP", + x: "VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE", + }, + }, +}; + +export const CHARLIE_SECRETS: Secret[] = [ + CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519, + CHARLIE_SECRET_AUTH_KEY_ED25519, +]; diff --git a/wasm/tests-js/src/test-vectors/secrets/charlie_rotated_to_alice.ts b/wasm/tests-js/src/test-vectors/secrets/charlie_rotated_to_alice.ts new file mode 100644 index 0000000..9000b64 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/secrets/charlie_rotated_to_alice.ts @@ -0,0 +1,104 @@ +import { Secret } from "didcomm"; + +export const CHARLIE_ROTATED_TO_ALICE_SECRETS: Secret[] = [ + { + id: "did:example:charlie#key-x25519-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "Z-BsgFe-eCvhuZlCBX5BV2XiDE2M92gkaORCe68YdZI", + kty: "OKP", + x: "nTiVFj7DChMsETDdxd5dIzLAJbSQ4j4UG6ZU1ogLNlw", + }, + }, + }, + { + id: "did:example:charlie#key-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "Ed25519", + d: "T2azVap7CYD_kB8ilbnFYqwwYb5N-GcD6yjGEvquZXg", + kty: "OKP", + x: "VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE", + }, + }, + }, + { + id: "did:example:alice#key-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "Ed25519", + d: "pFRUKkyzx4kHdJtFSnlPA9WzqkDT1HWV0xZ5OYZd2SY", + kty: "OKP", + x: "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", + }, + }, + }, + { + id: "did:example:alice#key-2", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "7TCIdt1rhThFtWcEiLnk_COEjh1ZfQhM4bW2wz-dp4A", + kty: "EC", + x: "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", + y: "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", + }, + }, + }, + { + id: "did:example:alice#key-3", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "secp256k1", + d: "N3Hm1LXA210YVGGsXw_GklMwcLu_bMgnzDese6YQIyA", + kty: "EC", + x: "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", + y: "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", + }, + }, + }, + { + id: "did:example:alice#key-x25519-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "X25519", + d: "r-jK2cO3taR8LQnJB1_ikLBTAnOtShJOsHXRUWT-aZA", + kty: "OKP", + x: "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", + }, + }, + }, + { + id: "did:example:alice#key-p256-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-256", + d: "sB0bYtpaXyp-h17dDpMx91N3Du1AdN4z1FUq02GbmLw", + kty: "EC", + x: "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", + y: "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", + }, + }, + }, + { + id: "did:example:alice#key-p521-1", + type: "JsonWebKey2020", + secret_material: { + JWK: { + crv: "P-521", + d: "AQCQKE7rZpxPnX9RgjXxeywrAMp1fJsyFe4cir1gWj-8t8xWaM_E2qBkTTzyjbRBu-JPXHe_auT850iYmE34SkWi", + kty: "EC", + x: "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", + y: "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", + }, + }, + }, +]; diff --git a/wasm/tests-js/src/test-vectors/secrets/index.ts b/wasm/tests-js/src/test-vectors/secrets/index.ts new file mode 100644 index 0000000..d5e5ece --- /dev/null +++ b/wasm/tests-js/src/test-vectors/secrets/index.ts @@ -0,0 +1,4 @@ +export * from "./alice"; +export * from "./bob"; +export * from "./charlie"; +export * from "./charlie_rotated_to_alice"; diff --git a/wasm/tests-js/src/test-vectors/secrets_resolver.ts b/wasm/tests-js/src/test-vectors/secrets_resolver.ts new file mode 100644 index 0000000..ee49e64 --- /dev/null +++ b/wasm/tests-js/src/test-vectors/secrets_resolver.ts @@ -0,0 +1,56 @@ +import { Secret, SecretsResolver } from "didcomm"; + +export class ExampleSecretsResolver implements SecretsResolver { + knownSecrets: Secret[]; + + constructor(knownSecrets: Secret[]) { + this.knownSecrets = knownSecrets; + } + + async get_secret(secretId: string): Promise { + const res = this.knownSecrets.find((secret) => secret.id === secretId); + return res ? res : null; + } + + async find_secrets(secretIds: string[]): Promise { + return secretIds.filter((id) => + this.knownSecrets.find((secret) => secret.id === id) + ); + } +} + +type MockGet = (secretId: string) => Secret | null; +type MockFind = (secretIds: string[]) => string[]; + +/* tslint:disable:max-classes-per-file */ +export class MockSecretsResolver implements SecretsResolver { + getHandlers: MockGet[]; + findHandlers: MockFind[]; + fallback: SecretsResolver; + + constructor( + getHandlers: MockGet[], + findHandlers: MockFind[], + fallback: SecretsResolver + ) { + this.getHandlers = getHandlers; + this.findHandlers = findHandlers; + this.fallback = fallback; + } + + async get_secret(secretId: string): Promise { + const handler = this.getHandlers.pop(); + + return handler + ? handler(secretId) + : await this.fallback.get_secret(secretId); + } + + async find_secrets(secretIds: string[]): Promise { + const handler = this.findHandlers.pop(); + + return handler + ? handler(secretIds) + : await this.fallback.find_secrets(secretIds); + } +} diff --git a/wasm/tests-js/tslint.json b/wasm/tests-js/tslint.json new file mode 100644 index 0000000..9e97bd2 --- /dev/null +++ b/wasm/tests-js/tslint.json @@ -0,0 +1,7 @@ +{ + "defaultSeverity": "error", + "extends": ["tslint:recommended"], + "jsRules": {}, + "rules": {}, + "rulesDirectory": [] +} diff --git a/wrappers/swift/README.md b/wrappers/swift/README.md new file mode 100644 index 0000000..a2bd024 --- /dev/null +++ b/wrappers/swift/README.md @@ -0,0 +1 @@ +// TBD \ No newline at end of file diff --git a/wrappers/swift/didcomm.swiftdoc b/wrappers/swift/didcomm.swiftdoc new file mode 100644 index 0000000..9cafff0 Binary files /dev/null and b/wrappers/swift/didcomm.swiftdoc differ diff --git a/wrappers/swift/didcomm.swiftsourceinfo b/wrappers/swift/didcomm.swiftsourceinfo new file mode 100644 index 0000000..dc3fb1e Binary files /dev/null and b/wrappers/swift/didcomm.swiftsourceinfo differ diff --git a/wrappers/swift/didcomm/didcomm.swift b/wrappers/swift/didcomm/didcomm.swift new file mode 100644 index 0000000..f09b7d4 --- /dev/null +++ b/wrappers/swift/didcomm/didcomm.swift @@ -0,0 +1,3854 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +import Foundation + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport(didcommFFI) +import didcommFFI +#endif + +fileprivate extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func from(_ ptr: UnsafeBufferPointer) -> RustBuffer { + try! rustCall { ffi_didcomm_f20e_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { ffi_didcomm_f20e_rustbuffer_free(self, $0) } + } +} + +fileprivate extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + +// Helper classes/extensions that don't change. +// Someday, this will be in a libray of its own. + +fileprivate extension Data { + init(rustBuffer: RustBuffer) { + // TODO: This copies the buffer. Can we read directly from a + // Rust buffer? + self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len)) + } +} + +// A helper class to read values out of a byte buffer. +fileprivate class Reader { + let data: Data + var offset: Data.Index + + init(data: Data) { + self.data = data + self.offset = 0 + } + + // Reads an integer at the current offset, in big-endian order, and advances + // the offset on success. Throws if reading the integer would move the + // offset past the end of the buffer. + func readInt() throws -> T { + let range = offset...size + guard data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = data[offset] + offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0, from: range)}) + offset = range.upperBound + return value.bigEndian + } + + // Reads an arbitrary number of bytes, to be used to read + // raw bytes, this is useful when lifting strings + func readBytes(count: Int) throws -> Array { + let range = offset..<(offset+count) + guard data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + data.copyBytes(to: buffer, from: range) + }) + offset = range.upperBound + return value + } + + // Reads a float at the current offset. + @inlinable + func readFloat() throws -> Float { + return Float(bitPattern: try readInt()) + } + + // Reads a float at the current offset. + @inlinable + func readDouble() throws -> Double { + return Double(bitPattern: try readInt()) + } + + // Indicates if the offset has reached the end of the buffer. + @inlinable + func hasRemaining() -> Bool { + return offset < data.count + } +} + +// A helper class to write values into a byte buffer. +fileprivate class Writer { + var bytes: [UInt8] + var offset: Array.Index + + init() { + self.bytes = [] + self.offset = 0 + } + + func writeBytes(_ byteArr: S) where S: Sequence, S.Element == UInt8 { + bytes.append(contentsOf: byteArr) + } + + // Writes an integer in big-endian order. + // + // Warning: make sure what you are trying to write + // is in the correct type! + func writeInt(_ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { bytes.append(contentsOf: $0) } + } + + @inlinable + func writeFloat(_ value: Float) { + writeInt(value.bitPattern) + } + + @inlinable + func writeDouble(_ value: Double) { + writeInt(value.bitPattern) + } +} + + +// Types conforming to `Serializable` can be read and written in a bytebuffer. +fileprivate protocol Serializable { + func write(into: Writer) + static func read(from: Reader) throws -> Self +} + +// Types confirming to `ViaFfi` can be transferred back-and-for over the FFI. +// This is analogous to the Rust trait of the same name. +fileprivate protocol ViaFfi: Serializable { + associatedtype FfiType + static func lift(_ v: FfiType) throws -> Self + func lower() -> FfiType +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +fileprivate protocol Primitive {} + +extension Primitive { + fileprivate typealias FfiType = Self + + fileprivate static func lift(_ v: Self) throws -> Self { + return v + } + + fileprivate func lower() -> Self { + return self + } +} + +// Types conforming to `ViaFfiUsingByteBuffer` lift and lower into a bytebuffer. +// Use this for complex types where it's hard to write a custom lift/lower. +fileprivate protocol ViaFfiUsingByteBuffer: Serializable {} + +extension ViaFfiUsingByteBuffer { + fileprivate typealias FfiType = RustBuffer + + fileprivate static func lift(_ buf: FfiType) throws -> Self { + let reader = Reader(data: Data(rustBuffer: buf)) + let value = try Self.read(from: reader) + if reader.hasRemaining() { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + + fileprivate func lower() -> FfiType { + let writer = Writer() + self.write(into: writer) + return RustBuffer(bytes: writer.bytes) + } +} +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +fileprivate enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +fileprivate let CALL_SUCCESS: Int8 = 0 +fileprivate let CALL_ERROR: Int8 = 1 +fileprivate let CALL_PANIC: Int8 = 2 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall(_ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: { + $0.deallocate() + return UniffiInternalError.unexpectedRustCallError + }) +} + +private func rustCallWithError(_ errorClass: E.Type, _ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: { return try E.lift($0) }) +} + +private func makeRustCall(_ callback: (UnsafeMutablePointer) -> T, errorHandler: (RustBuffer) throws -> Error) throws -> T { + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + switch callStatus.code { + case CALL_SUCCESS: + return returnedVal + + case CALL_ERROR: + throw try errorHandler(callStatus.errorBuf) + + case CALL_PANIC: + // When the rust code sees a panic, it tries to construct a RustBuffer + // with the message. But if that code panics, then it just sends back + // an empty buffer. + if callStatus.errorBuf.len > 0 { + throw UniffiInternalError.rustPanic(try String.lift(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} +// Protocols for converters we'll implement in templates + +fileprivate protocol FfiConverter { + associatedtype SwiftType + associatedtype FfiType + + static func lift(_ ffiValue: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + + static func read(from: Reader) throws -> SwiftType + static func write(_ value: SwiftType, into: Writer) +} + +fileprivate protocol FfiConverterUsingByteBuffer: FfiConverter where FfiType == RustBuffer { + // Empty, because we want to declare some helper methods in the extension below. +} + +extension FfiConverterUsingByteBuffer { + static func lower(_ value: SwiftType) -> FfiType { + let writer = Writer() + Self.write(value, into: writer) + return RustBuffer(bytes: writer.bytes) + } + + static func lift(_ buf: FfiType) throws -> SwiftType { + let reader = Reader(data: Data(rustBuffer: buf)) + let value = try Self.read(from: reader) + if reader.hasRemaining() { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } +} + +// Helpers for structural types. Note that because of canonical_names, it /should/ be impossible +// to make another `FfiConverterSequence` etc just using the UDL. +fileprivate enum FfiConverterSequence { + static func write(_ value: [T], into buf: Writer, writeItem: (T, Writer) -> Void) { + let len = Int32(value.count) + buf.writeInt(len) + for item in value { + writeItem(item, buf) + } + } + + static func read(from buf: Reader, readItem: (Reader) throws -> T) throws -> [T] { + let len: Int32 = try buf.readInt() + var seq = [T]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try readItem(buf)) + } + return seq + } +} + +fileprivate enum FfiConverterOptional { + static func write(_ value: T?, into buf: Writer, writeItem: (T, Writer) -> Void) { + guard let value = value else { + buf.writeInt(Int8(0)) + return + } + buf.writeInt(Int8(1)) + writeItem(value, buf) + } + + static func read(from buf: Reader, readItem: (Reader) throws -> T) throws -> T? { + switch try buf.readInt() as Int8 { + case 0: return nil + case 1: return try readItem(buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate enum FfiConverterDictionary { + static func write(_ value: [String: T], into buf: Writer, writeItem: (String, T, Writer) -> Void) { + let len = Int32(value.count) + buf.writeInt(len) + for (key, value) in value { + writeItem(key, value, buf) + } + } + + static func read(from buf: Reader, readItem: (Reader) throws -> (String, T)) throws -> [String: T] { + let len: Int32 = try buf.readInt() + var dict = [String: T]() + dict.reserveCapacity(Int(len)) + for _ in 0..(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + +fileprivate typealias Handle = UInt64 +fileprivate class ConcurrentHandleMap { + private var leftMap: [Handle: T] = [:] + private var counter: [Handle: UInt64] = [:] + private var rightMap: [ObjectIdentifier: Handle] = [:] + + private let lock = NSLock() + private var currentHandle: Handle = 0 + private let stride: Handle = 1 + + func insert(obj: T) -> Handle { + lock.withLock { + let id = ObjectIdentifier(obj as AnyObject) + let handle = rightMap[id] ?? { + currentHandle += stride + let handle = currentHandle + leftMap[handle] = obj + rightMap[id] = handle + return handle + }() + counter[handle] = (counter[handle] ?? 0) + 1 + return handle + } + } + + func get(handle: Handle) -> T? { + lock.withLock { + leftMap[handle] + } + } + + func delete(handle: Handle) { + remove(handle: handle) + } + + @discardableResult + func remove(handle: Handle) -> T? { + lock.withLock { + defer { counter[handle] = (counter[handle] ?? 1) - 1 } + guard counter[handle] == 1 else { return leftMap[handle] } + let obj = leftMap.removeValue(forKey: handle) + if let obj = obj { + rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject)) + } + return obj + } + } +} + +// Magic number for the Rust proxy to call using the same mechanism as every other method, +// to free the callback once it's dropped by Rust. +private let IDX_CALLBACK_FREE: Int32 = 0 + +fileprivate class FfiConverterCallbackInterface { + fileprivate let handleMap = ConcurrentHandleMap() + + func drop(handle: Handle) { + handleMap.remove(handle: handle) + } + + func lift(_ handle: Handle) throws -> CallbackInterface { + guard let callback = handleMap.get(handle: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return callback + } + + func read(from buf: Reader) throws -> CallbackInterface { + let handle: Handle = try buf.readInt() + return try lift(handle) + } + + func lower(_ v: CallbackInterface) -> Handle { + let handle = handleMap.insert(obj: v) + return handle + // assert(handleMap.get(handle: obj) == v, "Handle map is not returning the object we just placed there. This is a bug in the HandleMap.") + } + + func write(_ v: CallbackInterface, into buf: Writer) { + buf.writeInt(lower(v)) + } +} + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum AttachmentData { + + case base64(value: Base64AttachmentData ) + case json(value: JsonAttachmentData ) + case links(value: LinksAttachmentData ) +} + +extension AttachmentData: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> AttachmentData { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .base64( + value: try Base64AttachmentData.read(from: buf) + ) + case 2: return .json( + value: try JsonAttachmentData.read(from: buf) + ) + case 3: return .links( + value: try LinksAttachmentData.read(from: buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case let .base64(value): + buf.writeInt(Int32(1)) + value.write(into: buf) + + + + case let .json(value): + buf.writeInt(Int32(2)) + value.write(into: buf) + + + + case let .links(value): + buf.writeInt(Int32(3)) + value.write(into: buf) + + + } + } +} + + +extension AttachmentData: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum ErrorCode { + + case success + case error +} + +extension ErrorCode: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> ErrorCode { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .success + case 2: return .error + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case .success: + buf.writeInt(Int32(1)) + + + case .error: + buf.writeInt(Int32(2)) + + } + } +} + + +extension ErrorCode: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum VerificationMaterial { + + case jwk(value: String ) + case multibase(value: String ) + case base58(value: String ) + case hex(value: String ) + case other(value: String ) +} + +extension VerificationMaterial: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> VerificationMaterial { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .jwk( + value: try String.read(from: buf) + ) + case 2: return .multibase( + value: try String.read(from: buf) + ) + case 3: return .base58( + value: try String.read(from: buf) + ) + case 4: return .hex( + value: try String.read(from: buf) + ) + case 5: return .other( + value: try String.read(from: buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case let .jwk(value): + buf.writeInt(Int32(1)) + value.write(into: buf) + + + + case let .multibase(value): + buf.writeInt(Int32(2)) + value.write(into: buf) + + + + case let .base58(value): + buf.writeInt(Int32(3)) + value.write(into: buf) + + + + case let .hex(value): + buf.writeInt(Int32(4)) + value.write(into: buf) + + + + case let .other(value): + buf.writeInt(Int32(5)) + value.write(into: buf) + + + } + } +} + + +extension VerificationMaterial: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum VerificationMethodType { + + case jsonWebKey2020 + case x25519KeyAgreementKey2019 + case ed25519VerificationKey2018 + case ecdsaSecp256k1VerificationKey2019 + case x25519KeyAgreementKey2020 + case ed25519VerificationKey2020 + case other +} + +extension VerificationMethodType: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> VerificationMethodType { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .jsonWebKey2020 + case 2: return .x25519KeyAgreementKey2019 + case 3: return .ed25519VerificationKey2018 + case 4: return .ecdsaSecp256k1VerificationKey2019 + case 5: return .x25519KeyAgreementKey2020 + case 6: return .ed25519VerificationKey2020 + case 7: return .other + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case .jsonWebKey2020: + buf.writeInt(Int32(1)) + + + case .x25519KeyAgreementKey2019: + buf.writeInt(Int32(2)) + + + case .ed25519VerificationKey2018: + buf.writeInt(Int32(3)) + + + case .ecdsaSecp256k1VerificationKey2019: + buf.writeInt(Int32(4)) + + + case .x25519KeyAgreementKey2020: + buf.writeInt(Int32(5)) + + + case .ed25519VerificationKey2020: + buf.writeInt(Int32(6)) + + + case .other: + buf.writeInt(Int32(7)) + + } + } +} + + +extension VerificationMethodType: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum ServiceKind { + + case didCommMessaging(value: DidCommMessagingService ) + case other(value: String ) +} + +extension ServiceKind: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> ServiceKind { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .didCommMessaging( + value: try DidCommMessagingService.read(from: buf) + ) + case 2: return .other( + value: try String.read(from: buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case let .didCommMessaging(value): + buf.writeInt(Int32(1)) + value.write(into: buf) + + + + case let .other(value): + buf.writeInt(Int32(2)) + value.write(into: buf) + + + } + } +} + + +extension ServiceKind: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum SecretMaterial { + + case jwk(value: String ) + case multibase(value: String ) + case base58(value: String ) + case hex(value: String ) + case other(value: String ) +} + +extension SecretMaterial: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> SecretMaterial { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .jwk( + value: try String.read(from: buf) + ) + case 2: return .multibase( + value: try String.read(from: buf) + ) + case 3: return .base58( + value: try String.read(from: buf) + ) + case 4: return .hex( + value: try String.read(from: buf) + ) + case 5: return .other( + value: try String.read(from: buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case let .jwk(value): + buf.writeInt(Int32(1)) + value.write(into: buf) + + + + case let .multibase(value): + buf.writeInt(Int32(2)) + value.write(into: buf) + + + + case let .base58(value): + buf.writeInt(Int32(3)) + value.write(into: buf) + + + + case let .hex(value): + buf.writeInt(Int32(4)) + value.write(into: buf) + + + + case let .other(value): + buf.writeInt(Int32(5)) + value.write(into: buf) + + + } + } +} + + +extension SecretMaterial: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum SecretType { + + case jsonWebKey2020 + case x25519KeyAgreementKey2019 + case ed25519VerificationKey2018 + case ecdsaSecp256k1VerificationKey2019 + case x25519KeyAgreementKey2020 + case ed25519VerificationKey2020 + case other +} + +extension SecretType: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> SecretType { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .jsonWebKey2020 + case 2: return .x25519KeyAgreementKey2019 + case 3: return .ed25519VerificationKey2018 + case 4: return .ecdsaSecp256k1VerificationKey2019 + case 5: return .x25519KeyAgreementKey2020 + case 6: return .ed25519VerificationKey2020 + case 7: return .other + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case .jsonWebKey2020: + buf.writeInt(Int32(1)) + + + case .x25519KeyAgreementKey2019: + buf.writeInt(Int32(2)) + + + case .ed25519VerificationKey2018: + buf.writeInt(Int32(3)) + + + case .ecdsaSecp256k1VerificationKey2019: + buf.writeInt(Int32(4)) + + + case .x25519KeyAgreementKey2020: + buf.writeInt(Int32(5)) + + + case .ed25519VerificationKey2020: + buf.writeInt(Int32(6)) + + + case .other: + buf.writeInt(Int32(7)) + + } + } +} + + +extension SecretType: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum AuthCryptAlg { + + case a256cbcHs512Ecdh1puA256kw +} + +extension AuthCryptAlg: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> AuthCryptAlg { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .a256cbcHs512Ecdh1puA256kw + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case .a256cbcHs512Ecdh1puA256kw: + buf.writeInt(Int32(1)) + + } + } +} + + +extension AuthCryptAlg: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum AnonCryptAlg { + + case a256cbcHs512EcdhEsA256kw + case xc20pEcdhEsA256kw + case a256gcmEcdhEsA256kw +} + +extension AnonCryptAlg: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> AnonCryptAlg { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .a256cbcHs512EcdhEsA256kw + case 2: return .xc20pEcdhEsA256kw + case 3: return .a256gcmEcdhEsA256kw + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case .a256cbcHs512EcdhEsA256kw: + buf.writeInt(Int32(1)) + + + case .xc20pEcdhEsA256kw: + buf.writeInt(Int32(2)) + + + case .a256gcmEcdhEsA256kw: + buf.writeInt(Int32(3)) + + } + } +} + + +extension AnonCryptAlg: Equatable, Hashable {} + + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum SignAlg { + + case edDsa + case es256 + case es256k +} + +extension SignAlg: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> SignAlg { + let variant: Int32 = try buf.readInt() + switch variant { + + case 1: return .edDsa + case 2: return .es256 + case 3: return .es256k + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + case .edDsa: + buf.writeInt(Int32(1)) + + + case .es256: + buf.writeInt(Int32(2)) + + + case .es256k: + buf.writeInt(Int32(3)) + + } + } +} + + +extension SignAlg: Equatable, Hashable {} + + + +public protocol DIDCommProtocol { + func packPlaintext(msg: Message, cb: OnPackPlaintextResult ) -> ErrorCode + func packSigned(msg: Message, signBy: String, cb: OnPackSignedResult ) -> ErrorCode + func packEncrypted(msg: Message, to: String, from: String?, signBy: String?, options: PackEncryptedOptions, cb: OnPackEncryptedResult ) -> ErrorCode + func unpack(msg: String, options: UnpackOptions, cb: OnUnpackResult ) -> ErrorCode + func packFromPrior(msg: FromPrior, issuerKid: String?, cb: OnFromPriorPackResult ) -> ErrorCode + func unpackFromPrior(fromPriorJwt: String, cb: OnFromPriorUnpackResult ) -> ErrorCode + func wrapInForward(msg: String, headers: [String: String], to: String, routingKeys: [String], encAlgAnon: AnonCryptAlg, cb: OnWrapInForwardResult ) -> ErrorCode + +} + +public class DidComm: DIDCommProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `ViaFfi` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init(didResolver: DidResolver, secretResolver: SecretsResolver ) { + self.init(unsafeFromRawPointer: try! + + + rustCall() { + + didcomm_f20e_DIDComm_new(ffiConverterCallbackInterfaceDidResolver.lower(didResolver), ffiConverterCallbackInterfaceSecretsResolver.lower(secretResolver) , $0) +}) + } + + deinit { + try! rustCall { ffi_didcomm_f20e_DIDComm_object_free(pointer, $0) } + } + + + + + public func packPlaintext(msg: Message, cb: OnPackPlaintextResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_DIDComm_pack_plaintext(self.pointer, msg.lower(), ffiConverterCallbackInterfaceOnPackPlaintextResult.lower(cb) , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + public func packSigned(msg: Message, signBy: String, cb: OnPackSignedResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_DIDComm_pack_signed(self.pointer, msg.lower(), signBy.lower(), ffiConverterCallbackInterfaceOnPackSignedResult.lower(cb) , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + public func packEncrypted(msg: Message, to: String, from: String?, signBy: String?, options: PackEncryptedOptions, cb: OnPackEncryptedResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_DIDComm_pack_encrypted(self.pointer, msg.lower(), to.lower(), FfiConverterOptionString.lower(from), FfiConverterOptionString.lower(signBy), options.lower(), ffiConverterCallbackInterfaceOnPackEncryptedResult.lower(cb) , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + public func unpack(msg: String, options: UnpackOptions, cb: OnUnpackResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_DIDComm_unpack(self.pointer, msg.lower(), options.lower(), ffiConverterCallbackInterfaceOnUnpackResult.lower(cb) , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + public func packFromPrior(msg: FromPrior, issuerKid: String?, cb: OnFromPriorPackResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_DIDComm_pack_from_prior(self.pointer, msg.lower(), FfiConverterOptionString.lower(issuerKid), ffiConverterCallbackInterfaceOnFromPriorPackResult.lower(cb) , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + public func unpackFromPrior(fromPriorJwt: String, cb: OnFromPriorUnpackResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_DIDComm_unpack_from_prior(self.pointer, fromPriorJwt.lower(), ffiConverterCallbackInterfaceOnFromPriorUnpackResult.lower(cb) , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + public func wrapInForward(msg: String, headers: [String: String], to: String, routingKeys: [String], encAlgAnon: AnonCryptAlg, cb: OnWrapInForwardResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_DIDComm_wrap_in_forward(self.pointer, msg.lower(), FfiConverterDictionaryJsonValue.lower(headers), to.lower(), FfiConverterSequenceString.lower(routingKeys), encAlgAnon.lower(), ffiConverterCallbackInterfaceOnWrapInForwardResult.lower(cb) , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + +} + + +fileprivate extension DidComm { + fileprivate typealias FfiType = UnsafeMutableRawPointer + + fileprivate static func read(from buf: Reader) throws -> Self { + let v: UInt64 = try buf.readInt() + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try self.lift(ptr!) + } + + fileprivate func write(into buf: Writer) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: self.lower())))) + } + + fileprivate static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Self { + return Self(unsafeFromRawPointer: pointer) + } + + fileprivate func lower() -> UnsafeMutableRawPointer { + return self.pointer + } +} + +// Ideally this would be `fileprivate`, but Swift says: +// """ +// 'private' modifier cannot be used with extensions that declare protocol conformances +// """ +extension DidComm : ViaFfi, Serializable {} + + +public protocol OnDIDResolverResultProtocol { + func success(result: DidDoc? ) throws + func error(err: ErrorKind, msg: String ) throws + +} + +public class OnDidResolverResult: OnDIDResolverResultProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `ViaFfi` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { ffi_didcomm_f20e_OnDIDResolverResult_object_free(pointer, $0) } + } + + + + + public func success(result: DidDoc? ) throws { + try + rustCallWithError(ErrorKind.self) { + + didcomm_f20e_OnDIDResolverResult_success(self.pointer, FfiConverterOptionRecordDidDoc.lower(result) , $0 + ) +} + } + public func error(err: ErrorKind, msg: String ) throws { + try + rustCallWithError(ErrorKind.self) { + + didcomm_f20e_OnDIDResolverResult_error(self.pointer, err.lower(), msg.lower() , $0 + ) +} + } + +} + + +fileprivate extension OnDidResolverResult { + fileprivate typealias FfiType = UnsafeMutableRawPointer + + fileprivate static func read(from buf: Reader) throws -> Self { + let v: UInt64 = try buf.readInt() + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try self.lift(ptr!) + } + + fileprivate func write(into buf: Writer) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: self.lower())))) + } + + fileprivate static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Self { + return Self(unsafeFromRawPointer: pointer) + } + + fileprivate func lower() -> UnsafeMutableRawPointer { + return self.pointer + } +} + +// Ideally this would be `fileprivate`, but Swift says: +// """ +// 'private' modifier cannot be used with extensions that declare protocol conformances +// """ +extension OnDidResolverResult : ViaFfi, Serializable {} + + +public protocol ExampleDIDResolverProtocol { + func resolve(did: String, cb: OnDidResolverResult ) -> ErrorCode + +} + +public class ExampleDidResolver: ExampleDIDResolverProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `ViaFfi` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init(knownDids: [DidDoc] ) { + self.init(unsafeFromRawPointer: try! + + + rustCall() { + + didcomm_f20e_ExampleDIDResolver_new(FfiConverterSequenceRecordDidDoc.lower(knownDids) , $0) +}) + } + + deinit { + try! rustCall { ffi_didcomm_f20e_ExampleDIDResolver_object_free(pointer, $0) } + } + + + + + public func resolve(did: String, cb: OnDidResolverResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_ExampleDIDResolver_resolve(self.pointer, did.lower(), cb.lower() , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + +} + + +fileprivate extension ExampleDidResolver { + fileprivate typealias FfiType = UnsafeMutableRawPointer + + fileprivate static func read(from buf: Reader) throws -> Self { + let v: UInt64 = try buf.readInt() + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try self.lift(ptr!) + } + + fileprivate func write(into buf: Writer) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: self.lower())))) + } + + fileprivate static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Self { + return Self(unsafeFromRawPointer: pointer) + } + + fileprivate func lower() -> UnsafeMutableRawPointer { + return self.pointer + } +} + +// Ideally this would be `fileprivate`, but Swift says: +// """ +// 'private' modifier cannot be used with extensions that declare protocol conformances +// """ +extension ExampleDidResolver : ViaFfi, Serializable {} + + +public protocol OnGetSecretResultProtocol { + func success(result: Secret? ) throws + func error(err: ErrorKind, msg: String ) throws + +} + +public class OnGetSecretResult: OnGetSecretResultProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `ViaFfi` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { ffi_didcomm_f20e_OnGetSecretResult_object_free(pointer, $0) } + } + + + + + public func success(result: Secret? ) throws { + try + rustCallWithError(ErrorKind.self) { + + didcomm_f20e_OnGetSecretResult_success(self.pointer, FfiConverterOptionRecordSecret.lower(result) , $0 + ) +} + } + public func error(err: ErrorKind, msg: String ) throws { + try + rustCallWithError(ErrorKind.self) { + + didcomm_f20e_OnGetSecretResult_error(self.pointer, err.lower(), msg.lower() , $0 + ) +} + } + +} + + +fileprivate extension OnGetSecretResult { + fileprivate typealias FfiType = UnsafeMutableRawPointer + + fileprivate static func read(from buf: Reader) throws -> Self { + let v: UInt64 = try buf.readInt() + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try self.lift(ptr!) + } + + fileprivate func write(into buf: Writer) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: self.lower())))) + } + + fileprivate static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Self { + return Self(unsafeFromRawPointer: pointer) + } + + fileprivate func lower() -> UnsafeMutableRawPointer { + return self.pointer + } +} + +// Ideally this would be `fileprivate`, but Swift says: +// """ +// 'private' modifier cannot be used with extensions that declare protocol conformances +// """ +extension OnGetSecretResult : ViaFfi, Serializable {} + + +public protocol OnFindSecretsResultProtocol { + func success(result: [String] ) throws + func error(err: ErrorKind, msg: String ) throws + +} + +public class OnFindSecretsResult: OnFindSecretsResultProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `ViaFfi` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + deinit { + try! rustCall { ffi_didcomm_f20e_OnFindSecretsResult_object_free(pointer, $0) } + } + + + + + public func success(result: [String] ) throws { + try + rustCallWithError(ErrorKind.self) { + + didcomm_f20e_OnFindSecretsResult_success(self.pointer, FfiConverterSequenceString.lower(result) , $0 + ) +} + } + public func error(err: ErrorKind, msg: String ) throws { + try + rustCallWithError(ErrorKind.self) { + + didcomm_f20e_OnFindSecretsResult_error(self.pointer, err.lower(), msg.lower() , $0 + ) +} + } + +} + + +fileprivate extension OnFindSecretsResult { + fileprivate typealias FfiType = UnsafeMutableRawPointer + + fileprivate static func read(from buf: Reader) throws -> Self { + let v: UInt64 = try buf.readInt() + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try self.lift(ptr!) + } + + fileprivate func write(into buf: Writer) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: self.lower())))) + } + + fileprivate static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Self { + return Self(unsafeFromRawPointer: pointer) + } + + fileprivate func lower() -> UnsafeMutableRawPointer { + return self.pointer + } +} + +// Ideally this would be `fileprivate`, but Swift says: +// """ +// 'private' modifier cannot be used with extensions that declare protocol conformances +// """ +extension OnFindSecretsResult : ViaFfi, Serializable {} + + +public protocol ExampleSecretsResolverProtocol { + func getSecret(secretId: String, cb: OnGetSecretResult ) -> ErrorCode + func findSecrets(secretIds: [String], cb: OnFindSecretsResult ) -> ErrorCode + +} + +public class ExampleSecretsResolver: ExampleSecretsResolverProtocol { + fileprivate let pointer: UnsafeMutableRawPointer + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `ViaFfi` without making this `required` and we can't + // make it `required` without making it `public`. + required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + public convenience init(knownSecrets: [Secret] ) { + self.init(unsafeFromRawPointer: try! + + + rustCall() { + + didcomm_f20e_ExampleSecretsResolver_new(FfiConverterSequenceRecordSecret.lower(knownSecrets) , $0) +}) + } + + deinit { + try! rustCall { ffi_didcomm_f20e_ExampleSecretsResolver_object_free(pointer, $0) } + } + + + + + public func getSecret(secretId: String, cb: OnGetSecretResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_ExampleSecretsResolver_get_secret(self.pointer, secretId.lower(), cb.lower() , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + public func findSecrets(secretIds: [String], cb: OnFindSecretsResult ) -> ErrorCode { + let _retval = try! + rustCall() { + + didcomm_f20e_ExampleSecretsResolver_find_secrets(self.pointer, FfiConverterSequenceString.lower(secretIds), cb.lower() , $0 + ) +} + return try! ErrorCode.lift(_retval) + } + +} + + +fileprivate extension ExampleSecretsResolver { + fileprivate typealias FfiType = UnsafeMutableRawPointer + + fileprivate static func read(from buf: Reader) throws -> Self { + let v: UInt64 = try buf.readInt() + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try self.lift(ptr!) + } + + fileprivate func write(into buf: Writer) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + buf.writeInt(UInt64(bitPattern: Int64(Int(bitPattern: self.lower())))) + } + + fileprivate static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Self { + return Self(unsafeFromRawPointer: pointer) + } + + fileprivate func lower() -> UnsafeMutableRawPointer { + return self.pointer + } +} + +// Ideally this would be `fileprivate`, but Swift says: +// """ +// 'private' modifier cannot be used with extensions that declare protocol conformances +// """ +extension ExampleSecretsResolver : ViaFfi, Serializable {} + +public struct Message { + public var id: String + public var typ: String + public var type: String + public var body: String + public var from: String? + public var to: [String]? + public var thid: String? + public var pthid: String? + public var extraHeaders: [String: String] + public var createdTime: UInt64? + public var expiresTime: UInt64? + public var fromPrior: String? + public var attachments: [Attachment]? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(id: String, typ: String, type: String, body: String, from: String?, to: [String]?, thid: String?, pthid: String?, extraHeaders: [String: String], createdTime: UInt64?, expiresTime: UInt64?, fromPrior: String?, attachments: [Attachment]? ) { + self.id = id + self.typ = typ + self.type = type + self.body = body + self.from = from + self.to = to + self.thid = thid + self.pthid = pthid + self.extraHeaders = extraHeaders + self.createdTime = createdTime + self.expiresTime = expiresTime + self.fromPrior = fromPrior + self.attachments = attachments + } +} + + +extension Message: Equatable, Hashable { + public static func ==(lhs: Message, rhs: Message) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.typ != rhs.typ { + return false + } + if lhs.type != rhs.type { + return false + } + if lhs.body != rhs.body { + return false + } + if lhs.from != rhs.from { + return false + } + if lhs.to != rhs.to { + return false + } + if lhs.thid != rhs.thid { + return false + } + if lhs.pthid != rhs.pthid { + return false + } + if lhs.extraHeaders != rhs.extraHeaders { + return false + } + if lhs.createdTime != rhs.createdTime { + return false + } + if lhs.expiresTime != rhs.expiresTime { + return false + } + if lhs.fromPrior != rhs.fromPrior { + return false + } + if lhs.attachments != rhs.attachments { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(typ) + hasher.combine(type) + hasher.combine(body) + hasher.combine(from) + hasher.combine(to) + hasher.combine(thid) + hasher.combine(pthid) + hasher.combine(extraHeaders) + hasher.combine(createdTime) + hasher.combine(expiresTime) + hasher.combine(fromPrior) + hasher.combine(attachments) + } +} + + +fileprivate extension Message { + static func read(from buf: Reader) throws -> Message { + return try Message( + id: String.read(from: buf), + typ: String.read(from: buf), + type: String.read(from: buf), + body: String.read(from: buf), + from: FfiConverterOptionString.read(from: buf), + to: FfiConverterOptionSequenceString.read(from: buf), + thid: FfiConverterOptionString.read(from: buf), + pthid: FfiConverterOptionString.read(from: buf), + extraHeaders: FfiConverterDictionaryJsonValue.read(from: buf), + createdTime: FfiConverterOptionUInt64.read(from: buf), + expiresTime: FfiConverterOptionUInt64.read(from: buf), + fromPrior: FfiConverterOptionString.read(from: buf), + attachments: FfiConverterOptionSequenceRecordAttachment.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.id.write(into: buf) + self.typ.write(into: buf) + self.type.write(into: buf) + self.body.write(into: buf) + FfiConverterOptionString.write(self.from, into: buf) + FfiConverterOptionSequenceString.write(self.to, into: buf) + FfiConverterOptionString.write(self.thid, into: buf) + FfiConverterOptionString.write(self.pthid, into: buf) + FfiConverterDictionaryJsonValue.write(self.extraHeaders, into: buf) + FfiConverterOptionUInt64.write(self.createdTime, into: buf) + FfiConverterOptionUInt64.write(self.expiresTime, into: buf) + FfiConverterOptionString.write(self.fromPrior, into: buf) + FfiConverterOptionSequenceRecordAttachment.write(self.attachments, into: buf) + } +} + +extension Message: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct Attachment { + public var data: AttachmentData + public var id: String? + public var description: String? + public var filename: String? + public var mediaType: String? + public var format: String? + public var lastmodTime: UInt64? + public var byteCount: UInt64? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(data: AttachmentData, id: String?, description: String?, filename: String?, mediaType: String?, format: String?, lastmodTime: UInt64?, byteCount: UInt64? ) { + self.data = data + self.id = id + self.description = description + self.filename = filename + self.mediaType = mediaType + self.format = format + self.lastmodTime = lastmodTime + self.byteCount = byteCount + } +} + + +extension Attachment: Equatable, Hashable { + public static func ==(lhs: Attachment, rhs: Attachment) -> Bool { + if lhs.data != rhs.data { + return false + } + if lhs.id != rhs.id { + return false + } + if lhs.description != rhs.description { + return false + } + if lhs.filename != rhs.filename { + return false + } + if lhs.mediaType != rhs.mediaType { + return false + } + if lhs.format != rhs.format { + return false + } + if lhs.lastmodTime != rhs.lastmodTime { + return false + } + if lhs.byteCount != rhs.byteCount { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(data) + hasher.combine(id) + hasher.combine(description) + hasher.combine(filename) + hasher.combine(mediaType) + hasher.combine(format) + hasher.combine(lastmodTime) + hasher.combine(byteCount) + } +} + + +fileprivate extension Attachment { + static func read(from buf: Reader) throws -> Attachment { + return try Attachment( + data: AttachmentData.read(from: buf), + id: FfiConverterOptionString.read(from: buf), + description: FfiConverterOptionString.read(from: buf), + filename: FfiConverterOptionString.read(from: buf), + mediaType: FfiConverterOptionString.read(from: buf), + format: FfiConverterOptionString.read(from: buf), + lastmodTime: FfiConverterOptionUInt64.read(from: buf), + byteCount: FfiConverterOptionUInt64.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.data.write(into: buf) + FfiConverterOptionString.write(self.id, into: buf) + FfiConverterOptionString.write(self.description, into: buf) + FfiConverterOptionString.write(self.filename, into: buf) + FfiConverterOptionString.write(self.mediaType, into: buf) + FfiConverterOptionString.write(self.format, into: buf) + FfiConverterOptionUInt64.write(self.lastmodTime, into: buf) + FfiConverterOptionUInt64.write(self.byteCount, into: buf) + } +} + +extension Attachment: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct Base64AttachmentData { + public var base64: String + public var jws: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(base64: String, jws: String? ) { + self.base64 = base64 + self.jws = jws + } +} + + +extension Base64AttachmentData: Equatable, Hashable { + public static func ==(lhs: Base64AttachmentData, rhs: Base64AttachmentData) -> Bool { + if lhs.base64 != rhs.base64 { + return false + } + if lhs.jws != rhs.jws { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(base64) + hasher.combine(jws) + } +} + + +fileprivate extension Base64AttachmentData { + static func read(from buf: Reader) throws -> Base64AttachmentData { + return try Base64AttachmentData( + base64: String.read(from: buf), + jws: FfiConverterOptionString.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.base64.write(into: buf) + FfiConverterOptionString.write(self.jws, into: buf) + } +} + +extension Base64AttachmentData: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct JsonAttachmentData { + public var json: String + public var jws: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(json: String, jws: String? ) { + self.json = json + self.jws = jws + } +} + + +extension JsonAttachmentData: Equatable, Hashable { + public static func ==(lhs: JsonAttachmentData, rhs: JsonAttachmentData) -> Bool { + if lhs.json != rhs.json { + return false + } + if lhs.jws != rhs.jws { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(json) + hasher.combine(jws) + } +} + + +fileprivate extension JsonAttachmentData { + static func read(from buf: Reader) throws -> JsonAttachmentData { + return try JsonAttachmentData( + json: String.read(from: buf), + jws: FfiConverterOptionString.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.json.write(into: buf) + FfiConverterOptionString.write(self.jws, into: buf) + } +} + +extension JsonAttachmentData: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct LinksAttachmentData { + public var links: [String] + public var hash: String + public var jws: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(links: [String], hash: String, jws: String? ) { + self.links = links + self.hash = hash + self.jws = jws + } +} + + +extension LinksAttachmentData: Equatable, Hashable { + public static func ==(lhs: LinksAttachmentData, rhs: LinksAttachmentData) -> Bool { + if lhs.links != rhs.links { + return false + } + if lhs.hash != rhs.hash { + return false + } + if lhs.jws != rhs.jws { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(links) + hasher.combine(hash) + hasher.combine(jws) + } +} + + +fileprivate extension LinksAttachmentData { + static func read(from buf: Reader) throws -> LinksAttachmentData { + return try LinksAttachmentData( + links: FfiConverterSequenceString.read(from: buf), + hash: String.read(from: buf), + jws: FfiConverterOptionString.read(from: buf) + ) + } + + func write(into buf: Writer) { + FfiConverterSequenceString.write(self.links, into: buf) + self.hash.write(into: buf) + FfiConverterOptionString.write(self.jws, into: buf) + } +} + +extension LinksAttachmentData: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct DidDoc { + public var did: String + public var keyAgreements: [String] + public var authentications: [String] + public var verificationMethods: [VerificationMethod] + public var services: [Service] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(did: String, keyAgreements: [String], authentications: [String], verificationMethods: [VerificationMethod], services: [Service] ) { + self.did = did + self.keyAgreements = keyAgreements + self.authentications = authentications + self.verificationMethods = verificationMethods + self.services = services + } +} + + +extension DidDoc: Equatable, Hashable { + public static func ==(lhs: DidDoc, rhs: DidDoc) -> Bool { + if lhs.did != rhs.did { + return false + } + if lhs.keyAgreements != rhs.keyAgreements { + return false + } + if lhs.authentications != rhs.authentications { + return false + } + if lhs.verificationMethods != rhs.verificationMethods { + return false + } + if lhs.services != rhs.services { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(did) + hasher.combine(keyAgreements) + hasher.combine(authentications) + hasher.combine(verificationMethods) + hasher.combine(services) + } +} + + +fileprivate extension DidDoc { + static func read(from buf: Reader) throws -> DidDoc { + return try DidDoc( + did: String.read(from: buf), + keyAgreements: FfiConverterSequenceString.read(from: buf), + authentications: FfiConverterSequenceString.read(from: buf), + verificationMethods: FfiConverterSequenceRecordVerificationMethod.read(from: buf), + services: FfiConverterSequenceRecordService.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.did.write(into: buf) + FfiConverterSequenceString.write(self.keyAgreements, into: buf) + FfiConverterSequenceString.write(self.authentications, into: buf) + FfiConverterSequenceRecordVerificationMethod.write(self.verificationMethods, into: buf) + FfiConverterSequenceRecordService.write(self.services, into: buf) + } +} + +extension DidDoc: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct VerificationMethod { + public var id: String + public var type: VerificationMethodType + public var controller: String + public var verificationMaterial: VerificationMaterial + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(id: String, type: VerificationMethodType, controller: String, verificationMaterial: VerificationMaterial ) { + self.id = id + self.type = type + self.controller = controller + self.verificationMaterial = verificationMaterial + } +} + + +extension VerificationMethod: Equatable, Hashable { + public static func ==(lhs: VerificationMethod, rhs: VerificationMethod) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.type != rhs.type { + return false + } + if lhs.controller != rhs.controller { + return false + } + if lhs.verificationMaterial != rhs.verificationMaterial { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(type) + hasher.combine(controller) + hasher.combine(verificationMaterial) + } +} + + +fileprivate extension VerificationMethod { + static func read(from buf: Reader) throws -> VerificationMethod { + return try VerificationMethod( + id: String.read(from: buf), + type: VerificationMethodType.read(from: buf), + controller: String.read(from: buf), + verificationMaterial: VerificationMaterial.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.id.write(into: buf) + self.type.write(into: buf) + self.controller.write(into: buf) + self.verificationMaterial.write(into: buf) + } +} + +extension VerificationMethod: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct Service { + public var id: String + public var kind: ServiceKind + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(id: String, kind: ServiceKind ) { + self.id = id + self.kind = kind + } +} + + +extension Service: Equatable, Hashable { + public static func ==(lhs: Service, rhs: Service) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.kind != rhs.kind { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(kind) + } +} + + +fileprivate extension Service { + static func read(from buf: Reader) throws -> Service { + return try Service( + id: String.read(from: buf), + kind: ServiceKind.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.id.write(into: buf) + self.kind.write(into: buf) + } +} + +extension Service: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct DidCommMessagingService { + public var serviceEndpoint: String + public var accept: [String] + public var routingKeys: [String] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(serviceEndpoint: String, accept: [String], routingKeys: [String] ) { + self.serviceEndpoint = serviceEndpoint + self.accept = accept + self.routingKeys = routingKeys + } +} + + +extension DidCommMessagingService: Equatable, Hashable { + public static func ==(lhs: DidCommMessagingService, rhs: DidCommMessagingService) -> Bool { + if lhs.serviceEndpoint != rhs.serviceEndpoint { + return false + } + if lhs.accept != rhs.accept { + return false + } + if lhs.routingKeys != rhs.routingKeys { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(serviceEndpoint) + hasher.combine(accept) + hasher.combine(routingKeys) + } +} + + +fileprivate extension DidCommMessagingService { + static func read(from buf: Reader) throws -> DidCommMessagingService { + return try DidCommMessagingService( + serviceEndpoint: String.read(from: buf), + accept: FfiConverterSequenceString.read(from: buf), + routingKeys: FfiConverterSequenceString.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.serviceEndpoint.write(into: buf) + FfiConverterSequenceString.write(self.accept, into: buf) + FfiConverterSequenceString.write(self.routingKeys, into: buf) + } +} + +extension DidCommMessagingService: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct Secret { + public var id: String + public var type: SecretType + public var secretMaterial: SecretMaterial + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(id: String, type: SecretType, secretMaterial: SecretMaterial ) { + self.id = id + self.type = type + self.secretMaterial = secretMaterial + } +} + + +extension Secret: Equatable, Hashable { + public static func ==(lhs: Secret, rhs: Secret) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.type != rhs.type { + return false + } + if lhs.secretMaterial != rhs.secretMaterial { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(type) + hasher.combine(secretMaterial) + } +} + + +fileprivate extension Secret { + static func read(from buf: Reader) throws -> Secret { + return try Secret( + id: String.read(from: buf), + type: SecretType.read(from: buf), + secretMaterial: SecretMaterial.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.id.write(into: buf) + self.type.write(into: buf) + self.secretMaterial.write(into: buf) + } +} + +extension Secret: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct PackSignedMetadata { + public var signByKid: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(signByKid: String ) { + self.signByKid = signByKid + } +} + + +extension PackSignedMetadata: Equatable, Hashable { + public static func ==(lhs: PackSignedMetadata, rhs: PackSignedMetadata) -> Bool { + if lhs.signByKid != rhs.signByKid { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(signByKid) + } +} + + +fileprivate extension PackSignedMetadata { + static func read(from buf: Reader) throws -> PackSignedMetadata { + return try PackSignedMetadata( + signByKid: String.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.signByKid.write(into: buf) + } +} + +extension PackSignedMetadata: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct PackEncryptedMetadata { + public var messagingService: MessagingServiceMetadata? + public var fromKid: String? + public var signByKid: String? + public var toKids: [String] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(messagingService: MessagingServiceMetadata?, fromKid: String?, signByKid: String?, toKids: [String] ) { + self.messagingService = messagingService + self.fromKid = fromKid + self.signByKid = signByKid + self.toKids = toKids + } +} + + +extension PackEncryptedMetadata: Equatable, Hashable { + public static func ==(lhs: PackEncryptedMetadata, rhs: PackEncryptedMetadata) -> Bool { + if lhs.messagingService != rhs.messagingService { + return false + } + if lhs.fromKid != rhs.fromKid { + return false + } + if lhs.signByKid != rhs.signByKid { + return false + } + if lhs.toKids != rhs.toKids { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(messagingService) + hasher.combine(fromKid) + hasher.combine(signByKid) + hasher.combine(toKids) + } +} + + +fileprivate extension PackEncryptedMetadata { + static func read(from buf: Reader) throws -> PackEncryptedMetadata { + return try PackEncryptedMetadata( + messagingService: FfiConverterOptionRecordMessagingServiceMetadata.read(from: buf), + fromKid: FfiConverterOptionString.read(from: buf), + signByKid: FfiConverterOptionString.read(from: buf), + toKids: FfiConverterSequenceString.read(from: buf) + ) + } + + func write(into buf: Writer) { + FfiConverterOptionRecordMessagingServiceMetadata.write(self.messagingService, into: buf) + FfiConverterOptionString.write(self.fromKid, into: buf) + FfiConverterOptionString.write(self.signByKid, into: buf) + FfiConverterSequenceString.write(self.toKids, into: buf) + } +} + +extension PackEncryptedMetadata: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct MessagingServiceMetadata { + public var id: String + public var serviceEndpoint: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(id: String, serviceEndpoint: String ) { + self.id = id + self.serviceEndpoint = serviceEndpoint + } +} + + +extension MessagingServiceMetadata: Equatable, Hashable { + public static func ==(lhs: MessagingServiceMetadata, rhs: MessagingServiceMetadata) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.serviceEndpoint != rhs.serviceEndpoint { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(serviceEndpoint) + } +} + + +fileprivate extension MessagingServiceMetadata { + static func read(from buf: Reader) throws -> MessagingServiceMetadata { + return try MessagingServiceMetadata( + id: String.read(from: buf), + serviceEndpoint: String.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.id.write(into: buf) + self.serviceEndpoint.write(into: buf) + } +} + +extension MessagingServiceMetadata: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct PackEncryptedOptions { + public var protectSender: Bool + public var forward: Bool + public var forwardHeaders: [String: String]? + public var messagingService: String? + public var encAlgAuth: AuthCryptAlg + public var encAlgAnon: AnonCryptAlg + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(protectSender: Bool, forward: Bool, forwardHeaders: [String: String]?, messagingService: String?, encAlgAuth: AuthCryptAlg, encAlgAnon: AnonCryptAlg ) { + self.protectSender = protectSender + self.forward = forward + self.forwardHeaders = forwardHeaders + self.messagingService = messagingService + self.encAlgAuth = encAlgAuth + self.encAlgAnon = encAlgAnon + } +} + + +extension PackEncryptedOptions: Equatable, Hashable { + public static func ==(lhs: PackEncryptedOptions, rhs: PackEncryptedOptions) -> Bool { + if lhs.protectSender != rhs.protectSender { + return false + } + if lhs.forward != rhs.forward { + return false + } + if lhs.forwardHeaders != rhs.forwardHeaders { + return false + } + if lhs.messagingService != rhs.messagingService { + return false + } + if lhs.encAlgAuth != rhs.encAlgAuth { + return false + } + if lhs.encAlgAnon != rhs.encAlgAnon { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(protectSender) + hasher.combine(forward) + hasher.combine(forwardHeaders) + hasher.combine(messagingService) + hasher.combine(encAlgAuth) + hasher.combine(encAlgAnon) + } +} + + +fileprivate extension PackEncryptedOptions { + static func read(from buf: Reader) throws -> PackEncryptedOptions { + return try PackEncryptedOptions( + protectSender: Bool.read(from: buf), + forward: Bool.read(from: buf), + forwardHeaders: FfiConverterOptionDictionaryJsonValue.read(from: buf), + messagingService: FfiConverterOptionString.read(from: buf), + encAlgAuth: AuthCryptAlg.read(from: buf), + encAlgAnon: AnonCryptAlg.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.protectSender.write(into: buf) + self.forward.write(into: buf) + FfiConverterOptionDictionaryJsonValue.write(self.forwardHeaders, into: buf) + FfiConverterOptionString.write(self.messagingService, into: buf) + self.encAlgAuth.write(into: buf) + self.encAlgAnon.write(into: buf) + } +} + +extension PackEncryptedOptions: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct UnpackMetadata { + public var encrypted: Bool + public var authenticated: Bool + public var nonRepudiation: Bool + public var anonymousSender: Bool + public var reWrappedInForward: Bool + public var encryptedFromKid: String? + public var encryptedToKids: [String]? + public var signFrom: String? + public var fromPriorIssuerKid: String? + public var encAlgAuth: AuthCryptAlg? + public var encAlgAnon: AnonCryptAlg? + public var signAlg: SignAlg? + public var signedMessage: String? + public var fromPrior: FromPrior? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(encrypted: Bool, authenticated: Bool, nonRepudiation: Bool, anonymousSender: Bool, reWrappedInForward: Bool, encryptedFromKid: String?, encryptedToKids: [String]?, signFrom: String?, fromPriorIssuerKid: String?, encAlgAuth: AuthCryptAlg?, encAlgAnon: AnonCryptAlg?, signAlg: SignAlg?, signedMessage: String?, fromPrior: FromPrior? ) { + self.encrypted = encrypted + self.authenticated = authenticated + self.nonRepudiation = nonRepudiation + self.anonymousSender = anonymousSender + self.reWrappedInForward = reWrappedInForward + self.encryptedFromKid = encryptedFromKid + self.encryptedToKids = encryptedToKids + self.signFrom = signFrom + self.fromPriorIssuerKid = fromPriorIssuerKid + self.encAlgAuth = encAlgAuth + self.encAlgAnon = encAlgAnon + self.signAlg = signAlg + self.signedMessage = signedMessage + self.fromPrior = fromPrior + } +} + + +extension UnpackMetadata: Equatable, Hashable { + public static func ==(lhs: UnpackMetadata, rhs: UnpackMetadata) -> Bool { + if lhs.encrypted != rhs.encrypted { + return false + } + if lhs.authenticated != rhs.authenticated { + return false + } + if lhs.nonRepudiation != rhs.nonRepudiation { + return false + } + if lhs.anonymousSender != rhs.anonymousSender { + return false + } + if lhs.reWrappedInForward != rhs.reWrappedInForward { + return false + } + if lhs.encryptedFromKid != rhs.encryptedFromKid { + return false + } + if lhs.encryptedToKids != rhs.encryptedToKids { + return false + } + if lhs.signFrom != rhs.signFrom { + return false + } + if lhs.fromPriorIssuerKid != rhs.fromPriorIssuerKid { + return false + } + if lhs.encAlgAuth != rhs.encAlgAuth { + return false + } + if lhs.encAlgAnon != rhs.encAlgAnon { + return false + } + if lhs.signAlg != rhs.signAlg { + return false + } + if lhs.signedMessage != rhs.signedMessage { + return false + } + if lhs.fromPrior != rhs.fromPrior { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(encrypted) + hasher.combine(authenticated) + hasher.combine(nonRepudiation) + hasher.combine(anonymousSender) + hasher.combine(reWrappedInForward) + hasher.combine(encryptedFromKid) + hasher.combine(encryptedToKids) + hasher.combine(signFrom) + hasher.combine(fromPriorIssuerKid) + hasher.combine(encAlgAuth) + hasher.combine(encAlgAnon) + hasher.combine(signAlg) + hasher.combine(signedMessage) + hasher.combine(fromPrior) + } +} + + +fileprivate extension UnpackMetadata { + static func read(from buf: Reader) throws -> UnpackMetadata { + return try UnpackMetadata( + encrypted: Bool.read(from: buf), + authenticated: Bool.read(from: buf), + nonRepudiation: Bool.read(from: buf), + anonymousSender: Bool.read(from: buf), + reWrappedInForward: Bool.read(from: buf), + encryptedFromKid: FfiConverterOptionString.read(from: buf), + encryptedToKids: FfiConverterOptionSequenceString.read(from: buf), + signFrom: FfiConverterOptionString.read(from: buf), + fromPriorIssuerKid: FfiConverterOptionString.read(from: buf), + encAlgAuth: FfiConverterOptionEnumAuthCryptAlg.read(from: buf), + encAlgAnon: FfiConverterOptionEnumAnonCryptAlg.read(from: buf), + signAlg: FfiConverterOptionEnumSignAlg.read(from: buf), + signedMessage: FfiConverterOptionString.read(from: buf), + fromPrior: FfiConverterOptionRecordFromPrior.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.encrypted.write(into: buf) + self.authenticated.write(into: buf) + self.nonRepudiation.write(into: buf) + self.anonymousSender.write(into: buf) + self.reWrappedInForward.write(into: buf) + FfiConverterOptionString.write(self.encryptedFromKid, into: buf) + FfiConverterOptionSequenceString.write(self.encryptedToKids, into: buf) + FfiConverterOptionString.write(self.signFrom, into: buf) + FfiConverterOptionString.write(self.fromPriorIssuerKid, into: buf) + FfiConverterOptionEnumAuthCryptAlg.write(self.encAlgAuth, into: buf) + FfiConverterOptionEnumAnonCryptAlg.write(self.encAlgAnon, into: buf) + FfiConverterOptionEnumSignAlg.write(self.signAlg, into: buf) + FfiConverterOptionString.write(self.signedMessage, into: buf) + FfiConverterOptionRecordFromPrior.write(self.fromPrior, into: buf) + } +} + +extension UnpackMetadata: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct UnpackOptions { + public var expectDecryptByAllKeys: Bool + public var unwrapReWrappingForward: Bool + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(expectDecryptByAllKeys: Bool, unwrapReWrappingForward: Bool ) { + self.expectDecryptByAllKeys = expectDecryptByAllKeys + self.unwrapReWrappingForward = unwrapReWrappingForward + } +} + + +extension UnpackOptions: Equatable, Hashable { + public static func ==(lhs: UnpackOptions, rhs: UnpackOptions) -> Bool { + if lhs.expectDecryptByAllKeys != rhs.expectDecryptByAllKeys { + return false + } + if lhs.unwrapReWrappingForward != rhs.unwrapReWrappingForward { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(expectDecryptByAllKeys) + hasher.combine(unwrapReWrappingForward) + } +} + + +fileprivate extension UnpackOptions { + static func read(from buf: Reader) throws -> UnpackOptions { + return try UnpackOptions( + expectDecryptByAllKeys: Bool.read(from: buf), + unwrapReWrappingForward: Bool.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.expectDecryptByAllKeys.write(into: buf) + self.unwrapReWrappingForward.write(into: buf) + } +} + +extension UnpackOptions: ViaFfiUsingByteBuffer, ViaFfi {} + +public struct FromPrior { + public var iss: String + public var sub: String + public var aud: String? + public var exp: UInt64? + public var nbf: UInt64? + public var iat: UInt64? + public var jti: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(iss: String, sub: String, aud: String?, exp: UInt64?, nbf: UInt64?, iat: UInt64?, jti: String? ) { + self.iss = iss + self.sub = sub + self.aud = aud + self.exp = exp + self.nbf = nbf + self.iat = iat + self.jti = jti + } +} + + +extension FromPrior: Equatable, Hashable { + public static func ==(lhs: FromPrior, rhs: FromPrior) -> Bool { + if lhs.iss != rhs.iss { + return false + } + if lhs.sub != rhs.sub { + return false + } + if lhs.aud != rhs.aud { + return false + } + if lhs.exp != rhs.exp { + return false + } + if lhs.nbf != rhs.nbf { + return false + } + if lhs.iat != rhs.iat { + return false + } + if lhs.jti != rhs.jti { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(iss) + hasher.combine(sub) + hasher.combine(aud) + hasher.combine(exp) + hasher.combine(nbf) + hasher.combine(iat) + hasher.combine(jti) + } +} + + +fileprivate extension FromPrior { + static func read(from buf: Reader) throws -> FromPrior { + return try FromPrior( + iss: String.read(from: buf), + sub: String.read(from: buf), + aud: FfiConverterOptionString.read(from: buf), + exp: FfiConverterOptionUInt64.read(from: buf), + nbf: FfiConverterOptionUInt64.read(from: buf), + iat: FfiConverterOptionUInt64.read(from: buf), + jti: FfiConverterOptionString.read(from: buf) + ) + } + + func write(into buf: Writer) { + self.iss.write(into: buf) + self.sub.write(into: buf) + FfiConverterOptionString.write(self.aud, into: buf) + FfiConverterOptionUInt64.write(self.exp, into: buf) + FfiConverterOptionUInt64.write(self.nbf, into: buf) + FfiConverterOptionUInt64.write(self.iat, into: buf) + FfiConverterOptionString.write(self.jti, into: buf) + } +} + +extension FromPrior: ViaFfiUsingByteBuffer, ViaFfi {} + +public enum ErrorKind { + + + + // Simple error enums only carry a message + case DidNotResolved(message: String) + + // Simple error enums only carry a message + case DidUrlNotFound(message: String) + + // Simple error enums only carry a message + case SecretNotFound(message: String) + + // Simple error enums only carry a message + case Malformed(message: String) + + // Simple error enums only carry a message + case IoError(message: String) + + // Simple error enums only carry a message + case InvalidState(message: String) + + // Simple error enums only carry a message + case NoCompatibleCrypto(message: String) + + // Simple error enums only carry a message + case Unsupported(message: String) + + // Simple error enums only carry a message + case IllegalArgument(message: String) + +} + +extension ErrorKind: ViaFfiUsingByteBuffer, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> ErrorKind { + let variant: Int32 = try buf.readInt() + switch variant { + + + + + case 1: return .DidNotResolved( + message: try String.read(from: buf) + ) + + case 2: return .DidUrlNotFound( + message: try String.read(from: buf) + ) + + case 3: return .SecretNotFound( + message: try String.read(from: buf) + ) + + case 4: return .Malformed( + message: try String.read(from: buf) + ) + + case 5: return .IoError( + message: try String.read(from: buf) + ) + + case 6: return .InvalidState( + message: try String.read(from: buf) + ) + + case 7: return .NoCompatibleCrypto( + message: try String.read(from: buf) + ) + + case 8: return .Unsupported( + message: try String.read(from: buf) + ) + + case 9: return .IllegalArgument( + message: try String.read(from: buf) + ) + + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + fileprivate func write(into buf: Writer) { + switch self { + + + + + case let .DidNotResolved(message): + buf.writeInt(Int32(1)) + message.write(into: buf) + case let .DidUrlNotFound(message): + buf.writeInt(Int32(2)) + message.write(into: buf) + case let .SecretNotFound(message): + buf.writeInt(Int32(3)) + message.write(into: buf) + case let .Malformed(message): + buf.writeInt(Int32(4)) + message.write(into: buf) + case let .IoError(message): + buf.writeInt(Int32(5)) + message.write(into: buf) + case let .InvalidState(message): + buf.writeInt(Int32(6)) + message.write(into: buf) + case let .NoCompatibleCrypto(message): + buf.writeInt(Int32(7)) + message.write(into: buf) + case let .Unsupported(message): + buf.writeInt(Int32(8)) + message.write(into: buf) + case let .IllegalArgument(message): + buf.writeInt(Int32(9)) + message.write(into: buf) + } + } +} + + +extension ErrorKind: Equatable, Hashable {} + +extension ErrorKind: Error { } + + +// Declaration and FfiConverters for DidResolver Callback Interface + +public protocol DidResolver : AnyObject { + func resolve(did: String, cb: OnDidResolverResult ) -> ErrorCode + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceDidResolver : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeResolve(_ swiftCallbackInterface: DidResolver, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + let result = swiftCallbackInterface.resolve( + did: try String.read(from: reader), + cb: try OnDidResolverResult.read(from: reader) + ) + let writer = Writer() + result.write(into: writer) + return RustBuffer(bytes: writer.bytes) + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceDidResolver.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceDidResolver.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeResolve(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceDidResolver: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_didcomm_f20e_DIDResolver_init_callback(foreignCallbackCallbackInterfaceDidResolver, err) + } + return FfiConverterCallbackInterface() +}() + + +// Declaration and FfiConverters for SecretsResolver Callback Interface + +public protocol SecretsResolver : AnyObject { + func getSecret(secretid: String, cb: OnGetSecretResult ) -> ErrorCode + func findSecrets(secretids: [String], cb: OnFindSecretsResult ) -> ErrorCode + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceSecretsResolver : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeGetSecret(_ swiftCallbackInterface: SecretsResolver, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + let result = swiftCallbackInterface.getSecret( + secretid: try String.read(from: reader), + cb: try OnGetSecretResult.read(from: reader) + ) + let writer = Writer() + result.write(into: writer) + return RustBuffer(bytes: writer.bytes) + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + func invokeFindSecrets(_ swiftCallbackInterface: SecretsResolver, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + let result = swiftCallbackInterface.findSecrets( + secretids: try FfiConverterSequenceString.read(from: reader), + cb: try OnFindSecretsResult.read(from: reader) + ) + let writer = Writer() + result.write(into: writer) + return RustBuffer(bytes: writer.bytes) + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceSecretsResolver.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceSecretsResolver.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeGetSecret(cb, args) + case 2: return try! invokeFindSecrets(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceSecretsResolver: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_didcomm_f20e_SecretsResolver_init_callback(foreignCallbackCallbackInterfaceSecretsResolver, err) + } + return FfiConverterCallbackInterface() +}() + + +// Declaration and FfiConverters for OnPackSignedResult Callback Interface + +public protocol OnPackSignedResult : AnyObject { + func success(result: String, metadata: PackSignedMetadata ) + func error(err: ErrorKind, msg: String ) + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceOnPackSignedResult : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeSuccess(_ swiftCallbackInterface: OnPackSignedResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.success( + result: try String.read(from: reader), + metadata: try PackSignedMetadata.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + func invokeError(_ swiftCallbackInterface: OnPackSignedResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.error( + err: try ErrorKind.read(from: reader), + msg: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceOnPackSignedResult.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceOnPackSignedResult.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeSuccess(cb, args) + case 2: return try! invokeError(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceOnPackSignedResult: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_didcomm_f20e_OnPackSignedResult_init_callback(foreignCallbackCallbackInterfaceOnPackSignedResult, err) + } + return FfiConverterCallbackInterface() +}() + + +// Declaration and FfiConverters for OnPackEncryptedResult Callback Interface + +public protocol OnPackEncryptedResult : AnyObject { + func success(result: String, metadata: PackEncryptedMetadata ) + func error(err: ErrorKind, msg: String ) + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceOnPackEncryptedResult : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeSuccess(_ swiftCallbackInterface: OnPackEncryptedResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.success( + result: try String.read(from: reader), + metadata: try PackEncryptedMetadata.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + func invokeError(_ swiftCallbackInterface: OnPackEncryptedResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.error( + err: try ErrorKind.read(from: reader), + msg: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceOnPackEncryptedResult.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceOnPackEncryptedResult.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeSuccess(cb, args) + case 2: return try! invokeError(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceOnPackEncryptedResult: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_didcomm_f20e_OnPackEncryptedResult_init_callback(foreignCallbackCallbackInterfaceOnPackEncryptedResult, err) + } + return FfiConverterCallbackInterface() +}() + + +// Declaration and FfiConverters for OnPackPlaintextResult Callback Interface + +public protocol OnPackPlaintextResult : AnyObject { + func success(result: String ) + func error(err: ErrorKind, msg: String ) + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceOnPackPlaintextResult : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeSuccess(_ swiftCallbackInterface: OnPackPlaintextResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.success( + result: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + func invokeError(_ swiftCallbackInterface: OnPackPlaintextResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.error( + err: try ErrorKind.read(from: reader), + msg: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceOnPackPlaintextResult.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceOnPackPlaintextResult.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeSuccess(cb, args) + case 2: return try! invokeError(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceOnPackPlaintextResult: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_didcomm_f20e_OnPackPlaintextResult_init_callback(foreignCallbackCallbackInterfaceOnPackPlaintextResult, err) + } + return FfiConverterCallbackInterface() +}() + + +// Declaration and FfiConverters for OnUnpackResult Callback Interface + +public protocol OnUnpackResult : AnyObject { + func success(result: Message, metadata: UnpackMetadata ) + func error(err: ErrorKind, msg: String ) + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceOnUnpackResult : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeSuccess(_ swiftCallbackInterface: OnUnpackResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.success( + result: try Message.read(from: reader), + metadata: try UnpackMetadata.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + func invokeError(_ swiftCallbackInterface: OnUnpackResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.error( + err: try ErrorKind.read(from: reader), + msg: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceOnUnpackResult.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceOnUnpackResult.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeSuccess(cb, args) + case 2: return try! invokeError(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceOnUnpackResult: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_didcomm_f20e_OnUnpackResult_init_callback(foreignCallbackCallbackInterfaceOnUnpackResult, err) + } + return FfiConverterCallbackInterface() +}() + + +// Declaration and FfiConverters for OnFromPriorPackResult Callback Interface + +public protocol OnFromPriorPackResult : AnyObject { + func success(frompriorjwt: String, kid: String ) + func error(err: ErrorKind, msg: String ) + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceOnFromPriorPackResult : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeSuccess(_ swiftCallbackInterface: OnFromPriorPackResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.success( + frompriorjwt: try String.read(from: reader), + kid: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + func invokeError(_ swiftCallbackInterface: OnFromPriorPackResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.error( + err: try ErrorKind.read(from: reader), + msg: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceOnFromPriorPackResult.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceOnFromPriorPackResult.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeSuccess(cb, args) + case 2: return try! invokeError(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceOnFromPriorPackResult: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_didcomm_f20e_OnFromPriorPackResult_init_callback(foreignCallbackCallbackInterfaceOnFromPriorPackResult, err) + } + return FfiConverterCallbackInterface() +}() + + +// Declaration and FfiConverters for OnFromPriorUnpackResult Callback Interface + +public protocol OnFromPriorUnpackResult : AnyObject { + func success(fromprior: FromPrior, kid: String ) + func error(err: ErrorKind, msg: String ) + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceOnFromPriorUnpackResult : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeSuccess(_ swiftCallbackInterface: OnFromPriorUnpackResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.success( + fromprior: try FromPrior.read(from: reader), + kid: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + func invokeError(_ swiftCallbackInterface: OnFromPriorUnpackResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.error( + err: try ErrorKind.read(from: reader), + msg: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceOnFromPriorUnpackResult.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceOnFromPriorUnpackResult.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeSuccess(cb, args) + case 2: return try! invokeError(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceOnFromPriorUnpackResult: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_didcomm_f20e_OnFromPriorUnpackResult_init_callback(foreignCallbackCallbackInterfaceOnFromPriorUnpackResult, err) + } + return FfiConverterCallbackInterface() +}() + + +// Declaration and FfiConverters for OnWrapInForwardResult Callback Interface + +public protocol OnWrapInForwardResult : AnyObject { + func success(result: String ) + func error(err: ErrorKind, msg: String ) + +} + +// The ForeignCallback that is passed to Rust. +fileprivate let foreignCallbackCallbackInterfaceOnWrapInForwardResult : ForeignCallback = + { (handle: Handle, method: Int32, args: RustBuffer) -> RustBuffer in + func invokeSuccess(_ swiftCallbackInterface: OnWrapInForwardResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.success( + result: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + func invokeError(_ swiftCallbackInterface: OnWrapInForwardResult, _ args: RustBuffer) throws -> RustBuffer { + defer { args.deallocate() } + + let reader = Reader(data: Data(rustBuffer: args)) + swiftCallbackInterface.error( + err: try ErrorKind.read(from: reader), + msg: try String.read(from: reader) + ) + return RustBuffer() + // TODO catch errors and report them back to Rust. + // https://github.com/mozilla/uniffi-rs/issues/351 + + } + + + let cb = try! ffiConverterCallbackInterfaceOnWrapInForwardResult.lift(handle) + switch method { + case IDX_CALLBACK_FREE: + ffiConverterCallbackInterfaceOnWrapInForwardResult.drop(handle: handle) + return RustBuffer() + case 1: return try! invokeSuccess(cb, args) + case 2: return try! invokeError(cb, args) + + // This should never happen, because an out of bounds method index won't + // ever be used. Once we can catch errors, we should return an InternalError. + // https://github.com/mozilla/uniffi-rs/issues/351 + default: return RustBuffer() + } + } + +// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust. +private let ffiConverterCallbackInterfaceOnWrapInForwardResult: FfiConverterCallbackInterface = { + try! rustCall { (err: UnsafeMutablePointer) in + ffi_didcomm_f20e_OnWrapInForwardResult_init_callback(foreignCallbackCallbackInterfaceOnWrapInForwardResult, err) + } + return FfiConverterCallbackInterface() +}() +extension UInt64: Primitive, ViaFfi { + fileprivate static func read(from buf: Reader) throws -> Self { + return try self.lift(buf.readInt()) + } + + fileprivate func write(into buf: Writer) { + buf.writeInt(self.lower()) + } +} +extension Bool: ViaFfi { + fileprivate typealias FfiType = Int8 + + fileprivate static func read(from buf: Reader) throws -> Self { + return try self.lift(buf.readInt()) + } + + fileprivate func write(into buf: Writer) { + buf.writeInt(self.lower()) + } + + fileprivate static func lift(_ v: FfiType) throws -> Self { + return v != 0 + } + + fileprivate func lower() -> FfiType { + return self ? 1 : 0 + } +} +extension String: ViaFfi { + fileprivate typealias FfiType = RustBuffer + + fileprivate static func lift(_ v: FfiType) throws -> Self { + defer { + v.deallocate() + } + if v.data == nil { + return String() + } + let bytes = UnsafeBufferPointer(start: v.data!, count: Int(v.len)) + return String(bytes: bytes, encoding: String.Encoding.utf8)! + } + + fileprivate func lower() -> FfiType { + return self.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + fileprivate static func read(from buf: Reader) throws -> Self { + let len: Int32 = try buf.readInt() + return String(bytes: try buf.readBytes(count: Int(len)), encoding: String.Encoding.utf8)! + } + + fileprivate func write(into buf: Writer) { + let len = Int32(self.utf8.count) + buf.writeInt(len) + buf.writeBytes(self.utf8) + } +} +// Helper code for DidComm class is found in ObjectTemplate.swift +// Helper code for ExampleDidResolver class is found in ObjectTemplate.swift +// Helper code for ExampleSecretsResolver class is found in ObjectTemplate.swift +// Helper code for OnDidResolverResult class is found in ObjectTemplate.swift +// Helper code for OnFindSecretsResult class is found in ObjectTemplate.swift +// Helper code for OnGetSecretResult class is found in ObjectTemplate.swift +// Helper code for Attachment record is found in RecordTemplate.swift +// Helper code for Base64AttachmentData record is found in RecordTemplate.swift +// Helper code for DidCommMessagingService record is found in RecordTemplate.swift +// Helper code for DidDoc record is found in RecordTemplate.swift +// Helper code for FromPrior record is found in RecordTemplate.swift +// Helper code for JsonAttachmentData record is found in RecordTemplate.swift +// Helper code for LinksAttachmentData record is found in RecordTemplate.swift +// Helper code for Message record is found in RecordTemplate.swift +// Helper code for MessagingServiceMetadata record is found in RecordTemplate.swift +// Helper code for PackEncryptedMetadata record is found in RecordTemplate.swift +// Helper code for PackEncryptedOptions record is found in RecordTemplate.swift +// Helper code for PackSignedMetadata record is found in RecordTemplate.swift +// Helper code for Secret record is found in RecordTemplate.swift +// Helper code for Service record is found in RecordTemplate.swift +// Helper code for UnpackMetadata record is found in RecordTemplate.swift +// Helper code for UnpackOptions record is found in RecordTemplate.swift +// Helper code for VerificationMethod record is found in RecordTemplate.swift +// Helper code for AnonCryptAlg enum is found in EnumTemplate.swift +// Helper code for AttachmentData enum is found in EnumTemplate.swift +// Helper code for AuthCryptAlg enum is found in EnumTemplate.swift +// Helper code for ErrorCode enum is found in EnumTemplate.swift +// Helper code for SecretMaterial enum is found in EnumTemplate.swift +// Helper code for SecretType enum is found in EnumTemplate.swift +// Helper code for ServiceKind enum is found in EnumTemplate.swift +// Helper code for SignAlg enum is found in EnumTemplate.swift +// Helper code for VerificationMaterial enum is found in EnumTemplate.swift +// Helper code for VerificationMethodType enum is found in EnumTemplate.swift +// Helper code for ErrorKind error is found in ErrorTemplate.swift + +fileprivate enum FfiConverterOptionUInt64: FfiConverterUsingByteBuffer { + typealias SwiftType = UInt64? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try UInt64.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionString: FfiConverterUsingByteBuffer { + typealias SwiftType = String? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try String.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionRecordDidDoc: FfiConverterUsingByteBuffer { + typealias SwiftType = DidDoc? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try DidDoc.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionRecordFromPrior: FfiConverterUsingByteBuffer { + typealias SwiftType = FromPrior? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try FromPrior.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionRecordMessagingServiceMetadata: FfiConverterUsingByteBuffer { + typealias SwiftType = MessagingServiceMetadata? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try MessagingServiceMetadata.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionRecordSecret: FfiConverterUsingByteBuffer { + typealias SwiftType = Secret? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try Secret.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionEnumAnonCryptAlg: FfiConverterUsingByteBuffer { + typealias SwiftType = AnonCryptAlg? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try AnonCryptAlg.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionEnumAuthCryptAlg: FfiConverterUsingByteBuffer { + typealias SwiftType = AuthCryptAlg? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try AuthCryptAlg.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionEnumSignAlg: FfiConverterUsingByteBuffer { + typealias SwiftType = SignAlg? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try SignAlg.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionSequenceString: FfiConverterUsingByteBuffer { + typealias SwiftType = [String]? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + FfiConverterSequenceString.write(item, into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try FfiConverterSequenceString.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionSequenceRecordAttachment: FfiConverterUsingByteBuffer { + typealias SwiftType = [Attachment]? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + FfiConverterSequenceRecordAttachment.write(item, into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try FfiConverterSequenceRecordAttachment.read(from: buf) + } + } +} + +fileprivate enum FfiConverterOptionDictionaryJsonValue: FfiConverterUsingByteBuffer { + typealias SwiftType = [String: String]? + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterOptional.write(value, into: buf) { item, buf in + FfiConverterDictionaryJsonValue.write(item, into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterOptional.read(from: buf) { buf in + try FfiConverterDictionaryJsonValue.read(from: buf) + } + } +} + +fileprivate enum FfiConverterSequenceString: FfiConverterUsingByteBuffer { + typealias SwiftType = [String] + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterSequence.write(value, into: buf) { (item, buf) in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterSequence.read(from: buf) { buf in + try String.read(from: buf) + } + } +} + +fileprivate enum FfiConverterSequenceRecordAttachment: FfiConverterUsingByteBuffer { + typealias SwiftType = [Attachment] + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterSequence.write(value, into: buf) { (item, buf) in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterSequence.read(from: buf) { buf in + try Attachment.read(from: buf) + } + } +} + +fileprivate enum FfiConverterSequenceRecordDidDoc: FfiConverterUsingByteBuffer { + typealias SwiftType = [DidDoc] + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterSequence.write(value, into: buf) { (item, buf) in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterSequence.read(from: buf) { buf in + try DidDoc.read(from: buf) + } + } +} + +fileprivate enum FfiConverterSequenceRecordSecret: FfiConverterUsingByteBuffer { + typealias SwiftType = [Secret] + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterSequence.write(value, into: buf) { (item, buf) in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterSequence.read(from: buf) { buf in + try Secret.read(from: buf) + } + } +} + +fileprivate enum FfiConverterSequenceRecordService: FfiConverterUsingByteBuffer { + typealias SwiftType = [Service] + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterSequence.write(value, into: buf) { (item, buf) in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterSequence.read(from: buf) { buf in + try Service.read(from: buf) + } + } +} + +fileprivate enum FfiConverterSequenceRecordVerificationMethod: FfiConverterUsingByteBuffer { + typealias SwiftType = [VerificationMethod] + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterSequence.write(value, into: buf) { (item, buf) in + item.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterSequence.read(from: buf) { buf in + try VerificationMethod.read(from: buf) + } + } +} + +fileprivate enum FfiConverterDictionaryJsonValue: FfiConverterUsingByteBuffer { + typealias SwiftType = [String: String] + + static func write(_ value: SwiftType, into buf: Writer) { + FfiConverterDictionary.write(value, into: buf) { (key, value, buf) in + key.write(into: buf) + value.write(into: buf) + } + } + + static func read(from buf: Reader) throws -> SwiftType { + try FfiConverterDictionary.read(from: buf) { buf in + (try String.read(from: buf), + try String.read(from: buf)) + } + } +} + + +/** + * Top level initializers and tear down methods. + * + * This is generated by uniffi. + */ +public enum DidcommLifecycle { + /** + * Initialize the FFI and Rust library. This should be only called once per application. + */ + func initialize() { + + // No initialization code needed + + } +} \ No newline at end of file diff --git a/wrappers/swift/didcomm/didcomm.swiftmodule b/wrappers/swift/didcomm/didcomm.swiftmodule new file mode 100644 index 0000000..405804a Binary files /dev/null and b/wrappers/swift/didcomm/didcomm.swiftmodule differ diff --git a/wrappers/swift/didcomm/didcommFFI.h b/wrappers/swift/didcomm/didcommFFI.h new file mode 100644 index 0000000..10c4f62 --- /dev/null +++ b/wrappers/swift/didcomm/didcommFFI.h @@ -0,0 +1,200 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V3 + #ifndef UNIFFI_SHARED_HEADER_V3 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V3 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V3 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V3 in this file. ⚠️ + +typedef struct RustBuffer +{ + int32_t capacity; + int32_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef RustBuffer (*ForeignCallback)(uint64_t, int32_t, RustBuffer); + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V3 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H + +void ffi_didcomm_f20e_DIDComm_object_free( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void*_Nonnull didcomm_f20e_DIDComm_new( + uint64_t did_resolver,uint64_t secret_resolver, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_DIDComm_pack_plaintext( + void*_Nonnull ptr,RustBuffer msg,uint64_t cb, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_DIDComm_pack_signed( + void*_Nonnull ptr,RustBuffer msg,RustBuffer sign_by,uint64_t cb, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_DIDComm_pack_encrypted( + void*_Nonnull ptr,RustBuffer msg,RustBuffer to,RustBuffer from,RustBuffer sign_by,RustBuffer options,uint64_t cb, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_DIDComm_unpack( + void*_Nonnull ptr,RustBuffer msg,RustBuffer options,uint64_t cb, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_DIDComm_pack_from_prior( + void*_Nonnull ptr,RustBuffer msg,RustBuffer issuer_kid,uint64_t cb, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_DIDComm_unpack_from_prior( + void*_Nonnull ptr,RustBuffer from_prior_jwt,uint64_t cb, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_DIDComm_wrap_in_forward( + void*_Nonnull ptr,RustBuffer msg,RustBuffer headers,RustBuffer to,RustBuffer routing_keys,RustBuffer enc_alg_anon,uint64_t cb, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnDIDResolverResult_object_free( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void didcomm_f20e_OnDIDResolverResult_success( + void*_Nonnull ptr,RustBuffer result, + RustCallStatus *_Nonnull out_status + ); +void didcomm_f20e_OnDIDResolverResult_error( + void*_Nonnull ptr,RustBuffer err,RustBuffer msg, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_ExampleDIDResolver_object_free( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void*_Nonnull didcomm_f20e_ExampleDIDResolver_new( + RustBuffer known_dids, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_ExampleDIDResolver_resolve( + void*_Nonnull ptr,RustBuffer did,void*_Nonnull cb, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnGetSecretResult_object_free( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void didcomm_f20e_OnGetSecretResult_success( + void*_Nonnull ptr,RustBuffer result, + RustCallStatus *_Nonnull out_status + ); +void didcomm_f20e_OnGetSecretResult_error( + void*_Nonnull ptr,RustBuffer err,RustBuffer msg, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnFindSecretsResult_object_free( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void didcomm_f20e_OnFindSecretsResult_success( + void*_Nonnull ptr,RustBuffer result, + RustCallStatus *_Nonnull out_status + ); +void didcomm_f20e_OnFindSecretsResult_error( + void*_Nonnull ptr,RustBuffer err,RustBuffer msg, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_ExampleSecretsResolver_object_free( + void*_Nonnull ptr, + RustCallStatus *_Nonnull out_status + ); +void*_Nonnull didcomm_f20e_ExampleSecretsResolver_new( + RustBuffer known_secrets, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_ExampleSecretsResolver_get_secret( + void*_Nonnull ptr,RustBuffer secret_id,void*_Nonnull cb, + RustCallStatus *_Nonnull out_status + ); +RustBuffer didcomm_f20e_ExampleSecretsResolver_find_secrets( + void*_Nonnull ptr,RustBuffer secret_ids,void*_Nonnull cb, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_DIDResolver_init_callback( + ForeignCallback _Nonnull callback_stub, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_SecretsResolver_init_callback( + ForeignCallback _Nonnull callback_stub, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnPackSignedResult_init_callback( + ForeignCallback _Nonnull callback_stub, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnPackEncryptedResult_init_callback( + ForeignCallback _Nonnull callback_stub, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnPackPlaintextResult_init_callback( + ForeignCallback _Nonnull callback_stub, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnUnpackResult_init_callback( + ForeignCallback _Nonnull callback_stub, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnFromPriorPackResult_init_callback( + ForeignCallback _Nonnull callback_stub, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnFromPriorUnpackResult_init_callback( + ForeignCallback _Nonnull callback_stub, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_OnWrapInForwardResult_init_callback( + ForeignCallback _Nonnull callback_stub, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ffi_didcomm_f20e_rustbuffer_alloc( + int32_t size, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ffi_didcomm_f20e_rustbuffer_from_bytes( + ForeignBytes bytes, + RustCallStatus *_Nonnull out_status + ); +void ffi_didcomm_f20e_rustbuffer_free( + RustBuffer buf, + RustCallStatus *_Nonnull out_status + ); +RustBuffer ffi_didcomm_f20e_rustbuffer_reserve( + RustBuffer buf,int32_t additional, + RustCallStatus *_Nonnull out_status + ); diff --git a/wrappers/swift/didcomm/didcommFFI.modulemap b/wrappers/swift/didcomm/didcommFFI.modulemap new file mode 100644 index 0000000..c116ae2 --- /dev/null +++ b/wrappers/swift/didcomm/didcommFFI.modulemap @@ -0,0 +1,6 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! +module didcommFFI { + header "didcommFFI.h" + export * +} \ No newline at end of file diff --git a/wrappers/swift/didcomm/libdidcomm.dylib b/wrappers/swift/didcomm/libdidcomm.dylib new file mode 100755 index 0000000..0f3a9ac Binary files /dev/null and b/wrappers/swift/didcomm/libdidcomm.dylib differ diff --git a/wrappers/swift/examples/base.swift b/wrappers/swift/examples/base.swift new file mode 100644 index 0000000..a2bd024 --- /dev/null +++ b/wrappers/swift/examples/base.swift @@ -0,0 +1 @@ +// TBD \ No newline at end of file