diff --git a/.github/actions/build-napi/action.yml b/.github/actions/build-napi/action.yml index d8a5ed0a9a..f11358ea73 100644 --- a/.github/actions/build-napi/action.yml +++ b/.github/actions/build-napi/action.yml @@ -12,7 +12,7 @@ inputs: node-version: default: 18 rust-version: - default: 1.70.0 + required: true runs: using: "composite" @@ -45,11 +45,16 @@ runs: run: npm install working-directory: ${{ github.workspace }}/aries/wrappers/vcx-napi-rs shell: bash + - name: Build docker image + if: ${{ inputs.docker }} + shell: bash + run: | + docker build -f aries/wrappers/vcx-napi-rs/${{ inputs.docker }}.Dockerfile -t ghcr.io/hyperledger/aries-vcx/napi-rs-${{ inputs.docker }}:latest . - name: Build in docker uses: addnab/docker-run-action@v3 if: ${{ inputs.docker }} with: - image: ${{ inputs.docker }} + image: ghcr.io/hyperledger/aries-vcx/napi-rs-${{ inputs.docker }} options: -v /home/runner/.cargo/git/db:/root/.cargo/git/db -v /home/runner/.cargo/registry/cache:/root/.cargo/registry/cache -v /home/runner/.cargo/registry/index:/root/.cargo/registry/index -v ${{ github.workspace }}:/build -w /build run: ${{ inputs.build }} - name: Build diff --git a/.github/actions/setup-codecov-rust/action.yml b/.github/actions/setup-codecov-rust/action.yml index 1808bbec8b..b6c4c27587 100644 --- a/.github/actions/setup-codecov-rust/action.yml +++ b/.github/actions/setup-codecov-rust/action.yml @@ -10,10 +10,10 @@ inputs: runs: using: "composite" steps: - - name: Install nightly 1.71 + - name: Install nightly 1.72 uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2023-05-08 + toolchain: nightly-2023-08-24 override: true - uses: Swatinem/rust-cache@v2 - name: "Install dependencies" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8607a05791..a1b666ef21 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -141,7 +141,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - backend: ["credx,vdrtools_wallet", "vdr_proxy_ledger"] + backend: ["credx,vdrtools_wallet", "vdr_proxy_ledger,askar_wallet"] steps: - name: "Git checkout" uses: actions/checkout@v3 @@ -272,9 +272,32 @@ jobs: - name: "Run workspace unit tests" run: just test-unit + test-intergation-aries-vcx-core: + needs: workflow-setup + runs-on: ubuntu-20.04 + strategy: + matrix: + wallet: ["vdrtools_wallet", "askar_wallet"] + steps: + - name: "Git checkout" + uses: actions/checkout@v3 + - name: "Setup rust testing environment" + uses: ./.github/actions/setup-testing-rust + with: + rust-toolchain-version: ${{ env.RUST_TOOLCHAIN_VERSION }} + default: true + skip-docker-setup: true + - name: "Install just" + run: sudo snap install --edge --classic just + - name: "Run aries-vcx-core integration tests" + run: just test-integration-aries-vcx-core ${{ matrix.wallet }} + test-integration-aries-vcx: needs: workflow-setup runs-on: ubuntu-20.04 + strategy: + matrix: + wallet: ["vdrtools_wallet,credx", "askar_wallet,credx"] steps: - name: "Git checkout" uses: actions/checkout@v3 @@ -285,7 +308,7 @@ jobs: - name: "Install just" run: sudo snap install --edge --classic just - name: "Run aries-vcx integration tests" - run: just test-integration-aries-vcx + run: just test-integration-aries-vcx ${{ matrix.wallet }} test-integration-aries-vcx-anoncreds-rs: needs: workflow-setup @@ -476,7 +499,7 @@ jobs: strip *.node - host: ubuntu-20.04 target: x86_64-unknown-linux-musl - docker: ghcr.io/hyperledger/aries-vcx/napi-rs-alpine + docker: alpine build: |- set -e env @@ -495,8 +518,8 @@ jobs: target: aarch64-apple-darwin skip: ${{ needs.workflow-setup.outputs.SKIP_NAPI_M1 }} build: | - wget https://github.com/macports/macports-base/releases/download/v2.8.0/MacPorts-2.8.0-12-Monterey.pkg - sudo installer -pkg ./MacPorts-2.8.0-12-Monterey.pkg -target / + wget https://github.com/macports/macports-base/releases/download/v2.9.1/MacPorts-2.9.1-12-Monterey.pkg + sudo installer -pkg ./MacPorts-2.9.1-12-Monterey.pkg -target / export PATH=/opt/local/bin:/opt/local/sbin:$PATH sudo port install openssl +universal zmq +universal @@ -527,7 +550,6 @@ jobs: build: ${{ matrix.settings.build }} node-version: ${{ env.NODE_VERSION }} rust-version: ${{ env.RUST_TOOLCHAIN_VERSION }} - default: true publish-napi: runs-on: ubuntu-20.04 diff --git a/Cargo.lock b/Cargo.lock index 035ab609ad..8e47dc88f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "agency_client" version = "0.61.0" @@ -216,14 +251,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" dependencies = [ "cfg-if", "getrandom 0.2.10", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -378,6 +414,43 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "argon2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "aries-askar" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8b0eae3cf0cfe7d0bc25fbf827bee5f0af2af46296cce894661943e10542a2" +dependencies = [ + "askar-crypto", + "askar-storage", + "async-lock 3.2.0", + "env_logger 0.10.0", + "ffi-support", + "log", + "once_cell", + "serde", + "serde_cbor", + "serde_json", + "zeroize", +] + [[package]] name = "aries-vcx-agent" version = "0.61.0" @@ -456,8 +529,10 @@ dependencies = [ "agency_client", "anoncreds", "anoncreds_types", + "aries-askar", "async-trait", "bitvec", + "bs58 0.5.0", "did_parser", "futures", "indy-api-types", @@ -525,6 +600,76 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "askar-crypto" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39746932b19e345196a089e61a0f0175fc4d673db4b624424d8babf505e48a3d" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "argon2", + "base64", + "blake2", + "block-modes", + "bls12_381", + "cbc", + "chacha20", + "chacha20poly1305", + "cipher", + "crypto_box", + "curve25519-dalek", + "digest", + "ed25519-dalek", + "elliptic-curve", + "group", + "hkdf", + "hmac", + "k256", + "p256", + "p384", + "rand 0.8.5", + "serde", + "serde-json-core", + "sha2", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "askar-storage" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b82a5c62e62075c68e4504ecc281fccce88f4692df43d50194cbbf918179ee" +dependencies = [ + "arc-swap", + "askar-crypto", + "async-lock 3.2.0", + "async-stream", + "bs58 0.5.0", + "chrono", + "digest", + "futures-lite 2.0.0", + "hex", + "hmac", + "itertools 0.12.1", + "log", + "once_cell", + "percent-encoding", + "rmp-serde", + "serde", + "serde_cbor", + "serde_json", + "sha2", + "sqlx", + "tokio", + "url", + "uuid 1.5.0", + "zeroize", +] + [[package]] name = "async-attributes" version = "1.1.2" @@ -542,7 +687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] @@ -552,11 +697,11 @@ version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c1da3ae8dabd9c00f453a329dfe1fb28da3c0a72e2478cdcd93171740c20499" dependencies = [ - "async-lock", + "async-lock 2.8.0", "async-task", "concurrent-queue", "fastrand 2.0.1", - "futures-lite", + "futures-lite 1.13.0", "slab", ] @@ -569,9 +714,9 @@ dependencies = [ "async-channel", "async-executor", "async-io", - "async-lock", + "async-lock 2.8.0", "blocking", - "futures-lite", + "futures-lite 1.13.0", "once_cell", ] @@ -581,11 +726,11 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", - "futures-lite", + "futures-lite 1.13.0", "log", "parking", "polling", @@ -601,7 +746,18 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" +dependencies = [ + "event-listener 4.0.0", + "event-listener-strategy", + "pin-project-lite", ] [[package]] @@ -614,12 +770,12 @@ dependencies = [ "async-channel", "async-global-executor", "async-io", - "async-lock", + "async-lock 2.8.0", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite", + "futures-lite 1.13.0", "gloo-timers", "kv-log-macro", "log", @@ -685,6 +841,16 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atomic-write-file" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" +dependencies = [ + "nix", + "rand 0.8.5", +] + [[package]] name = "atty" version = "0.2.14" @@ -784,6 +950,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.21.7" @@ -842,6 +1014,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -851,6 +1032,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2211b0817f061502a8dd9f11a37e879e79763e3c698d2418cf824d8cb2f21e" + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "blocking" version = "1.4.1" @@ -858,15 +1054,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" dependencies = [ "async-channel", - "async-lock", + "async-lock 2.8.0", "async-task", "fastrand 2.0.1", "futures-io", - "futures-lite", + "futures-lite 1.13.0", "piper", "tracing", ] +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" +dependencies = [ + "ff", + "group", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "brotli" version = "3.4.0" @@ -962,6 +1171,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.83" @@ -978,6 +1196,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.31" @@ -993,6 +1235,17 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "3.2.25" @@ -1220,6 +1473,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1227,9 +1492,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto_box" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" +dependencies = [ + "aead", + "crypto_secretbox", + "curve25519-dalek", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + [[package]] name = "ctor" version = "0.2.5" @@ -1240,6 +1535,15 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "cursive" version = "0.20.0" @@ -1711,6 +2015,19 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "2.2.2" @@ -1747,6 +2064,25 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562cc8504a01eb20c10fb154abd7c4baeb9beba2329cf85838ee2bd48a468b18" +[[package]] +name = "elliptic-curve" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9775b22bc152ad86a0cf23f0f348b884b26add12bf741e7ffc4d4ab2ab4d205" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1880,6 +2216,27 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.0", + "pin-project-lite", +] + [[package]] name = "failure" version = "0.1.8" @@ -1923,6 +2280,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "ffi-support" version = "0.4.4" @@ -2098,6 +2465,21 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-lite" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1155db57329dca6d018b61e76b1488ce9a2e5e44028cac420a5898f4fcef63" +dependencies = [ + "fastrand 2.0.1", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.28" @@ -2147,6 +2529,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -2173,6 +2556,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.0" @@ -2222,6 +2615,17 @@ dependencies = [ "scroll", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.3.21" @@ -2241,6 +2645,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.12.3" @@ -2655,6 +3065,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -2725,6 +3145,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -2749,6 +3178,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + [[package]] name = "keccak" version = "0.1.4" @@ -2816,9 +3257,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" dependencies = [ "cc", "pkg-config", @@ -3239,6 +3680,17 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -3382,6 +3834,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.60" @@ -3447,6 +3905,30 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking" version = "2.1.1" @@ -3476,6 +3958,17 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" @@ -3623,6 +4116,29 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3659,6 +4175,15 @@ dependencies = [ "termtree", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3887,6 +4412,16 @@ dependencies = [ "winreg", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -4030,6 +4565,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "schannel" version = "0.1.22" @@ -4075,6 +4619,19 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -4116,6 +4673,26 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-json-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9e1ab533c0bc414c34920ec7e5f097101d126ed5eac1a1aac711222e0bbb33" +dependencies = [ + "ryu", + "serde", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.195" @@ -4365,9 +4942,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" dependencies = [ "sqlx-core", "sqlx-macros", @@ -4378,19 +4955,20 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" dependencies = [ "ahash", "atoi", "byteorder", "bytes", + "chrono", "crc", "crossbeam-queue", "dotenvy", "either", - "event-listener", + "event-listener 2.5.3", "futures-channel", "futures-core", "futures-intrusive", @@ -4421,9 +4999,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" dependencies = [ "proc-macro2", "quote", @@ -4434,10 +5012,11 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" dependencies = [ + "atomic-write-file", "dotenvy", "either", "heck", @@ -4450,6 +5029,7 @@ dependencies = [ "sha2", "sqlx-core", "sqlx-mysql", + "sqlx-postgres", "sqlx-sqlite", "syn 1.0.109", "tempfile", @@ -4459,15 +5039,16 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" dependencies = [ "atoi", "base64", "bitflags 2.4.0", "byteorder", "bytes", + "chrono", "crc", "digest", "dotenvy", @@ -4501,14 +5082,15 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" dependencies = [ "atoi", "base64", "bitflags 2.4.0", "byteorder", + "chrono", "crc", "dotenvy", "etcetera", @@ -4540,11 +5122,12 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" dependencies = [ "atoi", + "chrono", "flume", "futures-channel", "futures-core", @@ -4558,6 +5141,7 @@ dependencies = [ "sqlx-core", "tracing", "url", + "urlencoding", ] [[package]] @@ -5287,6 +5871,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "unsigned-varint" version = "0.7.2" @@ -5311,6 +5905,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "ursa" version = "0.3.7" @@ -5511,12 +6111,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.24.0" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" -dependencies = [ - "rustls-webpki", -] +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "weedle2" @@ -5675,6 +6272,26 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/aries/aries_vcx/Cargo.toml b/aries/aries_vcx/Cargo.toml index 62b9043972..772e0d3ff5 100644 --- a/aries/aries_vcx/Cargo.toml +++ b/aries/aries_vcx/Cargo.toml @@ -33,6 +33,10 @@ backtrace_errors = ["backtrace"] # Feature for allowing legacy proof verification legacy_proof = ["aries_vcx_core/legacy_proof"] +askar_wallet = [ + "aries_vcx_core/askar_wallet" +] + [dependencies] agency_client = { path = "../misc/legacy/agency_client" } messages = { path = "../messages" } diff --git a/aries/aries_vcx_core/Cargo.toml b/aries/aries_vcx_core/Cargo.toml index 9e93be077f..ccfb4e3b5c 100644 --- a/aries/aries_vcx_core/Cargo.toml +++ b/aries/aries_vcx_core/Cargo.toml @@ -13,8 +13,12 @@ vdr_proxy_ledger = ["credx", "dep:indy-vdr-proxy-client"] # Feature flag to allow legacy proof verification legacy_proof = [] +askar_wallet = ["dep:aries-askar", "dep:bs58"] + [dependencies] agency_client = { path = "../misc/legacy/agency_client" } +aries-askar = { version = "=0.3.0", optional = true } +bs58 = { version = "0.5", optional = true } indy-vdr = { git = "https://github.com/hyperledger/indy-vdr.git", rev = "c143268", default-features = false, features = ["log"] } indy-credx = { git = "https://github.com/hyperledger/indy-shared-rs", tag = "v1.1.0", optional = true } # anoncreds = { git = "https://github.com/hyperledger/anoncreds-rs", tag = "v0.2.0-dev.5", optional = true } diff --git a/aries/aries_vcx_core/src/anoncreds/anoncreds/mod.rs b/aries/aries_vcx_core/src/anoncreds/anoncreds/mod.rs index 875b70f1f6..ec29799aa3 100644 --- a/aries/aries_vcx_core/src/anoncreds/anoncreds/mod.rs +++ b/aries/aries_vcx_core/src/anoncreds/anoncreds/mod.rs @@ -75,8 +75,12 @@ use super::base_anoncreds::{ use crate::{ anoncreds::anoncreds::type_conversion::Convert, errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}, - wallet::base_wallet::{ - record::Record, record_category::RecordCategory, search_filter::SearchFilter, BaseWallet, + wallet::{ + base_wallet::{ + record::Record, record_category::RecordCategory, search_filter::SearchFilter, + BaseWallet, + }, + record_tags::{RecordTag, RecordTags}, }, }; @@ -1066,34 +1070,33 @@ impl BaseAnonCreds for Anoncreds { format!("Could not process credential.schema_id {schema_id} as parts."), ))?; - let mut tags = json!({ - "schema_id": schema_id.0, - "schema_issuer_did": schema_issuer_did.did(), - "schema_name": schema_name, - "schema_version": schema_version, - "issuer_did": issuer_did.did(), - "cred_def_id": cred_def_id - }); + let mut tags = RecordTags::new(vec![ + RecordTag::new("schema_id", &schema_id.0), + RecordTag::new("schema_issuer_did", schema_issuer_did.did()), + RecordTag::new("schema_name", &schema_name), + RecordTag::new("schema_version", &schema_version), + RecordTag::new("issuer_did", issuer_did.did()), + RecordTag::new("cred_def_id", &cred_def_id), + ]); if let Some(rev_reg_id) = &credential.rev_reg_id { - tags["rev_reg_id"] = serde_json::Value::String(rev_reg_id.0.to_string()) + tags.add(RecordTag::new("rev_reg_id", &rev_reg_id.0)); } for (raw_attr_name, attr_value) in credential.values.0.iter() { let attr_name = _normalize_attr_name(raw_attr_name.as_str()); // add attribute name and raw value pair let value_tag_name = _format_attribute_as_value_tag_name(&attr_name); - tags[value_tag_name] = Value::String(attr_value.raw.to_string()); + tags.add(RecordTag::new(&value_tag_name, &attr_value.raw)); // add attribute name and marker (used for checking existent) let marker_tag_name = _format_attribute_as_marker_tag_name(&attr_name); - tags[marker_tag_name] = Value::String("1".to_string()); + tags.add(RecordTag::new(&marker_tag_name, "1")) } let credential_id = Uuid::new_v4().to_string(); let record_value = serde_json::to_string(&credential)?; - let tags = serde_json::from_value(tags.clone())?; let record = Record::builder() .name(credential_id.clone()) diff --git a/aries/aries_vcx_core/src/anoncreds/credx_anoncreds/mod.rs b/aries/aries_vcx_core/src/anoncreds/credx_anoncreds/mod.rs index 76ce3fbfd7..531660ff31 100644 --- a/aries/aries_vcx_core/src/anoncreds/credx_anoncreds/mod.rs +++ b/aries/aries_vcx_core/src/anoncreds/credx_anoncreds/mod.rs @@ -69,7 +69,7 @@ use crate::{ record::Record, record_category::RecordCategory, search_filter::SearchFilter, BaseWallet, RecordWallet, }, - record_tags::RecordTags, + record_tags::{RecordTag, RecordTags}, }, }; @@ -1003,34 +1003,33 @@ impl BaseAnonCreds for IndyCredxAnonCreds { "Could not process credential.cred_def_id as parts.", ))?; - let mut tags = json!({ - "schema_id": schema_id.0, - "schema_issuer_did": schema_issuer_did.0, - "schema_name": schema_name, - "schema_version": schema_version, - "issuer_did": issuer_did.0, - "cred_def_id": cred_def_id.0 - }); + let mut tags = RecordTags::new(vec![ + RecordTag::new("schema_id", &schema_id.0), + RecordTag::new("schema_issuer_did", &schema_issuer_did.0), + RecordTag::new("schema_name", &schema_name), + RecordTag::new("schema_version", &schema_version), + RecordTag::new("issuer_did", &issuer_did.0), + RecordTag::new("cred_def_id", &cred_def_id.0), + ]); if let Some(rev_reg_id) = &credential.rev_reg_id { - tags["rev_reg_id"] = serde_json::Value::String(rev_reg_id.0.to_string()) + tags.add(RecordTag::new("rev_reg_id", &rev_reg_id.0)); } for (raw_attr_name, attr_value) in credential.values.0.iter() { let attr_name = _normalize_attr_name(raw_attr_name); // add attribute name and raw value pair let value_tag_name = _format_attribute_as_value_tag_name(&attr_name); - tags[value_tag_name] = Value::String(attr_value.raw.to_string()); + tags.add(RecordTag::new(&value_tag_name, &attr_value.raw)); // add attribute name and marker (used for checking existent) let marker_tag_name = _format_attribute_as_marker_tag_name(&attr_name); - tags[marker_tag_name] = Value::String("1".to_string()); + tags.add(RecordTag::new(&marker_tag_name, "1")) } let credential_id = Uuid::new_v4().to_string(); let record_value = serde_json::to_string(&credential)?; - let tags = serde_json::from_value(tags.clone())?; let record = Record::builder() .name(credential_id.clone()) diff --git a/aries/aries_vcx_core/src/errors/mapping_askar.rs b/aries/aries_vcx_core/src/errors/mapping_askar.rs new file mode 100644 index 0000000000..4e7aed47cf --- /dev/null +++ b/aries/aries_vcx_core/src/errors/mapping_askar.rs @@ -0,0 +1,25 @@ +use aries_askar::ErrorKind; + +use super::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}; + +impl From for AriesVcxCoreError { + fn from(err: aries_askar::Error) -> Self { + match err.kind() { + ErrorKind::Backend + | ErrorKind::Custom + | ErrorKind::Encryption + | ErrorKind::Input + | ErrorKind::Unexpected + | ErrorKind::Unsupported + | ErrorKind::Busy => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::WalletError, err) + } + ErrorKind::Duplicate => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::DuplicationWalletRecord, err) + } + ErrorKind::NotFound => { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::WalletRecordNotFound, err) + } + } + } +} diff --git a/aries/aries_vcx_core/src/errors/mapping_others.rs b/aries/aries_vcx_core/src/errors/mapping_others.rs index 5c227f6815..922546f63e 100644 --- a/aries/aries_vcx_core/src/errors/mapping_others.rs +++ b/aries/aries_vcx_core/src/errors/mapping_others.rs @@ -1,6 +1,7 @@ use std::sync::PoisonError; use did_parser::ParseError; +use public_key::PublicKeyError; use crate::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}; @@ -36,3 +37,9 @@ impl From for AriesVcxCoreError { AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidState, err.to_string()) } } + +impl From for AriesVcxCoreError { + fn from(value: PublicKeyError) -> Self { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::NotBase58, value) + } +} diff --git a/aries/aries_vcx_core/src/errors/mod.rs b/aries/aries_vcx_core/src/errors/mod.rs index 3e083082a4..e9dee3a39b 100644 --- a/aries/aries_vcx_core/src/errors/mod.rs +++ b/aries/aries_vcx_core/src/errors/mod.rs @@ -2,6 +2,8 @@ pub mod error; mod mapping_agency_client; #[cfg(feature = "anoncreds")] mod mapping_anoncreds; +#[cfg(feature = "askar_wallet")] +mod mapping_askar; #[cfg(feature = "credx")] mod mapping_credx; #[cfg(feature = "vdrtools_wallet")] diff --git a/aries/aries_vcx_core/src/wallet/agency_client_wallet.rs b/aries/aries_vcx_core/src/wallet/agency_client_wallet.rs index 1b6ad88cdf..7e40cb38a6 100644 --- a/aries/aries_vcx_core/src/wallet/agency_client_wallet.rs +++ b/aries/aries_vcx_core/src/wallet/agency_client_wallet.rs @@ -83,6 +83,10 @@ impl DidWallet for AgencyClientWallet { )) } + async fn key_count(&self) -> VcxCoreResult { + Err(unimplemented_agency_client_wallet_method("key_count")) + } + async fn key_for_did(&self, name: &str) -> VcxCoreResult { Err(unimplemented_agency_client_wallet_method("key_for_did")) } diff --git a/aries/aries_vcx_core/src/wallet/askar/askar_did_wallet.rs b/aries/aries_vcx_core/src/wallet/askar/askar_did_wallet.rs new file mode 100644 index 0000000000..d1ea85cd51 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/askar_did_wallet.rs @@ -0,0 +1,200 @@ +use aries_askar::{ + crypto::alg::Chacha20Types, + kms::{KeyAlg, LocalKey}, +}; +use async_trait::async_trait; +use public_key::Key; + +use super::{ + askar_utils::{local_key_to_public_key, seed_from_opt}, + pack::Pack, + rng_method::RngMethod, + sig_type::SigType, + unpack::unpack, + AskarWallet, +}; +use crate::{ + errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}, + wallet::{ + base_wallet::{did_data::DidData, record_category::RecordCategory, DidWallet}, + structs_io::UnpackMessageOutput, + utils::did_from_key, + }, +}; + +#[async_trait] +impl DidWallet for AskarWallet { + async fn key_count(&self) -> VcxCoreResult { + let mut session = self.session().await?; + + Ok(session + .fetch_all_keys(None, None, None, None, false) + .await? + .len()) + } + + async fn create_and_store_my_did( + &self, + seed: Option<&str>, + _did_method_name: Option<&str>, + ) -> VcxCoreResult { + let mut tx = self.transaction().await?; + let (did, local_key) = self + .insert_key( + &mut tx, + KeyAlg::Ed25519, + seed_from_opt(seed).as_bytes(), + RngMethod::RandomDet, + ) + .await?; + + let verkey = local_key_to_public_key(&local_key)?; + self.insert_did( + &mut tx, + &did, + &RecordCategory::Did.to_string(), + &verkey, + None, + ) + .await?; + tx.commit().await?; + Ok(DidData::new(&did, &verkey)) + } + + async fn key_for_did(&self, did: &str) -> VcxCoreResult { + let data = self + .find_current_did(&mut self.session().await?, did) + .await?; + + if let Some(did_data) = data { + Ok(did_data.verkey().to_owned()) + } else { + Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletRecordNotFound, + format!("did not found in wallet: {}", did), + )) + } + } + + async fn replace_did_key_start(&self, did: &str, seed: Option<&str>) -> VcxCoreResult { + let mut tx = self.transaction().await?; + if self.find_current_did(&mut tx, did).await?.is_some() { + let (_, local_key) = self + .insert_key( + &mut tx, + KeyAlg::Ed25519, + seed_from_opt(seed).as_bytes(), + RngMethod::RandomDet, + ) + .await?; + + let verkey = local_key_to_public_key(&local_key)?; + self.insert_did( + &mut tx, + did, + &RecordCategory::TmpDid.to_string(), + &verkey, + None, + ) + .await?; + tx.commit().await?; + + Ok(verkey) + } else { + Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletRecordNotFound, + format!("did not found in wallet: {}", did), + )) + } + } + + async fn replace_did_key_apply(&self, did: &str) -> VcxCoreResult<()> { + let mut tx = self.transaction().await?; + if let Some(did_value) = self.find_did(&mut tx, did, RecordCategory::TmpDid).await? { + tx.remove(&RecordCategory::TmpDid.to_string(), did).await?; + tx.remove_key(&did_from_key(did_value.verkey().clone())) + .await?; + self.update_did( + &mut tx, + did, + &RecordCategory::Did.to_string(), + did_value.verkey(), + None, + ) + .await?; + tx.commit().await?; + return Ok(()); + } else { + return Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletRecordNotFound, + "temporary did key not found in wallet", + )); + } + } + + async fn sign(&self, key: &Key, msg: &[u8]) -> VcxCoreResult> { + if let Some(key) = self + .session() + .await? + .fetch_key(&did_from_key(key.to_owned()), false) + .await? + { + let local_key = key.load_local_key()?; + let key_alg = SigType::try_from_key_alg(local_key.algorithm())?; + Ok(local_key.sign_message(msg, Some(key_alg.into()))?) + } else { + Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletError, + "key not found", + )) + } + } + + async fn verify(&self, key: &Key, msg: &[u8], signature: &[u8]) -> VcxCoreResult { + if let Some(key) = self + .session() + .await? + .fetch_key(&did_from_key(key.to_owned()), false) + .await? + { + let local_key = key.load_local_key()?; + let key_alg = SigType::try_from_key_alg(local_key.algorithm())?; + Ok(local_key.verify_signature(msg, signature, Some(key_alg.into()))?) + } else { + Ok(false) + } + } + + async fn pack_message( + &self, + sender_vk: Option, + recipient_keys: Vec, + msg: &[u8], + ) -> VcxCoreResult> { + if recipient_keys.is_empty() { + Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidInput, + "recipient keys should not be empty", + )) + } else { + let enc_key = LocalKey::generate(KeyAlg::Chacha20(Chacha20Types::C20P), true)?; + + let base64_data = if let Some(sender_verkey) = sender_vk { + let mut session = self.session().await?; + + let my_key = self + .fetch_local_key(&mut session, &did_from_key(sender_verkey)) + .await?; + enc_key.pack_authcrypt(recipient_keys, my_key)? + } else { + enc_key.pack_anoncrypt(recipient_keys)? + }; + + Ok(enc_key.pack_all(base64_data, msg)?) + } + } + + async fn unpack_message(&self, msg: &[u8]) -> VcxCoreResult { + Ok(unpack(serde_json::from_slice(msg)?, &mut self.session().await?).await?) + } +} diff --git a/aries/aries_vcx_core/src/wallet/askar/askar_record_wallet.rs b/aries/aries_vcx_core/src/wallet/askar/askar_record_wallet.rs new file mode 100644 index 0000000000..bdba4a2ea5 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/askar_record_wallet.rs @@ -0,0 +1,135 @@ +use aries_askar::entry::EntryTag; +use async_trait::async_trait; + +use super::AskarWallet; +use crate::{ + errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}, + wallet::{ + base_wallet::{ + record::Record, record_category::RecordCategory, search_filter::SearchFilter, + RecordWallet, + }, + record_tags::RecordTags, + }, +}; + +#[async_trait] +impl RecordWallet for AskarWallet { + async fn add_record(&self, record: Record) -> VcxCoreResult<()> { + let tags: Option> = Some(record.tags().clone().into()); + Ok(self + .session() + .await? + .insert( + &record.category().to_string(), + record.name(), + record.value().as_bytes(), + tags.as_deref(), + None, + ) + .await?) + } + + async fn get_record(&self, category: RecordCategory, name: &str) -> VcxCoreResult { + Ok(self + .session() + .await? + .fetch(&category.to_string(), name, false) + .await? + .ok_or_else(|| { + AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletRecordNotFound, + "record not found", + ) + }) + .map(TryFrom::try_from)??) + } + + async fn update_record_tags( + &self, + category: RecordCategory, + name: &str, + new_tags: RecordTags, + ) -> VcxCoreResult<()> { + let mut session = self.session().await?; + let askar_tags: Vec = new_tags.into(); + match session.fetch(&category.to_string(), name, true).await? { + Some(record) => Ok(session + .replace( + &category.to_string(), + name, + &record.value, + Some(&askar_tags), + None, + ) + .await?), + None => Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletRecordNotFound, + "wallet record not found", + )), + } + } + + async fn update_record_value( + &self, + category: RecordCategory, + name: &str, + new_value: &str, + ) -> VcxCoreResult<()> { + let mut session = self.session().await?; + match session.fetch(&category.to_string(), name, true).await? { + Some(record) => Ok(session + .replace( + &category.to_string(), + name, + new_value.as_bytes(), + Some(&record.tags), + None, + ) + .await?), + None => Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletRecordNotFound, + "wallet record not found", + )), + } + } + + async fn delete_record(&self, category: RecordCategory, name: &str) -> VcxCoreResult<()> { + Ok(self + .session() + .await? + .remove(&category.to_string(), name) + .await?) + } + + #[allow(unreachable_patterns)] + async fn search_record( + &self, + category: RecordCategory, + search_filter: Option, + ) -> VcxCoreResult> { + Ok(self + .session() + .await? + .fetch_all( + Some(&category.to_string()), + search_filter + .map(|filter| match filter { + SearchFilter::TagFilter(inner) => Ok(inner), + _ => Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletError, + "unsupported search filter", + )), + }) + .transpose()?, + None, + false, + ) + .await? + .into_iter() + .map(TryFrom::try_from) + .collect::>>() + .into_iter() + .collect::>()?) + } +} diff --git a/aries/aries_vcx_core/src/wallet/askar/askar_utils.rs b/aries/aries_vcx_core/src/wallet/askar/askar_utils.rs new file mode 100644 index 0000000000..782aaf7fb3 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/askar_utils.rs @@ -0,0 +1,55 @@ +use aries_askar::kms::{KeyAlg, LocalKey}; +use public_key::{Key, KeyType}; +use serde::Deserialize; + +use crate::{ + errors::error::{AriesVcxCoreErrorKind, VcxCoreResult}, + wallet::{askar::AriesVcxCoreError, utils::random_seed}, +}; + +pub fn local_key_to_bs58_name(local_key: &LocalKey) -> VcxCoreResult { + let res = local_key_to_bs58_public_key(local_key)?; + Ok(res[0..16].to_string()) +} + +pub fn local_key_to_bs58_public_key(local_key: &LocalKey) -> VcxCoreResult { + Ok(bs58::encode(local_key.to_public_bytes()?).into_string()) +} + +pub fn local_key_to_public_key(local_key: &LocalKey) -> VcxCoreResult { + Ok(Key::new( + local_key.to_public_bytes()?.to_vec(), + KeyType::Ed25519, + )?) +} + +pub fn ed25519_to_x25519(local_key: &LocalKey) -> VcxCoreResult { + Ok(local_key.convert_key(KeyAlg::X25519)?) +} + +pub fn seed_from_opt(maybe_seed: Option<&str>) -> String { + match maybe_seed { + Some(val) => val.into(), + None => random_seed(), + } +} + +pub fn from_json_str Deserialize<'a>>(json: &str) -> VcxCoreResult { + serde_json::from_str::(json) + .map_err(|err| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidJson, err)) +} + +pub fn bytes_to_bs58(bytes: &[u8]) -> String { + bs58::encode(bytes).into_string() +} + +pub fn bs58_to_bytes(key: &[u8]) -> VcxCoreResult> { + bs58::decode(key) + .into_vec() + .map_err(|err| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::WalletError, err)) +} + +pub fn bytes_to_string(vec: Vec) -> VcxCoreResult { + String::from_utf8(vec) + .map_err(|err| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidInput, err)) +} diff --git a/aries/aries_vcx_core/src/wallet/askar/entry.rs b/aries/aries_vcx_core/src/wallet/askar/entry.rs new file mode 100644 index 0000000000..b6acd43693 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/entry.rs @@ -0,0 +1,39 @@ +use std::str::FromStr; + +use aries_askar::entry::{Entry, EntryKind}; + +use crate::{ + errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind}, + wallet::base_wallet::{record::Record, record_category::RecordCategory}, +}; + +impl TryFrom for Record { + type Error = AriesVcxCoreError; + + fn try_from(entry: Entry) -> Result { + Ok(Self::builder() + .category(RecordCategory::from_str(&entry.category)?) + .name(entry.name) + .value( + std::str::from_utf8(&entry.value) + .map_err(|err| { + AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::WalletError, err) + })? + .into(), + ) + .tags(entry.tags.into()) + .build()) + } +} + +impl From for Entry { + fn from(record: Record) -> Self { + Self { + category: record.category().to_string(), + name: record.name().to_string(), + value: record.value().into(), + kind: EntryKind::Item, + tags: record.tags().clone().into_iter().map(From::from).collect(), + } + } +} diff --git a/aries/aries_vcx_core/src/wallet/askar/entry_tags.rs b/aries/aries_vcx_core/src/wallet/askar/entry_tags.rs new file mode 100644 index 0000000000..ea773957a9 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/entry_tags.rs @@ -0,0 +1,38 @@ +use aries_askar::entry::EntryTag; + +use crate::wallet::record_tags::{RecordTag, RecordTags}; + +impl From for RecordTag { + fn from(askar_tag: EntryTag) -> Self { + match askar_tag { + EntryTag::Encrypted(key, val) => RecordTag::new(&key, &val), + EntryTag::Plaintext(key, val) => RecordTag::new(&format!("~{}", key), &val), + } + } +} + +impl From for EntryTag { + fn from(entry_tag: RecordTag) -> Self { + if entry_tag.key().starts_with('~') { + Self::Plaintext( + entry_tag.key().to_string().trim_start_matches('~').into(), + entry_tag.value().into(), + ) + } else { + Self::Encrypted(entry_tag.key().into(), entry_tag.value().into()) + } + } +} + +impl From for Vec { + fn from(tags: RecordTags) -> Self { + let tags_vec: Vec = tags.into(); + tags_vec.into_iter().map(Into::into).collect() + } +} + +impl From> for RecordTags { + fn from(askar_tags: Vec) -> Self { + askar_tags.into_iter().map(Into::into).collect() + } +} diff --git a/aries/aries_vcx_core/src/wallet/askar/mod.rs b/aries/aries_vcx_core/src/wallet/askar/mod.rs new file mode 100644 index 0000000000..f78ac879a1 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/mod.rs @@ -0,0 +1,200 @@ +use aries_askar::{ + entry::EntryTag, + kms::{KeyAlg, KeyEntry, LocalKey}, + PassKey, Session, Store, StoreKeyMethod, +}; +use public_key::Key; + +use self::{askar_utils::local_key_to_bs58_name, rng_method::RngMethod}; +use super::base_wallet::{did_value::DidValue, record_category::RecordCategory, BaseWallet}; +use crate::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}; + +mod askar_did_wallet; +mod askar_record_wallet; +mod askar_utils; +mod entry; +mod entry_tags; +mod pack; +mod packing_types; +mod rng_method; +mod sig_type; +mod unpack; + +#[derive(Debug)] +pub struct AskarWallet { + backend: Store, + profile: String, +} + +impl BaseWallet for AskarWallet {} + +impl AskarWallet { + pub async fn create( + db_url: &str, + key_method: StoreKeyMethod, + pass_key: PassKey<'_>, + recreate: bool, + profile: &str, + ) -> Result { + let backend = + Store::provision(db_url, key_method, pass_key, Some(profile.into()), recreate).await?; + + Ok(Self { + backend, + profile: profile.into(), + }) + } + + pub async fn open( + db_url: &str, + key_method: Option, + pass_key: PassKey<'_>, + profile: &str, + ) -> Result { + Ok(Self { + backend: Store::open(db_url, key_method, pass_key, Some(profile.into())).await?, + profile: profile.into(), + }) + } + + async fn fetch_local_key( + &self, + session: &mut Session, + key_name: &str, + ) -> VcxCoreResult { + Ok(self + .fetch_key_entry(session, key_name) + .await? + .load_local_key()?) + } + + async fn fetch_key_entry( + &self, + session: &mut Session, + key_name: &str, + ) -> Result { + session.fetch_key(key_name, false).await?.ok_or_else(|| { + AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletRecordNotFound, + format!("no key with name '{}' found in wallet", key_name), + ) + }) + } + + async fn insert_key( + &self, + session: &mut Session, + alg: KeyAlg, + seed: &[u8], + rng_method: RngMethod, + ) -> Result<(String, LocalKey), AriesVcxCoreError> { + let key = LocalKey::from_seed(alg, seed, rng_method.into())?; + let key_name = local_key_to_bs58_name(&key)?; + session + .insert_key(&key_name, &key, None, None, None) + .await?; + Ok((key_name, key)) + } + + async fn find_did( + &self, + session: &mut Session, + did: &str, + category: RecordCategory, + ) -> VcxCoreResult> { + if let Some(entry) = session.fetch(&category.to_string(), did, false).await? { + if let Some(val) = entry.value.as_opt_str() { + return Ok(Some(serde_json::from_str(val)?)); + } + } + + Ok(None) + } + + async fn find_current_did( + &self, + session: &mut Session, + did: &str, + ) -> VcxCoreResult> { + self.find_did(session, did, RecordCategory::Did).await + } + + async fn insert_did( + &self, + session: &mut Session, + did: &str, + category: &str, + verkey: &Key, + tags: Option<&[EntryTag]>, + ) -> VcxCoreResult<()> { + if (session.fetch(did, category, false).await?).is_some() { + Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::DuplicationDid, + "did with given verkey already exists", + )) + } else { + Ok(session + .insert( + category, + did, + serde_json::to_string(&DidValue::new(verkey))?.as_bytes(), + tags, + None, + ) + .await?) + } + } + + async fn update_did( + &self, + session: &mut Session, + did: &str, + category: &str, + verkey: &Key, + tags: Option<&[EntryTag]>, + ) -> VcxCoreResult<()> { + session + .replace( + category, + did, + serde_json::to_string(&DidValue::new(verkey))?.as_bytes(), + tags, + None, + ) + .await?; + + Ok(()) + } + + async fn session(&self) -> VcxCoreResult { + Ok(self.backend.session(Some(self.profile.clone())).await?) + } + + async fn transaction(&self) -> VcxCoreResult { + Ok(self.backend.transaction(Some(self.profile.clone())).await?) + } +} + +#[cfg(test)] +pub mod tests { + use crate::wallet::base_wallet::BaseWallet; + + pub async fn dev_setup_askar_wallet() -> Box { + use aries_askar::StoreKeyMethod; + use uuid::Uuid; + + use crate::wallet::askar::AskarWallet; + + Box::new( + AskarWallet::create( + "sqlite://:memory:", + StoreKeyMethod::Unprotected, + None.into(), + true, + &Uuid::new_v4().to_string(), + ) + .await + .unwrap(), + ) + } +} diff --git a/aries/aries_vcx_core/src/wallet/askar/pack.rs b/aries/aries_vcx_core/src/wallet/askar/pack.rs new file mode 100644 index 0000000000..9d1200e4ca --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/pack.rs @@ -0,0 +1,163 @@ +use aries_askar::kms::{ + crypto_box, crypto_box_random_nonce, crypto_box_seal, KeyAlg::Ed25519, LocalKey, +}; +use public_key::Key; + +use super::{ + askar_utils::{bs58_to_bytes, bytes_to_bs58, ed25519_to_x25519}, + packing_types::{ + Base64String, Jwe, JweAlg, ProtectedData, ProtectedHeaderEnc, ProtectedHeaderTyp, Recipient, + }, +}; +use crate::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}; + +fn check_supported_key_alg(key: &LocalKey) -> VcxCoreResult<()> { + let supported_algs = vec![Ed25519]; + if !supported_algs.contains(&key.algorithm()) { + let msg = format!( + "Unsupported key algorithm, expected one of: {}", + supported_algs + .into_iter() + .map(|alg| alg.to_string()) + .collect::>() + .join(", ") + ); + Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidOption, + msg, + )) + } else { + Ok(()) + } +} + +fn encode_protected_data( + encrypted_recipients: Vec, + jwe_alg: JweAlg, +) -> VcxCoreResult { + let protected_data = ProtectedData { + enc: ProtectedHeaderEnc::XChaCha20Poly1305, + typ: ProtectedHeaderTyp::Jwm, + alg: jwe_alg, + recipients: encrypted_recipients, + }; + + let protected_encoded = serde_json::to_string(&protected_data).map_err(|err| { + AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::EncodeError, + format!("Failed to serialize protected field {}", err), + ) + })?; + + Ok(Base64String::from_bytes(protected_encoded.as_bytes())) +} + +fn pack_authcrypt_recipients( + enc_key: &LocalKey, + recipient_keys: Vec, + sender_local_key: LocalKey, +) -> VcxCoreResult> { + let mut encrypted_recipients = Vec::with_capacity(recipient_keys.len()); + + let sender_converted_key = ed25519_to_x25519(&sender_local_key)?; + + for recipient_key in recipient_keys { + let recipient_public_key = &LocalKey::from_public_bytes(Ed25519, recipient_key.key())?; + + let nonce = crypto_box_random_nonce()?; + let recipient_converted_key = ed25519_to_x25519(recipient_public_key)?; + + let enc_cek = crypto_box( + &recipient_converted_key, + &sender_converted_key, + &enc_key.to_secret_bytes()?, + &nonce, + )?; + + let enc_sender = crypto_box_seal( + &recipient_converted_key, + bytes_to_bs58(&sender_local_key.to_public_bytes()?).as_bytes(), + )?; + + encrypted_recipients.push(Recipient::new_authcrypt( + Base64String::from_bytes(&enc_cek), + &recipient_key.base58(), + Base64String::from_bytes(&nonce), + Base64String::from_bytes(&enc_sender), + )); + } + + Ok(encrypted_recipients) +} + +fn pack_anoncrypt_recipients( + enc_key: &LocalKey, + recipient_keys: Vec, +) -> VcxCoreResult> { + let mut encrypted_recipients = Vec::with_capacity(recipient_keys.len()); + + let enc_key_secret = &enc_key.to_secret_bytes()?; + + for recipient_key in recipient_keys { + let recipient_pubkey = bs58_to_bytes(recipient_key.base58().as_bytes())?; + let recipient_local_key = LocalKey::from_public_bytes(Ed25519, &recipient_pubkey)?; + let enc_cek = crypto_box_seal(&ed25519_to_x25519(&recipient_local_key)?, enc_key_secret)?; + + let kid = bytes_to_bs58(&recipient_pubkey); + + encrypted_recipients.push(Recipient::new_anoncrypt( + Base64String::from_bytes(&enc_cek), + &kid, + )); + } + + Ok(encrypted_recipients) +} + +pub trait Pack { + fn pack_authcrypt( + &self, + recipient_keys: Vec, + sender_local_key: LocalKey, + ) -> VcxCoreResult; + + fn pack_anoncrypt(&self, recipient_keys: Vec) -> VcxCoreResult; + + fn pack_all(&self, base64_data: Base64String, msg: &[u8]) -> VcxCoreResult>; +} + +impl Pack for LocalKey { + fn pack_authcrypt( + &self, + recipient_keys: Vec, + sender_local_key: LocalKey, + ) -> VcxCoreResult { + check_supported_key_alg(&sender_local_key)?; + encode_protected_data( + pack_authcrypt_recipients(self, recipient_keys, sender_local_key)?, + JweAlg::Authcrypt, + ) + } + + fn pack_anoncrypt(&self, recipient_keys: Vec) -> VcxCoreResult { + let encrypted_recipients = pack_anoncrypt_recipients(self, recipient_keys)?; + + encode_protected_data(encrypted_recipients, JweAlg::Anoncrypt) + } + + fn pack_all(&self, base64_data: Base64String, msg: &[u8]) -> VcxCoreResult> { + let enc = self.aead_encrypt(msg, &self.aead_random_nonce()?, &base64_data.as_bytes())?; + serde_json::to_vec(&Jwe { + protected: base64_data, + iv: Base64String::from_bytes(enc.nonce()), + ciphertext: Base64String::from_bytes(enc.ciphertext()), + tag: Base64String::from_bytes(enc.tag()), + }) + .map_err(|err| { + AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::EncodeError, + format!("Failed to serialize JWE {}", err), + ) + }) + } +} diff --git a/aries/aries_vcx_core/src/wallet/askar/packing_types.rs b/aries/aries_vcx_core/src/wallet/askar/packing_types.rs new file mode 100644 index 0000000000..8ce9e065b2 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/packing_types.rs @@ -0,0 +1,188 @@ +use indy_vdr::utils::base64::{decode_urlsafe, encode_urlsafe}; +use serde::{de::Unexpected, Deserialize, Serialize}; + +use crate::{ + errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}, + wallet::askar::askar_utils::bytes_to_string, +}; + +pub const PROTECTED_HEADER_ENC: &str = "xchacha20poly1305_ietf"; +pub const PROTECTED_HEADER_TYP: &str = "JWM/1.0"; + +#[derive(Debug)] +pub enum ProtectedHeaderEnc { + XChaCha20Poly1305, +} + +impl Serialize for ProtectedHeaderEnc { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(match self { + Self::XChaCha20Poly1305 => PROTECTED_HEADER_ENC, + }) + } +} + +impl<'de> Deserialize<'de> for ProtectedHeaderEnc { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + + match value.as_str() { + PROTECTED_HEADER_ENC => Ok(Self::XChaCha20Poly1305), + _ => Err(serde::de::Error::invalid_value( + Unexpected::Str(value.as_str()), + &PROTECTED_HEADER_ENC, + )), + } + } +} + +#[derive(Debug)] +pub enum ProtectedHeaderTyp { + Jwm, +} + +impl Serialize for ProtectedHeaderTyp { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(match self { + Self::Jwm => PROTECTED_HEADER_TYP, + }) + } +} + +impl<'de> Deserialize<'de> for ProtectedHeaderTyp { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + + match value.as_str() { + PROTECTED_HEADER_TYP => Ok(Self::Jwm), + _ => Err(serde::de::Error::invalid_value( + Unexpected::Str(value.as_str()), + &PROTECTED_HEADER_TYP, + )), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(transparent)] +pub struct Base64String(String); + +impl Base64String { + pub fn from_bytes(content: &[u8]) -> Self { + Self(encode_urlsafe(content)) + } + + pub fn decode(&self) -> VcxCoreResult> { + decode_urlsafe(&self.0) + .map_err(|e| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::InvalidJson, e)) + } + + pub fn decode_to_string(&self) -> VcxCoreResult { + bytes_to_string(self.decode()?) + } + + pub fn as_bytes(&self) -> Vec { + self.0.as_bytes().into() + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Jwe { + pub protected: Base64String, + pub iv: Base64String, + pub ciphertext: Base64String, + pub tag: Base64String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum JweAlg { + Authcrypt, + Anoncrypt, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ProtectedData { + pub enc: ProtectedHeaderEnc, + pub typ: ProtectedHeaderTyp, + pub alg: JweAlg, + pub recipients: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum Recipient { + Authcrypt(AuthcryptRecipient), + Anoncrypt(AnoncryptRecipient), +} + +impl Recipient { + pub fn new_authcrypt( + encrypted_key: Base64String, + kid: &str, + iv: Base64String, + sender: Base64String, + ) -> Self { + Self::Authcrypt(AuthcryptRecipient { + encrypted_key, + header: AuthcryptHeader { + kid: kid.into(), + iv, + sender, + }, + }) + } + + pub fn new_anoncrypt(encrypted_key: Base64String, kid: &str) -> Self { + Self::Anoncrypt(AnoncryptRecipient { + encrypted_key, + header: AnoncryptHeader { kid: kid.into() }, + }) + } + + pub fn unwrap_kid(&self) -> &str { + match self { + Self::Anoncrypt(inner) => &inner.header.kid, + Self::Authcrypt(inner) => &inner.header.kid, + } + } + + pub fn key_name(&self) -> &str { + &self.unwrap_kid()[0..16] + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct AuthcryptRecipient { + pub encrypted_key: Base64String, + pub header: AuthcryptHeader, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct AnoncryptRecipient { + pub encrypted_key: Base64String, + pub header: AnoncryptHeader, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct AuthcryptHeader { + pub kid: String, + pub iv: Base64String, + pub sender: Base64String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct AnoncryptHeader { + pub kid: String, +} diff --git a/aries/aries_vcx_core/src/wallet/askar/rng_method.rs b/aries/aries_vcx_core/src/wallet/askar/rng_method.rs new file mode 100644 index 0000000000..70d667f18e --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/rng_method.rs @@ -0,0 +1,15 @@ +#[derive(Clone, Default)] +pub enum RngMethod { + #[default] + RandomDet, + Bls, +} + +impl From for Option<&str> { + fn from(value: RngMethod) -> Self { + match value { + RngMethod::RandomDet => None, + RngMethod::Bls => Some("bls_keygen"), + } + } +} diff --git a/aries/aries_vcx_core/src/wallet/askar/sig_type.rs b/aries/aries_vcx_core/src/wallet/askar/sig_type.rs new file mode 100644 index 0000000000..8a1984c8f2 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/sig_type.rs @@ -0,0 +1,38 @@ +use aries_askar::{crypto::alg::EcCurves, kms::KeyAlg}; + +use crate::errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}; + +pub enum SigType { + EdDSA, + ES256, + ES256K, + ES384, +} + +impl From for &str { + fn from(value: SigType) -> Self { + match value { + SigType::EdDSA => "eddsa", + SigType::ES256 => "es256", + SigType::ES256K => "es256k", + SigType::ES384 => "es384", + } + } +} + +impl SigType { + pub fn try_from_key_alg(key_alg: KeyAlg) -> VcxCoreResult { + match key_alg { + KeyAlg::Ed25519 => Ok(SigType::EdDSA), + KeyAlg::EcCurve(item) => match item { + EcCurves::Secp256r1 => Ok(SigType::ES256), + EcCurves::Secp256k1 => Ok(SigType::ES256K), + EcCurves::Secp384r1 => Ok(SigType::ES384), + }, + _ => Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidInput, + "this key does not support signing", + )), + } + } +} diff --git a/aries/aries_vcx_core/src/wallet/askar/unpack.rs b/aries/aries_vcx_core/src/wallet/askar/unpack.rs new file mode 100644 index 0000000000..9f2486d6b3 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/askar/unpack.rs @@ -0,0 +1,123 @@ +use aries_askar::{ + crypto::alg::Chacha20Types, + kms::{ + crypto_box_open, crypto_box_seal_open, + KeyAlg::{self, Ed25519}, + KeyEntry, LocalKey, ToDecrypt, + }, + Session, +}; +use public_key::{Key, KeyType}; + +use super::{ + askar_utils::{bs58_to_bytes, bytes_to_string, ed25519_to_x25519, from_json_str}, + packing_types::{AnoncryptRecipient, AuthcryptRecipient, Jwe, ProtectedData, Recipient}, +}; +use crate::{ + errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}, + wallet::structs_io::UnpackMessageOutput, +}; + +trait Unpack { + fn unpack(&self, recipient: &Recipient, jwe: Jwe) -> VcxCoreResult; +} + +impl Unpack for LocalKey { + fn unpack(&self, recipient: &Recipient, jwe: Jwe) -> VcxCoreResult { + let (enc_key, sender_verkey) = unpack_recipient(recipient, self)?; + Ok(UnpackMessageOutput { + message: unpack_msg(&jwe, enc_key)?, + recipient_verkey: recipient.unwrap_kid().to_owned(), + sender_verkey: sender_verkey.map(|key| key.base58()), + }) + } +} + +pub async fn unpack(jwe: Jwe, session: &mut Session) -> VcxCoreResult { + let protected_data = unpack_protected_data(&jwe)?; + let (recipient, key_entry) = find_recipient_key(&protected_data, session).await?; + let local_key = key_entry.load_local_key()?; + local_key.unpack(recipient, jwe) +} + +fn unpack_recipient( + recipient: &Recipient, + local_key: &LocalKey, +) -> VcxCoreResult<(LocalKey, Option)> { + match recipient { + Recipient::Authcrypt(auth_recipient) => unpack_authcrypt(local_key, auth_recipient), + Recipient::Anoncrypt(anon_recipient) => unpack_anoncrypt(local_key, anon_recipient), + } +} + +fn unpack_protected_data(jwe: &Jwe) -> VcxCoreResult { + from_json_str(&jwe.protected.decode_to_string()?) +} + +fn unpack_msg(jwe: &Jwe, enc_key: LocalKey) -> VcxCoreResult { + let ciphertext = &jwe.ciphertext.decode()?; + let tag = &jwe.tag.decode()?; + + bytes_to_string( + enc_key + .aead_decrypt( + ToDecrypt::from((ciphertext.as_ref(), tag.as_ref())), + &jwe.iv.decode()?, + &jwe.protected.as_bytes(), + )? + .to_vec(), + ) +} + +fn unpack_authcrypt( + local_key: &LocalKey, + recipient: &AuthcryptRecipient, +) -> VcxCoreResult<(LocalKey, Option)> { + let recipient_key = ed25519_to_x25519(local_key)?; + let sender_vk = crypto_box_seal_open(&recipient_key, &recipient.header.sender.decode()?)?; + let sender_key = ed25519_to_x25519(&LocalKey::from_public_bytes( + Ed25519, + &bs58_to_bytes(&sender_vk.clone())?, + )?)?; + + let secret = crypto_box_open( + &recipient_key, + &sender_key, + &recipient.encrypted_key.decode()?, + &recipient.header.iv.decode()?, + )?; + + Ok(( + LocalKey::from_secret_bytes(KeyAlg::Chacha20(Chacha20Types::C20P), &secret)?, + Some(Key::new(sender_vk.to_vec(), KeyType::Ed25519)?), + )) +} + +fn unpack_anoncrypt( + local_key: &LocalKey, + recipient: &AnoncryptRecipient, +) -> VcxCoreResult<(LocalKey, Option)> { + let recipient_key = ed25519_to_x25519(local_key)?; + let key = crypto_box_seal_open(&recipient_key, &recipient.encrypted_key.decode()?)?; + + Ok(( + LocalKey::from_secret_bytes(KeyAlg::Chacha20(Chacha20Types::C20P), &key)?, + None, + )) +} + +async fn find_recipient_key<'a>( + protected_data: &'a ProtectedData, + session: &mut Session, +) -> VcxCoreResult<(&'a Recipient, KeyEntry)> { + for recipient in protected_data.recipients.iter() { + if let Some(key_entry) = session.fetch_key(recipient.key_name(), false).await? { + return Ok((recipient, key_entry)); + }; + } + + Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::WalletRecordNotFound, + "recipient key not found in wallet", + )) +} diff --git a/aries/aries_vcx_core/src/wallet/base_wallet/did_data.rs b/aries/aries_vcx_core/src/wallet/base_wallet/did_data.rs index f88132403e..3e7c37c4bc 100644 --- a/aries/aries_vcx_core/src/wallet/base_wallet/did_data.rs +++ b/aries/aries_vcx_core/src/wallet/base_wallet/did_data.rs @@ -8,10 +8,10 @@ pub struct DidData { } impl DidData { - pub fn new(did: &str, verkey: Key) -> Self { + pub fn new(did: &str, verkey: &Key) -> Self { Self { did: did.into(), - verkey, + verkey: verkey.clone(), } } diff --git a/aries/aries_vcx_core/src/wallet/base_wallet/did_value.rs b/aries/aries_vcx_core/src/wallet/base_wallet/did_value.rs new file mode 100644 index 0000000000..d8d99e372d --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/base_wallet/did_value.rs @@ -0,0 +1,19 @@ +use public_key::Key; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct DidValue { + verkey: Key, +} + +impl DidValue { + pub fn new(verkey: &Key) -> Self { + Self { + verkey: verkey.clone(), + } + } + + pub fn verkey(&self) -> &Key { + &self.verkey + } +} diff --git a/aries/aries_vcx_core/src/wallet/base_wallet/mod.rs b/aries/aries_vcx_core/src/wallet/base_wallet/mod.rs index 93f5bb350a..0b3fe7b3d7 100644 --- a/aries/aries_vcx_core/src/wallet/base_wallet/mod.rs +++ b/aries/aries_vcx_core/src/wallet/base_wallet/mod.rs @@ -1,19 +1,18 @@ use async_trait::async_trait; use public_key::Key; +use self::{did_data::DidData, record_category::RecordCategory}; use super::record_tags::RecordTags; use crate::{ errors::error::VcxCoreResult, wallet::{ - base_wallet::{ - did_data::DidData, record::Record, record_category::RecordCategory, - search_filter::SearchFilter, - }, + base_wallet::{record::Record, search_filter::SearchFilter}, structs_io::UnpackMessageOutput, }, }; pub mod did_data; +pub mod did_value; pub mod record; pub mod record_category; pub mod search_filter; @@ -30,6 +29,8 @@ pub trait DidWallet { async fn key_for_did(&self, did: &str) -> VcxCoreResult; + async fn key_count(&self) -> VcxCoreResult; + async fn replace_did_key_start(&self, did: &str, seed: Option<&str>) -> VcxCoreResult; async fn replace_did_key_apply(&self, did: &str) -> VcxCoreResult<()>; @@ -79,31 +80,45 @@ pub trait RecordWallet { #[cfg(test)] mod tests { - use rand::{distributions::Alphanumeric, Rng}; - use super::BaseWallet; use crate::{ errors::error::AriesVcxCoreErrorKind, wallet::{ - base_wallet::{record_category::RecordCategory, DidWallet, Record, RecordWallet}, - record_tags::RecordTags, + base_wallet::{record_category::RecordCategory, Record}, + record_tags::{RecordTag, RecordTags}, + utils::{did_from_key, random_seed}, }, }; - fn random_seed() -> String { - rand::thread_rng() - .sample_iter(Alphanumeric) - .take(32) - .map(char::from) - .collect() - } - - async fn build_test_wallet() -> impl BaseWallet { + #[allow(unused_variables)] + async fn build_test_wallet() -> Box { #[cfg(feature = "vdrtools_wallet")] - { + let wallet = { use crate::wallet::indy::tests::dev_setup_indy_wallet; dev_setup_indy_wallet().await - } + }; + + #[cfg(feature = "askar_wallet")] + let wallet = { + use crate::wallet::askar::tests::dev_setup_askar_wallet; + dev_setup_askar_wallet().await + }; + + wallet + } + + #[tokio::test] + async fn did_wallet_should_create_and_store_did_atomically() { + let wallet = build_test_wallet().await; + let seed = random_seed(); + wallet + .create_and_store_my_did(Some(&seed), None) + .await + .unwrap(); + let _ = wallet.create_and_store_my_did(Some(&seed), None).await; + let res = wallet.key_count().await.unwrap(); + + assert_eq!(1, res) } #[tokio::test] @@ -123,31 +138,113 @@ mod tests { } #[tokio::test] - async fn did_wallet_should_rotate_keys() { + async fn did_wallet_should_return_correct_key() { let wallet = build_test_wallet().await; - let did_data = wallet + let first_data = wallet.create_and_store_my_did(None, None).await.unwrap(); + + let new_key = wallet + .replace_did_key_start(first_data.did(), Some(&random_seed())) + .await + .unwrap(); + + assert_eq!(new_key.key().len(), 32); + + wallet + .replace_did_key_apply(first_data.did()) + .await + .unwrap(); + + let new_verkey = wallet.key_for_did(first_data.did()).await.unwrap(); + assert_eq!(new_verkey.key().len(), 32); + + assert_eq!( + did_from_key(new_key.clone()), + did_from_key(new_verkey.clone()) + ); + assert_eq!(new_key.key(), new_verkey.key()); + } + + #[tokio::test] + async fn did_wallet_should_replace_did_key_repeatedly() { + let wallet = build_test_wallet().await; + + let first_data = wallet.create_and_store_my_did(None, None).await.unwrap(); + + let new_key = wallet + .replace_did_key_start(first_data.did(), Some(&random_seed())) + .await + .unwrap(); + + wallet + .replace_did_key_apply(first_data.did()) + .await + .unwrap(); + + let new_verkey = wallet.key_for_did(first_data.did()).await.unwrap(); + + assert_eq!(did_from_key(new_key), did_from_key(new_verkey)); + + let second_new_key = wallet + .replace_did_key_start(first_data.did(), Some(&random_seed())) + .await + .unwrap(); + + wallet + .replace_did_key_apply(first_data.did()) + .await + .unwrap(); + + let second_new_verkey = wallet.key_for_did(first_data.did()).await.unwrap(); + + assert_eq!( + did_from_key(second_new_key), + did_from_key(second_new_verkey) + ); + } + + #[tokio::test] + async fn did_wallet_should_replace_did_key_interleaved() { + let wallet = build_test_wallet().await; + + let first_data = wallet.create_and_store_my_did(None, None).await.unwrap(); + + let second_data = wallet .create_and_store_my_did(Some(&random_seed()), None) .await .unwrap(); - let key = wallet.key_for_did(did_data.did()).await.unwrap(); + let first_new_key = wallet + .replace_did_key_start(first_data.did(), Some(&random_seed())) + .await + .unwrap(); - assert_eq!(did_data.verkey(), &key); + let second_new_key = wallet + .replace_did_key_start(second_data.did(), Some(&random_seed())) + .await + .unwrap(); - let res = wallet - .replace_did_key_start(did_data.did(), Some(&random_seed())) + wallet + .replace_did_key_apply(second_data.did()) + .await + .unwrap(); + wallet + .replace_did_key_apply(first_data.did()) .await .unwrap(); - wallet.replace_did_key_apply(did_data.did()).await.unwrap(); + let first_new_verkey = wallet.key_for_did(first_data.did()).await.unwrap(); + let second_new_verkey = wallet.key_for_did(second_data.did()).await.unwrap(); - let new_key = wallet.key_for_did(did_data.did()).await.unwrap(); - assert_eq!(res, new_key); + assert_eq!(did_from_key(first_new_key), did_from_key(first_new_verkey)); + assert_eq!( + did_from_key(second_new_key), + did_from_key(second_new_verkey) + ); } #[tokio::test] - async fn did_wallet_should_pack_and_unpack() { + async fn did_wallet_should_pack_and_unpack_authcrypt() { let wallet = build_test_wallet().await; let sender_data = wallet.create_and_store_my_did(None, None).await.unwrap(); @@ -170,6 +267,24 @@ mod tests { assert_eq!(msg, unpacked.message); } + #[tokio::test] + async fn did_wallet_should_pack_and_unpack_anoncrypt() { + let wallet = build_test_wallet().await; + + let receiver_data = wallet.create_and_store_my_did(None, None).await.unwrap(); + + let msg = "pack me"; + + let packed = wallet + .pack_message(None, vec![receiver_data.verkey().clone()], msg.as_bytes()) + .await + .unwrap(); + + let unpacked = wallet.unpack_message(&packed).await.unwrap(); + + assert_eq!(msg, unpacked.message); + } + #[tokio::test] async fn record_wallet_should_create_record() { let wallet = build_test_wallet().await; @@ -231,7 +346,7 @@ mod tests { let name2 = "foa"; let name3 = "fob"; let category1 = RecordCategory::Cred; - let category2 = RecordCategory::CredDef; + let category2 = RecordCategory::default(); let value = "xxx"; let record1 = Record::builder() @@ -268,7 +383,7 @@ mod tests { let category = RecordCategory::default(); let value1 = "xxx"; let value2 = "yyy"; - let tags1: RecordTags = vec![("a".into(), "b".into())].into(); + let tags1: RecordTags = vec![RecordTag::new("a", "b")].into(); let tags2 = RecordTags::default(); let record = Record::builder() @@ -301,7 +416,7 @@ mod tests { let category = RecordCategory::default(); let value1 = "xxx"; let value2 = "yyy"; - let tags: RecordTags = vec![("a".into(), "b".into())].into(); + let tags: RecordTags = vec![RecordTag::new("a", "b")].into(); let record = Record::builder() .name(name.into()) @@ -328,8 +443,8 @@ mod tests { let name = "foo"; let category = RecordCategory::default(); let value = "xxx"; - let tags1: RecordTags = vec![("a".into(), "b".into())].into(); - let tags2: RecordTags = vec![("c".into(), "d".into())].into(); + let tags1: RecordTags = vec![RecordTag::new("a", "b")].into(); + let tags2: RecordTags = vec![RecordTag::new("c", "d")].into(); let record = Record::builder() .name(name.into()) diff --git a/aries/aries_vcx_core/src/wallet/base_wallet/record.rs b/aries/aries_vcx_core/src/wallet/base_wallet/record.rs index fe16fd588e..3803f83c8f 100644 --- a/aries/aries_vcx_core/src/wallet/base_wallet/record.rs +++ b/aries/aries_vcx_core/src/wallet/base_wallet/record.rs @@ -1,6 +1,7 @@ use typed_builder::TypedBuilder; -use crate::wallet::{base_wallet::record_category::RecordCategory, record_tags::RecordTags}; +use super::record_category::RecordCategory; +use crate::wallet::record_tags::RecordTags; #[derive(Debug, Default, Clone, TypedBuilder)] pub struct Record { diff --git a/aries/aries_vcx_core/src/wallet/base_wallet/record_category.rs b/aries/aries_vcx_core/src/wallet/base_wallet/record_category.rs index 5ef557a99c..de9b973719 100644 --- a/aries/aries_vcx_core/src/wallet/base_wallet/record_category.rs +++ b/aries/aries_vcx_core/src/wallet/base_wallet/record_category.rs @@ -17,6 +17,8 @@ const REV_REG_DELTA: &str = "VCX_REV_REG_DELTA"; const REV_REG_INFO: &str = "VCX_REV_REG_INFO"; const REV_REG_DEF: &str = "VCX_REV_REG_DEF"; const REV_REG_DEF_PRIV: &str = "VCX_REV_REG_DEF_PRIV"; +const DID: &str = "Indy::Did"; +const TMP_DID: &str = "Indy::TemporaryDid"; #[derive(Clone, Copy, Debug, Default)] pub enum RecordCategory { @@ -33,6 +35,8 @@ pub enum RecordCategory { RevRegInfo, RevRegDef, RevRegDefPriv, + Did, + TmpDid, } impl FromStr for RecordCategory { @@ -52,6 +56,8 @@ impl FromStr for RecordCategory { REV_REG_INFO => Ok(RecordCategory::RevRegInfo), REV_REG_DEF => Ok(RecordCategory::RevRegDef), REV_REG_DEF_PRIV => Ok(RecordCategory::RevRegDefPriv), + DID => Ok(RecordCategory::Did), + TMP_DID => Ok(RecordCategory::TmpDid), _ => Err(Self::Err::from_msg( AriesVcxCoreErrorKind::InvalidInput, format!("unknown category: {}", s), @@ -75,6 +81,8 @@ impl Display for RecordCategory { RecordCategory::RevRegInfo => REV_REG_INFO, RecordCategory::RevRegDef => REV_REG_DEF, RecordCategory::RevRegDefPriv => REV_REG_DEF_PRIV, + RecordCategory::Did => DID, + RecordCategory::TmpDid => TMP_DID, }; write!(f, "{}", value) diff --git a/aries/aries_vcx_core/src/wallet/base_wallet/search_filter.rs b/aries/aries_vcx_core/src/wallet/base_wallet/search_filter.rs index a8b28360e2..49091a37ad 100644 --- a/aries/aries_vcx_core/src/wallet/base_wallet/search_filter.rs +++ b/aries/aries_vcx_core/src/wallet/base_wallet/search_filter.rs @@ -1,3 +1,6 @@ pub enum SearchFilter { + #[cfg(feature = "vdrtools_wallet")] JsonFilter(String), + #[cfg(feature = "askar_wallet")] + TagFilter(aries_askar::entry::TagFilter), } diff --git a/aries/aries_vcx_core/src/wallet/constants.rs b/aries/aries_vcx_core/src/wallet/constants.rs new file mode 100644 index 0000000000..3d4c1cd6d9 --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/constants.rs @@ -0,0 +1,2 @@ +pub const DID_CATEGORY: &str = "Indy::Did"; +pub const TMP_DID_CATEGORY: &str = "Indy::TemporaryDid"; diff --git a/aries/aries_vcx_core/src/wallet/indy/indy_did_wallet.rs b/aries/aries_vcx_core/src/wallet/indy/indy_did_wallet.rs index b553b2c183..9406937f15 100644 --- a/aries/aries_vcx_core/src/wallet/indy/indy_did_wallet.rs +++ b/aries/aries_vcx_core/src/wallet/indy/indy_did_wallet.rs @@ -5,7 +5,7 @@ use vdrtools::{DidMethod, DidValue, KeyInfo, Locator, MyDidInfo}; use crate::{ errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}, wallet::{ - base_wallet::{did_data::DidData, DidWallet}, + base_wallet::{did_data::DidData, record_category::RecordCategory, DidWallet}, indy::IndySdkWallet, structs_io::UnpackMessageOutput, }, @@ -13,6 +13,10 @@ use crate::{ #[async_trait] impl DidWallet for IndySdkWallet { + async fn key_count(&self) -> VcxCoreResult { + Ok(self.search(RecordCategory::Did, None).await?.len()) + } + async fn create_and_store_my_did( &self, seed: Option<&str>, @@ -33,7 +37,7 @@ impl DidWallet for IndySdkWallet { let verkey = Key::from_base58(&vk, KeyType::Ed25519) .map_err(|err| AriesVcxCoreError::from_msg(AriesVcxCoreErrorKind::WalletError, err))?; - Ok(DidData::new(&did, verkey)) + Ok(DidData::new(&did, &verkey)) } async fn key_for_did(&self, did: &str) -> VcxCoreResult { diff --git a/aries/aries_vcx_core/src/wallet/indy/indy_record_wallet.rs b/aries/aries_vcx_core/src/wallet/indy/indy_record_wallet.rs index 81567d3f6b..c82e2ebae7 100644 --- a/aries/aries_vcx_core/src/wallet/indy/indy_record_wallet.rs +++ b/aries/aries_vcx_core/src/wallet/indy/indy_record_wallet.rs @@ -1,12 +1,10 @@ use async_trait::async_trait; use indy_api_types::domain::wallet::IndyRecord; -use serde::Deserialize; -use serde_json::Value; use vdrtools::Locator; -use super::{indy_tags::IndyTags, SEARCH_OPTIONS, WALLET_OPTIONS}; +use super::{indy_tags::IndyTags, WALLET_OPTIONS}; use crate::{ - errors::error::{AriesVcxCoreError, VcxCoreResult}, + errors::error::VcxCoreResult, wallet::{ base_wallet::{ record::Record, record_category::RecordCategory, search_filter::SearchFilter, @@ -23,7 +21,7 @@ impl RecordWallet for IndySdkWallet { let tags_map = if record.tags().is_empty() { None } else { - Some(IndyTags::from_entry_tags(record.tags().clone()).into_inner()) + Some(IndyTags::from_record_tags(record.tags().clone()).into_inner()) }; Ok(Locator::instance() @@ -66,7 +64,7 @@ impl RecordWallet for IndySdkWallet { self.wallet_handle, category.to_string(), name.into(), - IndyTags::from_entry_tags(new_tags).into_inner(), + IndyTags::from_record_tags(new_tags).into_inner(), ) .await?) } @@ -100,45 +98,6 @@ impl RecordWallet for IndySdkWallet { category: RecordCategory, search_filter: Option, ) -> VcxCoreResult> { - let json_filter = search_filter - .map(|filter| match filter { - SearchFilter::JsonFilter(inner) => Ok::(inner), - }) - .transpose()?; - - let query_json = json_filter.unwrap_or("{}".into()); - - let search_handle = Locator::instance() - .non_secret_controller - .open_search( - self.wallet_handle, - category.to_string(), - query_json, - SEARCH_OPTIONS.into(), - ) - .await?; - - let next = || async { - let record = Locator::instance() - .non_secret_controller - .fetch_search_next_records(self.wallet_handle, search_handle, 1) - .await?; - - let indy_res: Value = serde_json::from_str(&record)?; - - indy_res - .get("records") - .and_then(|v| v.as_array()) - .and_then(|arr| arr.first()) - .map(|item| IndyRecord::deserialize(item).map_err(AriesVcxCoreError::from)) - .transpose() - }; - - let mut records = Vec::new(); - while let Some(record) = next().await? { - records.push(Record::try_from_indy_record(record)?); - } - - Ok(records) + self.search(category, search_filter).await } } diff --git a/aries/aries_vcx_core/src/wallet/indy/indy_tags.rs b/aries/aries_vcx_core/src/wallet/indy/indy_tags.rs index 5bb58ad45c..71881b7866 100644 --- a/aries/aries_vcx_core/src/wallet/indy/indy_tags.rs +++ b/aries/aries_vcx_core/src/wallet/indy/indy_tags.rs @@ -13,15 +13,19 @@ impl IndyTags { self.0 } - pub fn from_entry_tags(tags: RecordTags) -> Self { + pub fn from_record_tags(tags: RecordTags) -> Self { let mut map = HashMap::new(); - let tags_vec: Vec<_> = tags.into_iter().collect(); + let tags_vec: Vec<_> = tags.into_iter().map(|tag| tag.into_pair()).collect(); map.extend(tags_vec); Self(map) } - pub fn into_entry_tags(self) -> RecordTags { - let mut items: Vec = self.0.into_iter().collect(); + pub fn into_record_tags(self) -> RecordTags { + let mut items: Vec<_> = self + .0 + .into_iter() + .map(|(key, val)| RecordTag::new(&key, &val)) + .collect(); items.sort(); RecordTags::new(items) diff --git a/aries/aries_vcx_core/src/wallet/indy/mod.rs b/aries/aries_vcx_core/src/wallet/indy/mod.rs index 0ab22d3b29..2a37f25a80 100644 --- a/aries/aries_vcx_core/src/wallet/indy/mod.rs +++ b/aries/aries_vcx_core/src/wallet/indy/mod.rs @@ -2,11 +2,18 @@ use std::str::FromStr; use indy_api_types::domain::wallet::IndyRecord; use serde::{Deserialize, Serialize}; +use serde_json::Value; use typed_builder::TypedBuilder; +use vdrtools::Locator; use self::indy_tags::IndyTags; -use super::base_wallet::{record::Record, record_category::RecordCategory, BaseWallet}; -use crate::{errors::error::VcxCoreResult, WalletHandle}; +use super::base_wallet::{ + record::Record, record_category::RecordCategory, search_filter::SearchFilter, BaseWallet, +}; +use crate::{ + errors::error::{AriesVcxCoreError, AriesVcxCoreErrorKind, VcxCoreResult}, + WalletHandle, +}; mod indy_did_wallet; mod indy_record_wallet; @@ -27,6 +34,58 @@ impl IndySdkWallet { pub fn get_wallet_handle(&self) -> WalletHandle { self.wallet_handle } + + #[allow(unreachable_patterns)] + async fn search( + &self, + category: RecordCategory, + search_filter: Option, + ) -> VcxCoreResult> { + let json_filter = search_filter + .map(|filter| match filter { + SearchFilter::JsonFilter(inner) => Ok::(inner), + _ => Err(AriesVcxCoreError::from_msg( + AriesVcxCoreErrorKind::InvalidInput, + "filter type not supported", + )), + }) + .transpose()?; + + let query_json = json_filter.unwrap_or("{}".into()); + + let search_handle = Locator::instance() + .non_secret_controller + .open_search( + self.wallet_handle, + category.to_string(), + query_json, + SEARCH_OPTIONS.into(), + ) + .await?; + + let next = || async { + let record = Locator::instance() + .non_secret_controller + .fetch_search_next_records(self.wallet_handle, search_handle, 1) + .await?; + + let indy_res: Value = serde_json::from_str(&record)?; + + indy_res + .get("records") + .and_then(|v| v.as_array()) + .and_then(|arr| arr.first()) + .map(|item| IndyRecord::deserialize(item).map_err(AriesVcxCoreError::from)) + .transpose() + }; + + let mut records = Vec::new(); + while let Some(indy_record) = next().await? { + records.push(Record::try_from_indy_record(indy_record)?); + } + + Ok(records) + } } #[derive(Clone, Debug, TypedBuilder, Serialize, Deserialize)] @@ -102,11 +161,22 @@ impl Record { .name(indy_record.id) .category(RecordCategory::from_str(&indy_record.type_)?) .value(indy_record.value) - .tags(IndyTags::new(indy_record.tags).into_entry_tags()) + .tags(IndyTags::new(indy_record.tags).into_record_tags()) .build()) } } +impl From for IndyRecord { + fn from(record: Record) -> Self { + Self { + id: record.name().into(), + type_: record.category().to_string(), + value: record.value().into(), + tags: IndyTags::from_record_tags(record.tags().to_owned()).into_inner(), + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RestoreWalletConfigs { pub wallet_name: String, @@ -127,8 +197,9 @@ impl BaseWallet for IndySdkWallet {} #[cfg(test)] pub mod tests { use super::IndySdkWallet; + use crate::wallet::base_wallet::BaseWallet; - pub async fn dev_setup_indy_wallet() -> IndySdkWallet { + pub async fn dev_setup_indy_wallet() -> Box { use crate::wallet::indy::{wallet::create_and_open_wallet, WalletConfig}; let config_wallet = WalletConfig { @@ -143,6 +214,6 @@ pub mod tests { }; let wallet_handle = create_and_open_wallet(&config_wallet).await.unwrap(); - IndySdkWallet::new(wallet_handle) + Box::new(IndySdkWallet::new(wallet_handle)) } } diff --git a/aries/aries_vcx_core/src/wallet/mod.rs b/aries/aries_vcx_core/src/wallet/mod.rs index 990de0d0d1..3615cdc99b 100644 --- a/aries/aries_vcx_core/src/wallet/mod.rs +++ b/aries/aries_vcx_core/src/wallet/mod.rs @@ -1,6 +1,9 @@ pub mod agency_client_wallet; +#[cfg(feature = "askar_wallet")] +pub mod askar; pub mod base_wallet; #[cfg(feature = "vdrtools_wallet")] pub mod indy; pub mod record_tags; pub mod structs_io; +mod utils; diff --git a/aries/aries_vcx_core/src/wallet/record_tags.rs b/aries/aries_vcx_core/src/wallet/record_tags.rs index ed731959a3..cabb85815b 100644 --- a/aries/aries_vcx_core/src/wallet/record_tags.rs +++ b/aries/aries_vcx_core/src/wallet/record_tags.rs @@ -2,7 +2,39 @@ use std::fmt; use serde::{de::Visitor, ser::SerializeMap, Deserialize, Serialize}; -pub(crate) type RecordTag = (String, String); +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct RecordTag { + key: String, + value: String, +} + +impl RecordTag { + pub fn new(key: &str, value: &str) -> Self { + Self { + key: key.to_owned(), + value: value.to_owned(), + } + } + + pub fn key(&self) -> &str { + &self.key + } + + pub fn value(&self) -> &str { + &self.value + } + + pub fn into_pair(self) -> (String, String) { + (self.key, self.value) + } + + pub fn from_pair(pair: (String, String)) -> Self { + Self { + key: pair.0, + value: pair.1, + } + } +} #[derive(Debug, Default, Clone, PartialEq)] pub struct RecordTags { @@ -16,7 +48,7 @@ impl Serialize for RecordTags { { let mut map = serializer.serialize_map(Some(self.inner.len()))?; for tag in self.inner.iter() { - map.serialize_entry(&tag.0, &tag.1)? + map.serialize_entry(tag.key(), tag.value())? } map.end() } @@ -37,8 +69,8 @@ impl<'de> Visitor<'de> for RecordTagsVisitor { { let mut tags = RecordTags::new(vec![]); - while let Some(tag) = map.next_entry()? { - tags.add(tag); + while let Some((key, val)) = map.next_entry()? { + tags.add(RecordTag::new(key, val)); } Ok(tags) @@ -81,7 +113,8 @@ impl RecordTags { } pub fn remove(&mut self, tag: RecordTag) { - self.inner.retain(|existing_tag| existing_tag.0 != tag.0); + self.inner + .retain(|existing_tag| existing_tag.key() != tag.key()); self.inner.sort(); } } @@ -126,23 +159,20 @@ impl From for Vec { mod tests { use serde_json::json; - use crate::wallet::record_tags::RecordTags; + use crate::wallet::record_tags::{RecordTag, RecordTags}; #[test] - fn test_entry_tags_serialize() { - let tags = RecordTags::new(vec![("~a".into(), "b".into()), ("c".into(), "d".into())]); - + fn test_record_tags_serialize() { + let tags = RecordTags::new(vec![RecordTag::new("~a", "b"), RecordTag::new("c", "d")]); let res = serde_json::to_string(&tags).unwrap(); assert_eq!(json!({ "~a": "b", "c": "d" }).to_string(), res); } #[test] - fn test_entry_tags_deserialize() { + fn test_record_tags_deserialize() { let json = json!({"a":"b", "~c":"d"}); - - let tags = RecordTags::new(vec![("a".into(), "b".into()), ("~c".into(), "d".into())]); - + let tags = RecordTags::new(vec![RecordTag::new("a", "b"), RecordTag::new("~c", "d")]); let res = serde_json::from_str(&json.to_string()).unwrap(); assert_eq!(tags, res); diff --git a/aries/aries_vcx_core/src/wallet/utils.rs b/aries/aries_vcx_core/src/wallet/utils.rs new file mode 100644 index 0000000000..13c550c40e --- /dev/null +++ b/aries/aries_vcx_core/src/wallet/utils.rs @@ -0,0 +1,16 @@ +use public_key::Key; +use rand::{distributions::Alphanumeric, Rng}; + +#[allow(dead_code)] +pub fn random_seed() -> String { + rand::thread_rng() + .sample_iter(Alphanumeric) + .take(32) + .map(char::from) + .collect() +} + +#[allow(dead_code)] +pub fn did_from_key(key: Key) -> String { + key.base58()[0..16].to_string() +} diff --git a/aries/misc/test_utils/src/mock_wallet.rs b/aries/misc/test_utils/src/mock_wallet.rs index 64c5389df0..7e3b58ce52 100644 --- a/aries/misc/test_utils/src/mock_wallet.rs +++ b/aries/misc/test_utils/src/mock_wallet.rs @@ -79,10 +79,14 @@ impl DidWallet for MockWallet { ) -> VcxCoreResult { Ok(DidData::new( DID, - Key::new(VERKEY.into(), KeyType::Ed25519).unwrap(), + &Key::new(VERKEY.into(), KeyType::Ed25519).unwrap(), )) } + async fn key_count(&self) -> VcxCoreResult { + Ok(0) + } + async fn key_for_did(&self, name: &str) -> VcxCoreResult { Ok(Key::new(VERKEY.into(), KeyType::Ed25519).unwrap()) } diff --git a/justfile b/justfile index 4ad11bec80..12cadea4cc 100644 --- a/justfile +++ b/justfile @@ -35,10 +35,13 @@ check-aries-vcx-core-credx: cargo test --manifest-path="aries/aries_vcx_core/Cargo.toml" -F vdrtools_wallet,credx --tests test-unit test_name="": - RUST_TEST_THREADS=1 cargo test --workspace --lib --exclude aries-vcx-agent --exclude libvdrtools --exclude wallet_migrator --exclude mediator -- {{test_name}} + RUST_TEST_THREADS=1 cargo test --workspace --lib --exclude aries-vcx-agent --exclude libvdrtools --exclude wallet_migrator --exclude mediator --exclude aries_vcx_core -- --ignored {{test_name}} -test-integration-aries-vcx test_name="": - cargo test --manifest-path="aries/aries_vcx/Cargo.toml" -F vdrtools_wallet,credx -- --ignored {{test_name}} +test-integration-aries-vcx-core features: + cargo test --manifest-path="aries/aries_vcx_core/Cargo.toml" -F {{features}} + +test-integration-aries-vcx features test_name="": + cargo test --manifest-path="aries/aries_vcx/Cargo.toml" -F {{features}} -- --ignored {{test_name}} test-integration-aries-vcx-anoncreds-rs test_name="": cargo test --manifest-path="aries/aries_vcx/Cargo.toml" -F anoncreds --test test_revocations --test test_proof_presentation --test test_anoncreds --test test_verifier -- --ignored {{test_name}}