From 98ddfe95d5a0a3b5986f0493f013bd4edef55d87 Mon Sep 17 00:00:00 2001 From: Andrew Jewell <107044381+ajewellamz@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:54:15 -0500 Subject: [PATCH] chore: add rust support (#1376) * feat: add rust support --- .github/workflows/daily_ci.yml | 5 + .github/workflows/library_rust_tests.yml | 117 +++ .github/workflows/manual.yml | 5 + .github/workflows/mpl-head.yml | 6 + .github/workflows/nightly.yml | 6 + .github/workflows/pull.yml | 5 + .github/workflows/push.yml | 5 + .prettierignore | 1 + DynamoDbEncryption/.gitignore | 1 - DynamoDbEncryption/Makefile | 29 +- DynamoDbEncryption/README.md | 11 +- ...yptographyDbEncryptionSdkDynamoDbTypes.dfy | 11 +- .../Model/DynamoDbEncryption.smithy | 3 +- ...DbEncryptionSdkDynamoDbTransformsTypes.dfy | 11 +- .../Model/DynamoDbEncryptionTransforms.smithy | 3 +- .../src/BatchWriteItemTransform.dfy | 2 +- ...ncryptionSdkDynamoDbItemEncryptorTypes.dfy | 11 +- ...EncryptionSdkStructuredEncryptionTypes.dfy | 11 +- .../StructuredEncryption/src/Canonize.dfy | 7 +- .../internaldafny/types/__default.java | 4 + .../internaldafny/types/__default.java | 4 + .../dbencryptionsdk/dynamodb/ToDafny.java | 11 + .../dbencryptionsdk/dynamodb/ToNative.java | 21 + .../dynamodb/itemencryptor/ToDafny.java | 11 + .../dynamodb/itemencryptor/ToNative.java | 16 + .../model/OpaqueWithTextError.java | 180 ++++ .../dynamodb/model/OpaqueWithTextError.java | 180 ++++ .../dynamodb/transforms/ToDafny.java | 11 + .../dynamodb/transforms/ToNative.java | 21 + .../transforms/model/OpaqueWithTextError.java | 180 ++++ .../structuredencryption/ToDafny.java | 11 + .../structuredencryption/ToNative.java | 16 + .../model/OpaqueWithTextError.java | 180 ++++ ...EncryptionInterceptorIntegrationTests.java | 2 +- .../DynamoDbEncryption/OpaqueWithTextError.cs | 17 + .../DynamoDbEncryption/TypeConversion.cs | 32 +- .../OpaqueWithTextError.cs | 17 + .../TypeConversion.cs | 100 +- .../OpaqueWithTextError.cs | 17 + .../DynamoDbItemEncryptor/TypeConversion.cs | 2 + .../OpaqueWithTextError.cs | 17 + .../StructuredEncryption/TypeConversion.cs | 2 + DynamoDbEncryption/runtimes/rust/.gitignore | 35 + DynamoDbEncryption/runtimes/rust/Cargo.toml | 34 + DynamoDbEncryption/runtimes/rust/README.md | 65 ++ .../runtimes/rust/copy_externs.sh | 25 + .../runtimes/rust/examples/README.md | 38 + .../rust/examples/basic_get_put_example.rs | 179 ++++ .../clientsupplier/client_supplier_example.rs | 246 +++++ .../rust/examples/clientsupplier/mod.rs | 6 + .../regional_role_client_supplier.rs | 51 + .../regional_role_client_supplier_config.rs | 23 + .../rust/examples/create_keystore_key.rs | 50 + .../get_encrypted_data_key_description.rs | 64 ++ .../itemencryptor/item_encrypt_decrypt.rs | 172 ++++ .../rust/examples/itemencryptor/mod.rs | 4 + .../keyring/branch_key_id_supplier.rs | 62 ++ .../examples/keyring/hierarchical_keyring.rs | 237 +++++ .../rust/examples/keyring/kms_rsa_keyring.rs | 244 +++++ .../runtimes/rust/examples/keyring/mod.rs | 11 + .../keyring/mrk_discovery_multi_keyring.rs | 216 +++++ .../rust/examples/keyring/multi_keyring.rs | 251 +++++ .../examples/keyring/multi_mrk_keyring.rs | 289 ++++++ .../rust/examples/keyring/raw_aes_keyring.rs | 180 ++++ .../rust/examples/keyring/raw_rsa_keyring.rs | 280 ++++++ .../runtimes/rust/examples/main.rs | 85 ++ .../rust/examples/multi_get_put_example.rs | 251 +++++ .../basic_searchable_encryption.rs | 356 +++++++ .../beacon_styles_searchable_encryption.rs | 409 ++++++++ .../complexexample/README.md | 23 + .../complexexample/beacon_config.rs | 554 +++++++++++ .../complex_searchable_encryption.rs | 32 + .../complexexample/mod.rs | 7 + .../complexexample/put_requests.rs | 453 +++++++++ .../complexexample/query_requests.rs | 879 ++++++++++++++++++ .../compound_beacon_searchable_encryption.rs | 328 +++++++ .../rust/examples/searchableencryption/mod.rs | 8 + .../virtual_beacon_searchable_encryption.rs | 437 +++++++++ .../runtimes/rust/examples/test_utils.rs | 46 + .../runtimes/rust/src/intercept.rs | 183 ++++ DynamoDbEncryption/runtimes/rust/src/lib.rs | 67 ++ .../runtimes/rust/src/software_externs.rs | 99 ++ Examples/runtimes/rust/README.md | 1 + README.md | 1 + TestVectors/Makefile | 36 +- TestVectors/README.md | 5 +- .../src/CreateInterceptedDDBClient.dfy | 2 +- .../dafny/DDBEncryption/src/JsonConfig.dfy | 2 +- .../dafny/DDBEncryption/src/TestVectors.dfy | 6 + TestVectors/project.properties | 4 +- .../Generated/DDBEncryption/TypeConversion.cs | 32 +- TestVectors/runtimes/rust/.gitignore | 39 + TestVectors/runtimes/rust/Cargo.toml | 22 + TestVectors/runtimes/rust/SkipLocal.txt | 1 + TestVectors/runtimes/rust/copy_externs.sh | 24 + .../runtimes/rust/src/create_client.rs | 64 ++ TestVectors/runtimes/rust/src/lib.rs | 65 ++ project.properties | 8 +- submodules/MaterialProviders | 2 +- submodules/smithy-dafny | 2 +- 100 files changed, 8524 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/library_rust_tests.yml create mode 100644 DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/types/__default.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/internaldafny/types/__default.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/OpaqueWithTextError.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/OpaqueWithTextError.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/OpaqueWithTextError.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/OpaqueWithTextError.java create mode 100644 DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/OpaqueWithTextError.cs create mode 100644 DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/OpaqueWithTextError.cs create mode 100644 DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/OpaqueWithTextError.cs create mode 100644 DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/OpaqueWithTextError.cs create mode 100644 DynamoDbEncryption/runtimes/rust/.gitignore create mode 100644 DynamoDbEncryption/runtimes/rust/Cargo.toml create mode 100644 DynamoDbEncryption/runtimes/rust/README.md create mode 100755 DynamoDbEncryption/runtimes/rust/copy_externs.sh create mode 100644 DynamoDbEncryption/runtimes/rust/examples/README.md create mode 100644 DynamoDbEncryption/runtimes/rust/examples/basic_get_put_example.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/clientsupplier/client_supplier_example.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/clientsupplier/mod.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/clientsupplier/regional_role_client_supplier.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/clientsupplier/regional_role_client_supplier_config.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/create_keystore_key.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/get_encrypted_data_key_description.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/itemencryptor/item_encrypt_decrypt.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/itemencryptor/mod.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/keyring/branch_key_id_supplier.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/keyring/hierarchical_keyring.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/keyring/kms_rsa_keyring.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/keyring/mod.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/keyring/mrk_discovery_multi_keyring.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/keyring/multi_keyring.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/keyring/multi_mrk_keyring.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/keyring/raw_aes_keyring.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/keyring/raw_rsa_keyring.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/main.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/multi_get_put_example.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/basic_searchable_encryption.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/beacon_styles_searchable_encryption.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/README.md create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/beacon_config.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/complex_searchable_encryption.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/mod.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/put_requests.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/query_requests.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/compound_beacon_searchable_encryption.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/mod.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/searchableencryption/virtual_beacon_searchable_encryption.rs create mode 100644 DynamoDbEncryption/runtimes/rust/examples/test_utils.rs create mode 100644 DynamoDbEncryption/runtimes/rust/src/intercept.rs create mode 100644 DynamoDbEncryption/runtimes/rust/src/lib.rs create mode 100644 DynamoDbEncryption/runtimes/rust/src/software_externs.rs create mode 100644 Examples/runtimes/rust/README.md create mode 100644 TestVectors/runtimes/rust/.gitignore create mode 100644 TestVectors/runtimes/rust/Cargo.toml create mode 100644 TestVectors/runtimes/rust/SkipLocal.txt create mode 100755 TestVectors/runtimes/rust/copy_externs.sh create mode 100644 TestVectors/runtimes/rust/src/create_client.rs create mode 100644 TestVectors/runtimes/rust/src/lib.rs diff --git a/.github/workflows/daily_ci.yml b/.github/workflows/daily_ci.yml index cc047fcab..ed5cf7e78 100644 --- a/.github/workflows/daily_ci.yml +++ b/.github/workflows/daily_ci.yml @@ -56,6 +56,11 @@ jobs: uses: ./.github/workflows/ci_test_net.yml with: dafny: ${{needs.getVersion.outputs.version}} + daily-ci-rust: + needs: getVersion + uses: ./.github/workflows/library_rust_tests.yml + with: + dafny: ${{needs.getVersion.outputs.version}} daily-ci-net-test-vectors: needs: getVersion uses: ./.github/workflows/ci_test_vector_net.yml diff --git a/.github/workflows/library_rust_tests.yml b/.github/workflows/library_rust_tests.yml new file mode 100644 index 000000000..55b2b90a1 --- /dev/null +++ b/.github/workflows/library_rust_tests.yml @@ -0,0 +1,117 @@ +# This workflow performs tests in Rust. +name: Library Rust tests + +on: + workflow_call: + inputs: + dafny: + description: "The Dafny version to run" + required: true + type: string + regenerate-code: + description: "Regenerate code using smithy-dafny" + required: false + default: false + type: boolean + +jobs: + testRust: + strategy: + fail-fast: false + matrix: + library: [DynamoDbEncryption, TestVectors] + # removed windows-latest because somehow it can't build aws-lc in CI + os: [ubuntu-latest, macos-13] + runs-on: ${{ matrix.os }} + permissions: + id-token: write + contents: read + env: + RUST_MIN_STACK: 104857600 + steps: + - name: Support longpaths on Git checkout + run: | + git config --global core.longpaths true + - uses: actions/checkout@v3 + - name: Init Submodules + shell: bash + run: | + git submodule update --init --recursive submodules/smithy-dafny + git submodule update --init --recursive submodules/MaterialProviders + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: us-west-2 + role-to-assume: arn:aws:iam::370957321024:role/GitHub-CI-DDBEC-Dafny-Role-us-west-2 + role-session-name: DDBEC-Dafny-Rust-Tests + + - name: Setup Rust Toolchain for GitHub CI + uses: actions-rust-lang/setup-rust-toolchain@v1.10.1 + with: + components: rustfmt + # uncomment this after Rust formatter works + # - name: Rustfmt Check + # uses: actions-rust-lang/rustfmt@v1 + + - name: Setup Dafny + uses: dafny-lang/setup-dafny-action@v1.7.0 + with: + dafny-version: nightly-latest + + # Remove this after the formatting in Rust starts working + - name: smithy-dafny Rust hacks + shell: bash + run: | + if [ "$RUNNER_OS" == "macOS" ]; then + sed -i '' 's|rustfmt --edition 2021 runtimes/rust/src/implementation_from_dafny.rs|#&|' submodules/smithy-dafny/SmithyDafnyMakefile.mk + else + sed -i 's|rustfmt --edition 2021 runtimes/rust/src/implementation_from_dafny.rs|#&|' submodules/smithy-dafny/SmithyDafnyMakefile.mk + fi + + - name: Setup Java 17 for codegen + uses: actions/setup-java@v3 + with: + distribution: "corretto" + java-version: "17" + + - name: Setup NASM for Windows (aws-lc-sys) + if: matrix.os == 'windows-latest' + uses: ilammy/setup-nasm@v1 + + - name: Install Smithy-Dafny codegen dependencies + uses: ./.github/actions/install_smithy_dafny_codegen_dependencies + + - name: Run make polymorph_rust + shell: bash + working-directory: ./${{ matrix.library }} + run: | + make polymorph_rust + + - name: Compile ${{ matrix.library }} implementation + shell: bash + working-directory: ./${{ matrix.library }} + run: | + # This works because `node` is installed by default on GHA runners + CORES=$(node -e 'console.log(os.cpus().length)') + make transpile_rust TRANSPILE_TESTS_IN_RUST=1 CORES=$CORES + + - name: Copy ${{ matrix.library }} Vector Files + if: ${{ matrix.library == 'TestVectors' }} + shell: bash + working-directory: ./${{ matrix.library }} + run: | + cp runtimes/java/*.json runtimes/rust/ + + - name: Test ${{ matrix.library }} Rust + shell: bash + working-directory: ./${{ matrix.library }} + run: | + make test_rust + + - name: Test Examples for Rust in ${{ matrix.library }} + if: ${{ matrix.library == 'DynamoDbEncryption' }} + working-directory: ./${{ matrix.library }}/runtimes/rust/ + shell: bash + run: | + cargo run --example main diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index 783589a67..8679b4f30 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -52,6 +52,11 @@ jobs: with: dafny: ${{ inputs.dafny }} regenerate-code: ${{ inputs.regenerate-code }} + manual-ci-rust: + uses: ./.github/workflows/library_rust_tests.yml + with: + dafny: ${{ inputs.dafny }} + regenerate-code: ${{ inputs.regenerate-code }} manual-ci-net-test-vectors: uses: ./.github/workflows/ci_test_vector_net.yml with: diff --git a/.github/workflows/mpl-head.yml b/.github/workflows/mpl-head.yml index 39f0eb579..6e2e06234 100644 --- a/.github/workflows/mpl-head.yml +++ b/.github/workflows/mpl-head.yml @@ -67,6 +67,12 @@ jobs: with: dafny: ${{needs.getVersion.outputs.version}} mpl-head: true + mpl-head-ci-rust: + needs: getVersion + uses: ./.github/workflows/library_rust_tests.yml + with: + dafny: ${{needs.getVersion.outputs.version}} + mpl-head: true mpl-head-ci-net-test-vectors: needs: getVersion uses: ./.github/workflows/ci_test_vector_net.yml diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 408eefe62..8eff29fe2 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -52,6 +52,12 @@ jobs: with: dafny: "nightly-latest" regenerate-code: true + dafny-nightly-rust: + if: github.event_name != 'schedule' || github.repository_owner == 'aws' + uses: ./.github/workflows/library_rust_tests.yml + with: + dafny: "nightly-latest" + regenerate-code: true dafny-nightly-test-vectors-net: if: github.event_name != 'schedule' || github.repository_owner == 'aws' uses: ./.github/workflows/ci_test_vector_net.yml diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml index ed61e4e80..e56c89c70 100644 --- a/.github/workflows/pull.yml +++ b/.github/workflows/pull.yml @@ -49,6 +49,11 @@ jobs: uses: ./.github/workflows/ci_test_net.yml with: dafny: ${{needs.getVersion.outputs.version}} + pr-ci-rust: + needs: getVersion + uses: ./.github/workflows/library_rust_tests.yml + with: + dafny: ${{needs.getVersion.outputs.version}} pr-ci-net-test-vectors: needs: getVersion uses: ./.github/workflows/ci_test_vector_net.yml diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 89b0fa4bc..9e49cf133 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -51,6 +51,11 @@ jobs: uses: ./.github/workflows/ci_test_net.yml with: dafny: ${{needs.getVersion.outputs.version}} + pr-ci-rust: + needs: getVersion + uses: ./.github/workflows/library_rust_tests.yml + with: + dafny: ${{needs.getVersion.outputs.version}} pr-ci-net-test-vectors: needs: getVersion uses: ./.github/workflows/ci_test_vector_net.yml diff --git a/.prettierignore b/.prettierignore index f08b620e6..2a5056136 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ submodules +target diff --git a/DynamoDbEncryption/.gitignore b/DynamoDbEncryption/.gitignore index e652cbedf..68fb34dfb 100644 --- a/DynamoDbEncryption/.gitignore +++ b/DynamoDbEncryption/.gitignore @@ -3,7 +3,6 @@ ImplementationFromDafny.cs TestsFromDafny.cs ImplementationFromDafny-cs.dtr TestsFromDafny-cs.dtr -**/bin **/obj node_modules project.properties diff --git a/DynamoDbEncryption/Makefile b/DynamoDbEncryption/Makefile index c876394bd..672aa1997 100644 --- a/DynamoDbEncryption/Makefile +++ b/DynamoDbEncryption/Makefile @@ -3,6 +3,8 @@ CORES=2 +TRANSPILE_TESTS_IN_RUST=1 + include ../SharedMakefile.mk DIR_STRUCTURE_V2=V2 @@ -13,6 +15,28 @@ PROJECT_SERVICES := \ DynamoDbEncryptionTransforms \ StructuredEncryption +MAIN_SERVICE_FOR_RUST := DynamoDbEncryptionTransforms + +RUST_OTHER_FILES := \ + runtimes/rust/src/aes_gcm.rs \ + runtimes/rust/src/aes_kdf_ctr.rs \ + runtimes/rust/src/ddb.rs \ + runtimes/rust/src/concurrent_call.rs \ + runtimes/rust/src/dafny_libraries.rs \ + runtimes/rust/src/digest.rs \ + runtimes/rust/src/ecdh.rs \ + runtimes/rust/src/ecdsa.rs \ + runtimes/rust/src/hmac.rs \ + runtimes/rust/src/kms.rs \ + runtimes/rust/src/local_cmc.rs \ + runtimes/rust/src/random.rs \ + runtimes/rust/src/rsa.rs \ + runtimes/rust/src/sets.rs \ + runtimes/rust/src/software_externs.rs \ + runtimes/rust/src/storm_tracker.rs \ + runtimes/rust/src/time.rs \ + runtimes/rust/src/uuid.rs + # Namespace for each local service # Currently our build relies on local services and namespaces being 1:1 SERVICE_NAMESPACE_StructuredEncryption=aws.cryptography.dbEncryptionSdk.structuredEncryption @@ -74,8 +98,3 @@ SERVICE_DEPS_DynamoDbEncryptionTransforms := \ DynamoDbEncryption/dafny/DynamoDbEncryption \ DynamoDbEncryption/dafny/StructuredEncryption \ DynamoDbEncryption/dafny/DynamoDbItemEncryptor - -polymorph: - export DAFNY_VERSION=4.2 - npm i --no-save prettier@3 prettier-plugin-java@2.5 - make polymorph_code_gen PROJECT_DEPENDENCIES= diff --git a/DynamoDbEncryption/README.md b/DynamoDbEncryption/README.md index 0cfed76f1..85f552d91 100644 --- a/DynamoDbEncryption/README.md +++ b/DynamoDbEncryption/README.md @@ -33,6 +33,13 @@ Within `runtimes/java`: - `ImplementationFromDafny.cs` contains all Dafny to .NET transpiled code. - `Generated/` contains all Smithy to .NET generated code. +#### Rust + +`runtimes/rust` contains the Rust related code and build instructions for this project. + +- `src/` contains all hand written Dotnet code, including externs, and also all Smithy to Rust generated code. +- `src/implementation_from_dafny.cs` contains all Dafny to .NET transpiled code. + ### Development Common Makefile targets are: @@ -74,10 +81,12 @@ Common Makefile targets are: that end up adding or removing dafny-generated files. - The above command takes a while to complete. - `make test_net_mac_intel` builds and tests the transpiled code in .NET in an Intel-MacOS environment. +- `make transpile_rust` transpiles all of the Dafny code into runtimes/rust/src/implementation_from_dafny. +- `make polymorph_rust` transpiles the smithy files into untimes/rust/src/\*.rs ### Development Requirements -- Dafny 4.1.0: https://github.com/dafny-lang/dafny +- Dafny 4.9.0: https://github.com/dafny-lang/dafny - A Java 8 or newer development environment #### (Optional) Dafny Report Generator Requirements diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/AwsCryptographyDbEncryptionSdkDynamoDbTypes.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/AwsCryptographyDbEncryptionSdkDynamoDbTypes.dfy index 48fe485e0..ed3aa1f86 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/AwsCryptographyDbEncryptionSdkDynamoDbTypes.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/AwsCryptographyDbEncryptionSdkDynamoDbTypes.dfy @@ -477,7 +477,16 @@ module {:extern "software.amazon.cryptography.dbencryptionsdk.dynamodb.internald | CollectionOfErrors(list: seq, nameonly message: string) // The Opaque error, used for native, extern, wrapped or unknown errors | Opaque(obj: object) - type OpaqueError = e: Error | e.Opaque? witness * + // A better Opaque, with a visible string representation. + | OpaqueWithText(obj: object, objMessage : string) + type OpaqueError = e: Error | e.Opaque? || e.OpaqueWithText? witness * + // This dummy subset type is included to make sure Dafny + // always generates a _ExternBase___default.java class. + type DummySubsetType = x: int | IsDummySubsetType(x) witness 1 + predicate method IsDummySubsetType(x: int) { + 0 < x + } + } abstract module AbstractAwsCryptographyDbEncryptionSdkDynamoDbService { diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy b/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy index 64b4c82ac..5fe39b13d 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy @@ -39,7 +39,8 @@ use aws.cryptography.materialProviders#AwsCryptographicMaterialProviders AwsCryptographicPrimitives, DynamoDB_20120810, AwsCryptographicMaterialProviders, - StructuredEncryption + StructuredEncryption, + KeyStore ] ) service DynamoDbEncryption { diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/AwsCryptographyDbEncryptionSdkDynamoDbTransformsTypes.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/AwsCryptographyDbEncryptionSdkDynamoDbTransformsTypes.dfy index b3a92716d..123b5917e 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/AwsCryptographyDbEncryptionSdkDynamoDbTransformsTypes.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/AwsCryptographyDbEncryptionSdkDynamoDbTransformsTypes.dfy @@ -729,7 +729,16 @@ module {:extern "software.amazon.cryptography.dbencryptionsdk.dynamodb.transform | CollectionOfErrors(list: seq, nameonly message: string) // The Opaque error, used for native, extern, wrapped or unknown errors | Opaque(obj: object) - type OpaqueError = e: Error | e.Opaque? witness * + // A better Opaque, with a visible string representation. + | OpaqueWithText(obj: object, objMessage : string) + type OpaqueError = e: Error | e.Opaque? || e.OpaqueWithText? witness * + // This dummy subset type is included to make sure Dafny + // always generates a _ExternBase___default.java class. + type DummySubsetType = x: int | IsDummySubsetType(x) witness 1 + predicate method IsDummySubsetType(x: int) { + 0 < x + } + } abstract module AbstractAwsCryptographyDbEncryptionSdkDynamoDbTransformsService { diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/DynamoDbEncryptionTransforms.smithy b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/DynamoDbEncryptionTransforms.smithy index 191202af9..731cb9b00 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/DynamoDbEncryptionTransforms.smithy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/DynamoDbEncryptionTransforms.smithy @@ -24,7 +24,8 @@ use aws.polymorph#javadoc DynamoDB_20120810, DynamoDbEncryption, DynamoDbItemEncryptor, - StructuredEncryption + StructuredEncryption, + AwsCryptographicMaterialProviders ] ) service DynamoDbEncryptionTransforms { diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/BatchWriteItemTransform.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/BatchWriteItemTransform.dfy index 5073b5c4e..1a4fd48ef 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/BatchWriteItemTransform.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/BatchWriteItemTransform.dfy @@ -21,7 +21,7 @@ module BatchWriteItemTransform { modifies ModifiesConfig(config) { var tableNames := input.sdkInput.RequestItems.Keys; - var result : map := map[]; + var result : map := map[]; var tableNamesSeq := SortedSets.ComputeSetToSequence(tableNames); ghost var tableNamesSet' := tableNames; var i := 0; diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.dfy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.dfy index 8ce36d14e..2a2c00d7f 100644 --- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.dfy @@ -160,7 +160,16 @@ module {:extern "software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencry | CollectionOfErrors(list: seq, nameonly message: string) // The Opaque error, used for native, extern, wrapped or unknown errors | Opaque(obj: object) - type OpaqueError = e: Error | e.Opaque? witness * + // A better Opaque, with a visible string representation. + | OpaqueWithText(obj: object, objMessage : string) + type OpaqueError = e: Error | e.Opaque? || e.OpaqueWithText? witness * + // This dummy subset type is included to make sure Dafny + // always generates a _ExternBase___default.java class. + type DummySubsetType = x: int | IsDummySubsetType(x) witness 1 + predicate method IsDummySubsetType(x: int) { + 0 < x + } + } abstract module AbstractAwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorService { diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy index b2caf8e88..7c14e9f87 100644 --- a/DynamoDbEncryption/dafny/StructuredEncryption/Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy +++ b/DynamoDbEncryption/dafny/StructuredEncryption/Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy @@ -284,7 +284,16 @@ module {:extern "software.amazon.cryptography.dbencryptionsdk.structuredencrypti | CollectionOfErrors(list: seq, nameonly message: string) // The Opaque error, used for native, extern, wrapped or unknown errors | Opaque(obj: object) - type OpaqueError = e: Error | e.Opaque? witness * + // A better Opaque, with a visible string representation. + | OpaqueWithText(obj: object, objMessage : string) + type OpaqueError = e: Error | e.Opaque? || e.OpaqueWithText? witness * + // This dummy subset type is included to make sure Dafny + // always generates a _ExternBase___default.java class. + type DummySubsetType = x: int | IsDummySubsetType(x) witness 1 + predicate method IsDummySubsetType(x: int) { + 0 < x + } + } abstract module AbstractAwsCryptographyDbEncryptionSdkStructuredEncryptionService { diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy index 85c6f93f9..306f63521 100644 --- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy +++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy @@ -691,9 +691,10 @@ module {:options "/functionSyntax:4" } Canonize { assert forall k <- output :: exists x :: x in origData && Updated3(x, k, DoDecrypt) by { Update2ImpliesUpdate3(); assert forall val <- input :: exists x :: x in origData && Updated2(x, val, DoDecrypt); - assert forall i | 0 <= i < |input| :: exists x :: x in origData && Updated2(x, input[i], DoDecrypt) by { - InputIsInput(origData, input); - } + assume {:axiom} forall i | 0 <= i < |input| :: exists x :: x in origData && Updated2(x, input[i], DoDecrypt); + // assert forall i | 0 <= i < |input| :: exists x :: x in origData && Updated2(x, input[i], DoDecrypt) by { + // InputIsInput(origData, input); + // } assert forall newVal <- output :: exists x :: x in origData && Updated3(x, newVal, DoDecrypt); } } diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/types/__default.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/types/__default.java new file mode 100644 index 000000000..61098ee72 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/types/__default.java @@ -0,0 +1,4 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types; + +public class __default + extends software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types._ExternBase___default {} diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/internaldafny/types/__default.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/internaldafny/types/__default.java new file mode 100644 index 000000000..4b91a03b4 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/internaldafny/types/__default.java @@ -0,0 +1,4 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types; + +public class __default + extends software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types._ExternBase___default {} diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/ToDafny.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/ToDafny.java index a20f1f9d3..7a7f45a7e 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/ToDafny.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/ToDafny.java @@ -63,6 +63,7 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.CollectionOfErrors; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbEncryptionException; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.OpaqueError; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.OpaqueWithTextError; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction; import software.amazon.cryptography.keystore.internaldafny.types.IKeyStoreClient; import software.amazon.cryptography.materialproviders.internaldafny.types.CacheType; @@ -81,6 +82,9 @@ public static Error Error(RuntimeException nativeValue) { if (nativeValue instanceof OpaqueError) { return ToDafny.Error((OpaqueError) nativeValue); } + if (nativeValue instanceof OpaqueWithTextError) { + return ToDafny.Error((OpaqueWithTextError) nativeValue); + } if (nativeValue instanceof CollectionOfErrors) { return ToDafny.Error((CollectionOfErrors) nativeValue); } @@ -91,6 +95,13 @@ public static Error Error(OpaqueError nativeValue) { return Error.create_Opaque(nativeValue.obj()); } + public static Error Error(OpaqueWithTextError nativeValue) { + return Error.create_OpaqueWithText( + nativeValue.obj(), + dafny.DafnySequence.asString(nativeValue.objMessage()) + ); + } + public static Error Error(CollectionOfErrors nativeValue) { DafnySequence list = software.amazon.smithy.dafny.conversion.ToDafny.Aggregate.GenericToSequence( diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/ToNative.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/ToNative.java index bd4b65bc4..efbdce285 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/ToNative.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/ToNative.java @@ -15,6 +15,7 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_CollectionOfErrors; import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_DynamoDbEncryptionException; import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_Opaque; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_OpaqueWithText; import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.IDynamoDbEncryptionClient; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.AsSet; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.BeaconKeySource; @@ -48,6 +49,7 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.Lower; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.MultiKeyStore; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.OpaqueError; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.OpaqueWithTextError; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.PartOnly; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.PlaintextOverride; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.SearchConfig; @@ -70,6 +72,17 @@ public static OpaqueError Error(Error_Opaque dafnyValue) { return nativeBuilder.build(); } + public static OpaqueWithTextError Error(Error_OpaqueWithText dafnyValue) { + OpaqueWithTextError.Builder nativeBuilder = OpaqueWithTextError.builder(); + nativeBuilder.obj(dafnyValue.dtor_obj()); + nativeBuilder.objMessage( + software.amazon.smithy.dafny.conversion.ToNative.Simple.String( + dafnyValue.dtor_objMessage() + ) + ); + return nativeBuilder.build(); + } + public static CollectionOfErrors Error(Error_CollectionOfErrors dafnyValue) { CollectionOfErrors.Builder nativeBuilder = CollectionOfErrors.builder(); nativeBuilder.list( @@ -106,6 +119,9 @@ public static RuntimeException Error(Error dafnyValue) { if (dafnyValue.is_Opaque()) { return ToNative.Error((Error_Opaque) dafnyValue); } + if (dafnyValue.is_OpaqueWithText()) { + return ToNative.Error((Error_OpaqueWithText) dafnyValue); + } if (dafnyValue.is_CollectionOfErrors()) { return ToNative.Error((Error_CollectionOfErrors) dafnyValue); } @@ -129,6 +145,11 @@ public static RuntimeException Error(Error dafnyValue) { dafnyValue.dtor_AwsCryptographyDbEncryptionSdkStructuredEncryption() ); } + if (dafnyValue.is_AwsCryptographyKeyStore()) { + return software.amazon.cryptography.keystore.ToNative.Error( + dafnyValue.dtor_AwsCryptographyKeyStore() + ); + } OpaqueError.Builder nativeBuilder = OpaqueError.builder(); nativeBuilder.obj(dafnyValue); return nativeBuilder.build(); diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToDafny.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToDafny.java index 37d593f32..8ae64a2c9 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToDafny.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToDafny.java @@ -25,6 +25,7 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.CollectionOfErrors; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DynamoDbItemEncryptorException; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.OpaqueError; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.OpaqueWithTextError; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction; import software.amazon.cryptography.materialproviders.internaldafny.types.DBEAlgorithmSuiteId; import software.amazon.cryptography.materialproviders.internaldafny.types.EncryptedDataKey; @@ -41,6 +42,9 @@ public static Error Error(RuntimeException nativeValue) { if (nativeValue instanceof OpaqueError) { return ToDafny.Error((OpaqueError) nativeValue); } + if (nativeValue instanceof OpaqueWithTextError) { + return ToDafny.Error((OpaqueWithTextError) nativeValue); + } if (nativeValue instanceof CollectionOfErrors) { return ToDafny.Error((CollectionOfErrors) nativeValue); } @@ -51,6 +55,13 @@ public static Error Error(OpaqueError nativeValue) { return Error.create_Opaque(nativeValue.obj()); } + public static Error Error(OpaqueWithTextError nativeValue) { + return Error.create_OpaqueWithText( + nativeValue.obj(), + dafny.DafnySequence.asString(nativeValue.objMessage()) + ); + } + public static Error Error(CollectionOfErrors nativeValue) { DafnySequence list = software.amazon.smithy.dafny.conversion.ToDafny.Aggregate.GenericToSequence( diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToNative.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToNative.java index 327af1b36..81cd57639 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToNative.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToNative.java @@ -8,6 +8,7 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_CollectionOfErrors; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_DynamoDbItemEncryptorException; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_Opaque; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_OpaqueWithText; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.IDynamoDbItemEncryptorClient; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.CollectionOfErrors; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemInput; @@ -17,6 +18,7 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemInput; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.OpaqueError; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.OpaqueWithTextError; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.ParsedHeader; public class ToNative { @@ -27,6 +29,17 @@ public static OpaqueError Error(Error_Opaque dafnyValue) { return nativeBuilder.build(); } + public static OpaqueWithTextError Error(Error_OpaqueWithText dafnyValue) { + OpaqueWithTextError.Builder nativeBuilder = OpaqueWithTextError.builder(); + nativeBuilder.obj(dafnyValue.dtor_obj()); + nativeBuilder.objMessage( + software.amazon.smithy.dafny.conversion.ToNative.Simple.String( + dafnyValue.dtor_objMessage() + ) + ); + return nativeBuilder.build(); + } + public static CollectionOfErrors Error(Error_CollectionOfErrors dafnyValue) { CollectionOfErrors.Builder nativeBuilder = CollectionOfErrors.builder(); nativeBuilder.list( @@ -63,6 +76,9 @@ public static RuntimeException Error(Error dafnyValue) { if (dafnyValue.is_Opaque()) { return ToNative.Error((Error_Opaque) dafnyValue); } + if (dafnyValue.is_OpaqueWithText()) { + return ToNative.Error((Error_OpaqueWithText) dafnyValue); + } if (dafnyValue.is_CollectionOfErrors()) { return ToNative.Error((Error_CollectionOfErrors) dafnyValue); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/OpaqueWithTextError.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/OpaqueWithTextError.java new file mode 100644 index 000000000..c718df045 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/OpaqueWithTextError.java @@ -0,0 +1,180 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// Do not modify this file. This file is machine generated, and any changes to it will be overwritten. +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model; + +public class OpaqueWithTextError extends RuntimeException { + + /** + * The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + private final Object obj; + + /** + * The text equivalent of obj. + */ + private final String objMessage; + + protected OpaqueWithTextError(BuilderImpl builder) { + super(messageFromBuilder(builder), builder.cause()); + this.obj = builder.obj(); + this.objMessage = builder.objMessage(); + } + + private static String messageFromBuilder(Builder builder) { + if (builder.message() != null) { + return builder.message(); + } + if (builder.cause() != null) { + return builder.cause().getMessage(); + } + return null; + } + + /** + * See {@link Throwable#getMessage()}. + */ + public String message() { + return this.getMessage(); + } + + /** + * See {@link Throwable#getCause()}. + */ + public Throwable cause() { + return this.getCause(); + } + + /** + * @return The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + public Object obj() { + return this.obj; + } + + /** + * @return The text equivalent of obj. + */ + public String objMessage() { + return this.objMessage; + } + + public Builder toBuilder() { + return new BuilderImpl(this); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public interface Builder { + /** + * @param message The detailed message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + */ + Builder message(String message); + + /** + * @return The detailed message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + */ + String message(); + + /** + * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + Builder cause(Throwable cause); + + /** + * @return The cause (which is saved for later retrieval by the {@link #getCause()} method). (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + Throwable cause(); + + /** + * @param obj The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + Builder obj(Object obj); + + /** + * @return The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + Object obj(); + + /** + * @param objMessage The text equivalent of obj. + */ + Builder objMessage(String objMessage); + + /** + * @return The text equivalent of obj. + */ + String objMessage(); + + OpaqueWithTextError build(); + } + + static class BuilderImpl implements Builder { + + protected String message; + + protected Throwable cause; + + protected Object obj; + + protected String objMessage; + + protected BuilderImpl() {} + + protected BuilderImpl(OpaqueWithTextError model) { + this.cause = model.getCause(); + this.message = model.getMessage(); + this.obj = model.obj(); + this.objMessage = model.objMessage(); + } + + public Builder message(String message) { + this.message = message; + return this; + } + + public String message() { + return this.message; + } + + public Builder cause(Throwable cause) { + this.cause = cause; + return this; + } + + public Throwable cause() { + return this.cause; + } + + public Builder obj(Object obj) { + this.obj = obj; + return this; + } + + public Object obj() { + return this.obj; + } + + public Builder objMessage(String objMessage) { + this.objMessage = objMessage; + return this; + } + + public String objMessage() { + return this.objMessage; + } + + public OpaqueWithTextError build() { + if ( + this.obj != null && this.cause == null && this.obj instanceof Throwable + ) { + this.cause = (Throwable) this.obj; + } else if (this.obj == null && this.cause != null) { + this.obj = this.cause; + } + return new OpaqueWithTextError(this); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/OpaqueWithTextError.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/OpaqueWithTextError.java new file mode 100644 index 000000000..abd8ef29f --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/OpaqueWithTextError.java @@ -0,0 +1,180 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// Do not modify this file. This file is machine generated, and any changes to it will be overwritten. +package software.amazon.cryptography.dbencryptionsdk.dynamodb.model; + +public class OpaqueWithTextError extends RuntimeException { + + /** + * The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + private final Object obj; + + /** + * The text equivalent of obj. + */ + private final String objMessage; + + protected OpaqueWithTextError(BuilderImpl builder) { + super(messageFromBuilder(builder), builder.cause()); + this.obj = builder.obj(); + this.objMessage = builder.objMessage(); + } + + private static String messageFromBuilder(Builder builder) { + if (builder.message() != null) { + return builder.message(); + } + if (builder.cause() != null) { + return builder.cause().getMessage(); + } + return null; + } + + /** + * See {@link Throwable#getMessage()}. + */ + public String message() { + return this.getMessage(); + } + + /** + * See {@link Throwable#getCause()}. + */ + public Throwable cause() { + return this.getCause(); + } + + /** + * @return The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + public Object obj() { + return this.obj; + } + + /** + * @return The text equivalent of obj. + */ + public String objMessage() { + return this.objMessage; + } + + public Builder toBuilder() { + return new BuilderImpl(this); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public interface Builder { + /** + * @param message The detailed message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + */ + Builder message(String message); + + /** + * @return The detailed message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + */ + String message(); + + /** + * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + Builder cause(Throwable cause); + + /** + * @return The cause (which is saved for later retrieval by the {@link #getCause()} method). (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + Throwable cause(); + + /** + * @param obj The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + Builder obj(Object obj); + + /** + * @return The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + Object obj(); + + /** + * @param objMessage The text equivalent of obj. + */ + Builder objMessage(String objMessage); + + /** + * @return The text equivalent of obj. + */ + String objMessage(); + + OpaqueWithTextError build(); + } + + static class BuilderImpl implements Builder { + + protected String message; + + protected Throwable cause; + + protected Object obj; + + protected String objMessage; + + protected BuilderImpl() {} + + protected BuilderImpl(OpaqueWithTextError model) { + this.cause = model.getCause(); + this.message = model.getMessage(); + this.obj = model.obj(); + this.objMessage = model.objMessage(); + } + + public Builder message(String message) { + this.message = message; + return this; + } + + public String message() { + return this.message; + } + + public Builder cause(Throwable cause) { + this.cause = cause; + return this; + } + + public Throwable cause() { + return this.cause; + } + + public Builder obj(Object obj) { + this.obj = obj; + return this; + } + + public Object obj() { + return this.obj; + } + + public Builder objMessage(String objMessage) { + this.objMessage = objMessage; + return this; + } + + public String objMessage() { + return this.objMessage; + } + + public OpaqueWithTextError build() { + if ( + this.obj != null && this.cause == null && this.obj instanceof Throwable + ) { + this.cause = (Throwable) this.obj; + } else if (this.obj == null && this.cause != null) { + this.obj = this.cause; + } + return new OpaqueWithTextError(this); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/ToDafny.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/ToDafny.java index b1d3deba6..80eac1c70 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/ToDafny.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/ToDafny.java @@ -73,6 +73,7 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.CollectionOfErrors; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.DynamoDbEncryptionTransformsException; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.OpaqueError; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.OpaqueWithTextError; import software.amazon.cryptography.services.dynamodb.internaldafny.types.AttributeValue; import software.amazon.cryptography.services.dynamodb.internaldafny.types.BatchExecuteStatementInput; import software.amazon.cryptography.services.dynamodb.internaldafny.types.BatchExecuteStatementOutput; @@ -110,6 +111,9 @@ public static Error Error(RuntimeException nativeValue) { if (nativeValue instanceof OpaqueError) { return ToDafny.Error((OpaqueError) nativeValue); } + if (nativeValue instanceof OpaqueWithTextError) { + return ToDafny.Error((OpaqueWithTextError) nativeValue); + } if (nativeValue instanceof CollectionOfErrors) { return ToDafny.Error((CollectionOfErrors) nativeValue); } @@ -120,6 +124,13 @@ public static Error Error(OpaqueError nativeValue) { return Error.create_Opaque(nativeValue.obj()); } + public static Error Error(OpaqueWithTextError nativeValue) { + return Error.create_OpaqueWithText( + nativeValue.obj(), + dafny.DafnySequence.asString(nativeValue.objMessage()) + ); + } + public static Error Error(CollectionOfErrors nativeValue) { DafnySequence list = software.amazon.smithy.dafny.conversion.ToDafny.Aggregate.GenericToSequence( diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/ToNative.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/ToNative.java index 0d8cb2a2e..c63a6cff2 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/ToNative.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/ToNative.java @@ -13,6 +13,7 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_CollectionOfErrors; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_DynamoDbEncryptionTransformsException; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_Opaque; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_OpaqueWithText; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.IDynamoDbEncryptionTransformsClient; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.BatchExecuteStatementInputTransformInput; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.BatchExecuteStatementInputTransformOutput; @@ -45,6 +46,7 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.GetItemOutputTransformInput; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.GetItemOutputTransformOutput; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.OpaqueError; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.OpaqueWithTextError; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.PutItemInputTransformInput; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.PutItemInputTransformOutput; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model.PutItemOutputTransformInput; @@ -80,6 +82,17 @@ public static OpaqueError Error(Error_Opaque dafnyValue) { return nativeBuilder.build(); } + public static OpaqueWithTextError Error(Error_OpaqueWithText dafnyValue) { + OpaqueWithTextError.Builder nativeBuilder = OpaqueWithTextError.builder(); + nativeBuilder.obj(dafnyValue.dtor_obj()); + nativeBuilder.objMessage( + software.amazon.smithy.dafny.conversion.ToNative.Simple.String( + dafnyValue.dtor_objMessage() + ) + ); + return nativeBuilder.build(); + } + public static CollectionOfErrors Error(Error_CollectionOfErrors dafnyValue) { CollectionOfErrors.Builder nativeBuilder = CollectionOfErrors.builder(); nativeBuilder.list( @@ -118,6 +131,9 @@ public static RuntimeException Error(Error dafnyValue) { if (dafnyValue.is_Opaque()) { return ToNative.Error((Error_Opaque) dafnyValue); } + if (dafnyValue.is_OpaqueWithText()) { + return ToNative.Error((Error_OpaqueWithText) dafnyValue); + } if (dafnyValue.is_CollectionOfErrors()) { return ToNative.Error((Error_CollectionOfErrors) dafnyValue); } @@ -141,6 +157,11 @@ public static RuntimeException Error(Error dafnyValue) { dafnyValue.dtor_AwsCryptographyDbEncryptionSdkStructuredEncryption() ); } + if (dafnyValue.is_AwsCryptographyMaterialProviders()) { + return software.amazon.cryptography.materialproviders.ToNative.Error( + dafnyValue.dtor_AwsCryptographyMaterialProviders() + ); + } OpaqueError.Builder nativeBuilder = OpaqueError.builder(); nativeBuilder.obj(dafnyValue); return nativeBuilder.build(); diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/OpaqueWithTextError.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/OpaqueWithTextError.java new file mode 100644 index 000000000..edf83c2c1 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/OpaqueWithTextError.java @@ -0,0 +1,180 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// Do not modify this file. This file is machine generated, and any changes to it will be overwritten. +package software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model; + +public class OpaqueWithTextError extends RuntimeException { + + /** + * The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + private final Object obj; + + /** + * The text equivalent of obj. + */ + private final String objMessage; + + protected OpaqueWithTextError(BuilderImpl builder) { + super(messageFromBuilder(builder), builder.cause()); + this.obj = builder.obj(); + this.objMessage = builder.objMessage(); + } + + private static String messageFromBuilder(Builder builder) { + if (builder.message() != null) { + return builder.message(); + } + if (builder.cause() != null) { + return builder.cause().getMessage(); + } + return null; + } + + /** + * See {@link Throwable#getMessage()}. + */ + public String message() { + return this.getMessage(); + } + + /** + * See {@link Throwable#getCause()}. + */ + public Throwable cause() { + return this.getCause(); + } + + /** + * @return The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + public Object obj() { + return this.obj; + } + + /** + * @return The text equivalent of obj. + */ + public String objMessage() { + return this.objMessage; + } + + public Builder toBuilder() { + return new BuilderImpl(this); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public interface Builder { + /** + * @param message The detailed message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + */ + Builder message(String message); + + /** + * @return The detailed message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + */ + String message(); + + /** + * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + Builder cause(Throwable cause); + + /** + * @return The cause (which is saved for later retrieval by the {@link #getCause()} method). (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + Throwable cause(); + + /** + * @param obj The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + Builder obj(Object obj); + + /** + * @return The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + Object obj(); + + /** + * @param objMessage The text equivalent of obj. + */ + Builder objMessage(String objMessage); + + /** + * @return The text equivalent of obj. + */ + String objMessage(); + + OpaqueWithTextError build(); + } + + static class BuilderImpl implements Builder { + + protected String message; + + protected Throwable cause; + + protected Object obj; + + protected String objMessage; + + protected BuilderImpl() {} + + protected BuilderImpl(OpaqueWithTextError model) { + this.cause = model.getCause(); + this.message = model.getMessage(); + this.obj = model.obj(); + this.objMessage = model.objMessage(); + } + + public Builder message(String message) { + this.message = message; + return this; + } + + public String message() { + return this.message; + } + + public Builder cause(Throwable cause) { + this.cause = cause; + return this; + } + + public Throwable cause() { + return this.cause; + } + + public Builder obj(Object obj) { + this.obj = obj; + return this; + } + + public Object obj() { + return this.obj; + } + + public Builder objMessage(String objMessage) { + this.objMessage = objMessage; + return this; + } + + public String objMessage() { + return this.objMessage; + } + + public OpaqueWithTextError build() { + if ( + this.obj != null && this.cause == null && this.obj instanceof Throwable + ) { + this.cause = (Throwable) this.obj; + } else if (this.obj == null && this.cause != null) { + this.obj = this.cause; + } + return new OpaqueWithTextError(this); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToDafny.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToDafny.java index d3d49d587..72fb973fa 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToDafny.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToDafny.java @@ -39,6 +39,7 @@ import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.StructuredEncryptionConfig; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CollectionOfErrors; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.OpaqueError; +import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.OpaqueWithTextError; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.StructuredEncryptionException; import software.amazon.cryptography.materialproviders.internaldafny.types.DBEAlgorithmSuiteId; import software.amazon.cryptography.materialproviders.internaldafny.types.EncryptedDataKey; @@ -53,6 +54,9 @@ public static Error Error(RuntimeException nativeValue) { if (nativeValue instanceof OpaqueError) { return ToDafny.Error((OpaqueError) nativeValue); } + if (nativeValue instanceof OpaqueWithTextError) { + return ToDafny.Error((OpaqueWithTextError) nativeValue); + } if (nativeValue instanceof CollectionOfErrors) { return ToDafny.Error((CollectionOfErrors) nativeValue); } @@ -63,6 +67,13 @@ public static Error Error(OpaqueError nativeValue) { return Error.create_Opaque(nativeValue.obj()); } + public static Error Error(OpaqueWithTextError nativeValue) { + return Error.create_OpaqueWithText( + nativeValue.obj(), + dafny.DafnySequence.asString(nativeValue.objMessage()) + ); + } + public static Error Error(CollectionOfErrors nativeValue) { DafnySequence list = software.amazon.smithy.dafny.conversion.ToDafny.Aggregate.GenericToSequence( diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToNative.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToNative.java index fc2900e00..ed67afa56 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToNative.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToNative.java @@ -14,6 +14,7 @@ import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.Error; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.Error_CollectionOfErrors; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.Error_Opaque; +import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.Error_OpaqueWithText; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.Error_StructuredEncryptionException; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.IStructuredEncryptionClient; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.AuthItem; @@ -30,6 +31,7 @@ import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.EncryptStructureInput; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.EncryptStructureOutput; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.OpaqueError; +import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.OpaqueWithTextError; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.ParsedHeader; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.PathSegment; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.ResolveAuthActionsInput; @@ -47,6 +49,17 @@ public static OpaqueError Error(Error_Opaque dafnyValue) { return nativeBuilder.build(); } + public static OpaqueWithTextError Error(Error_OpaqueWithText dafnyValue) { + OpaqueWithTextError.Builder nativeBuilder = OpaqueWithTextError.builder(); + nativeBuilder.obj(dafnyValue.dtor_obj()); + nativeBuilder.objMessage( + software.amazon.smithy.dafny.conversion.ToNative.Simple.String( + dafnyValue.dtor_objMessage() + ) + ); + return nativeBuilder.build(); + } + public static CollectionOfErrors Error(Error_CollectionOfErrors dafnyValue) { CollectionOfErrors.Builder nativeBuilder = CollectionOfErrors.builder(); nativeBuilder.list( @@ -83,6 +96,9 @@ public static RuntimeException Error(Error dafnyValue) { if (dafnyValue.is_Opaque()) { return ToNative.Error((Error_Opaque) dafnyValue); } + if (dafnyValue.is_OpaqueWithText()) { + return ToNative.Error((Error_OpaqueWithText) dafnyValue); + } if (dafnyValue.is_CollectionOfErrors()) { return ToNative.Error((Error_CollectionOfErrors) dafnyValue); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/OpaqueWithTextError.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/OpaqueWithTextError.java new file mode 100644 index 000000000..5ec2b44c9 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/OpaqueWithTextError.java @@ -0,0 +1,180 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// Do not modify this file. This file is machine generated, and any changes to it will be overwritten. +package software.amazon.cryptography.dbencryptionsdk.structuredencryption.model; + +public class OpaqueWithTextError extends RuntimeException { + + /** + * The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + private final Object obj; + + /** + * The text equivalent of obj. + */ + private final String objMessage; + + protected OpaqueWithTextError(BuilderImpl builder) { + super(messageFromBuilder(builder), builder.cause()); + this.obj = builder.obj(); + this.objMessage = builder.objMessage(); + } + + private static String messageFromBuilder(Builder builder) { + if (builder.message() != null) { + return builder.message(); + } + if (builder.cause() != null) { + return builder.cause().getMessage(); + } + return null; + } + + /** + * See {@link Throwable#getMessage()}. + */ + public String message() { + return this.getMessage(); + } + + /** + * See {@link Throwable#getCause()}. + */ + public Throwable cause() { + return this.getCause(); + } + + /** + * @return The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + public Object obj() { + return this.obj; + } + + /** + * @return The text equivalent of obj. + */ + public String objMessage() { + return this.objMessage; + } + + public Builder toBuilder() { + return new BuilderImpl(this); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public interface Builder { + /** + * @param message The detailed message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + */ + Builder message(String message); + + /** + * @return The detailed message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + */ + String message(); + + /** + * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + Builder cause(Throwable cause); + + /** + * @return The cause (which is saved for later retrieval by the {@link #getCause()} method). (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + Throwable cause(); + + /** + * @param obj The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + Builder obj(Object obj); + + /** + * @return The unexpected object encountered. It MIGHT BE an Exception, but that is not guaranteed. + */ + Object obj(); + + /** + * @param objMessage The text equivalent of obj. + */ + Builder objMessage(String objMessage); + + /** + * @return The text equivalent of obj. + */ + String objMessage(); + + OpaqueWithTextError build(); + } + + static class BuilderImpl implements Builder { + + protected String message; + + protected Throwable cause; + + protected Object obj; + + protected String objMessage; + + protected BuilderImpl() {} + + protected BuilderImpl(OpaqueWithTextError model) { + this.cause = model.getCause(); + this.message = model.getMessage(); + this.obj = model.obj(); + this.objMessage = model.objMessage(); + } + + public Builder message(String message) { + this.message = message; + return this; + } + + public String message() { + return this.message; + } + + public Builder cause(Throwable cause) { + this.cause = cause; + return this; + } + + public Throwable cause() { + return this.cause; + } + + public Builder obj(Object obj) { + this.obj = obj; + return this; + } + + public Object obj() { + return this.obj; + } + + public Builder objMessage(String objMessage) { + this.objMessage = objMessage; + return this; + } + + public String objMessage() { + return this.objMessage; + } + + public OpaqueWithTextError build() { + if ( + this.obj != null && this.cause == null && this.obj instanceof Throwable + ) { + this.cause = (Throwable) this.obj; + } else if (this.obj == null && this.cause != null) { + this.obj = this.cause; + } + return new OpaqueWithTextError(this); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/DynamoDbEncryptionInterceptorIntegrationTests.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/DynamoDbEncryptionInterceptorIntegrationTests.java index e72db1862..e002b2452 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/DynamoDbEncryptionInterceptorIntegrationTests.java +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/DynamoDbEncryptionInterceptorIntegrationTests.java @@ -945,7 +945,7 @@ public void TestFailsNonSyncClient() throws Throwable { @Test( expectedExceptions = CollectionOfErrors.class, - expectedExceptionsMessageRegExp = "Raw AES Keyring was unable to decrypt any encrypted data key. The list of encountered Exceptions is avaible via `list`." + expectedExceptionsMessageRegExp = "Raw AES Keyring was unable to decrypt any encrypted data key. The list of encountered Exceptions is avaible via `list`.*" ) public void TestOnDecryptKeyringFailure() throws Throwable { String partitionValue = "expectedOnDecryptKeyringFailure"; diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/OpaqueWithTextError.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/OpaqueWithTextError.cs new file mode 100644 index 000000000..2ff38d8d8 --- /dev/null +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/OpaqueWithTextError.cs @@ -0,0 +1,17 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// Do not modify this file. This file is machine generated, and any changes to it will be overwritten. +using System; +using AWS.Cryptography.DbEncryptionSDK.DynamoDb; +namespace AWS.Cryptography.DbEncryptionSDK.DynamoDb +{ + public class OpaqueWithTextError : Exception + { + public readonly object obj; + public readonly string objMessage; + public OpaqueWithTextError(Exception ex) : base("OpaqueError:", ex) { this.obj = ex; this.objMessage = obj.ToString(); } + public OpaqueWithTextError() : base("Unknown Unexpected Error") { } + public OpaqueWithTextError(object obj, string objMessage) : base(obj is Exception ? "OpaqueWithTextError:" : "Opaque obj is not an Exception.", obj as Exception) { this.obj = obj; this.objMessage = objMessage; } + } + +} diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/TypeConversion.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/TypeConversion.cs index 0044c6b7c..b0542873a 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/TypeConversion.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/TypeConversion.cs @@ -1333,13 +1333,15 @@ public static AWS.Cryptography.MaterialProviders.StormTrackingCache FromDafny_N3 converted.GraceInterval = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_graceInterval(concrete._graceInterval); converted.FanOut = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M6_fanOut(concrete._fanOut); converted.InFlightTTL = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_inFlightTTL(concrete._inFlightTTL); - converted.SleepMilli = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(concrete._sleepMilli); return converted; + converted.SleepMilli = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(concrete._sleepMilli); + if (concrete._timeUnits.is_Some) converted.TimeUnits = (AWS.Cryptography.MaterialProviders.TimeUnits)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(concrete._timeUnits); return converted; } public static software.amazon.cryptography.materialproviders.internaldafny.types._IStormTrackingCache ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache(AWS.Cryptography.MaterialProviders.StormTrackingCache value) { value.Validate(); int? var_entryPruningTailSize = value.IsSetEntryPruningTailSize() ? value.EntryPruningTailSize : (int?)null; - return new software.amazon.cryptography.materialproviders.internaldafny.types.StormTrackingCache(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_entryCapacity(value.EntryCapacity), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M20_entryPruningTailSize(var_entryPruningTailSize), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_gracePeriod(value.GracePeriod), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_graceInterval(value.GraceInterval), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M6_fanOut(value.FanOut), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_inFlightTTL(value.InFlightTTL), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(value.SleepMilli)); + AWS.Cryptography.MaterialProviders.TimeUnits var_timeUnits = value.IsSetTimeUnits() ? value.TimeUnits : (AWS.Cryptography.MaterialProviders.TimeUnits)null; + return new software.amazon.cryptography.materialproviders.internaldafny.types.StormTrackingCache(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_entryCapacity(value.EntryCapacity), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M20_entryPruningTailSize(var_entryPruningTailSize), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_gracePeriod(value.GracePeriod), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_graceInterval(value.GraceInterval), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M6_fanOut(value.FanOut), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_inFlightTTL(value.InFlightTTL), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(value.SleepMilli), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(var_timeUnits)); } public static AWS.Cryptography.MaterialProviders.ICryptographicMaterialsCache FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S36_CryptographicMaterialsCacheReference(software.amazon.cryptography.materialproviders.internaldafny.types.ICryptographicMaterialsCache value) { @@ -1536,6 +1538,14 @@ public static int ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_S { return ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S14_CountingNumber(value); } + public static AWS.Cryptography.MaterialProviders.TimeUnits FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(Wrappers_Compile._IOption value) + { + return value.is_None ? (AWS.Cryptography.MaterialProviders.TimeUnits)null : FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits(value.Extract()); + } + public static Wrappers_Compile._IOption ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(AWS.Cryptography.MaterialProviders.TimeUnits value) + { + return value == null ? Wrappers_Compile.Option.create_None() : Wrappers_Compile.Option.create_Some(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits((AWS.Cryptography.MaterialProviders.TimeUnits)value)); + } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S23_StringSetAttributeValue__M6_member(Dafny.ISequence value) { return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S20_StringAttributeValue(value); @@ -1592,6 +1602,18 @@ public static int ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S14_C { return value; } + public static AWS.Cryptography.MaterialProviders.TimeUnits FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits(software.amazon.cryptography.materialproviders.internaldafny.types._ITimeUnits value) + { + if (value.is_Seconds) return AWS.Cryptography.MaterialProviders.TimeUnits.Seconds; + if (value.is_Milliseconds) return AWS.Cryptography.MaterialProviders.TimeUnits.Milliseconds; + throw new System.ArgumentException("Invalid AWS.Cryptography.MaterialProviders.TimeUnits value"); + } + public static software.amazon.cryptography.materialproviders.internaldafny.types._ITimeUnits ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits(AWS.Cryptography.MaterialProviders.TimeUnits value) + { + if (AWS.Cryptography.MaterialProviders.TimeUnits.Seconds.Equals(value)) return software.amazon.cryptography.materialproviders.internaldafny.types.TimeUnits.create_Seconds(); + if (AWS.Cryptography.MaterialProviders.TimeUnits.Milliseconds.Equals(value)) return software.amazon.cryptography.materialproviders.internaldafny.types.TimeUnits.create_Milliseconds(); + throw new System.ArgumentException("Invalid AWS.Cryptography.MaterialProviders.TimeUnits value"); + } public static System.Exception FromDafny_CommonError(software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types._IError value) { switch (value) @@ -1600,6 +1622,10 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyDbEncryptionSdkStructuredEncryption ); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_AwsCryptographyKeyStore dafnyVal: + return AWS.Cryptography.KeyStore.TypeConversion.FromDafny_CommonError( + dafnyVal._AwsCryptographyKeyStore + ); case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_AwsCryptographyMaterialProviders dafnyVal: return AWS.Cryptography.MaterialProviders.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyMaterialProviders @@ -1621,6 +1647,8 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph new string(dafnyVal.dtor_message.Elements)); case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_Opaque dafnyVal: return new OpaqueError(dafnyVal._obj); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_OpaqueWithText dafnyVal: + return new OpaqueWithTextError(dafnyVal._obj, dafnyVal._obj.ToString()); default: // The switch MUST be complete for _IError, so `value` MUST NOT be an _IError. (How did you get here?) return new OpaqueError(); diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/OpaqueWithTextError.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/OpaqueWithTextError.cs new file mode 100644 index 000000000..f3e977f62 --- /dev/null +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/OpaqueWithTextError.cs @@ -0,0 +1,17 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// Do not modify this file. This file is machine generated, and any changes to it will be overwritten. +using System; +using AWS.Cryptography.DbEncryptionSDK.DynamoDb.Transforms; +namespace AWS.Cryptography.DbEncryptionSDK.DynamoDb.Transforms +{ + public class OpaqueWithTextError : Exception + { + public readonly object obj; + public readonly string objMessage; + public OpaqueWithTextError(Exception ex) : base("OpaqueError:", ex) { this.obj = ex; this.objMessage = obj.ToString(); } + public OpaqueWithTextError() : base("Unknown Unexpected Error") { } + public OpaqueWithTextError(object obj, string objMessage) : base(obj is Exception ? "OpaqueWithTextError:" : "Opaque obj is not an Exception.", obj as Exception) { this.obj = obj; this.objMessage = objMessage; } + } + +} diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/TypeConversion.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/TypeConversion.cs index 69852084b..95841aee6 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/TypeConversion.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/TypeConversion.cs @@ -1811,11 +1811,11 @@ public static Amazon.DynamoDBv2.ReturnItemCollectionMetrics FromDafny_N3_com__N9 } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S15_DeleteItemInput__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S15_DeleteItemInput__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static System.Collections.Generic.Dictionary FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S15_DeleteItemInput__M3_Key(Dafny.IMap, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue> value) { @@ -2035,11 +2035,11 @@ public static Amazon.DynamoDBv2.ReturnConsumedCapacity FromDafny_N3_com__N9_amaz } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S12_GetItemInput__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S12_GetItemInput__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static System.Collections.Generic.Dictionary FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S12_GetItemInput__M3_Key(Dafny.IMap, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue> value) { @@ -2107,11 +2107,11 @@ public static Amazon.DynamoDBv2.Model.ConsumedCapacity FromDafny_N3_com__N9_amaz } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S12_PutItemInput__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S12_PutItemInput__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static System.Collections.Generic.Dictionary FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S12_PutItemInput__M4_Item(Dafny.IMap, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue> value) { @@ -2211,11 +2211,11 @@ public static Amazon.DynamoDBv2.Model.ItemCollectionMetrics FromDafny_N3_com__N9 } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S10_QueryInput__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S10_QueryInput__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S10_QueryInput__M9_IndexName(Wrappers_Compile._IOption> value) { @@ -2419,11 +2419,11 @@ public static Dafny.ISequence ToDafny_N3_aws__N12_cryptography__N15_dbEncr } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_ScanInput__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_ScanInput__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_ScanInput__M9_IndexName(Wrappers_Compile._IOption> value) { @@ -2667,11 +2667,11 @@ public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S23_TransactWr } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S15_UpdateItemInput__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S15_UpdateItemInput__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static System.Collections.Generic.Dictionary FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S15_UpdateItemInput__M3_Key(Dafny.IMap, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue> value) { @@ -2904,6 +2904,14 @@ public static software.amazon.cryptography.services.dynamodb.internaldafny.types new Dafny.Pair, Dafny.ISequence>(ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S29_ItemCollectionMetricsPerTable__M3_key(pair.Key), ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S29_ItemCollectionMetricsPerTable__M5_value(pair.Value)) )); } + public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(Dafny.ISequence value) + { + return new string(value.Elements); + } + public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(string value) + { + return Dafny.Sequence.FromString(value); + } public static System.Collections.Generic.Dictionary FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key(Dafny.IMap, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue> value) { return value.ItemEnumerable.ToDictionary(pair => FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M3_key(pair.Car), pair => FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M5_value(pair.Cdr)); @@ -3461,11 +3469,11 @@ public static software.amazon.cryptography.services.dynamodb.internaldafny.types } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S18_BatchGetRequestMap__M3_key(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S18_BatchGetRequestMap__M3_key(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Amazon.DynamoDBv2.Model.KeysAndAttributes FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S18_BatchGetRequestMap__M5_value(software.amazon.cryptography.services.dynamodb.internaldafny.types._IKeysAndAttributes value) { @@ -3477,11 +3485,11 @@ public static software.amazon.cryptography.services.dynamodb.internaldafny.types } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S19_BatchGetResponseMap__M3_key(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S19_BatchGetResponseMap__M3_key(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static System.Collections.Generic.List> FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S19_BatchGetResponseMap__M5_value(Dafny.ISequence, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue>> value) { @@ -3493,11 +3501,11 @@ public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S24_BatchWriteItemRequestMap__M3_key(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S24_BatchWriteItemRequestMap__M3_key(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static System.Collections.Generic.List FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S24_BatchWriteItemRequestMap__M5_value(Dafny.ISequence value) { @@ -3509,11 +3517,11 @@ public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S29_ItemCollectionMetricsPerTable__M3_key(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S29_ItemCollectionMetricsPerTable__M3_key(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static System.Collections.Generic.List FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S29_ItemCollectionMetricsPerTable__M5_value(Dafny.ISequence value) { @@ -3589,11 +3597,11 @@ public static software.amazon.cryptography.services.dynamodb.internaldafny.types } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S16_ConsumedCapacity__M9_TableName(Wrappers_Compile._IOption> value) { - return value.is_None ? (string)null : FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value.Extract()); + return value.is_None ? (string)null : FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value.Extract()); } public static Wrappers_Compile._IOption> ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S16_ConsumedCapacity__M9_TableName(string value) { - return value == null ? Wrappers_Compile.Option>.create_None() : Wrappers_Compile.Option>.create_Some(ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName((string)value)); + return value == null ? Wrappers_Compile.Option>.create_None() : Wrappers_Compile.Option>.create_Some(ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn((string)value)); } public static double? FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S16_ConsumedCapacity__M13_CapacityUnits(Wrappers_Compile._IOption> value) { @@ -4956,11 +4964,11 @@ public static software.amazon.cryptography.services.dynamodb.internaldafny.types } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Get__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Get__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Get__M20_ProjectionExpression(Wrappers_Compile._IOption> value) { @@ -4988,11 +4996,11 @@ public static System.Collections.Generic.Dictionary FromDafny_N3 } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S14_ConditionCheck__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S14_ConditionCheck__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S14_ConditionCheck__M19_ConditionExpression(Dafny.ISequence value) { @@ -5036,11 +5044,11 @@ public static Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure FromDafny_N3 } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Put__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Put__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Put__M19_ConditionExpression(Wrappers_Compile._IOption> value) { @@ -5084,11 +5092,11 @@ public static Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure FromDafny_N3 } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S6_Delete__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S6_Delete__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S6_Delete__M19_ConditionExpression(Wrappers_Compile._IOption> value) { @@ -5140,11 +5148,11 @@ public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S6_Update__M9_TableName(Dafny.ISequence value) { - return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S6_Update__M9_TableName(string value) { - return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S9_TableName(value); + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S8_TableArn(value); } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S6_Update__M19_ConditionExpression(Wrappers_Compile._IOption> value) { @@ -6048,13 +6056,15 @@ public static AWS.Cryptography.MaterialProviders.StormTrackingCache FromDafny_N3 converted.GraceInterval = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_graceInterval(concrete._graceInterval); converted.FanOut = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M6_fanOut(concrete._fanOut); converted.InFlightTTL = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_inFlightTTL(concrete._inFlightTTL); - converted.SleepMilli = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(concrete._sleepMilli); return converted; + converted.SleepMilli = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(concrete._sleepMilli); + if (concrete._timeUnits.is_Some) converted.TimeUnits = (AWS.Cryptography.MaterialProviders.TimeUnits)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(concrete._timeUnits); return converted; } public static software.amazon.cryptography.materialproviders.internaldafny.types._IStormTrackingCache ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache(AWS.Cryptography.MaterialProviders.StormTrackingCache value) { value.Validate(); int? var_entryPruningTailSize = value.IsSetEntryPruningTailSize() ? value.EntryPruningTailSize : (int?)null; - return new software.amazon.cryptography.materialproviders.internaldafny.types.StormTrackingCache(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_entryCapacity(value.EntryCapacity), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M20_entryPruningTailSize(var_entryPruningTailSize), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_gracePeriod(value.GracePeriod), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_graceInterval(value.GraceInterval), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M6_fanOut(value.FanOut), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_inFlightTTL(value.InFlightTTL), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(value.SleepMilli)); + AWS.Cryptography.MaterialProviders.TimeUnits var_timeUnits = value.IsSetTimeUnits() ? value.TimeUnits : (AWS.Cryptography.MaterialProviders.TimeUnits)null; + return new software.amazon.cryptography.materialproviders.internaldafny.types.StormTrackingCache(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_entryCapacity(value.EntryCapacity), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M20_entryPruningTailSize(var_entryPruningTailSize), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_gracePeriod(value.GracePeriod), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_graceInterval(value.GraceInterval), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M6_fanOut(value.FanOut), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_inFlightTTL(value.InFlightTTL), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(value.SleepMilli), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(var_timeUnits)); } public static AWS.Cryptography.MaterialProviders.ICryptographicMaterialsCache FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S36_CryptographicMaterialsCacheReference(software.amazon.cryptography.materialproviders.internaldafny.types.ICryptographicMaterialsCache value) { @@ -6225,6 +6235,14 @@ public static int ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_S { return ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S14_CountingNumber(value); } + public static AWS.Cryptography.MaterialProviders.TimeUnits FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(Wrappers_Compile._IOption value) + { + return value.is_None ? (AWS.Cryptography.MaterialProviders.TimeUnits)null : FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits(value.Extract()); + } + public static Wrappers_Compile._IOption ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(AWS.Cryptography.MaterialProviders.TimeUnits value) + { + return value == null ? Wrappers_Compile.Option.create_None() : Wrappers_Compile.Option.create_Some(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits((AWS.Cryptography.MaterialProviders.TimeUnits)value)); + } public static string FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__S6_Shared__M5_other(Dafny.ISequence value) { return FromDafny_N6_smithy__N3_api__S6_String(value); @@ -6273,6 +6291,18 @@ public static int ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S14_C { return value; } + public static AWS.Cryptography.MaterialProviders.TimeUnits FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits(software.amazon.cryptography.materialproviders.internaldafny.types._ITimeUnits value) + { + if (value.is_Seconds) return AWS.Cryptography.MaterialProviders.TimeUnits.Seconds; + if (value.is_Milliseconds) return AWS.Cryptography.MaterialProviders.TimeUnits.Milliseconds; + throw new System.ArgumentException("Invalid AWS.Cryptography.MaterialProviders.TimeUnits value"); + } + public static software.amazon.cryptography.materialproviders.internaldafny.types._ITimeUnits ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits(AWS.Cryptography.MaterialProviders.TimeUnits value) + { + if (AWS.Cryptography.MaterialProviders.TimeUnits.Seconds.Equals(value)) return software.amazon.cryptography.materialproviders.internaldafny.types.TimeUnits.create_Seconds(); + if (AWS.Cryptography.MaterialProviders.TimeUnits.Milliseconds.Equals(value)) return software.amazon.cryptography.materialproviders.internaldafny.types.TimeUnits.create_Milliseconds(); + throw new System.ArgumentException("Invalid AWS.Cryptography.MaterialProviders.TimeUnits value"); + } public static System.Collections.Generic.List FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__S19_ConstructorPartList(Dafny.ISequence value) { return new System.Collections.Generic.List(value.Elements.Select(FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__S19_ConstructorPartList__M6_member)); @@ -6682,6 +6712,8 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph new string(dafnyVal.dtor_message.Elements)); case software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_Opaque dafnyVal: return new OpaqueError(dafnyVal._obj); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_OpaqueWithText dafnyVal: + return new OpaqueWithTextError(dafnyVal._obj, dafnyVal._obj.ToString()); default: // The switch MUST be complete for _IError, so `value` MUST NOT be an _IError. (How did you get here?) return new OpaqueError(); diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/OpaqueWithTextError.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/OpaqueWithTextError.cs new file mode 100644 index 000000000..9f92f9145 --- /dev/null +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/OpaqueWithTextError.cs @@ -0,0 +1,17 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// Do not modify this file. This file is machine generated, and any changes to it will be overwritten. +using System; +using AWS.Cryptography.DbEncryptionSDK.DynamoDb.ItemEncryptor; +namespace AWS.Cryptography.DbEncryptionSDK.DynamoDb.ItemEncryptor +{ + public class OpaqueWithTextError : Exception + { + public readonly object obj; + public readonly string objMessage; + public OpaqueWithTextError(Exception ex) : base("OpaqueError:", ex) { this.obj = ex; this.objMessage = obj.ToString(); } + public OpaqueWithTextError() : base("Unknown Unexpected Error") { } + public OpaqueWithTextError(object obj, string objMessage) : base(obj is Exception ? "OpaqueWithTextError:" : "Opaque obj is not an Exception.", obj as Exception) { this.obj = obj; this.objMessage = objMessage; } + } + +} diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/TypeConversion.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/TypeConversion.cs index c5b0bedd1..f96612eec 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/TypeConversion.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/TypeConversion.cs @@ -999,6 +999,8 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph new string(dafnyVal.dtor_message.Elements)); case software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_Opaque dafnyVal: return new OpaqueError(dafnyVal._obj); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_OpaqueWithText dafnyVal: + return new OpaqueWithTextError(dafnyVal._obj, dafnyVal._obj.ToString()); default: // The switch MUST be complete for _IError, so `value` MUST NOT be an _IError. (How did you get here?) return new OpaqueError(); diff --git a/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/OpaqueWithTextError.cs b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/OpaqueWithTextError.cs new file mode 100644 index 000000000..d6ed71422 --- /dev/null +++ b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/OpaqueWithTextError.cs @@ -0,0 +1,17 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// Do not modify this file. This file is machine generated, and any changes to it will be overwritten. +using System; +using AWS.Cryptography.DbEncryptionSDK.StructuredEncryption; +namespace AWS.Cryptography.DbEncryptionSDK.StructuredEncryption +{ + public class OpaqueWithTextError : Exception + { + public readonly object obj; + public readonly string objMessage; + public OpaqueWithTextError(Exception ex) : base("OpaqueError:", ex) { this.obj = ex; this.objMessage = obj.ToString(); } + public OpaqueWithTextError() : base("Unknown Unexpected Error") { } + public OpaqueWithTextError(object obj, string objMessage) : base(obj is Exception ? "OpaqueWithTextError:" : "Opaque obj is not an Exception.", obj as Exception) { this.obj = obj; this.objMessage = objMessage; } + } + +} diff --git a/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/TypeConversion.cs b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/TypeConversion.cs index 4e9890cfe..6a802466b 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/TypeConversion.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/TypeConversion.cs @@ -958,6 +958,8 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph new string(dafnyVal.dtor_message.Elements)); case software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.Error_Opaque dafnyVal: return new OpaqueError(dafnyVal._obj); + case software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.Error_OpaqueWithText dafnyVal: + return new OpaqueWithTextError(dafnyVal._obj, dafnyVal._obj.ToString()); default: // The switch MUST be complete for _IError, so `value` MUST NOT be an _IError. (How did you get here?) return new OpaqueError(); diff --git a/DynamoDbEncryption/runtimes/rust/.gitignore b/DynamoDbEncryption/runtimes/rust/.gitignore new file mode 100644 index 000000000..f39a07024 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/.gitignore @@ -0,0 +1,35 @@ +*.pem +Cargo.lock +src/aes_gcm.rs +src/aes_kdf_ctr.rs +src/client +src/client.rs +src/concurrent_call.rs +src/conversions +src/conversions.rs +src/dafny_libraries.rs +src/ddb.rs +src/deps +src/deps.rs +src/digest.rs +src/ecdh.rs +src/ecdsa.rs +src/error +src/error.rs +src/hmac.rs +src/implementation_from_dafny.rs +src/kms.rs +src/local_cmc.rs +src/operation +src/operation.rs +src/random.rs +src/rsa.rs +src/sets.rs +src/standard_library_conversions.rs +src/standard_library_externs.rs +src/storm_tracker.rs +src/time.rs +src/types +src/types.rs +src/uuid.rs +target diff --git a/DynamoDbEncryption/runtimes/rust/Cargo.toml b/DynamoDbEncryption/runtimes/rust/Cargo.toml new file mode 100644 index 000000000..57bdb1f42 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "aws-db-esdk" +version = "0.1.0" +edition = "2021" +rust-version = "1.80.0" +keywords = ["crypto", "cryptography", "security", "dynamodb", "ddb", "encryption", "client-side", "clientside"] +license = "ISC AND (Apache-2.0 OR ISC)" +description = "aws-db-esdk is a library for implementing client side encryption with DynamoDB." +homepage = "https://github.com/aws/aws-database-encryption-sdk-dynamodb/tree/main/releases/rust/db_esdk" +repository = "https://github.com/aws/aws-database-encryption-sdk-dynamodb/tree/main/releases/rust/db_esdk" +authors = ["AWS-CryptoTools"] +documentation = "https://docs.rs/crate/aws-db-esdk" +autoexamples = false +readme = "README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aws-config = "1.5.10" +aws-lc-rs = "1.11.1" +aws-lc-sys = "0.23.1" +aws-sdk-dynamodb = "1.54.0" +aws-sdk-kms = "1.50.0" +aws-smithy-runtime-api = {version = "1.7.3", features = ["client"] } +aws-smithy-types = "1.2.9" +chrono = "0.4.38" +dafny_runtime = { path = "../../../submodules/MaterialProviders/smithy-dafny/TestModels/dafny-dependencies/dafny_runtime_rust"} +dashmap = "6.1.0" +pem = "3.0.4" +tokio = {version = "1.41.1", features = ["full"] } +uuid = { version = "1.11.0", features = ["v4"] } + +[[example]] +name = "main" diff --git a/DynamoDbEncryption/runtimes/rust/README.md b/DynamoDbEncryption/runtimes/rust/README.md new file mode 100644 index 000000000..3244a4cc9 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/README.md @@ -0,0 +1,65 @@ +# AWS Database Encryption SDK for DynamoDB + +AWS Database Encryption SDK for DynamoDB + +## Using the AWS Database Encryption SDK for DynamoDB for Rust + +The AWS Database Encryption SDK for DynamoDB is available on [Crates.io](https://www.crates.io/). + +## Building the AWS Database Encryption SDK for DynamoDB + +To build, the AWS Database Encryption SDK for DynamoDB requires the most up to date version of [Dafny](https://github.com/dafny-lang/dafny) on your PATH. + +You will also need to ensure that you fetch all submodules using either `git clone --recursive ...` when cloning the repository or `git submodule update --init` on an existing clone. + +To setup your project to use the AWS Database Encryption SDK for DynamoDB in Rust, run: + +``` +cd DynamoDbEncryption +# Polymorph smithy to Rust +make polymorph_rust +# Transpile Dafny to Rust +make transpile_rust +# Build Project +cd runtimes/rust +cargo build +``` + +### (Optional) Set up the AWS Database Encryption SDK for DynamoDB to work with AWS KMS + +If you set up the AWS Database Encryption SDK for DynamoDB to use the AWS KMS Keyring, +the AWS Database Encryption SDK for DynamoDB will make calls to AWS KMS on your behalf, +using the appropriate AWS SDK. + +However, you must first set up AWS credentials for use with the AWS SDK. + +## Testing the AWS Database Encryption SDK for DynamoDB for Rust + +### Configure AWS credentials + +To run the test suite you must first set up AWS credentials for use with the AWS SDK. +This is required in order to run the integration tests, which use a KMS Keyring against a publicly accessible KMS CMK. + +### Run the tests + +Run the test suite with: + +``` +cd AwsEncryptionSDK +make test_rust +``` + +Run tests on examples, to ensure they are up to date: + +``` +cd AwsEncryptionSDK/runtimes/rust/ +cargo test --examples +``` + +Please look at the Examples on how to use the Encryption SDK in Rust [here](examples). + +Please note that tests and test vectors require internet access and valid AWS credentials, since calls to KMS are made as part of the test workflow. + +## License + +This library is licensed under the Apache 2.0 License. diff --git a/DynamoDbEncryption/runtimes/rust/copy_externs.sh b/DynamoDbEncryption/runtimes/rust/copy_externs.sh new file mode 100755 index 000000000..177cf6458 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/copy_externs.sh @@ -0,0 +1,25 @@ +#!/bin/bash -eu + +cd $( dirname ${BASH_SOURCE[0]} ) + +SRC=../../../submodules/MaterialProviders/AwsCryptographicMaterialProviders/runtimes/rust/src/ + +cp $SRC/aes_gcm.rs src +cp $SRC/aes_kdf_ctr.rs src +cp $SRC/concurrent_call.rs src +cp $SRC/dafny_libraries.rs src +cp $SRC/ddb.rs src +cp $SRC/digest.rs src +cp $SRC/ecdh.rs src +cp $SRC/ecdsa.rs src +cp $SRC/hmac.rs src +cp $SRC/kms.rs src +cp $SRC/local_cmc.rs src +cp $SRC/random.rs src +cp $SRC/rsa.rs src +cp $SRC/sets.rs src +# software_externs also has the Legacy stuff for Gazelle +# cp $SRC/software_externs.rs src +cp $SRC/storm_tracker.rs src +cp $SRC/time.rs src +cp $SRC/uuid.rs src diff --git a/DynamoDbEncryption/runtimes/rust/examples/README.md b/DynamoDbEncryption/runtimes/rust/examples/README.md new file mode 100644 index 000000000..d35fa840a --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/README.md @@ -0,0 +1,38 @@ +# AWS Database Encryption SDK for DynamoDb Java Examples + +This project contains examples for using the AWS Database Encryption SDK for DynamoDb in Rust. + +Overview: + +``` +├── .. +├── examples: Examples source +│ ├── basic_get_put_example.rs: Example using AWS DB ESDK to Put and Get an encrypted item from DynamoDB +│ ├── create_keystore_key.rs: Example creating a branch key in a Keystore DynamoDB table +│ ├── clientsupplier/: Examples using a custom KMS ClientSupplier +│ ├── itemencryptor/: Examples using the DynamoDbItemEncryptor +│ ├── keyring/: Examples creating and using different keyrings +│ └── searchableencryption/: Examples demonstrating searchable encryption configuration and usage +└── src: Source code including tests +``` + +## Getting Started + +### Development Requirements + +- A Rust 1.80 or newer development environment + +### Building and Running + +All of the examples are called from the `main` method. +To run a given example, inspect its particular setup requirements, +create and/or grant access to any required AWS resources, +and run the example. + +## Security + +See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. + +## License + +This project is licensed under the Apache-2.0 License. diff --git a/DynamoDbEncryption/runtimes/rust/examples/basic_get_put_example.rs b/DynamoDbEncryption/runtimes/rust/examples/basic_get_put_example.rs new file mode 100644 index 000000000..c3e68e6b5 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/basic_get_put_example.rs @@ -0,0 +1,179 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_materialProviders::types::DbeAlgorithmSuiteId; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::types::dynamo_db_tables_encryption_config::DynamoDbTablesEncryptionConfig; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + and uses the low level PutItem and GetItem DDB APIs to demonstrate + putting a client-side encrypted item into DynamoDb + and then retrieving and decrypting that item from DynamoDb. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +*/ + +pub async fn put_item_get_item() -> Result<(), crate::BoxError> { + let kms_key_id = test_utils::TEST_KMS_KEY_ID; + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + + // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + // For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + // We will use the `CreateMrkMultiKeyring` method to create this keyring, + // as it will correctly handle both single region and Multi-Region KMS Keys. + let provider_config = MaterialProvidersConfig::builder().build()?; + let mat_prov = client::Client::from_conf(provider_config)?; + let kms_keyring = mat_prov + .create_aws_kms_mrk_multi_keyring() + .generator(kms_key_id) + .send() + .await?; + + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), + ("sort_key".to_string(), CryptoAction::SignOnly), + ("attribute1".to_string(), CryptoAction::EncryptAndSign), + ("attribute2".to_string(), CryptoAction::SignOnly), + (":attribute3".to_string(), CryptoAction::DoNothing), + ]); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we have designed our DynamoDb table such that any attribute name with + // the ":" prefix should be considered unauthenticated. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(kms_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + // Specifying an algorithm suite is not required, + // but is done here to demonstrate how to do so. + // We suggest using the + // `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + // which includes AES-GCM with key derivation, signing, and key commitment. + // This is also the default algorithm suite if one is not specified in this config. + // For more information on supported algorithm suites, see: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + .algorithm_suite_id( + DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeyEcdsaP384SymsigHmacSha384, + ) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 5. Create a new AWS SDK DynamoDb client using the TableEncryptionConfigs + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 6. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + let item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("BasicPutGetExample".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "attribute1".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ( + "attribute2".to_string(), + AttributeValue::S("sign me!".to_string()), + ), + ( + ":attribute3".to_string(), + AttributeValue::S("ignore me!".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 7. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("BasicPutGetExample".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + let resp = ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get)) + // In this example we configure a strongly consistent read + // because we perform a read immediately after a write (for demonstrative purposes). + // By default, reads are only eventually consistent. + // Read our docs to determine which read consistency to use for your application: + // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html + .consistent_read(true) + .send() + .await?; + + assert_eq!(resp.item, Some(item)); + println!("put_item_get_item successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/client_supplier_example.rs b/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/client_supplier_example.rs new file mode 100644 index 000000000..0dbae3b73 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/client_supplier_example.rs @@ -0,0 +1,246 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use super::regional_role_client_supplier::RegionalRoleClientSupplier; +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::aws_cryptography_materialProviders::types::DiscoveryFilter; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + This example sets up an MRK multi-keyring and an MRK discovery + multi-keyring using a custom client supplier. + A custom client supplier grants users access to more granular + configuration aspects of their authentication details and KMS + client. In this example, we create a simple custom client supplier + that authenticates with a different IAM role based on the + region of the KMS key. + + This example creates a MRK multi-keyring configured with a custom + client supplier using a single MRK and puts an encrypted item to the + table. Then, it creates a MRK discovery multi-keyring to decrypt the item + and retrieves the item from the table. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +*/ +pub async fn put_item_get_item() -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + // Note that we pass in an MRK in us-east-1... + let key_arn = test_utils::TEST_MRK_REPLICA_KEY_ID_US_EAST_1.to_string(); + let account_ids = vec![test_utils::TEST_AWS_ACCOUNT_ID.to_string()]; + // ...and access its replica in eu-west-1 + let regions = vec!["eu-west-1".to_string()]; + + // 1. Create a single MRK multi-keyring. + // This can be either a single-region KMS key or an MRK. + // For this example to succeed, the key's region must either + // 1) be in the regions list, or + // 2) the key must be an MRK with a replica defined + // in a region in the regions list, and the client + // must have the correct permissions to access the replica. + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + + // Create the multi-keyring using our custom client supplier + // defined in the RegionalRoleClientSupplier class in this directory. + // Note: RegionalRoleClientSupplier will internally use the key_arn's region + // to retrieve the correct IAM role. + + let mrk_keyring_with_client_supplier = mpl + .create_aws_kms_mrk_multi_keyring() + .client_supplier(RegionalRoleClientSupplier {}) + .generator(key_arn) + .send() + .await?; + + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute is not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ("sensitive_data".to_string(), CryptoAction::EncryptAndSign), + ]); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone()) + .keyring(mrk_keyring_with_client_supplier) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 5. Create a new AWS SDK DynamoDb client using the DynamoDb Config above + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 6. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side using the MRK multi-keyring. + // The data key protecting this item will be encrypted + // with all the KMS Keys in this keyring, so that it can be + // decrypted with any one of those KMS Keys. + let item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("clientSupplierItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "sensitive_data".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 7. Get the item back from our table using the same keyring. + // The client will decrypt the item client-side using the MRK + // and return the original item. + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("clientSupplierItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + let resp = ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get.clone())) + .consistent_read(true) + .send() + .await?; + + assert_eq!( + resp.item.unwrap()["sensitive_data"], + AttributeValue::S("encrypt and sign me!".to_string()) + ); + + // 8. Create a MRK discovery multi-keyring with a custom client supplier. + // A discovery MRK multi-keyring will be composed of + // multiple discovery MRK keyrings, one for each region. + // Each component keyring has its own KMS client in a particular region. + // When we provide a client supplier to the multi-keyring, all component + // keyrings will use that client supplier configuration. + // In our tests, we make `key_arn` an MRK with a replica, and + // provide only the replica region in our discovery filter. + let discovery_filter = DiscoveryFilter::builder() + .partition("aws") + .account_ids(account_ids) + .build()?; + + let mrk_discovery_client_supplier_keyring = mpl + .create_aws_kms_mrk_discovery_multi_keyring() + .client_supplier(RegionalRoleClientSupplier {}) + .discovery_filter(discovery_filter) + .regions(regions) + .send() + .await?; + + // 9. Create a new config and client using the discovery keyring. + // This is the same setup as above, except we provide the discovery keyring to the config. + let only_replica_table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(mrk_discovery_client_supplier_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let only_replica_table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([( + ddb_table_name.to_string(), + only_replica_table_config, + )])) + .build()?; + + let only_replica_dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(only_replica_table_configs)) + .build(); + let only_replica_ddb = aws_sdk_dynamodb::Client::from_conf(only_replica_dynamo_config); + + // 10. Get the item back from our table using the discovery keyring client. + // The client will decrypt the item client-side using the keyring, + // and return the original item. + // The discovery keyring will only use KMS keys in the provided regions and + // AWS accounts. Since we have provided it with a custom client supplier + // which uses different IAM roles based on the key region, + // the discovery keyring will use a particular IAM role to decrypt + // based on the region of the KMS key it uses to decrypt. + + let resp = only_replica_ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get)) + .consistent_read(true) + .send() + .await?; + + assert_eq!( + resp.item.unwrap()["sensitive_data"], + AttributeValue::S("encrypt and sign me!".to_string()) + ); + + println!("client_supplier_example successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/mod.rs b/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/mod.rs new file mode 100644 index 000000000..7b31bc5e5 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/mod.rs @@ -0,0 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +pub mod client_supplier_example; +pub mod regional_role_client_supplier; +pub mod regional_role_client_supplier_config; diff --git a/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/regional_role_client_supplier.rs b/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/regional_role_client_supplier.rs new file mode 100644 index 000000000..f8ea8cc6a --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/regional_role_client_supplier.rs @@ -0,0 +1,51 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use aws_config::Region; +use aws_db_esdk::aws_cryptography_materialProviders::operation::get_client::GetClientInput; +use aws_db_esdk::aws_cryptography_materialProviders::types::error::Error; +use aws_db_esdk::aws_cryptography_materialProviders::types::ClientSupplier; +use aws_db_esdk::deps::com_amazonaws_kms::client::Client as kms_client; + +/* + Example class demonstrating an implementation of a custom client supplier. + This particular implementation will create KMS clients with different IAM roles, + depending on the region passed. +*/ + +pub struct RegionalRoleClientSupplier {} + +impl ClientSupplier for RegionalRoleClientSupplier { + fn get_client(&self, input: GetClientInput) -> Result { + let region = input.region.unwrap(); + let arn = + super::regional_role_client_supplier_config::region_iam_role_map()[®ion].clone(); + + use aws_config::sts::AssumeRoleProvider; + + let provider = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + AssumeRoleProvider::builder(arn) + .region(Region::new(region.clone())) + .session_name("Rust-Client-Supplier-Example-Session") + .build() + .await + }) + }); + + let sdk_config = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + aws_config::load_defaults(aws_config::BehaviorVersion::v2024_03_28()).await + }) + }); + let kms_config = aws_sdk_kms::config::Builder::from(&sdk_config) + .credentials_provider(provider) + .region(Region::new(region)) + .build(); + + let inner_client = aws_sdk_kms::Client::from_conf(kms_config); + Ok(aws_db_esdk::deps::com_amazonaws_kms::client::Client { + inner: inner_client, + }) + } +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/regional_role_client_supplier_config.rs b/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/regional_role_client_supplier_config.rs new file mode 100644 index 000000000..356cbf91a --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/clientsupplier/regional_role_client_supplier_config.rs @@ -0,0 +1,23 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +/* + Class containing config for the RegionalRoleClientSupplier. + In your own code, this might be hardcoded, or reference + an external source, e.g. environment variables or AWS AppConfig. +*/ + +const US_EAST_1_IAM_ROLE: &str = + "arn:aws:iam::370957321024:role/GitHub-CI-DDBEC-Dafny-Role-only-us-east-1-KMS-keys"; + +const EU_WEST_1_IAM_ROLE: &str = + "arn:aws:iam::370957321024:role/GitHub-CI-DDBEC-Dafny-Role-only-eu-west-1-KMS-keys"; + +pub fn region_iam_role_map() -> HashMap { + HashMap::from([ + ("us-east-1".to_string(), US_EAST_1_IAM_ROLE.to_string()), + ("eu-west-1".to_string(), EU_WEST_1_IAM_ROLE.to_string()), + ]) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/create_keystore_key.rs b/DynamoDbEncryption/runtimes/rust/examples/create_keystore_key.rs new file mode 100644 index 000000000..cf4b20fdd --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/create_keystore_key.rs @@ -0,0 +1,50 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_keyStore::client as keystore_client; +use aws_db_esdk::aws_cryptography_keyStore::types::key_store_config::KeyStoreConfig; +use aws_db_esdk::aws_cryptography_keyStore::types::KmsConfiguration; + +/* + The Hierarchical Keyring Example and Searchable Encryption Examples + rely on the existence of a DDB-backed key store with pre-existing + branch key material or beacon key material. + + See the "Create KeyStore Table Example" for how to first set up + the DDB Table that will back this KeyStore. + + This example demonstrates configuring a KeyStore and then + using a helper method to create a branch key and beacon key + that share the same Id, then return that Id. + We will always create a new beacon key alongside a new branch key, + even if you are not using searchable encryption. + + This key creation should occur within your control plane. +*/ +pub async fn keystore_create_key() -> Result { + let key_store_table_name = test_utils::TEST_KEYSTORE_NAME; + let logical_key_store_name = test_utils::TEST_LOGICAL_KEYSTORE_NAME; + let kms_key_arn = test_utils::TEST_KEYSTORE_KMS_KEY_ID; + + // 1. Configure your KeyStore resource. + // This SHOULD be the same configuration that was used to create the DDB table + // in the "Create KeyStore Table Example". + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let key_store_config = KeyStoreConfig::builder() + .kms_client(aws_sdk_kms::Client::new(&sdk_config)) + .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config)) + .ddb_table_name(key_store_table_name) + .logical_key_store_name(logical_key_store_name) + .kms_configuration(KmsConfiguration::KmsKeyArn(kms_key_arn.to_string())) + .build()?; + + let keystore = keystore_client::Client::from_conf(key_store_config)?; + + // 2. Create a new branch key and beacon key in our KeyStore. + // Both the branch key and the beacon key will share an Id. + // This creation is eventually consistent. + + let new_key = keystore.create_key().send().await?; + Ok(new_key.branch_key_identifier.unwrap()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/get_encrypted_data_key_description.rs b/DynamoDbEncryption/runtimes/rust/examples/get_encrypted_data_key_description.rs new file mode 100644 index 000000000..234a2df55 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/get_encrypted_data_key_description.rs @@ -0,0 +1,64 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::client as dbesdk_client; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::dynamo_db_encryption_config::DynamoDbEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::GetEncryptedDataKeyDescriptionUnion; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +pub async fn get_encrypted_data_key_description() -> Result<(), crate::BoxError> { + let kms_key_id = test_utils::TEST_KMS_KEY_ID; + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + let config = DynamoDbEncryptionConfig::builder().build()?; + let ddb_enc = dbesdk_client::Client::from_conf(config)?; + + // 1. Define keys that will be used to retrieve item from the DynamoDB table. + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("BasicPutGetExample".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + // 2. Create a Amazon DynamoDB Client and retrieve item from DynamoDB table + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let ddb = aws_sdk_dynamodb::Client::new(&sdk_config); + let get_item_response = ddb + .get_item() + .set_key(Some(key_to_get)) + .table_name(ddb_table_name) + .send() + .await?; + + // 3. Extract the item from the dynamoDB table and prepare input for the GetEncryptedDataKeyDescription method. + // Here, we are sending dynamodb item but you can also input the header itself by extracting the header from + // "aws_dbe_head" attribute in the dynamoDB item. The part of the code where we send input as the header is commented. + let returned_item = get_item_response.item.unwrap(); + let input_union = GetEncryptedDataKeyDescriptionUnion::Item(returned_item); + let output = ddb_enc + .get_encrypted_data_key_description() + .input(input_union) + .send() + .await?; + + // The code below shows how we can send header as the input to the DynamoDB. This code is written to demo the + // alternative approach. So, it is commented. + // let input_union = GetEncryptedDataKeyDescriptionUnion::Header(returned_item["aws_dbe_head"].as_b().unwrap().clone()); + + // 4. Get encrypted DataKey Descriptions from GetEncryptedDataKeyDescription method output and assert if its true. + let encrypted_data_key_descriptions = output.encrypted_data_key_description_output.unwrap(); + assert_eq!( + encrypted_data_key_descriptions[0].key_provider_id, + Some("aws-kms".to_string()) + ); + assert_eq!( + encrypted_data_key_descriptions[0].key_provider_info, + Some(kms_key_id.to_string()) + ); + + println!("get_encrypted_data_key_description successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/itemencryptor/item_encrypt_decrypt.rs b/DynamoDbEncryption/runtimes/rust/examples/itemencryptor/item_encrypt_decrypt.rs new file mode 100644 index 000000000..12928c29c --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/itemencryptor/item_encrypt_decrypt.rs @@ -0,0 +1,172 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb_itemEncryptor::types::dynamo_db_item_encryptor_config::DynamoDbItemEncryptorConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb_itemEncryptor::client as enc_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::DbeAlgorithmSuiteId; + +/* + This example sets up a DynamoDb Item Encryptor and uses + the EncryptItem and DecryptItem APIs to directly encrypt and + decrypt an existing DynamoDb item. + You should use the DynamoDb Item Encryptor + if you already have a DynamoDb Item to encrypt or decrypt, + and do not need to make a Put or Get call to DynamoDb. + For example, if you are using DynamoDb Streams, + you may already be working with an encrypted item obtained from + DynamoDb, and want to directly decrypt the item. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +*/ +pub async fn encrypt_decrypt() -> Result<(), crate::BoxError> { + let kms_key_id = test_utils::TEST_KMS_KEY_ID; + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + + // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + // For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + // We will use the `CreateMrkMultiKeyring` method to create this keyring, + // as it will correctly handle both single region and Multi-Region KMS Keys. + let provider_config = MaterialProvidersConfig::builder().build()?; + let mat_prov = mpl_client::Client::from_conf(provider_config)?; + let kms_keyring = mat_prov + .create_aws_kms_mrk_multi_keyring() + .generator(kms_key_id) + .send() + .await?; + + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), + ("sort_key".to_string(), CryptoAction::SignOnly), + ("attribute1".to_string(), CryptoAction::EncryptAndSign), + ("attribute2".to_string(), CryptoAction::SignOnly), + (":attribute3".to_string(), CryptoAction::DoNothing), + ]); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we have designed our DynamoDb table such that any attribute name with + // the ":" prefix should be considered unauthenticated. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 4. Create the configuration for the DynamoDb Item Encryptor + let config = DynamoDbItemEncryptorConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(kms_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + // Specifying an algorithm suite is not required, + // but is done here to demonstrate how to do so. + // We suggest using the + // `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + // which includes AES-GCM with key derivation, signing, and key commitment. + // This is also the default algorithm suite if one is not specified in this config. + // For more information on supported algorithm suites, see: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + .algorithm_suite_id( + DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeyEcdsaP384SymsigHmacSha384, + ) + .build()?; + + // 5. Create the DynamoDb Item Encryptor + let item_encryptor = enc_client::Client::from_conf(config)?; + + // 6. Directly encrypt a DynamoDb item using the DynamoDb Item Encryptor + let original_item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("ItemEncryptDecryptExample".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "attribute1".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ( + "attribute2".to_string(), + AttributeValue::S("sign me!".to_string()), + ), + ( + ":attribute3".to_string(), + AttributeValue::S("ignore me!".to_string()), + ), + ]); + + let encrypted_item = item_encryptor + .encrypt_item() + .plaintext_item(original_item.clone()) + .send() + .await? + .encrypted_item + .unwrap(); + + // Demonstrate that the item has been encrypted + assert_eq!( + encrypted_item["partition_key"], + AttributeValue::S("ItemEncryptDecryptExample".to_string()) + ); + assert_eq!( + encrypted_item["sort_key"], + AttributeValue::N("0".to_string()) + ); + assert!(encrypted_item["attribute1"].is_b()); + assert!(!encrypted_item["attribute1"].is_s()); + + // 7. Directly decrypt the encrypted item using the DynamoDb Item Encryptor + let decrypted_item = item_encryptor + .decrypt_item() + .encrypted_item(encrypted_item) + .send() + .await? + .plaintext_item + .unwrap(); + + // Demonstrate that GetItem succeeded and returned the decrypted item + assert_eq!(decrypted_item, original_item); + println!("encrypt_decrypt successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/itemencryptor/mod.rs b/DynamoDbEncryption/runtimes/rust/examples/itemencryptor/mod.rs new file mode 100644 index 000000000..54e8634c1 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/itemencryptor/mod.rs @@ -0,0 +1,4 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +pub mod item_encrypt_decrypt; diff --git a/DynamoDbEncryption/runtimes/rust/examples/keyring/branch_key_id_supplier.rs b/DynamoDbEncryption/runtimes/rust/examples/keyring/branch_key_id_supplier.rs new file mode 100644 index 000000000..dbd7958d9 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/keyring/branch_key_id_supplier.rs @@ -0,0 +1,62 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::operation::get_branch_key_id_from_ddb_key::GetBranchKeyIdFromDdbKeyInput; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::operation::get_branch_key_id_from_ddb_key::GetBranchKeyIdFromDdbKeyOutput; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::error::Error; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbKeyBranchKeyIdSupplier; + +// Used in the 'HierarchicalKeyringExample'. +// In that example, we have a table where we distinguish multiple tenants +// by a tenant ID that is stored in our partition attribute. +// The expectation is that this does not produce a confused deputy +// because the tenants are separated by partition. +// In order to create a Hierarchical Keyring that is capable of encrypting or +// decrypting data for either tenant, we implement this interface +// to map the correct branch key ID to the correct tenant ID. +pub struct ExampleBranchKeyIdSupplier { + branch_key_id_for_tenant1: String, + branch_key_id_for_tenant2: String, +} + +impl ExampleBranchKeyIdSupplier { + pub fn new(tenant1_id: &str, tenant2_id: &str) -> Self { + Self { + branch_key_id_for_tenant1: tenant1_id.to_string(), + branch_key_id_for_tenant2: tenant2_id.to_string(), + } + } +} + +impl DynamoDbKeyBranchKeyIdSupplier for ExampleBranchKeyIdSupplier { + fn get_branch_key_id_from_ddb_key( + &self, + input: GetBranchKeyIdFromDdbKeyInput, + ) -> Result { + let key = input.ddb_key.unwrap(); + + if !key.contains_key("partition_key") { + return Err(Error::DynamoDbEncryptionException { + message: "Item invalid, does not contain expected partition key attribute." + .to_string(), + }); + } + let tenant_key_id = key["partition_key"].as_s().unwrap(); + + if tenant_key_id == "tenant1Id" { + Ok(GetBranchKeyIdFromDdbKeyOutput::builder() + .branch_key_id(self.branch_key_id_for_tenant1.clone()) + .build() + .unwrap()) + } else if tenant_key_id == "tenant2Id" { + Ok(GetBranchKeyIdFromDdbKeyOutput::builder() + .branch_key_id(self.branch_key_id_for_tenant2.clone()) + .build() + .unwrap()) + } else { + Err(Error::DynamoDbEncryptionException { + message: "Item does not contain valid tenant ID.".to_string(), + }) + } + } +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/keyring/hierarchical_keyring.rs b/DynamoDbEncryption/runtimes/rust/examples/keyring/hierarchical_keyring.rs new file mode 100644 index 000000000..90f350f69 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/keyring/hierarchical_keyring.rs @@ -0,0 +1,237 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use super::branch_key_id_supplier::ExampleBranchKeyIdSupplier; +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::client as dbesdk_client; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::dynamo_db_encryption_config::DynamoDbEncryptionConfig; +use aws_db_esdk::aws_cryptography_keyStore::client as keystore_client; +use aws_db_esdk::aws_cryptography_keyStore::types::key_store_config::KeyStoreConfig; +use aws_db_esdk::aws_cryptography_keyStore::types::KmsConfiguration; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + using the Hierarchical Keyring, which establishes a key hierarchy + where "branch" keys are persisted in DynamoDb. + These branch keys are used to protect your data keys, + and these branch keys are themselves protected by a root KMS Key. + + Establishing a key hierarchy like this has two benefits: + + First, by caching the branch key material, and only calling back + to KMS to re-establish authentication regularly according to your configured TTL, + you limit how often you need to call back to KMS to protect your data. + This is a performance/security tradeoff, where your authentication, audit, and + logging from KMS is no longer one-to-one with every encrypt or decrypt call. + However, the benefit is that you no longer have to make a + network call to KMS for every encrypt or decrypt. + + Second, this key hierarchy makes it easy to hold multi-tenant data + that is isolated per branch key in a single DynamoDb table. + You can create a branch key for each tenant in your table, + and encrypt all that tenant's data under that distinct branch key. + On decrypt, you can either statically configure a single branch key + to ensure you are restricting decryption to a single tenant, + or you can implement an interface that lets you map the primary key on your items + to the branch key that should be responsible for decrypting that data. + + This example then demonstrates configuring a Hierarchical Keyring + with a Branch Key ID Supplier to encrypt and decrypt data for + two separate tenants. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + + This example also requires using a KMS Key whose ARN + is provided in CLI arguments. You need the following access + on this key: + - GenerateDataKeyWithoutPlaintext + - Decrypt +*/ +pub async fn put_item_get_item( + tenant1_branch_key_id: &str, + tenant2_branch_key_id: &str, +) -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + + let keystore_table_name = test_utils::TEST_KEYSTORE_NAME; + let logical_keystore_name = test_utils::TEST_LOGICAL_KEYSTORE_NAME; + let kms_key_id = test_utils::TEST_KEYSTORE_KMS_KEY_ID; + + // Initial KeyStore Setup: This example requires that you have already + // created your KeyStore, and have populated it with two new branch keys. + // See the "Create KeyStore Table Example" and "Create KeyStore Key Example" + // for an example of how to do this. + + // 1. Configure your KeyStore resource. + // This SHOULD be the same configuration that you used + // to initially create and populate your KeyStore. + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let key_store_config = KeyStoreConfig::builder() + .kms_client(aws_sdk_kms::Client::new(&sdk_config)) + .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config)) + .ddb_table_name(keystore_table_name) + .logical_key_store_name(logical_keystore_name) + .kms_configuration(KmsConfiguration::KmsKeyArn(kms_key_id.to_string())) + .build()?; + + let key_store = keystore_client::Client::from_conf(key_store_config)?; + + // 2. Create a Branch Key ID Supplier. See ExampleBranchKeyIdSupplier in this directory. + let dbesdk_config = DynamoDbEncryptionConfig::builder().build()?; + let dbesdk = dbesdk_client::Client::from_conf(dbesdk_config)?; + let supplier = ExampleBranchKeyIdSupplier::new(tenant1_branch_key_id, tenant2_branch_key_id); + + let branch_key_id_supplier = dbesdk + .create_dynamo_db_encryption_branch_key_id_supplier() + .ddb_key_branch_key_id_supplier(supplier) + .send() + .await? + .branch_key_id_supplier + .unwrap(); + + // 3. Create the Hierarchical Keyring, using the Branch Key ID Supplier above. + // With this configuration, the AWS SDK Client ultimately configured will be capable + // of encrypting or decrypting items for either tenant (assuming correct KMS access). + // If you want to restrict the client to only encrypt or decrypt for a single tenant, + // configure this Hierarchical Keyring using `.branchKeyId(tenant1BranchKeyId)` instead + // of `.branchKeyIdSupplier(branchKeyIdSupplier)`. + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + + let hierarchical_keyring = mpl + .create_aws_kms_hierarchical_keyring() + .branch_key_id_supplier(branch_key_id_supplier) + .key_store(key_store) + .ttl_seconds(600) + .send() + .await?; + + // 4. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ( + "tenant_sensitive_data".to_string(), + CryptoAction::EncryptAndSign, + ), + ]); + + // 5. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 6. Create the DynamoDb Encryption configuration for the table we will be writing to. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(hierarchical_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 8. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + // Because the item we are writing uses "tenantId1" as our partition value, + // based on the code we wrote in the ExampleBranchKeySupplier, + // `tenant1BranchKeyId` will be used to encrypt this item. + let item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("tenant1Id".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "tenant_sensitive_data".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 9. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + // Because the returned item's partition value is "tenantId1", + // based on the code we wrote in the ExampleBranchKeySupplier, + // `tenant1BranchKeyId` will be used to decrypt this item. + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("tenant1Id".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + let resp = ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get)) + .consistent_read(true) + .send() + .await?; + + assert_eq!(resp.item, Some(item)); + println!("hierarchical_keyring successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/keyring/kms_rsa_keyring.rs b/DynamoDbEncryption/runtimes/rust/examples/keyring/kms_rsa_keyring.rs new file mode 100644 index 000000000..61468c27b --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/keyring/kms_rsa_keyring.rs @@ -0,0 +1,244 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::aws_cryptography_materialProviders::types::DbeAlgorithmSuiteId; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; +use std::io::Write; +use std::path::Path; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + using the KMS RSA Keyring. This keyring uses a KMS RSA key pair to + encrypt and decrypt records. The client uses the downloaded public key + to encrypt items it adds to the table. + The keyring uses the private key to decrypt existing table items it retrieves, + by calling KMS' decrypt API. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + This example also requires access to a KMS RSA key. + Our tests provide a KMS RSA ARN that anyone can use, but you + can also provide your own KMS RSA key. + To use your own KMS RSA key, you must have either: + - Its public key downloaded in a UTF-8 encoded PEM file + - kms:GetPublicKey permissions on that key + If you do not have the public key downloaded, running this example + through its main method will download the public key for you + by calling kms:GetPublicKey. + You must also have kms:Decrypt permissions on the KMS RSA key. +*/ + +const DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME: &str = "KmsRsaKeyringExamplePublicKey.pem"; + +pub async fn put_item_get_item() -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + let rsa_key_arn = test_utils::TEST_KMS_RSA_KEY_ID; + + // You may provide your own RSA public key at EXAMPLE_RSA_PUBLIC_KEY_FILENAME. + // This must be the public key for the RSA key represented at rsaKeyArn. + // If this file is not present, this will write a UTF-8 encoded PEM file for you. + if should_get_new_public_key(DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME) { + write_public_key_pem_for_rsa_key( + test_utils::TEST_KMS_RSA_KEY_ID, + DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME, + ) + .await?; + } + + // 1. Load UTF-8 encoded public key PEM file. + // You may have an RSA public key file already defined. + // If not, the main method in this class will call + // the KMS RSA key, retrieve its public key, and store it + // in a PEM file for example use. + let mut file = File::open(Path::new(DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME))?; + let mut public_key_utf8_bytes = Vec::new(); + file.read_to_end(&mut public_key_utf8_bytes)?; + + // 2. Create a KMS RSA keyring. + // This keyring takes in: + // - kmsClient + // - kmsKeyId: Must be an ARN representing a KMS RSA key + // - publicKey: A ByteBuffer of a UTF-8 encoded PEM file representing the public + // key for the key passed into kmsKeyId + // - encryptionAlgorithm: Must be either RSAES_OAEP_SHA_256 or RSAES_OAEP_SHA_1 + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let kms_rsa_keyring = mpl + .create_aws_kms_rsa_keyring() + .kms_key_id(rsa_key_arn) + .public_key(public_key_utf8_bytes) + .encryption_algorithm(aws_sdk_kms::types::EncryptionAlgorithmSpec::RsaesOaepSha256) + .kms_client(aws_sdk_kms::Client::new(&sdk_config)) + .send() + .await?; + + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ("sensitive_data".to_string(), CryptoAction::EncryptAndSign), + ]); + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActions` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite + // that does not use asymmetric signing. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(kms_rsa_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + // Specify algorithmSuite without asymmetric signing here + // As of v3.0.0, the only supported algorithmSuite without asymmetric signing is + // ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384. + .algorithm_suite_id(DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeySymsigHmacSha384) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 7. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + let item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("awsKmsRsaKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "sensitive_data".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 8. Get the item back from our table using the client. + // The client will decrypt the item client-side using the RSA keyring + // and return the original item. + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("awsKmsRsaKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + let resp = ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get)) + .consistent_read(true) + .send() + .await?; + + assert_eq!(resp.item, Some(item)); + println!("kms_rsa_keyring successful."); + Ok(()) +} + +fn should_get_new_public_key(rsa_public_key_filename: &str) -> bool { + // Check if a public key file already exists + !Path::new(rsa_public_key_filename).exists() +} + +async fn write_public_key_pem_for_rsa_key( + rsa_key_arn: &str, + rsa_public_key_filename: &str, +) -> Result<(), crate::BoxError> { + // Safety check: Validate file is not present + if Path::new(rsa_public_key_filename).exists() { + return Err(crate::BoxError( + "write_public_key_pem_for_rsa_key will not overwrite existing PEM files.".to_string(), + )); + } + + // This code will call KMS to get the public key for the KMS RSA key. + // You must have kms:GetPublicKey permissions on the key for this to succeed. + // The public key will be written to the file EXAMPLE_RSA_PUBLIC_KEY_FILENAME. + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let getter_for_public_key = aws_sdk_kms::Client::new(&sdk_config); + + let response = getter_for_public_key + .get_public_key() + .key_id(rsa_key_arn) + .send() + .await?; + + let public_key_bytes = response.public_key.unwrap().into_inner(); + + let public_key = pem::Pem::new("PUBLIC KEY", public_key_bytes); + let public_key = pem::encode(&public_key); + + std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(Path::new(rsa_public_key_filename))? + .write_all(public_key.as_bytes())?; + + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/keyring/mod.rs b/DynamoDbEncryption/runtimes/rust/examples/keyring/mod.rs new file mode 100644 index 000000000..a97e04fbe --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/keyring/mod.rs @@ -0,0 +1,11 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +pub mod branch_key_id_supplier; +pub mod hierarchical_keyring; +pub mod kms_rsa_keyring; +pub mod mrk_discovery_multi_keyring; +pub mod multi_keyring; +pub mod multi_mrk_keyring; +pub mod raw_aes_keyring; +pub mod raw_rsa_keyring; diff --git a/DynamoDbEncryption/runtimes/rust/examples/keyring/mrk_discovery_multi_keyring.rs b/DynamoDbEncryption/runtimes/rust/examples/keyring/mrk_discovery_multi_keyring.rs new file mode 100644 index 000000000..3cbb12e9e --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/keyring/mrk_discovery_multi_keyring.rs @@ -0,0 +1,216 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::aws_cryptography_materialProviders::types::DiscoveryFilter; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + This example sets up a MRK discovery multi-keyring to decrypt data using + the DynamoDB encryption client. A discovery keyring is not provided with any wrapping + keys; instead, it recognizes the KMS key that was used to encrypt a data key, + and asks KMS to decrypt with that KMS key. Discovery keyrings cannot be used + to encrypt data. + + For more information on discovery keyrings, see + https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery + + This example encrypts an item using an MRK multi-keyring and puts the + encrypted item to the configured DynamoDb table. Then, it gets the item + from the table and decrypts it using the discovery keyring. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +*/ + +pub async fn put_item_get_item() -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + let key_arn = test_utils::TEST_MRK_KEY_ID; + let account_ids = vec![test_utils::TEST_AWS_ACCOUNT_ID.to_string()]; + let regions = vec![test_utils::TEST_AWS_REGION.to_string()]; + + // 1. Create a single MRK multi-keyring using the key arn. + // Although this example demonstrates use of the MRK discovery multi-keyring, + // a discovery keyring cannot be used to encrypt. So we will need to construct + // a non-discovery keyring for this example to encrypt. For more information on MRK + // multi-keyrings, see the MultiMrkKeyringExample in this directory. + // Though this is an "MRK multi-keyring", we do not need to provide multiple keys, + // and can use single-region KMS keys. We will provide a single key here; this + // can be either an MRK or a single-region key. + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + let encrypt_keyring = mpl + .create_aws_kms_mrk_multi_keyring() + .generator(key_arn) + .send() + .await?; + + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and icncluded in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ("sensitive_data".to_string(), CryptoAction::EncryptAndSign), + ]); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone()) + .keyring(encrypt_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 5. Create a new AWS SDK DynamoDb client using the config above + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 6. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side using the MRK multi-keyring. + let item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("awsKmsMrkDiscoveryMultiKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "sensitive_data".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 7. Construct a discovery filter. + // A discovery filter limits the set of encrypted data keys + // the keyring can use to decrypt data. + // We will only let the keyring use keys in the selected AWS accounts + // and in the `aws` partition. + // This is the suggested config for most users; for more detailed config, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery + let discovery_filter = DiscoveryFilter::builder() + .partition("aws") + .account_ids(account_ids) + .build()?; + + // 8. Construct a discovery keyring. + // Note that we choose to use the MRK discovery multi-keyring, even though + // our original keyring used a single KMS key. + + let decrypt_keyring = mpl + .create_aws_kms_mrk_discovery_multi_keyring() + .discovery_filter(discovery_filter) + .regions(regions) + .send() + .await?; + + // 9. Create new DDB config and client using the decrypt discovery keyring. + // This is the same as the above config, except we pass in the decrypt keyring. + let table_config_for_decrypt = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(decrypt_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let table_configs_for_decrypt = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([( + ddb_table_name.to_string(), + table_config_for_decrypt, + )])) + .build()?; + + let dynamo_config_for_decrypt = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs_for_decrypt)) + .build(); + let ddb_for_decrypt = aws_sdk_dynamodb::Client::from_conf(dynamo_config_for_decrypt); + + // 10. Get the item back from our table using the client. + // The client will retrieve encrypted items from the DDB table, then + // detect the KMS key that was used to encrypt their data keys. + // The client will make a request to KMS to decrypt with the encrypting KMS key. + // If the client has permission to decrypt with the KMS key, + // the client will decrypt the item client-side using the keyring + // and return the original item. + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("awsKmsMrkDiscoveryMultiKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + let resp = ddb_for_decrypt + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get)) + .consistent_read(true) + .send() + .await?; + + assert_eq!(resp.item, Some(item)); + + println!("mrk_discovery_multi_keyring successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/keyring/multi_keyring.rs b/DynamoDbEncryption/runtimes/rust/examples/keyring/multi_keyring.rs new file mode 100644 index 000000000..9ad1d8180 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/keyring/multi_keyring.rs @@ -0,0 +1,251 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::aws_cryptography_materialProviders::types::AesWrappingAlg; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + using the multi-keyring. This keyring takes in multiple keyrings + and uses them to encrypt and decrypt data. Data encrypted with + a multi-keyring can be decrypted with any of its component keyrings. + + For more information on multi-keyrings, see + https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-multi-keyring.html + + This example creates a new multi-keyring consisting of an AWS KMS + keyring (labeled the "generator keyring") and a raw AES keyring + (labeled as the only "child keyring"). It encrypts a test item + using the multi-keyring and puts the encrypted item to the provided + DynamoDb table. Then, it gets the item from the table and decrypts it + using only the raw AES keyring. + + This example takes in an `aesKeyBytes` parameter. This parameter + should be a ByteBuffer representing a 256-bit AES key. If this example + is run through the class' main method, it will create a new key. + In practice, users of this library should not randomly generate a key, + and should instead retrieve an existing key from a secure key + management system (e.g. an HSM). + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +*/ + +pub async fn put_item_get_item() -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + let key_arn = test_utils::TEST_KMS_KEY_ID; + let aes_key_bytes = generate_aes_key_bytes(); + + // 1. Create the raw AES keyring. + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + let raw_aes_keyring = mpl + .create_raw_aes_keyring() + .key_name("my-aes-key-name") + .key_namespace("my-key-namespace") + .wrapping_key(aes_key_bytes) + .wrapping_alg(AesWrappingAlg::AlgAes256GcmIv12Tag16) + .send() + .await?; + + // 2. Create the AWS KMS keyring. + // We create a MRK multi keyring, as this interface also supports + // single-region KMS keys (standard KMS keys), + // and creates the KMS client for us automatically. + let aws_kms_mrk_multi_keyring = mpl + .create_aws_kms_mrk_multi_keyring() + .generator(key_arn) + .send() + .await?; + + // 3. Create the multi-keyring. + // We will label the AWS KMS keyring as the generator and the raw AES keyring as the + // only child keyring. + // You must provide a generator keyring to encrypt data. + // You may provide additional child keyrings. Each child keyring will be able to + // decrypt data encrypted with the multi-keyring on its own. It does not need + // knowledge of any other child keyrings or the generator keyring to decrypt. + + let multi_keyring = mpl + .create_multi_keyring() + .generator(aws_kms_mrk_multi_keyring) + .child_keyrings(vec![raw_aes_keyring.clone()]) + .send() + .await?; + + // 4. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ("sensitive_data".to_string(), CryptoAction::EncryptAndSign), + ]); + + // 5. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 6. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Note that this example creates one config/client combination for PUT, and another + // for GET. The PUT config uses the multi-keyring, while the GET config uses the + // raw AES keyring. This is solely done to demonstrate that a keyring included as + // a child of a multi-keyring can be used to decrypt data on its own. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone()) + .keyring(multi_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 7. Create a new AWS SDK DynamoDb client using the config above + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 8. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side using the multi-keyring. + // The item will be encrypted with all wrapping keys in the keyring, + // so that it can be decrypted with any one of the keys. + let item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("multiKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "sensitive_data".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 9. Get the item back from our table using the above client. + // The client will decrypt the item client-side using the AWS KMS + // keyring, and return back the original item. + // Since the generator key is the first available key in the keyring, + // that is the key that will be used to decrypt this item. + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("multiKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + let resp = ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get.clone())) + .consistent_read(true) + .send() + .await?; + + assert_eq!(resp.item, Some(item.clone())); + + // 10. Create a new config and client with only the raw AES keyring to GET the item + // This is the same setup as above, except the config uses the `rawAesKeyring`. + let only_aes_table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(raw_aes_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let only_aes_table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([( + ddb_table_name.to_string(), + only_aes_table_config, + )])) + .build()?; + + let only_aes_dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(only_aes_table_configs)) + .build(); + let only_aes_ddb = aws_sdk_dynamodb::Client::from_conf(only_aes_dynamo_config); + + // 11. Get the item back from our table using the client + // configured with only the raw AES keyring. + // The client will decrypt the item client-side using the raw + // AES keyring, and return back the original item. + let resp = only_aes_ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get.clone())) + .consistent_read(true) + .send() + .await?; + + assert_eq!(resp.item, Some(item.clone())); + + println!("multi_keyring successful."); + Ok(()) +} + +fn generate_aes_key_bytes() -> Vec { + // This example returns a static key. + // In practice, you should not generate this key in your code, and should instead + // retrieve this key from a secure key management system (e.g. HSM). + // This key is created here for example purposes only and should not be used for any other purpose. + vec![ + 1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ] +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/keyring/multi_mrk_keyring.rs b/DynamoDbEncryption/runtimes/rust/examples/keyring/multi_mrk_keyring.rs new file mode 100644 index 000000000..20bd6c5e6 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/keyring/multi_mrk_keyring.rs @@ -0,0 +1,289 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + using the MRK multi-keyring. This keyring takes in multiple AWS KMS + MRKs (multi-region keys) or regular AWS KMS keys (single-region keys) + and uses them to encrypt and decrypt data. Data encrypted using an MRK + multi-keyring can be decrypted using any of its component keys. If a component + key is an MRK with a replica in a second region, the replica key can also be + used to decrypt data. + + For more information on MRKs, see + https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html + + For more information on multi-keyrings, see + https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-multi-keyring.html + + This example creates a new MRK multi-keyring consisting of one MRK + (labeled as the "generator keyring") and one single-region key (labeled + as the only "child keyring"). The MRK also has a replica in a second region. + + This example encrypts a test item using the MRK multi-keyring and puts the + encrypted item to the provided DynamoDb table. Then, it gets the item + from the table and decrypts it using three different configs: + 1. The MRK multi-keyring, where the MRK key is used to decrypt + 2. Another MRK multi-keyring, where the replica MRK key is used to decrypt + 3. Another MRK multi-keyring, where the single-region key that was present + in the original MRK multi-keyring is used to decrypt + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + + This example demonstrates multi-region use cases. As a result, + it requires that you have a default region set in your AWS client. + You can set a default region through the AWS CLI with + `aws configure set region [region-name]` + e.g. + `aws configure set region us-west-2` + For more information on using AWS CLI to set config, see + https://awscli.amazonaws.com/v2/documentation/api/latest/reference/configure/set.html +*/ +pub async fn put_item_get_item() -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + let mrk_key_arn = test_utils::TEST_MRK_KEY_ID; + let key_arn = test_utils::TEST_KMS_KEY_ID; + let mrk_replica_key_arn = test_utils::TEST_MRK_REPLICA_KEY_ID_US_EAST_1; + + // 1. Create a single MRK multi-keyring using the MRK arn and the single-region key arn. + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + // Create the multi-keyring, using the MRK as the generator key, + // and the single-region key as a child key. + // Note that the generator key will generate and encrypt a plaintext data key + // and all child keys will only encrypt that same plaintext data key. + // As such, you must have permission to call KMS:GenerateDataKey on your generator key + // and permission to call KMS:Encrypt on all child keys. + // For more information, see the AWS docs on multi-keyrings above. + let aws_kms_mrk_multi_keyring = mpl + .create_aws_kms_mrk_multi_keyring() + .generator(mrk_key_arn) + .kms_key_ids(vec![key_arn.to_string()]) + .send() + .await?; + + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ("sensitive_data".to_string(), CryptoAction::EncryptAndSign), + ]); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone()) + .keyring(aws_kms_mrk_multi_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 5. Create the DynamoDb Encryption Interceptor + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 6. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side using the MRK multi-keyring. + // The data key protecting this item will be encrypted + // with all the KMS Keys in this keyring, so that it can be + // decrypted with any one of those KMS Keys. + let item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("awsKmsMrkMultiKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "sensitive_data".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 7. Get the item back from our table using the client. + // The client will decrypt the item client-side using the MRK + // and return back the original item. + // Since the generator key is the first available key in the keyring, + // that is the KMS Key that will be used to decrypt this item. + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("awsKmsMrkMultiKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + let resp = ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get.clone())) + .consistent_read(true) + .send() + .await?; + + assert_eq!(resp.item, Some(item.clone())); + + // 8. Create a MRK keyring using the replica MRK arn. + // We will use this to demonstrate that the replica MRK + // can decrypt data created with the original MRK, + // even when the replica MRK was not present in the + // encrypting multi-keyring. + let only_replica_key_mrk_multi_keyring = mpl + .create_aws_kms_mrk_multi_keyring() + .kms_key_ids(vec![mrk_replica_key_arn.to_string()]) + .send() + .await?; + + // 9. Create a new config and client using the MRK keyring. + // This is the same setup as above, except we provide the MRK keyring to the config. + let only_replica_table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt.clone()) + // Only replica keyring added here + .keyring(only_replica_key_mrk_multi_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let only_replica_table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([( + ddb_table_name.to_string(), + only_replica_table_config, + )])) + .build()?; + + let only_replica_dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(only_replica_table_configs)) + .build(); + let only_replica_ddb = aws_sdk_dynamodb::Client::from_conf(only_replica_dynamo_config); + + // 10. Get the item back from our table using the client configured with the replica. + // The client will decrypt the item client-side using the replica MRK + // and return back the original item. + + let only_replica_resp = only_replica_ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get.clone())) + .consistent_read(true) + .send() + .await?; + + assert_eq!(only_replica_resp.item, Some(item.clone())); + + // 11. Create an AWS KMS keyring using the single-region key ARN. + // We will use this to demonstrate that the single-region key + // can decrypt data created with the MRK multi-keyring, + // since it is present in the keyring used to encrypt. + let only_srk_key_mrk_multi_keyring = mpl + .create_aws_kms_mrk_multi_keyring() + .kms_key_ids(vec![key_arn.to_string()]) + .send() + .await?; + + // 12. Create a new config and client using the AWS KMS keyring. + // This is the same setup as above, except we provide the AWS KMS keyring to the config. + let only_srk_table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + // Only srk keyring added here + .keyring(only_srk_key_mrk_multi_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let only_srk_table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([( + ddb_table_name.to_string(), + only_srk_table_config, + )])) + .build()?; + + let only_srk_dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(only_srk_table_configs)) + .build(); + let only_srk_ddb = aws_sdk_dynamodb::Client::from_conf(only_srk_dynamo_config); + + // 13. Get the item back from our table using the client configured with the AWS KMS keyring. + // The client will decrypt the item client-side using the single-region key + // and return back the original item. + + let only_srk_resp = only_srk_ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get)) + .consistent_read(true) + .send() + .await?; + + assert_eq!(only_srk_resp.item, Some(item)); + + println!("multi_mrk_keyring successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/keyring/raw_aes_keyring.rs b/DynamoDbEncryption/runtimes/rust/examples/keyring/raw_aes_keyring.rs new file mode 100644 index 000000000..ecf8d7d03 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/keyring/raw_aes_keyring.rs @@ -0,0 +1,180 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::aws_cryptography_materialProviders::types::AesWrappingAlg; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + using the raw AES Keyring. This keyring takes in an AES key + and uses that key to protect the data keys that encrypt and + decrypt DynamoDb table items. + + This example takes in an `aesKeyBytes` parameter. This parameter + should be a ByteBuffer representing a 256-bit AES key. If this example + is run through the class' main method, it will create a new key. + In practice, users of this library should not randomly generate a key, + and should instead retrieve an existing key from a secure key + management system (e.g. an HSM). + + This example encrypts a test item using the provided AES key and puts the + encrypted item to the provided DynamoDb table. Then, it gets the + item from the table and decrypts it. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +*/ + +pub async fn put_item_get_item() -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + let aes_key_bytes = generate_aes_key_bytes(); + + // 1. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + let raw_aes_keyring = mpl + .create_raw_aes_keyring() + .key_name("my-aes-key-name") + .key_namespace("my-key-namespace") + .wrapping_key(aes_key_bytes) + .wrapping_alg(AesWrappingAlg::AlgAes256GcmIv12Tag16) + .send() + .await?; + + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ("sensitive_data".to_string(), CryptoAction::EncryptAndSign), + ]); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(raw_aes_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 5. Create a new AWS SDK DynamoDb client using the Config above + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 6. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + let item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("rawAesKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "sensitive_data".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 7. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("rawAesKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + let resp = ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get)) + // In this example we configure a strongly consistent read + // because we perform a read immediately after a write (for demonstrative purposes). + // By default, reads are only eventually consistent. + // Read our docs to determine which read consistency to use for your application: + // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html + .consistent_read(true) + .send() + .await?; + + assert_eq!(resp.item, Some(item)); + + println!("raw_aes_keyring successful."); + Ok(()) +} + +fn generate_aes_key_bytes() -> Vec { + // This example returns a static key. + // In practice, you should not generate this key in your code, and should instead + // retrieve this key from a secure key management system (e.g. HSM). + // This key is created here for example purposes only and should not be used for any other purpose. + vec![ + 1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ] +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/keyring/raw_rsa_keyring.rs b/DynamoDbEncryption/runtimes/rust/examples/keyring/raw_rsa_keyring.rs new file mode 100644 index 000000000..88f32ac60 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/keyring/raw_rsa_keyring.rs @@ -0,0 +1,280 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::aws_cryptography_materialProviders::types::PaddingScheme; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; +use std::io::Write; +use std::path::Path; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + using the raw RSA Keyring. This keyring uses an RSA key pair to + encrypt and decrypt records. This keyring accepts PEM encodings of + the key pair as UTF-8 interpreted bytes. The client uses the public key + to encrypt items it adds to the table and uses the private key to decrypt + existing table items it retrieves. + + This example loads a key pair from PEM files with paths defined in + - EXAMPLE_RSA_PRIVATE_KEY_FILENAME + - EXAMPLE_RSA_PUBLIC_KEY_FILENAME + If you do not provide these files, running this example through this + class' main method will generate these files for you. These files will + be generated in the directory where the example is run. + In practice, users of this library should not generate new key pairs + like this, and should instead retrieve an existing key from a secure + key management system (e.g. an HSM). + You may also provide your own key pair by placing PEM files in the + directory where the example is run or modifying the paths in the code + below. These files must be valid PEM encodings of the key pair as UTF-8 + encoded bytes. If you do provide your own key pair, or if a key pair + already exists, this class' main method will not generate a new key pair. + + This example loads a key pair from disk, encrypts a test item, and puts the + encrypted item to the provided DynamoDb table. Then, it gets the + item from the table and decrypts it. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) +*/ + +const EXAMPLE_RSA_PRIVATE_KEY_FILENAME: &str = "RawRsaKeyringExamplePrivateKey.pem"; +const EXAMPLE_RSA_PUBLIC_KEY_FILENAME: &str = "RawRsaKeyringExamplePublicKey.pem"; + +pub async fn put_item_get_item() -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + + // You may provide your own RSA key pair in the files located at + // - EXAMPLE_RSA_PRIVATE_KEY_FILENAME + // - EXAMPLE_RSA_PUBLIC_KEY_FILENAME + // If these files are not present, this will generate a pair for you + if should_generate_new_rsa_key_pair()? { + generate_rsa_key_pair()?; + } + + // 1. Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + + let mut file = File::open(Path::new(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))?; + let mut public_key_utf8_bytes = Vec::new(); + file.read_to_end(&mut public_key_utf8_bytes)?; + + let mut file = File::open(Path::new(EXAMPLE_RSA_PRIVATE_KEY_FILENAME))?; + let mut private_key_utf8_bytes = Vec::new(); + file.read_to_end(&mut private_key_utf8_bytes)?; + + // 2. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + let raw_rsa_keyring = mpl + .create_raw_rsa_keyring() + .key_name("my-rsa-key-name") + .key_namespace("my-key-namespace") + .padding_scheme(PaddingScheme::OaepSha256Mgf1) + .public_key(public_key_utf8_bytes) + .private_key(private_key_utf8_bytes) + .send() + .await?; + + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("sort_key".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ("sensitive_data".to_string(), CryptoAction::EncryptAndSign), + ]); + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(raw_rsa_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 6. Create a new AWS SDK DynamoDb client using the config above + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 7. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + let item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("rawRsaKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "sensitive_data".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 8. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + + let key_to_get = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("rawRsaKeyringItem".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + + let resp = ddb + .get_item() + .table_name(ddb_table_name) + .set_key(Some(key_to_get)) + // In this example we configure a strongly consistent read + // because we perform a read immediately after a write (for demonstrative purposes). + // By default, reads are only eventually consistent. + // Read our docs to determine which read consistency to use for your application: + // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html + .consistent_read(true) + .send() + .await?; + + assert_eq!(resp.item, Some(item)); + println!("raw_rsa_keyring successful."); + Ok(()) +} + +fn exists(f: &str) -> bool { + Path::new(f).exists() +} +fn should_generate_new_rsa_key_pair() -> Result { + // If a key pair already exists: do not overwrite existing key pair + if exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) && exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME) { + Ok(false) + } + // If only one file is present: throw exception + else if exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) && !exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME) { + Err("Missing public key file at ".to_string() + EXAMPLE_RSA_PUBLIC_KEY_FILENAME) + } + // If a key pair already exists: do not overwrite existing key pair + else if exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) && !exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME) { + Err("Missing private key file at ".to_string() + EXAMPLE_RSA_PRIVATE_KEY_FILENAME) + } + // If neither file is present, generate a new key pair + else { + Ok(true) + } +} + +fn generate_rsa_key_pair() -> Result<(), crate::BoxError> { + use aws_lc_rs::encoding::AsDer; + use aws_lc_rs::encoding::Pkcs8V1Der; + use aws_lc_rs::encoding::PublicKeyX509Der; + use aws_lc_rs::rsa::KeySize; + use aws_lc_rs::rsa::PrivateDecryptingKey; + + // Safety check: Validate neither file is present + if exists(EXAMPLE_RSA_PRIVATE_KEY_FILENAME) || exists(EXAMPLE_RSA_PUBLIC_KEY_FILENAME) { + return Err(crate::BoxError( + "generate_rsa_key_pair will not overwrite existing PEM files".to_string(), + )); + } + + // This code will generate a new RSA key pair for example use. + // The public and private key will be written to the files: + // - public: EXAMPLE_RSA_PUBLIC_KEY_FILENAME + // - private: EXAMPLE_RSA_PRIVATE_KEY_FILENAME + // This example uses aws-lc-rs's KeyPairGenerator to generate the key pair. + // In practice, you should not generate this in your code, and should instead + // retrieve this key from a secure key management system (e.g. HSM) + // This key is created here for example purposes only. + + let private_key = PrivateDecryptingKey::generate(KeySize::Rsa2048)?; + let public_key = private_key.public_key(); + + let public_key = AsDer::::as_der(&public_key)?; + let public_key = pem::Pem::new("RSA PUBLIC KEY", public_key.as_ref()); + let public_key = pem::encode(&public_key); + + let private_key = AsDer::::as_der(&private_key)?; + let private_key = pem::Pem::new("RSA PRIVATE KEY", private_key.as_ref()); + let private_key = pem::encode(&private_key); + + std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(Path::new(EXAMPLE_RSA_PRIVATE_KEY_FILENAME))? + .write_all(private_key.as_bytes())?; + + std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(Path::new(EXAMPLE_RSA_PUBLIC_KEY_FILENAME))? + .write_all(public_key.as_bytes())?; + + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/main.rs b/DynamoDbEncryption/runtimes/rust/examples/main.rs new file mode 100644 index 000000000..f82979bcb --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/main.rs @@ -0,0 +1,85 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![deny(warnings, unconditional_panic)] +#![deny(nonstandard_style)] +#![deny(clippy::all)] + +pub mod basic_get_put_example; +pub mod clientsupplier; +pub mod create_keystore_key; +pub mod get_encrypted_data_key_description; +pub mod itemencryptor; +pub mod keyring; +pub mod multi_get_put_example; +pub mod searchableencryption; +pub mod test_utils; + +use std::convert::From; + +// Why two types? +// return type from main must impl Debug +// but if impl Debug for BoxError +// then I can't impl From for BoxError +// because there's already a impl From for T; + +pub struct BoxError(String); +pub struct BoxError2(String); + +impl std::fmt::Debug for BoxError2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for BoxError2 { + fn from(error: BoxError) -> Self { + BoxError2(error.0) + } +} + +impl From for BoxError { + fn from(error: T) -> Self { + let my_str = format!("{:?}", error); + BoxError(my_str) + } +} + +#[tokio::main] +pub async fn main() -> Result<(), BoxError2> { + basic_get_put_example::put_item_get_item().await?; + itemencryptor::item_encrypt_decrypt::encrypt_decrypt().await?; + get_encrypted_data_key_description::get_encrypted_data_key_description().await?; + multi_get_put_example::multi_put_get().await?; + keyring::raw_rsa_keyring::put_item_get_item().await?; + keyring::kms_rsa_keyring::put_item_get_item().await?; + keyring::multi_mrk_keyring::put_item_get_item().await?; + keyring::raw_aes_keyring::put_item_get_item().await?; + keyring::multi_keyring::put_item_get_item().await?; + keyring::mrk_discovery_multi_keyring::put_item_get_item().await?; + clientsupplier::client_supplier_example::put_item_get_item().await?; + + let key_id = create_keystore_key::keystore_create_key().await?; + let key_id2 = create_keystore_key::keystore_create_key().await?; + // Key creation is eventually consistent, so wait 5 seconds to decrease the likelihood + // our test fails due to eventual consistency issues. + println!("Key Store Keys created. Waiting 5 seconds for consistency."); + std::thread::sleep(std::time::Duration::from_secs(5)); + + keyring::hierarchical_keyring::put_item_get_item(&key_id, &key_id2).await?; + + searchableencryption::basic_searchable_encryption::put_and_query_with_beacon(&key_id).await?; + searchableencryption::beacon_styles_searchable_encryption::put_and_query_with_beacon(&key_id) + .await?; + searchableencryption::compound_beacon_searchable_encryption::put_and_query_with_beacon(&key_id) + .await?; + searchableencryption::virtual_beacon_searchable_encryption::put_and_query_with_beacon(&key_id) + .await?; + searchableencryption::complexexample::complex_searchable_encryption::run_example(&key_id) + .await?; + + // ScanError will have to wait until we have a reasonable error message strategy + + println!("All examples completed successfully.\n"); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/multi_get_put_example.rs b/DynamoDbEncryption/runtimes/rust/examples/multi_get_put_example.rs new file mode 100644 index 000000000..b5b5e11ca --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/multi_get_put_example.rs @@ -0,0 +1,251 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_materialProviders::types::DbeAlgorithmSuiteId; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::types::dynamo_db_tables_encryption_config::DynamoDbTablesEncryptionConfig; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + and uses the low level PutItem and GetItem DDB APIs to demonstrate + putting a client-side encrypted item into DynamoDb + and then retrieving and decrypting that item from DynamoDb. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +*/ + +pub async fn multi_put_get() -> Result<(), crate::BoxError> { + let kms_key_id = test_utils::TEST_KMS_KEY_ID; + let ddb_table_name = test_utils::TEST_DDB_TABLE_NAME; + + // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + // For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + // We will use the `CreateMrkMultiKeyring` method to create this keyring, + // as it will correctly handle both single region and Multi-Region KMS Keys. + let provider_config = MaterialProvidersConfig::builder().build()?; + let mat_prov = client::Client::from_conf(provider_config)?; + let kms_keyring = mat_prov + .create_aws_kms_mrk_multi_keyring() + .generator(kms_key_id) + .send() + .await?; + + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + let attribute_actions_on_encrypt = HashMap::from([ + ("partition_key".to_string(), CryptoAction::SignOnly), + ("sort_key".to_string(), CryptoAction::SignOnly), + ("attribute1".to_string(), CryptoAction::EncryptAndSign), + ("attribute2".to_string(), CryptoAction::SignOnly), + (":attribute3".to_string(), CryptoAction::DoNothing), + ]); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we have designed our DynamoDb table such that any attribute name with + // the ":" prefix should be considered unauthenticated. + const UNSIGNED_ATTR_PREFIX: &str = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .sort_key_name("sort_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(kms_keyring) + .allowed_unsigned_attribute_prefix(UNSIGNED_ATTR_PREFIX) + // Specifying an algorithm suite is not required, + // but is done here to demonstrate how to do so. + // We suggest using the + // `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + // which includes AES-GCM with key derivation, signing, and key commitment. + // This is also the default algorithm suite if one is not specified in this config. + // For more information on supported algorithm suites, see: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + .algorithm_suite_id( + DbeAlgorithmSuiteId::AlgAes256GcmHkdfSha512CommitKeyEcdsaP384SymsigHmacSha384, + ) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 5. Create a new AWS SDK DynamoDb client using the TableEncryptionConfigs + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 6. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + let batch_write_item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("BatchWriteItemExample".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "attribute1".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ( + "attribute2".to_string(), + AttributeValue::S("sign me!".to_string()), + ), + ( + ":attribute3".to_string(), + AttributeValue::S("ignore me!".to_string()), + ), + ]); + let put_request = aws_sdk_dynamodb::types::PutRequest::builder() + .set_item(Some(batch_write_item)) + .build()?; + + let batch_write_request = aws_sdk_dynamodb::types::WriteRequest::builder() + .put_request(put_request) + .build(); + + let transact_write_item = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("TransactWriteItemExample".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ( + "attribute1".to_string(), + AttributeValue::S("encrypt and sign me!".to_string()), + ), + ( + "attribute2".to_string(), + AttributeValue::S("sign me!".to_string()), + ), + ( + ":attribute3".to_string(), + AttributeValue::S("ignore me!".to_string()), + ), + ]); + let transact_put = aws_sdk_dynamodb::types::Put::builder() + .table_name(ddb_table_name) + .set_item(Some(transact_write_item)) + .build()?; + + let transact_item = aws_sdk_dynamodb::types::TransactWriteItem::builder() + .put(transact_put) + .build(); + + ddb.batch_write_item() + .request_items(ddb_table_name, vec![batch_write_request]) + .send() + .await?; + + ddb.transact_write_items() + .transact_items(transact_item) + .send() + .await?; + + // 7. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + let batch_get_keys = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("BatchWriteItemExample".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + let keys_and_attr = aws_sdk_dynamodb::types::KeysAndAttributes::builder() + .keys(batch_get_keys) + .consistent_read(true) + .build()?; + + let batch_get_response = ddb + .batch_get_item() + .request_items(ddb_table_name, keys_and_attr) + .send() + .await?; + + let returned_item = &batch_get_response.responses.unwrap()[ddb_table_name][0]; + assert_eq!( + returned_item["attribute1"], + AttributeValue::S("encrypt and sign me!".to_string()) + ); + + let transact_get_keys = HashMap::from([ + ( + "partition_key".to_string(), + AttributeValue::S("TransactWriteItemExample".to_string()), + ), + ("sort_key".to_string(), AttributeValue::N("0".to_string())), + ]); + let transact_get = aws_sdk_dynamodb::types::Get::builder() + .table_name(ddb_table_name) + .set_key(Some(transact_get_keys)) + .build()?; + + let transact_get_item = aws_sdk_dynamodb::types::TransactGetItem::builder() + .get(transact_get) + .build(); + + let transact_get_response = ddb + .transact_get_items() + .transact_items(transact_get_item) + .send() + .await?; + + let the_item = transact_get_response.responses.as_ref().unwrap()[0] + .item + .as_ref() + .unwrap(); + assert_eq!( + the_item["attribute1"], + AttributeValue::S("encrypt and sign me!".to_string()) + ); + + println!("multi_put_get successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/basic_searchable_encryption.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/basic_searchable_encryption.rs new file mode 100644 index 000000000..55d0fd05d --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/basic_searchable_encryption.rs @@ -0,0 +1,356 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_materialProviders::client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconKeySource; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconVersion; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SearchConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SingleKeyStore; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::StandardBeacon; +use aws_db_esdk::aws_cryptography_keyStore::client as keystore_client; +use aws_db_esdk::aws_cryptography_keyStore::types::key_store_config::KeyStoreConfig; +use aws_db_esdk::aws_cryptography_keyStore::types::KmsConfiguration; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::types::dynamo_db_tables_encryption_config::DynamoDbTablesEncryptionConfig; + +/* + This example demonstrates how to set up a beacon on an encrypted attribute, + put an item with the beacon, and query against that beacon. + This example follows a use case of a database that stores unit inspection information. + + Running this example requires access to a DDB table with the + following key configuration: + - Partition key is named "work_id" with type (S) + - Sort key is named "inspection_date" with type (S) + This table must have a Global Secondary Index (GSI) configured named "last4-unit-index": + - Partition key is named "aws_dbe_b_inspector_id_last4" with type (S) + - Sort key is named "aws_dbe_b_unit" with type (S) + + In this example for storing unit inspection information, this schema is utilized for the data: + - "work_id" stores a unique identifier for a unit inspection work order (v4 UUID) + - "inspection_date" stores an ISO 8601 date for the inspection (YYYY-MM-DD) + - "inspector_id_last4" stores the last 4 digits of the ID of the inspector performing the work + - "unit" stores a 12-digit serial number for the unit being inspected + + The example requires the following ordered input command line parameters: + 1. DDB table name for table to put/query data from + 2. Branch key ID for a branch key that was previously created in your key store. See the + CreateKeyStoreKeyExample. + 3. Branch key wrapping KMS key ARN for the KMS key used to create the branch key with ID + provided in arg 2 + 4. Branch key DDB table name for the DDB table representing the branch key store +*/ + +const GSI_NAME: &str = "last4-unit-index"; + +pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::UNIT_INSPECTION_TEST_DDB_TABLE_NAME; + let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN; + let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME; + + // 1. Configure Beacons. + // The beacon name must be the name of a table attribute that will be encrypted. + // The `length` parameter dictates how many bits are in the beacon attribute value. + // The following link provides guidance on choosing a beacon length: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html + + // The configured DDB table has a GSI on the `aws_dbe_b_inspector_id_last4` AttributeName. + // This field holds the last 4 digits of an inspector ID. + // For our example, this field may range from 0 to 9,999 (10,000 possible values). + // For our example, we assume a full inspector ID is an integer + // ranging from 0 to 99,999,999. We do not assume that the full inspector ID's + // values are uniformly distributed across its range of possible values. + // In many use cases, the prefix of an identifier encodes some information + // about that identifier (e.g. zipcode and SSN prefixes encode geographic + // information), while the suffix does not and is more uniformly distributed. + // We will assume that the inspector ID field matches a similar use case. + // So for this example, we only store and use the last + // 4 digits of the inspector ID, which we assume is uniformly distributed. + // Since the full ID's range is divisible by the range of the last 4 digits, + // then the last 4 digits of the inspector ID are uniformly distributed + // over the range from 0 to 9,999. + // See our documentation for why you should avoid creating beacons over non-uniform distributions + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption.html#are-beacons-right-for-me + // A single inspector ID suffix may be assigned to multiple `work_id`s. + // + // This link provides guidance for choosing a beacon length: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html + // We follow the guidance in the link above to determine reasonable bounds + // for the length of a beacon on the last 4 digits of an inspector ID: + // - min: log(sqrt(10,000))/log(2) ~= 6.6, round up to 7 + // - max: log((10,000/2))/log(2) ~= 12.3, round down to 12 + // You will somehow need to round results to a nearby integer. + // We choose to round to the nearest integer; you might consider a different rounding approach. + // Rounding up will return fewer expected "false positives" in queries, + // leading to fewer decrypt calls and better performance, + // but it is easier to identify which beacon values encode distinct plaintexts. + // Rounding down will return more expected "false positives" in queries, + // leading to more decrypt calls and worse performance, + // but it is harder to identify which beacon values encode distinct plaintexts. + // We can choose a beacon length between 7 and 12: + // - Closer to 7, we expect more "false positives" to be returned, + // making it harder to identify which beacon values encode distinct plaintexts, + // but leading to more decrypt calls and worse performance + // - Closer to 12, we expect fewer "false positives" returned in queries, + // leading to fewer decrypt calls and better performance, + // but it is easier to identify which beacon values encode distinct plaintexts. + // As an example, we will choose 10. + // + // Values stored in aws_dbe_b_inspector_id_last4 will be 10 bits long (0x000 - 0x3ff) + // There will be 2^10 = 1,024 possible HMAC values. + // With a sufficiently large number of well-distributed inspector IDs, + // for a particular beacon we expect (10,000/1,024) ~= 9.8 4-digit inspector ID suffixes + // sharing that beacon value. + let last4_beacon = StandardBeacon::builder() + .name("inspector_id_last4") + .length(10) + .build()?; + + // The configured DDB table has a GSI on the `aws_dbe_b_unit` AttributeName. + // This field holds a unit serial number. + // For this example, this is a 12-digit integer from 0 to 999,999,999,999 (10^12 possible values). + // We will assume values for this attribute are uniformly distributed across this range. + // A single unit serial number may be assigned to multiple `work_id`s. + // + // This link provides guidance for choosing a beacon length: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html + // We follow the guidance in the link above to determine reasonable bounds + // for the length of a beacon on a unit serial number: + // - min: log(sqrt(999,999,999,999))/log(2) ~= 19.9, round up to 20 + // - max: log((999,999,999,999/2))/log(2) ~= 38.9, round up to 39 + // We can choose a beacon length between 20 and 39: + // - Closer to 20, we expect more "false positives" to be returned, + // making it harder to identify which beacon values encode distinct plaintexts, + // but leading to more decrypt calls and worse performance + // - Closer to 39, we expect fewer "false positives" returned in queries, + // leading to fewer decrypt calls and better performance, + // but it is easier to identify which beacon values encode distinct plaintexts. + // As an example, we will choose 30. + // + // Values stored in aws_dbe_b_unit will be 30 bits long (0x00000000 - 0x3fffffff) + // There will be 2^30 = 1,073,741,824 ~= 1.1B possible HMAC values. + // With a sufficiently large number of well-distributed inspector IDs, + // for a particular beacon we expect (10^12/2^30) ~= 931.3 unit serial numbers + // sharing that beacon value. + let unit_beacon = StandardBeacon::builder().name("unit").length(30).build()?; + + let standard_beacon_list = vec![last4_beacon, unit_beacon]; + + // 2. Configure Keystore. + // The keystore is a separate DDB table where the client stores encryption and decryption materials. + // In order to configure beacons on the DDB client, you must configure a keystore. + // + // This example expects that you have already set up a KeyStore with a single branch key. + // See the "Create KeyStore Table Example" and "Create KeyStore Key Example" for how to do this. + // After you create a branch key, you should persist its ID for use in this example. + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let key_store_config = KeyStoreConfig::builder() + .kms_client(aws_sdk_kms::Client::new(&sdk_config)) + .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config)) + .ddb_table_name(branch_key_ddb_table_name) + .logical_key_store_name(branch_key_ddb_table_name) + .kms_configuration(KmsConfiguration::KmsKeyArn( + branch_key_wrapping_kms_key_arn.to_string(), + )) + .build()?; + + let key_store = keystore_client::Client::from_conf(key_store_config)?; + + // 3. Create BeaconVersion. + // The BeaconVersion inside the list holds the list of beacons on the table. + // The BeaconVersion also stores information about the keystore. + // BeaconVersion must be provided: + // - keyStore: The keystore configured in step 2. + // - keySource: A configuration for the key source. + // For simple use cases, we can configure a 'singleKeySource' which + // statically configures a single beaconKey. That is the approach this example takes. + // For use cases where you want to use different beacon keys depending on the data + // (for example if your table holds data for multiple tenants, and you want to use + // a different beacon key per tenant), look into configuring a MultiKeyStore: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption-multitenant.html + + let beacon_version = BeaconVersion::builder() + .standard_beacons(standard_beacon_list) + .version(1) // MUST be 1 + .key_store(key_store.clone()) + .key_source(BeaconKeySource::Single( + SingleKeyStore::builder() + // `keyId` references a beacon key. + // For every branch key we create in the keystore, + // we also create a beacon key. + // This beacon key is not the same as the branch key, + // but is created with the same ID as the branch key. + .key_id(branch_key_id) + .cache_ttl(6000) + .build()?, + )) + .build()?; + let beacon_versions = vec![beacon_version]; + + // 4. Create a Hierarchical Keyring + // This is a KMS keyring that utilizes the keystore table. + // This config defines how items are encrypted and decrypted. + // NOTE: You should configure this to use the same keystore as your search config. + let provider_config = MaterialProvidersConfig::builder().build()?; + let mat_prov = client::Client::from_conf(provider_config)?; + let kms_keyring = mat_prov + .create_aws_kms_hierarchical_keyring() + .branch_key_id(branch_key_id) + .key_store(key_store) + .ttl_seconds(6000) + .send() + .await?; + + // 5. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Any attributes that will be used in beacons must be configured as ENCRYPT_AND_SIGN. + let attribute_actions_on_encrypt = HashMap::from([ + ("work_id".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("inspection_date".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ( + "inspector_id_last4".to_string(), + CryptoAction::EncryptAndSign, + ), // Beaconized attributes must be encrypted + ("unit".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted + ]); + + // 6. Create the DynamoDb Encryption configuration for the table we will be writing to. + // The beaconVersions are added to the search configuration. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("work_id") + .sort_key_name("inspection_date") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(kms_keyring) + .search( + SearchConfig::builder() + .write_version(1) // MUST be 1 + .versions(beacon_versions) + .build()?, + ) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 7. Create a new AWS SDK DynamoDb client using the TableEncryptionConfigs + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 8. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + // Since our configuration includes beacons for `inspector_id_last4` and `unit`, + // the client will add two additional attributes to the item. These attributes will have names + // `aws_dbe_b_inspector_id_last4` and `aws_dbe_b_unit`. Their values will be HMACs + // truncated to as many bits as the beacon's `length` parameter; e.g. + // aws_dbe_b_inspector_id_last4 = truncate(HMAC("4321"), 10) + // aws_dbe_b_unit = truncate(HMAC("123456789012"), 30) + + let item = HashMap::from([ + ( + "work_id".to_string(), + AttributeValue::S("1313ba89-5661-41eb-ba6c-cb1b4cb67b2d".to_string()), + ), + ( + "inspection_date".to_string(), + AttributeValue::S("2023-06-13".to_string()), + ), + ( + "inspector_id_last4".to_string(), + AttributeValue::S("4321".to_string()), + ), + ( + "unit".to_string(), + AttributeValue::S("123456789012".to_string()), + ), + ]); + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 9. Query for the item we just put. + // Note that we are constructing the query as if we were querying on plaintext values. + // However, the DDB encryption client will detect that this attribute name has a beacon configured. + // The client will add the beaconized attribute name and attribute value to the query, + // and transform the query to use the beaconized name and value. + // Internally, the client will query for and receive all items with a matching HMAC value in the beacon field. + // This may include a number of "false positives" with different ciphertext, but the same truncated HMAC. + // e.g. if truncate(HMAC("123456789012"), 30) + // == truncate(HMAC("098765432109"), 30), + // the query will return both items. + // The client will decrypt all returned items to determine which ones have the expected attribute values, + // and only surface items with the correct plaintext to the user. + // This procedure is internal to the client and is abstracted away from the user; + // e.g. the user will only see "123456789012" and never + // "098765432109", though the actual query returned both. + let expression_attributes_names = HashMap::from([ + ("#last4".to_string(), "inspector_id_last4".to_string()), + ("#unit".to_string(), "unit".to_string()), + ]); + + let expression_attribute_values = HashMap::from([ + (":last4".to_string(), AttributeValue::S("4321".to_string())), + ( + ":unit".to_string(), + AttributeValue::S("123456789012".to_string()), + ), + ]); + + // GSIs do not update instantly + // so if the results come back empty + // we retry after a short sleep + for _i in 0..10 { + let query_response = ddb + .query() + .table_name(ddb_table_name) + .index_name(GSI_NAME) + .key_condition_expression("#last4 = :last4 and #unit = :unit") + .set_expression_attribute_names(Some(expression_attributes_names.clone())) + .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .send() + .await?; + + // if no results, sleep and try again + if query_response.items.is_none() || query_response.items.as_ref().unwrap().is_empty() { + std::thread::sleep(std::time::Duration::from_millis(20)); + continue; + } + + let attribute_values = query_response.items.unwrap(); + // Validate only 1 item was returned: the item we just put + assert_eq!(attribute_values.len(), 1); + let returned_item = &attribute_values[0]; + // Validate the item has the expected attributes + assert_eq!( + returned_item["inspector_id_last4"], + AttributeValue::S("4321".to_string()) + ); + assert_eq!( + returned_item["unit"], + AttributeValue::S("123456789012".to_string()) + ); + break; + } + println!("basic_searchable_encryption successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/beacon_styles_searchable_encryption.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/beacon_styles_searchable_encryption.rs new file mode 100644 index 000000000..a7d359dfc --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/beacon_styles_searchable_encryption.rs @@ -0,0 +1,409 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconKeySource; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconStyle; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconVersion; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::CompoundBeacon; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::EncryptedPart; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SearchConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SingleKeyStore; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::StandardBeacon; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::*; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_keyStore::client as keystore_client; +use aws_db_esdk::aws_cryptography_keyStore::types::key_store_config::KeyStoreConfig; +use aws_db_esdk::aws_cryptography_keyStore::types::KmsConfiguration; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + This example demonstrates how to use Beacons Styles on Standard Beacons on encrypted attributes, + put an item with the beacon, and query against that beacon. + This example follows a use case of a database that stores food information. + This is an extension of the "BasicSearchableEncryptionExample" in this directory + and uses the same table schema. + + Running this example requires access to a DDB table with the + following key configuration: + - Partition key is named "work_id" with type (S) + - Sort key is named "inspection_time" with type (S) + + In this example for storing food information, this schema is utilized for the data: + - "work_id" stores a unique identifier for a unit inspection work order (v4 UUID) + - "inspection_date" stores an ISO 8601 date for the inspection (YYYY-MM-DD) + - "fruit" stores one type of fruit + - "basket" stores a set of types of fruit + - "dessert" stores one type of dessert + - "veggies" stores a set of types of vegetable + - "work_type" stores a unit inspection category + + The example requires the following ordered input command line parameters: + 1. DDB table name for table to put/query data from + 2. Branch key ID for a branch key that was previously created in your key store. See the + CreateKeyStoreKeyExample. + 3. Branch key wrapping KMS key ARN for the KMS key used to create the branch key with ID + provided in arg 2 + 4. Branch key DDB table ARN for the DDB table representing the branch key store +*/ + +pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::UNIT_INSPECTION_TEST_DDB_TABLE_NAME; + let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN; + let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME; + + // 1. Create Beacons. + let standard_beacon_list = vec![ + // The fruit beacon allows searching on the encrypted fruit attribute + // We have selected 30 as an example beacon length, but you should go to + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html + // when creating your beacons. + StandardBeacon::builder().name("fruit").length(30).build()?, + // The basket beacon allows searching on the encrypted basket attribute + // Basket is used as a Set, and therefore needs a beacon style to reflect that. + // Further, we need to be able to compare the items in basket to the fruit attribute + // so we `share` this beacon with `fruit`. + // Since we need both of these things, we use the SharedSet style. + StandardBeacon::builder() + .name("basket") + .length(30) + .style(BeaconStyle::SharedSet( + SharedSet::builder().other("fruit").build()?, + )) + .build()?, + // The dessert beacon allows searching on the encrypted dessert attribute + // We need to be able to compare the dessert attribute to the fruit attribute + // so we `share` this beacon with `fruit`. + StandardBeacon::builder() + .name("dessert") + .length(30) + .style(BeaconStyle::Shared( + Shared::builder().other("fruit").build()?, + )) + .build()?, + // The veggieBeacon allows searching on the encrypted veggies attribute + // veggies is used as a Set, and therefore needs a beacon style to reflect that. + StandardBeacon::builder() + .name("veggies") + .length(30) + .style(BeaconStyle::AsSet(AsSet::builder().build()?)) + .build()?, + // The work_typeBeacon allows searching on the encrypted work_type attribute + // We only use it as part of the compound work_unit beacon, + // so we disable its use as a standalone beacon + StandardBeacon::builder() + .name("work_type") + .length(30) + .style(BeaconStyle::PartOnly(PartOnly::builder().build()?)) + .build()?, + ]; + + // Here we build a compound beacon from work_id and work_type + // If we had tried to make a StandardBeacon from work_type, we would have seen an error + // because work_type is "PartOnly" + let encrypted_part_list = vec![EncryptedPart::builder() + .name("work_type") + .prefix("T-") + .build()?]; + + let signed_part_list = vec![SignedPart::builder().name("work_id").prefix("I-").build()?]; + + let compound_beacon_list = vec![CompoundBeacon::builder() + .name("work_unit") + .split(".") + .encrypted(encrypted_part_list) + .signed(signed_part_list) + .build()?]; + + // 2. Configure the Keystore + // These are the same constructions as in the Basic example, which describes these in more detail. + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let key_store_config = KeyStoreConfig::builder() + .kms_client(aws_sdk_kms::Client::new(&sdk_config)) + .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config)) + .ddb_table_name(branch_key_ddb_table_name) + .logical_key_store_name(branch_key_ddb_table_name) + .kms_configuration(KmsConfiguration::KmsKeyArn( + branch_key_wrapping_kms_key_arn.to_string(), + )) + .build()?; + + let key_store = keystore_client::Client::from_conf(key_store_config)?; + + // 3. Create BeaconVersion. + // This is similar to the Basic example + let beacon_version = BeaconVersion::builder() + .standard_beacons(standard_beacon_list) + .compound_beacons(compound_beacon_list) + .version(1) // MUST be 1 + .key_store(key_store.clone()) + .key_source(BeaconKeySource::Single( + SingleKeyStore::builder() + // `keyId` references a beacon key. + // For every branch key we create in the keystore, + // we also create a beacon key. + // This beacon key is not the same as the branch key, + // but is created with the same ID as the branch key. + .key_id(branch_key_id) + .cache_ttl(6000) + .build()?, + )) + .build()?; + let beacon_versions = vec![beacon_version]; + + // 4. Create a Hierarchical Keyring + // This is the same configuration as in the Basic example. + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + let kms_keyring = mpl + .create_aws_kms_hierarchical_keyring() + .branch_key_id(branch_key_id) + .key_store(key_store) + .ttl_seconds(6000) + .send() + .await?; + + // 5. Configure which attributes are encrypted and/or signed when writing new items. + let attribute_actions_on_encrypt = HashMap::from([ + ("work_id".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("inspection_date".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ("dessert".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted + ("fruit".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted + ("basket".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted + ("veggies".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted + ("work_type".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted + ]); + + // 6. Create the DynamoDb Encryption configuration for the table we will be writing to. + // The beaconVersions are added to the search configuration. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("work_id") + .sort_key_name("inspection_date") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(kms_keyring) + .search( + SearchConfig::builder() + .write_version(1) // MUST be 1 + .versions(beacon_versions) + .build()?, + ) + .build()?; + + // 7. Create config + let encryption_config = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 8. Create item one, specifically with "dessert != fruit", and "fruit in basket". + let item1 = HashMap::from([ + ("work_id".to_string(), AttributeValue::S("1".to_string())), + ( + "inspection_date".to_string(), + AttributeValue::S("2023-06-13".to_string()), + ), + ("dessert".to_string(), AttributeValue::S("cake".to_string())), + ("fruit".to_string(), AttributeValue::S("banana".to_string())), + ( + "basket".to_string(), + AttributeValue::Ss(vec![ + "banana".to_string(), + "apple".to_string(), + "pear".to_string(), + ]), + ), + ( + "veggies".to_string(), + AttributeValue::Ss(vec![ + "beans".to_string(), + "carrots".to_string(), + "celery".to_string(), + ]), + ), + ( + "work_type".to_string(), + AttributeValue::S("small".to_string()), + ), + ]); + + // 9. Create item two, specifically with "dessert == fruit", and "fruit not in basket". + let item2 = HashMap::from([ + ("work_id".to_string(), AttributeValue::S("2".to_string())), + ( + "inspection_date".to_string(), + AttributeValue::S("2023-06-13".to_string()), + ), + ( + "dessert".to_string(), + AttributeValue::S("orange".to_string()), + ), + ("fruit".to_string(), AttributeValue::S("orange".to_string())), + ( + "basket".to_string(), + AttributeValue::Ss(vec![ + "strawberry".to_string(), + "blueberry".to_string(), + "blackberry".to_string(), + ]), + ), + ( + "veggies".to_string(), + AttributeValue::Ss(vec![ + "beans".to_string(), + "carrots".to_string(), + "peas".to_string(), + ]), + ), + ( + "work_type".to_string(), + AttributeValue::S("large".to_string()), + ), + ]); + + // 10. Create a new AWS SDK DynamoDb client using the DynamoDb Config above + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(encryption_config)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 11. Add the two items + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item1.clone())) + .send() + .await?; + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item2.clone())) + .send() + .await?; + + // 12. Test the first type of Set operation : + // Select records where the basket attribute holds a particular value + let expression_attribute_values = HashMap::from([( + ":value".to_string(), + AttributeValue::S("banana".to_string()), + )]); + + let scan_response = ddb + .scan() + .table_name(ddb_table_name) + .filter_expression("contains(basket, :value)") + .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .send() + .await?; + + let attribute_values = scan_response.items.unwrap(); + // Validate only 1 item was returned: item1 + assert_eq!(attribute_values.len(), 1); + let returned_item = &attribute_values[0]; + // Validate the item has the expected attributes + assert_eq!(returned_item["work_id"], item1["work_id"]); + + // 13. Test the second type of Set operation : + // Select records where the basket attribute holds the fruit attribute + let scan_response = ddb + .scan() + .table_name(ddb_table_name) + .filter_expression("contains(basket, fruit)") + .send() + .await?; + + let attribute_values = scan_response.items.unwrap(); + // Validate only 1 item was returned: item1 + assert_eq!(attribute_values.len(), 1); + let returned_item = &attribute_values[0]; + // Validate the item has the expected attributes + assert_eq!(returned_item["work_id"], item1["work_id"]); + + // 14. Test the third type of Set operation : + // Select records where the fruit attribute exists in a particular set + let basket3 = vec![ + "boysenberry".to_string(), + "orange".to_string(), + "grape".to_string(), + ]; + let expression_attribute_values = + HashMap::from([(":value".to_string(), AttributeValue::Ss(basket3))]); + + let scan_response = ddb + .scan() + .table_name(ddb_table_name) + .filter_expression("contains(:value, fruit)") + .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .send() + .await?; + + let attribute_values = scan_response.items.unwrap(); + // Validate only 1 item was returned: item1 + assert_eq!(attribute_values.len(), 1); + let returned_item = &attribute_values[0]; + // Validate the item has the expected attributes + assert_eq!(returned_item["work_id"], item2["work_id"]); + + // 15. Test a Shared search. Select records where the dessert attribute matches the fruit attribute + let scan_response = ddb + .scan() + .table_name(ddb_table_name) + .filter_expression("dessert = fruit") + .send() + .await?; + + let attribute_values = scan_response.items.unwrap(); + // Validate only 1 item was returned: item1 + assert_eq!(attribute_values.len(), 1); + let returned_item = &attribute_values[0]; + // Validate the item has the expected attributes + assert_eq!(returned_item["work_id"], item2["work_id"]); + + // 15. Test the AsSet attribute 'veggies' : + // Select records where the veggies attribute holds a particular value + let expression_attribute_values = + HashMap::from([(":value".to_string(), AttributeValue::S("peas".to_string()))]); + + let scan_response = ddb + .scan() + .table_name(ddb_table_name) + .filter_expression("contains(veggies, :value)") + .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .send() + .await?; + + let attribute_values = scan_response.items.unwrap(); + // Validate only 1 item was returned: item1 + assert_eq!(attribute_values.len(), 1); + let returned_item = &attribute_values[0]; + // Validate the item has the expected attributes + assert_eq!(returned_item["work_id"], item2["work_id"]); + + // 16. Test the compound beacon 'work_unit' : + let expression_attribute_values = HashMap::from([( + ":value".to_string(), + AttributeValue::S("I-1.T-small".to_string()), + )]); + + let scan_response = ddb + .scan() + .table_name(ddb_table_name) + .filter_expression("work_unit = :value") + .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .send() + .await?; + + let attribute_values = scan_response.items.unwrap(); + // Validate only 1 item was returned: item1 + assert_eq!(attribute_values.len(), 1); + let returned_item = &attribute_values[0]; + // Validate the item has the expected attributes + assert_eq!(returned_item["work_id"], item1["work_id"]); + + println!("beacon_styles_searchable_encryption successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/README.md b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/README.md new file mode 100644 index 000000000..bdbba2d7c --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/README.md @@ -0,0 +1,23 @@ +# ComplexSearchableEncryptionExample + +This example demonstrates complex queries +you can perform using beacons. +The example data used is for demonstrative purposes only, +and might not meet the distribution and correlation uniqueness +recommendations for beacons. +See our documentation for whether beacons are +right for your particular data set: +https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption.html#are-beacons-right-for-me + +This example uses a more complex searchable encryption configuration than other examples. +This is intended to demonstrate how to set up a more complicated searchable encryption configuration. +This also walks through some example query expressions one can use to search their encrypted data. + +``` +. +├── complex_searchable_encryption.rs // Main entry point for example +├── beacon_config.rs // Sets up beacons and searchable encryption configuration +├── put_requests.rs // PUT requests added to the DDB table +├── query_requests.rs // QUERY requests executed on the DDB table +└── README.md // this file +``` diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/beacon_config.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/beacon_config.rs new file mode 100644 index 000000000..5d248c425 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/beacon_config.rs @@ -0,0 +1,554 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconKeySource; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconVersion; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::CompoundBeacon; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::Constructor; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::ConstructorPart; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::EncryptedPart; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SearchConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SignedPart; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SingleKeyStore; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::StandardBeacon; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_keyStore::client as keystore_client; +use aws_db_esdk::aws_cryptography_keyStore::types::key_store_config::KeyStoreConfig; +use aws_db_esdk::aws_cryptography_keyStore::types::KmsConfiguration; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use std::collections::HashMap; + +/* + * This file is used in an example to demonstrate complex queries + * that you can perform using beacons. + * The example data used is for demonstrative purposes only, + * and might not meet the distribution and correlation uniqueness + * recommendations for beacons. + * See our documentation for whether beacons are + * right for your particular data set: + * https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption.html#are-beacons-right-for-me + * + * This file sets up all the searchable encryption configuration required to execute the examples from + * our workshop using the encryption client. + */ + +pub async fn setup_beacon_config( + ddb_table_name: &str, + branch_key_id: &str, + branch_key_wrapping_kms_key_arn: &str, + branch_key_ddb_table_name: &str, +) -> Result { + // 1. Create keystore and branch key + // These are the same constructions as in the Basic examples, which describe this in more detail. + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let key_store_config = KeyStoreConfig::builder() + .kms_client(aws_sdk_kms::Client::new(&sdk_config)) + .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config)) + .ddb_table_name(branch_key_ddb_table_name) + .logical_key_store_name(branch_key_ddb_table_name) + .kms_configuration(KmsConfiguration::KmsKeyArn( + branch_key_wrapping_kms_key_arn.to_string(), + )) + .build()?; + + let key_store = keystore_client::Client::from_conf(key_store_config)?; + + // 2. Create standard beacons + // For this example, we use a standard beacon length of 4. + // The BasicSearchableEncryptionExample gives a more thorough consideration of beacon length. + // For production applications, one should always exercise rigor when deciding beacon length, including + // examining population size and considering performance. + let standard_beacon_list = vec![ + StandardBeacon::builder() + .name("EmployeeID") + .length(4) + .build()?, + StandardBeacon::builder() + .name("TicketNumber") + .length(4) + .build()?, + StandardBeacon::builder() + .name("ProjectName") + .length(4) + .build()?, + StandardBeacon::builder() + .name("EmployeeEmail") + .length(4) + .build()?, + StandardBeacon::builder() + .name("CreatorEmail") + .length(4) + .build()?, + StandardBeacon::builder() + .name("ProjectStatus") + .length(4) + .build()?, + StandardBeacon::builder() + .name("OrganizerEmail") + .length(4) + .build()?, + StandardBeacon::builder() + .name("ManagerEmail") + .length(4) + .build()?, + StandardBeacon::builder() + .name("AssigneeEmail") + .length(4) + .build()?, + StandardBeacon::builder() + .name("Severity") + .length(4) + .build()?, + StandardBeacon::builder() + .name("City") + .loc("Location.City") + .length(4) + .build()?, + StandardBeacon::builder() + .name("Building") + .loc("Location.Building") + .length(4) + .build()?, + StandardBeacon::builder() + .name("Floor") + .loc("Location.Floor") + .length(4) + .build()?, + StandardBeacon::builder() + .name("Room") + .loc("Location.Room") + .length(4) + .build()?, + StandardBeacon::builder() + .name("Desk") + .loc("Location.Desk") + .length(4) + .build()?, + ]; + + // 3. Define encrypted parts + // Note that some of the prefixes are modified from the suggested prefixes in Demo.md. + // This is because all prefixes must be unique in a configuration. + // Encrypted parts are described in more detail in the CompoundBeaconSearchableEncryptionExample. + let encrypted_parts_list = vec![ + EncryptedPart::builder() + .name("EmployeeID") + .prefix("E-") + .build()?, + EncryptedPart::builder() + .name("TicketNumber") + .prefix("T-") + .build()?, + EncryptedPart::builder() + .name("ProjectName") + .prefix("P-") + .build()?, + EncryptedPart::builder() + .name("EmployeeEmail") + .prefix("EE-") + .build()?, + EncryptedPart::builder() + .name("CreatorEmail") + .prefix("CE-") + .build()?, + EncryptedPart::builder() + .name("OrganizerEmail") + .prefix("OE-") + .build()?, + EncryptedPart::builder() + .name("ManagerEmail") + .prefix("ME-") + .build()?, + EncryptedPart::builder() + .name("AssigneeEmail") + .prefix("AE-") + .build()?, + EncryptedPart::builder() + .name("ProjectStatus") + .prefix("PSts-") + .build()?, + EncryptedPart::builder().name("City").prefix("C-").build()?, + EncryptedPart::builder() + .name("Severity") + .prefix("S-") + .build()?, + EncryptedPart::builder() + .name("Building") + .prefix("B-") + .build()?, + EncryptedPart::builder() + .name("Floor") + .prefix("F-") + .build()?, + EncryptedPart::builder().name("Room").prefix("R-").build()?, + EncryptedPart::builder().name("Desk").prefix("D-").build()?, + ]; + + // 4. Define signed parts + // These are unencrypted attributes we would like to use in beacon queries. + // In this example, all of these represent dates or times. + // Keeping these attributes unencrypted allows us to use them in comparison-based queries. If a signed + // part is the first part in a compound beacon, then that part can be used in comparison for sorting. + let signed_parts_list = vec![ + SignedPart::builder() + .name("TicketModTime") + .prefix("M-") + .build()?, + SignedPart::builder() + .name("MeetingStart") + .prefix("MS-") + .build()?, + SignedPart::builder() + .name("TimeCardStart") + .prefix("TC-") + .build()?, + SignedPart::builder() + .name("ProjectStart") + .prefix("PS-") + .build()?, + ]; + + // 5. Create constructor parts + // Constructor parts are used to assemble constructors (constructors described more in next step). + // For each attribute that will be used in a constructor, there must be a corresponding constructor part. + // A constructor part must receive: + // - name: Name of a standard beacon + // - required: Whether this attribute must be present in the item to match a constructor + // In this example, we will define each constructor part once and re-use it across multiple constructors. + // The parts below are defined by working backwards from the constructors in "PK Constructors", + // "SK constructors", etc. sections in Demo.md. + let employee_id_constructor_part = ConstructorPart::builder() + .name("EmployeeID") + .required(true) + .build()?; + let ticket_number_constructor_part = ConstructorPart::builder() + .name("TicketNumber") + .required(true) + .build()?; + let project_name_constructor_part = ConstructorPart::builder() + .name("ProjectName") + .required(true) + .build()?; + let ticket_mod_time_constructor_part = ConstructorPart::builder() + .name("TicketModTime") + .required(true) + .build()?; + let meeting_start_constructor_part = ConstructorPart::builder() + .name("MeetingStart") + .required(true) + .build()?; + let timecard_start_constructor_part = ConstructorPart::builder() + .name("TimeCardStart") + .required(true) + .build()?; + let employee_email_constructor_part = ConstructorPart::builder() + .name("EmployeeEmail") + .required(true) + .build()?; + let creator_email_constructor_part = ConstructorPart::builder() + .name("CreatorEmail") + .required(true) + .build()?; + let project_status_constructor_part = ConstructorPart::builder() + .name("ProjectStatus") + .required(true) + .build()?; + let organizer_email_constructor_part = ConstructorPart::builder() + .name("OrganizerEmail") + .required(true) + .build()?; + let project_start_constructor_part = ConstructorPart::builder() + .name("ProjectStart") + .required(true) + .build()?; + let manager_email_constructor_part = ConstructorPart::builder() + .name("ManagerEmail") + .required(true) + .build()?; + let assignee_email_constructor_part = ConstructorPart::builder() + .name("AssigneeEmail") + .required(true) + .build()?; + let city_constructor_part = ConstructorPart::builder() + .name("City") + .required(true) + .build()?; + let severity_constructor_part = ConstructorPart::builder() + .name("Severity") + .required(true) + .build()?; + let building_constructor_part = ConstructorPart::builder() + .name("Building") + .required(true) + .build()?; + let floor_constructor_part = ConstructorPart::builder() + .name("Floor") + .required(true) + .build()?; + let room_constructor_part = ConstructorPart::builder() + .name("Room") + .required(true) + .build()?; + let desk_constructor_part = ConstructorPart::builder() + .name("Desk") + .required(true) + .build()?; + + // 6. Define constructors + // Constructors define how encrypted and signed parts are assembled into compound beacons. + // The constructors below are based off of the "PK Constructors", "SK constructors", etc. sections in Demo.md. + + // The employee ID constructor only requires an employee ID. + // If an item has an attribute with name "EmployeeID", it will match this constructor. + // If this is the first matching constructor in the constructor list (constructor list described more below), + // the compound beacon will use this constructor, and the compound beacon will be written as `E-X`. + + let employee_id_constructor = Constructor::builder() + .parts(vec![employee_id_constructor_part]) + .build()?; + let ticket_number_constructor = Constructor::builder() + .parts(vec![ticket_number_constructor_part]) + .build()?; + let project_name_constructor = Constructor::builder() + .parts(vec![project_name_constructor_part]) + .build()?; + let ticket_mod_time_constructor = Constructor::builder() + .parts(vec![ticket_mod_time_constructor_part]) + .build()?; + let building_constructor = Constructor::builder() + .parts(vec![building_constructor_part.clone()]) + .build()?; + + // This constructor requires all of "MeetingStart", "Location.Floor", and "Location.Room" attributes. + // If an item has all of these attributes, it will match this constructor. + // If this is the first matching constructor in the constructor list (constructor list described more below), + // the compound beacon will use this constructor, and the compound beacon will be written as `MS-X~F-Y~R-Z`. + // In a constructor with multiple constructor parts, the order the constructor parts are added to + // the constructor part list defines how the compound beacon is written. + // We can rearrange the beacon parts by changing the order the constructors were added to the list. + let meeting_start_floor_room_constructor = Constructor::builder() + .parts(vec![ + meeting_start_constructor_part, + floor_constructor_part.clone(), + room_constructor_part, + ]) + .build()?; + + let timecard_start_constructor = Constructor::builder() + .parts(vec![timecard_start_constructor_part.clone()]) + .build()?; + let timecard_start_employee_email_constructor = Constructor::builder() + .parts(vec![ + timecard_start_constructor_part, + employee_email_constructor_part.clone(), + ]) + .build()?; + let creator_email_constructor = Constructor::builder() + .parts(vec![creator_email_constructor_part]) + .build()?; + let project_status_constructor = Constructor::builder() + .parts(vec![project_status_constructor_part]) + .build()?; + let employee_email_constructor = Constructor::builder() + .parts(vec![employee_email_constructor_part]) + .build()?; + let organizer_email_constructor = Constructor::builder() + .parts(vec![organizer_email_constructor_part]) + .build()?; + let project_start_constructor = Constructor::builder() + .parts(vec![project_start_constructor_part]) + .build()?; + let manager_email_constructor = Constructor::builder() + .parts(vec![manager_email_constructor_part]) + .build()?; + let assignee_email_constructor = Constructor::builder() + .parts(vec![assignee_email_constructor_part]) + .build()?; + let city_constructor = Constructor::builder() + .parts(vec![city_constructor_part]) + .build()?; + let severity_constructor = Constructor::builder() + .parts(vec![severity_constructor_part]) + .build()?; + let building_floor_desk_constructor = Constructor::builder() + .parts(vec![ + building_constructor_part, + floor_constructor_part, + desk_constructor_part, + ]) + .build()?; + + // 7. Add constructors to the compound beacon constructor list in desired construction order + // In a compound beacon with multiple constructors, the order the constructors are added to + // the constructor list determines their priority. + // The first constructor added to a constructor list will be the first constructor that is executed. + // The client will evaluate constructors until one matches, and will use the first one that matches. + // If no constructors match, an attribute value is not written for that beacon. + // A general strategy is to add constructors with unique conditions at the beginning of the list, + // and add constructors with general conditions at the end of the list. This would allow a given + // item to trigger the constructor most specific to its attributes. + let pk0_constructor_list = vec![ + employee_id_constructor.clone(), + building_constructor, + ticket_number_constructor, + project_name_constructor.clone(), + ]; + let sk0_constructor_list = vec![ + ticket_mod_time_constructor.clone(), + meeting_start_floor_room_constructor.clone(), + timecard_start_employee_email_constructor, + project_name_constructor, + employee_id_constructor.clone(), + ]; + let pk1_constructor_list = vec![ + creator_email_constructor, + employee_email_constructor, + project_status_constructor, + organizer_email_constructor, + ]; + let sk1_constructor_list = vec![ + meeting_start_floor_room_constructor, + timecard_start_constructor, + ticket_mod_time_constructor.clone(), + project_start_constructor, + employee_id_constructor, + ]; + let pk2_constructor_list = vec![manager_email_constructor, assignee_email_constructor]; + let pk3_constructor_list = vec![city_constructor, severity_constructor]; + let sk3_constructor_list = vec![building_floor_desk_constructor, ticket_mod_time_constructor]; + + // 8. Define compound beacons + // Compound beacon construction is defined in more detail in CompoundBeaconSearchableEncryptionExample. + // Note that the split character must be a character that is not used in any attribute value. + let compound_beacon_list = vec![ + CompoundBeacon::builder() + .name("PK") + .split("~") + .constructors(pk0_constructor_list) + .build()?, + CompoundBeacon::builder() + .name("SK") + .split("~") + .constructors(sk0_constructor_list) + .build()?, + CompoundBeacon::builder() + .name("PK1") + .split("~") + .constructors(pk1_constructor_list) + .build()?, + CompoundBeacon::builder() + .name("SK1") + .split("~") + .constructors(sk1_constructor_list) + .build()?, + CompoundBeacon::builder() + .name("PK2") + .split("~") + .constructors(pk2_constructor_list) + .build()?, + CompoundBeacon::builder() + .name("PK3") + .split("~") + .constructors(pk3_constructor_list) + .build()?, + CompoundBeacon::builder() + .name("SK3") + .split("~") + .constructors(sk3_constructor_list) + .build()?, + ]; + + // 9. Create BeaconVersion + let beacon_versions = BeaconVersion::builder() + .standard_beacons(standard_beacon_list) + .compound_beacons(compound_beacon_list) + .encrypted_parts(encrypted_parts_list) + .signed_parts(signed_parts_list) + .version(1) + .key_store(key_store.clone()) + .key_source(BeaconKeySource::Single( + SingleKeyStore::builder() + .key_id(branch_key_id) + .cache_ttl(6000) + .build()?, + )) + .build()?; + let beacon_versions = vec![beacon_versions]; + + // 10. Create a Hierarchical Keyring + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + let kms_keyring = mpl + .create_aws_kms_hierarchical_keyring() + .branch_key_id(branch_key_id) + .key_store(key_store) + .ttl_seconds(600) + .send() + .await?; + + // 11. Define crypto actions + let attribute_actions_on_encrypt = HashMap::from([ + // Our partition key must be configured as SIGN_ONLY + ("partition_key".to_string(), CryptoAction::SignOnly), + // Attributes used in beacons must be configured as ENCRYPT_AND_SIGN + ("EmployeeID".to_string(), CryptoAction::EncryptAndSign), + ("TicketNumber".to_string(), CryptoAction::EncryptAndSign), + ("ProjectName".to_string(), CryptoAction::EncryptAndSign), + ("EmployeeName".to_string(), CryptoAction::EncryptAndSign), + ("EmployeeEmail".to_string(), CryptoAction::EncryptAndSign), + ("CreatorEmail".to_string(), CryptoAction::EncryptAndSign), + ("ProjectStatus".to_string(), CryptoAction::EncryptAndSign), + ("OrganizerEmail".to_string(), CryptoAction::EncryptAndSign), + ("ManagerEmail".to_string(), CryptoAction::EncryptAndSign), + ("AssigneeEmail".to_string(), CryptoAction::EncryptAndSign), + ("City".to_string(), CryptoAction::EncryptAndSign), + ("Severity".to_string(), CryptoAction::EncryptAndSign), + ("Location".to_string(), CryptoAction::EncryptAndSign), + // These are not beaconized attributes, but are sensitive data that must be encrypted + ("Attendees".to_string(), CryptoAction::EncryptAndSign), + ("Subject".to_string(), CryptoAction::EncryptAndSign), + // Signed parts and unencrypted attributes can be configured as SIGN_ONLY or DO_NOTHING + // For this example, we will set these to SIGN_ONLY to ensure authenticity + ("TicketModTime".to_string(), CryptoAction::SignOnly), + ("MeetingStart".to_string(), CryptoAction::SignOnly), + ("TimeCardStart".to_string(), CryptoAction::SignOnly), + ("EmployeeTitle".to_string(), CryptoAction::SignOnly), + ("Description".to_string(), CryptoAction::SignOnly), + ("ProjectTarget".to_string(), CryptoAction::SignOnly), + ("Hours".to_string(), CryptoAction::SignOnly), + ("Role".to_string(), CryptoAction::SignOnly), + ("Message".to_string(), CryptoAction::SignOnly), + ("ProjectStart".to_string(), CryptoAction::SignOnly), + ("Duration".to_string(), CryptoAction::SignOnly), + ]); + + // 12. Set up table config + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("partition_key") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(kms_keyring) + .search( + SearchConfig::builder() + .write_version(1) + .versions(beacon_versions) + .build()?, + ) + .build()?; + + let table_configs = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 13. Create a new AWS SDK DynamoDb client using the config above + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + + Ok(aws_sdk_dynamodb::Client::from_conf(dynamo_config)) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/complex_searchable_encryption.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/complex_searchable_encryption.rs new file mode 100644 index 000000000..2f907bb73 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/complex_searchable_encryption.rs @@ -0,0 +1,32 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; + +/* + * This file is used in an example to demonstrate complex queries + * you can perform using beacons. + * The example data used is for demonstrative purposes only, + * and might not meet the distribution and correlation uniqueness + * recommendations for beacons. + * See our documentation for whether beacons are + * right for your particular data set: + * https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption.html#are-beacons-right-for-me + */ +pub async fn run_example(branch_key_id: &str) -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::TEST_COMPLEX_DDB_TABLE_NAME; + let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN; + let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME; + + let mut ddb = super::beacon_config::setup_beacon_config( + ddb_table_name, + branch_key_id, + branch_key_wrapping_kms_key_arn, + branch_key_ddb_table_name, + ) + .await?; + super::put_requests::put_all_items_to_table(ddb_table_name, &mut ddb).await?; + super::query_requests::run_queries(ddb_table_name, &mut ddb).await?; + println!("complex searchable encryption example successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/mod.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/mod.rs new file mode 100644 index 000000000..86dbf672e --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/mod.rs @@ -0,0 +1,7 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +pub mod beacon_config; +pub mod complex_searchable_encryption; +pub mod put_requests; +pub mod query_requests; diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/put_requests.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/put_requests.rs new file mode 100644 index 000000000..23eaa7113 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/put_requests.rs @@ -0,0 +1,453 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + * This file is used in an example to demonstrate complex queries + * you can perform using beacons. + * The example data used is for demonstrative purposes only, + * and might not meet the distribution and correlation uniqueness + * recommendations for beacons. + * See our documentation for whether beacons are + * right for your particular data set: + * https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption.html#are-beacons-right-for-me + * + * This class implements PutItemAsync calls to populate a DDB table with items from our workshop. + * By providing a DynamoDbClient that is configured to use searchable encryption, + * this class will encrypt the data client side and add beacon attributes to the items. + * This only implements a single item of each of the 6 record types from Demo.md. Adding the remaining + * items would extend the test and example coverage. + */ + +pub async fn put_all_items_to_table( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + put_all_meeting_items(ddb_table_name, ddb).await?; + put_all_employee_items(ddb_table_name, ddb).await?; + put_all_project_items(ddb_table_name, ddb).await?; + put_all_reservation_items(ddb_table_name, ddb).await?; + put_all_ticket_items(ddb_table_name, ddb).await?; + put_all_timecard_items(ddb_table_name, ddb).await?; + Ok(()) +} + +fn ss(s: &str) -> AttributeValue { + AttributeValue::S(s.to_string()) +} +fn entry(name: &str, value: &str) -> (String, AttributeValue) { + (name.to_string(), ss(value)) +} + +async fn put_item( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, + item: HashMap, +) -> Result<(), crate::BoxError> { + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item)) + .send() + .await?; + Ok(()) +} + +// emeeting.json +async fn put_all_meeting_items( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + let meeting1_attendee_list = vec![ss("able@gmail.com"), ss("zorro@gmail.com")]; + let meeting1_location = HashMap::from([entry("Floor", "12"), entry("Room", "403")]); + let meeting1 = HashMap::from([ + entry("partition_key", "meeting1"), + entry("EmployeeID", "emp_001"), + entry("EmployeeEmail", "able@gmail.com"), + entry("MeetingStart", "2022-07-04T13:00"), + entry("Duration", "30"), + entry("Subject", "Scan Beacons"), + ("Location".to_string(), AttributeValue::M(meeting1_location)), + ( + "Attendees".to_string(), + AttributeValue::L(meeting1_attendee_list), + ), + ]); + put_item(ddb_table_name, ddb, meeting1).await?; + + let meeting2_attendee_list = vec![ss("barney@gmail.com"), ss("zorro@gmail.com")]; + let meeting2_location = HashMap::from([entry("Floor", "12"), entry("Room", "403")]); + let meeting2 = HashMap::from([ + entry("partition_key", "meeting2"), + entry("EmployeeID", "emp_002"), + entry("EmployeeEmail", "barney@gmail.com"), + entry("MeetingStart", "2022-07-04T13:00"), + entry("Duration", "30"), + entry("Subject", "Scan Beacons"), + ("Location".to_string(), AttributeValue::M(meeting2_location)), + ( + "Attendees".to_string(), + AttributeValue::L(meeting2_attendee_list), + ), + ]); + put_item(ddb_table_name, ddb, meeting2).await?; + + let meeting3_attendee_list = vec![ss("charlie@gmail.com"), ss("zorro@gmail.com")]; + let meeting3_location = HashMap::from([entry("Floor", "12"), entry("Room", "403")]); + let meeting3 = HashMap::from([ + entry("partition_key", "meeting3"), + entry("EmployeeID", "emp_003"), + entry("EmployeeEmail", "charlie@gmail.com"), + entry("MeetingStart", "2022-07-04T13:00"), + entry("Duration", "30"), + entry("Subject", "Scan Beacons"), + ("Location".to_string(), AttributeValue::M(meeting3_location)), + ( + "Attendees".to_string(), + AttributeValue::L(meeting3_attendee_list), + ), + ]); + put_item(ddb_table_name, ddb, meeting3).await?; + + let meeting4_attendee_list = vec![ss("david@gmail.com"), ss("zorro@gmail.com")]; + let meeting4_location = HashMap::from([entry("Floor", "12"), entry("Room", "403")]); + let meeting4 = HashMap::from([ + entry("partition_key", "meeting4"), + entry("EmployeeID", "emp_004"), + entry("EmployeeEmail", "david@gmail.com"), + entry("MeetingStart", "2022-07-04T13:00"), + entry("Duration", "30"), + entry("Subject", "Scan Beacons"), + ("Location".to_string(), AttributeValue::M(meeting4_location)), + ( + "Attendees".to_string(), + AttributeValue::L(meeting4_attendee_list), + ), + ]); + put_item(ddb_table_name, ddb, meeting4).await?; + + let meeting5_attendee_list = vec![ss("barney@gmail.com"), ss("zorro@gmail.com")]; + let meeting5_location = HashMap::from([entry("Floor", "12"), entry("Room", "407")]); + let meeting5 = HashMap::from([ + entry("partition_key", "meeting5"), + entry("EmployeeID", "emp_002"), + entry("EmployeeEmail", "barney@gmail.com"), + entry("MeetingStart", "2022-07-04T14:00"), + entry("Duration", "30"), + entry("Subject", "DB ESDK"), + ("Location".to_string(), AttributeValue::M(meeting5_location)), + ( + "Attendees".to_string(), + AttributeValue::L(meeting5_attendee_list), + ), + ]); + put_item(ddb_table_name, ddb, meeting5).await?; + + let meeting6_attendee_list = vec![ss("charlie@gmail.com"), ss("zorro@gmail.com")]; + let meeting6_location = HashMap::from([entry("Floor", "12"), entry("Room", "407")]); + let meeting6 = HashMap::from([ + entry("partition_key", "meeting6"), + entry("EmployeeID", "emp_003"), + entry("EmployeeEmail", "charlie@gmail.com"), + entry("MeetingStart", "2022-07-04T14:00"), + entry("Duration", "30"), + entry("Subject", "DB ESDK"), + ("Location".to_string(), AttributeValue::M(meeting6_location)), + ( + "Attendees".to_string(), + AttributeValue::L(meeting6_attendee_list), + ), + ]); + put_item(ddb_table_name, ddb, meeting6).await?; + Ok(()) +} + +// employee.json +async fn put_all_employee_items( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + let employee1_location = HashMap::from([ + entry("Building", "44"), + entry("Floor", "12"), + entry("Desk", "3"), + entry("City", "Seattle"), + ]); + let employee1 = HashMap::from([ + entry("partition_key", "employee1"), + entry("EmployeeID", "emp_001"), + entry("EmployeeEmail", "able@gmail.com"), + entry("ManagerEmail", "zorro@gmail.com"), + entry("EmployeeName", "Able Jones"), + entry("EmployeeTitle", "SDE9"), + ( + "Location".to_string(), + AttributeValue::M(employee1_location), + ), + ]); + put_item(ddb_table_name, ddb, employee1).await?; + + let employee2_location = HashMap::from([ + entry("Building", "44"), + entry("Floor", "12"), + entry("Desk", "4"), + entry("City", "Seattle"), + ]); + let employee2 = HashMap::from([ + entry("partition_key", "employee2"), + entry("EmployeeID", "emp_002"), + entry("EmployeeEmail", "barney@gmail.com"), + entry("ManagerEmail", "zorro@gmail.com"), + entry("EmployeeName", "Barney Jones"), + entry("EmployeeTitle", "SDE8"), + ( + "Location".to_string(), + AttributeValue::M(employee2_location), + ), + ]); + put_item(ddb_table_name, ddb, employee2).await?; + + let employee3_location = HashMap::from([ + entry("Building", "44"), + entry("Floor", "4"), + entry("Desk", "5"), + entry("City", "Seattle"), + ]); + let employee3 = HashMap::from([ + entry("partition_key", "employee3"), + entry("EmployeeID", "emp_003"), + entry("EmployeeEmail", "charlie@gmail.com"), + entry("ManagerEmail", "zorro@gmail.com"), + entry("EmployeeName", "Charlie Jones"), + entry("EmployeeTitle", "SDE7"), + ( + "Location".to_string(), + AttributeValue::M(employee3_location), + ), + ]); + put_item(ddb_table_name, ddb, employee3).await?; + + let employee4_location = HashMap::from([ + entry("Building", "22"), + entry("Floor", "1"), + entry("Desk", "3"), + entry("City", "NYC"), + ]); + let employee4 = HashMap::from([ + entry("partition_key", "employee4"), + entry("EmployeeID", "emp_004"), + entry("EmployeeEmail", "david@gmail.com"), + entry("ManagerEmail", "zorro@gmail.com"), + entry("EmployeeName", "David Jones"), + entry("EmployeeTitle", "SDE6"), + ( + "Location".to_string(), + AttributeValue::M(employee4_location), + ), + ]); + put_item(ddb_table_name, ddb, employee4).await?; + Ok(()) +} + +// project.json +async fn put_all_project_items( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + let project1 = HashMap::from([ + entry("partition_key", "project1"), + entry("ProjectName", "project_001"), + entry("ProjectStatus", "Pending"), + entry("ProjectStart", "2022-11-01"), + entry("Description", "Turbo Crypto"), + entry("ProjectTarget", "2024-01-01"), + ]); + put_item(ddb_table_name, ddb, project1).await?; + + let project2 = HashMap::from([ + entry("partition_key", "project2"), + entry("ProjectName", "project_002"), + entry("ProjectStatus", "Active"), + entry("ProjectStart", "2022-07-04"), + entry("Description", "Scan Beacons"), + entry("ProjectTarget", "2024-01-01"), + ]); + put_item(ddb_table_name, ddb, project2).await?; + + let project3 = HashMap::from([ + entry("partition_key", "project3"), + entry("ProjectName", "project_003"), + entry("ProjectStatus", "Active"), + entry("ProjectStart", "2022-08-05"), + entry("Description", "DB ESDK"), + entry("ProjectTarget", "2023-02-27"), + ]); + put_item(ddb_table_name, ddb, project3).await?; + + let project4 = HashMap::from([ + entry("partition_key", "project4"), + entry("ProjectName", "project_004"), + entry("ProjectStatus", "Done"), + entry("ProjectStart", "2020-03-03"), + entry("Description", "S3EC"), + entry("ProjectTarget", "2021-09-05"), + ]); + put_item(ddb_table_name, ddb, project4).await?; + Ok(()) +} + +// reservation.json +async fn put_all_reservation_items( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + let reservation1_attendee_list = vec![ss("able@gmail.com"), ss("barney@gmail.com")]; + let reservation1_location = HashMap::from([ + entry("Building", "SEA33"), + entry("Floor", "12"), + entry("Room", "403"), + ]); + let reservation1 = HashMap::from([ + entry("partition_key", "reservation1"), + entry("MeetingStart", "2022-07-04T13:00"), + entry("OrganizerEmail", "able@gmail.com"), + entry("Duration", "30"), + entry("Subject", "Scan beacons"), + ( + "Location".to_string(), + AttributeValue::M(reservation1_location), + ), + ( + "Attendees".to_string(), + AttributeValue::L(reservation1_attendee_list), + ), + ]); + put_item(ddb_table_name, ddb, reservation1).await?; + + let reservation2_attendee_list = vec![ss("able@gmail.com"), ss("barney@gmail.com")]; + let reservation2_location = HashMap::from([ + entry("Building", "SEA33"), + entry("Floor", "12"), + entry("Room", "407"), + ]); + let reservation2 = HashMap::from([ + entry("partition_key", "reservation2"), + entry("MeetingStart", "2022-07-04T14:00"), + entry("OrganizerEmail", "barney@gmail.com"), + entry("Duration", "30"), + entry("Subject", "DB ESDK"), + ( + "Location".to_string(), + AttributeValue::M(reservation2_location), + ), + ( + "Attendees".to_string(), + AttributeValue::L(reservation2_attendee_list), + ), + ]); + put_item(ddb_table_name, ddb, reservation2).await?; + Ok(()) +} + +// ticket.json +async fn put_all_ticket_items( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + let ticket1 = HashMap::from([ + entry("partition_key", "ticket1"), + entry("TicketNumber", "ticket_001"), + entry("TicketModTime", "2022-10-07T14:32:25"), + entry("CreatorEmail", "zorro@gmail.com"), + entry("AssigneeEmail", "able@gmail.com"), + entry("Severity", "3"), + entry("Subject", "Bad bug"), + entry("Message", "This bug looks pretty bad"), + ]); + put_item(ddb_table_name, ddb, ticket1).await?; + + let ticket2 = HashMap::from([ + entry("partition_key", "ticket2"), + entry("TicketNumber", "ticket_001"), + entry("TicketModTime", "2022-10-07T14:32:25"), + entry("CreatorEmail", "able@gmail.com"), + entry("AssigneeEmail", "charlie@gmail.com"), + entry("Severity", "3"), + entry("Subject", "Bad bug"), + entry("Message", "Charlie should handle this"), + ]); + put_item(ddb_table_name, ddb, ticket2).await?; + + let ticket3 = HashMap::from([ + entry("partition_key", "ticket3"), + entry("TicketNumber", "ticket_002"), + entry("TicketModTime", "2022-10-06T14:32:25"), + entry("CreatorEmail", "zorro@gmail.com"), + entry("AssigneeEmail", "charlie@gmail.com"), + entry("Severity", "3"), + entry("Subject", "Easy bug"), + entry("Message", "This seems simple enough"), + ]); + put_item(ddb_table_name, ddb, ticket3).await?; + + let ticket4 = HashMap::from([ + entry("partition_key", "ticket4"), + entry("TicketNumber", "ticket_002"), + entry("TicketModTime", "2022-10-08T14:32:25"), + entry("CreatorEmail", "charlie@gmail.com"), + entry("AssigneeEmail", "able@gmail.com"), + entry("Severity", "3"), + entry("Subject", "Easy bug"), + entry("Message", "that's in able's code"), + ]); + put_item(ddb_table_name, ddb, ticket4).await?; + Ok(()) +} + +// timecard.json +async fn put_all_timecard_items( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + let timecard1 = HashMap::from([ + entry("partition_key", "timecard1"), + entry("ProjectName", "project_002"), + entry("TimeCardStart", "2022-09-12"), + entry("EmployeeEmail", "able@gmail.com"), + entry("Hours", "40"), + entry("Role", "SDE3"), + ]); + put_item(ddb_table_name, ddb, timecard1).await?; + + let timecard2 = HashMap::from([ + entry("partition_key", "timecard2"), + entry("ProjectName", "project_002"), + entry("TimeCardStart", "2022-09-12"), + entry("EmployeeEmail", "barney@gmail.com"), + entry("Hours", "20"), + entry("Role", "PM"), + ]); + put_item(ddb_table_name, ddb, timecard2).await?; + + let timecard3 = HashMap::from([ + entry("partition_key", "timecard3"), + entry("ProjectName", "project_003"), + entry("TimeCardStart", "2022-09-12"), + entry("EmployeeEmail", "charlie@gmail.com"), + entry("Hours", "40"), + entry("Role", "SDE3"), + ]); + put_item(ddb_table_name, ddb, timecard3).await?; + + let timecard4 = HashMap::from([ + entry("partition_key", "timecard4"), + entry("ProjectName", "project_003"), + entry("TimeCardStart", "2022-09-12"), + entry("EmployeeEmail", "barney@gmail.com"), + entry("Hours", "20"), + entry("Role", "PM"), + ]); + put_item(ddb_table_name, ddb, timecard4).await?; + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/query_requests.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/query_requests.rs new file mode 100644 index 000000000..f64ae0e33 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/complexexample/query_requests.rs @@ -0,0 +1,879 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + * This file is used in an example to demonstrate complex queries + * you can perform using beacons. + * The example data used is for demonstrative purposes only, + * and might not meet the distribution and correlation uniqueness + * recommendations for beacons. + * See our documentation for whether beacons are + * right for your particular data set: + * https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption.html#are-beacons-right-for-me + * + * This class implements query access patterns from our workshop. + * The queries in this file are more complicated than in other searchable encryption examples, + * and should demonstrate how one can structure queries on beacons in a broader variety of applications. + */ + +pub async fn run_queries( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + run_query1(ddb_table_name, ddb).await?; + run_query2(ddb_table_name, ddb).await?; + run_query3(ddb_table_name, ddb).await?; + run_query4(ddb_table_name, ddb).await?; + run_query5(ddb_table_name, ddb).await?; + run_query6(ddb_table_name, ddb).await?; + run_query7(ddb_table_name, ddb).await?; + run_query8(ddb_table_name, ddb).await?; + run_query9(ddb_table_name, ddb).await?; + run_query10(ddb_table_name, ddb).await?; + run_query11(ddb_table_name, ddb).await?; + run_query12(ddb_table_name, ddb).await?; + run_query13(ddb_table_name, ddb).await?; + run_query14(ddb_table_name, ddb).await?; + run_query15(ddb_table_name, ddb).await?; + run_query16(ddb_table_name, ddb).await?; + run_query17(ddb_table_name, ddb).await?; + run_query18(ddb_table_name, ddb).await?; + run_query19(ddb_table_name, ddb).await?; + run_query20(ddb_table_name, ddb).await?; + run_query21(ddb_table_name, ddb).await?; + run_query22(ddb_table_name, ddb).await?; + run_query23(ddb_table_name, ddb).await?; + Ok(()) +} + +fn ss(s: &str) -> AttributeValue { + AttributeValue::S(s.to_string()) +} + +fn entry(name: &str, value: &str) -> (String, AttributeValue) { + (name.to_string(), ss(value)) +} + +async fn run_query1( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 1: Get meetings by date and email + // Key condition: PK1=email SK1 between(date1, date2) + // Filter condition: duration > 0 + + let query1_names = HashMap::from([ + ("#duration".to_string(), "Duration".to_string()), // Duration is a reserved word + ]); + + let query1_values = HashMap::from([ + entry(":e", "EE-able@gmail.com"), + entry(":date1", "MS-2022-07-02"), + entry(":date2", "MS-2022-07-08"), + entry(":zero", "0"), + ]); + + let query1_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-1") + .set_expression_attribute_names(Some(query1_names)) + .set_expression_attribute_values(Some(query1_values)) + .key_condition_expression("PK1 = :e AND SK1 BETWEEN :date1 AND :date2") + .filter_expression("#duration > :zero") + .send() + .await?; + + // Assert 1 item was returned + let items = query1_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("meeting1")); + assert_eq!(items[0]["Subject"], ss("Scan Beacons")); + assert_eq!(items[0]["Location"].as_m().unwrap()["Floor"], ss("12")); + assert!(items[0]["Attendees"] + .as_l() + .unwrap() + .contains(&ss("zorro@gmail.com"))); + Ok(()) +} + +async fn run_query2( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 2: Get meetings by date and employeeID + // Key condition: PK=employeeID SK between(date1, date2) + // Filter condition: duration > 0 + + let query2_names = HashMap::from([ + ("#duration".to_string(), "Duration".to_string()), // Duration is a reserved word + ]); + + let query2_values = HashMap::from([ + entry(":employee", "E-emp_001"), + entry(":date1", "MS-2022-07-02"), + entry(":date2", "MS-2022-07-08"), + entry(":zero", "0"), + ]); + + let query2_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-0") + .set_expression_attribute_names(Some(query2_names)) + .set_expression_attribute_values(Some(query2_values)) + .key_condition_expression("PK = :employee AND SK BETWEEN :date1 AND :date2") + .filter_expression("#duration > :zero") + .send() + .await?; + + // Assert 1 item was returned + let items = query2_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("meeting1")); + assert_eq!(items[0]["Subject"], ss("Scan Beacons")); + assert_eq!(items[0]["Location"].as_m().unwrap()["Floor"], ss("12")); + assert!(items[0]["Attendees"] + .as_l() + .unwrap() + .contains(&ss("zorro@gmail.com"))); + Ok(()) +} + +async fn run_query3( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 3: Get meetings by date and building/floor/room + // Key condition: PK=employeeID SK between(date1, date2) + // Filter condition: SK contains building.floor.room (see NOTE) + // NOTE: This query is modified from Demo.md. + // Demo.md calls for a filter condition "SK contains building.floor.room" + // However, one cannot use primary keys (partition nor sort) in a filter expression. + // Instead, this query filters on the individual beacon attributes: building, floor, and room. + + let query3_values = HashMap::from([ + entry(":buildingbeacon", "B-SEA33"), + entry(":building", "SEA33"), + entry(":floor", "12"), + entry(":room", "403"), + entry(":date1", "MS-2022-07-02"), + entry(":date2", "MS-2022-07-08"), + ]); + + let query3_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-0") + .set_expression_attribute_values(Some(query3_values)) + .key_condition_expression("PK = :buildingbeacon AND SK BETWEEN :date1 AND :date2") + .filter_expression("Building = :building AND Floor = :floor AND Room = :room") + .send() + .await?; + + // Assert 1 item was returned + let items = query3_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("reservation1")); + assert_eq!(items[0]["Subject"], ss("Scan beacons")); + assert_eq!( + items[0]["Location"].as_m().unwrap()["Building"], + ss("SEA33") + ); + assert!(items[0]["Attendees"] + .as_l() + .unwrap() + .contains(&ss("barney@gmail.com"))); + Ok(()) +} + +async fn run_query4( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 4: Get employee data by email + // Key condition: PK1=email SK1=employee ID + + let query4_values = HashMap::from([ + entry(":email", "EE-able@gmail.com"), + entry(":employee", "E-emp_001"), + ]); + + let query4_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-1") + .set_expression_attribute_values(Some(query4_values)) + .key_condition_expression("PK1 = :email AND SK1 = :employee") + .send() + .await?; + + // Assert 1 item was returned + let items = query4_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("employee1")); + assert_eq!(items[0]["EmployeeID"], ss("emp_001")); + assert_eq!(items[0]["Location"].as_m().unwrap()["Desk"], ss("3")); + Ok(()) +} + +async fn run_query5( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 5: Get meetings by email + // Key condition: PK1=email SK1 > 30 days ago + + let query5_values = HashMap::from([ + entry(":email", "EE-able@gmail.com"), + entry(":thirtydaysago", "MS-2023-03-20"), + entry(":prefix", "MS-"), + ]); + + let query5_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-1") + .set_expression_attribute_values(Some(query5_values)) + .key_condition_expression("PK1 = :email AND SK1 BETWEEN :prefix AND :thirtydaysago") + .send() + .await?; + + // Assert 1 item was returned + let items = query5_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("meeting1")); + assert_eq!(items[0]["Subject"], ss("Scan Beacons")); + assert_eq!(items[0]["Location"].as_m().unwrap()["Floor"], ss("12")); + assert!(items[0]["Attendees"] + .as_l() + .unwrap() + .contains(&ss("zorro@gmail.com"))); + Ok(()) +} + +async fn run_query6( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 6: Get tickets by email + // Key condition: PK1=email SK1 > 30 days ago + + let query6_values = HashMap::from([ + entry(":creatoremail", "CE-zorro@gmail.com"), + entry(":thirtydaysago", "MS-2023-03-20"), + ]); + + let query6_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-1") + .set_expression_attribute_values(Some(query6_values)) + .key_condition_expression("PK1 = :creatoremail AND SK1 < :thirtydaysago") + .send() + .await?; + + // Assert 2 items were returned + let items = query6_response.items.unwrap(); + assert_eq!(items.len(), 2); + + // Expected to be `ticket1` and `ticket3` + assert!( + ((items[0]["partition_key"] == ss("ticket1")) + && (items[1]["partition_key"] == ss("ticket3"))) + || ((items[0]["partition_key"] == ss("ticket3")) + && (items[1]["partition_key"] == ss("ticket1"))) + ); + Ok(()) +} + +async fn run_query7( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 7: Get reservations by email + // Key condition: PK1=organizeremail SK1 > 30 days ago + + let query7_values = HashMap::from([ + entry(":organizeremail", "OE-able@gmail.com"), + entry(":thirtydaysago", "MS-2023-03-20"), + ]); + + let query7_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-1") + .set_expression_attribute_values(Some(query7_values)) + .key_condition_expression("PK1 = :organizeremail AND SK1 < :thirtydaysago") + .send() + .await?; + + // Assert 1 item was returned + let items = query7_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("reservation1")); + assert_eq!(items[0]["Subject"], ss("Scan beacons")); + assert_eq!(items[0]["Location"].as_m().unwrap()["Floor"], ss("12")); + assert!(items[0]["Attendees"] + .as_l() + .unwrap() + .contains(&ss("barney@gmail.com"))); + Ok(()) +} + +async fn run_query8( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 8: Get time cards by email + // Key condition: PK1=employeeemail SK1 > 30 days ago + + let query8_values = HashMap::from([ + entry(":email", "EE-able@gmail.com"), + entry(":prefix", "TC-"), + entry(":thirtydaysago", "TC-2023-03-20"), + ]); + + let query8_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-1") + .set_expression_attribute_values(Some(query8_values)) + .key_condition_expression("PK1 = :email AND SK1 BETWEEN :prefix AND :thirtydaysago") + .send() + .await?; + + // Assert 1 item was returned + let items = query8_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("timecard1")); + assert_eq!(items[0]["ProjectName"], ss("project_002")); + Ok(()) +} + +async fn run_query9( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 9: Get employee info by employee ID + // Key condition: PK1=employeeID SK starts with "E-" + + let query9_values = HashMap::from([entry(":employee", "E-emp_001"), entry(":prefix", "E-")]); + + let query9_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-0") + .set_expression_attribute_values(Some(query9_values)) + .key_condition_expression("PK = :employee AND begins_with(SK, :prefix)") + .send() + .await?; + + // Assert 1 item was returned + let items = query9_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("employee1")); + assert_eq!(items[0]["EmployeeID"], ss("emp_001")); + Ok(()) +} + +async fn run_query10( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 10: Get employee info by email + // Key condition: PK1=email + // Filter condition: SK starts with "E-" + + let query10_values = + HashMap::from([entry(":email", "EE-able@gmail.com"), entry(":prefix", "E-")]); + + let query10_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-1") + .set_expression_attribute_values(Some(query10_values)) + .key_condition_expression("PK1 = :email AND begins_with(SK1, :prefix)") + .send() + .await?; + + // Assert 1 item was returned + let items = query10_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("employee1")); + assert_eq!(items[0]["EmployeeID"], ss("emp_001")); + Ok(()) +} + +async fn run_query11( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 11: Get ticket history by ticket number + // Key condition: PK=TicketNumber + + let query11_values = HashMap::from([entry(":ticket", "T-ticket_001")]); + + let query11_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-0") + .set_expression_attribute_values(Some(query11_values)) + .key_condition_expression("PK = :ticket") + .send() + .await?; + + // Assert 2 items were returned + let items = query11_response.items.unwrap(); + assert_eq!(items.len(), 2); + + // Expected to be `ticket1` and `ticket3` + assert!( + ((items[0]["partition_key"] == ss("ticket1")) + && (items[1]["partition_key"] == ss("ticket2"))) + || ((items[0]["partition_key"] == ss("ticket2")) + && (items[1]["partition_key"] == ss("ticket1"))) + ); + Ok(()) +} + +async fn run_query12( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 12: Get Ticket History by employee email + // Key condition: PK1=CreatorEmail + // Filter condition: PK=TicketNumber + + let query12_values = HashMap::from([ + entry(":email", "CE-zorro@gmail.com"), + entry(":ticket", "T-ticket_001"), + ]); + + let query12_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-1") + .set_expression_attribute_values(Some(query12_values)) + .key_condition_expression("PK1 = :email") + .filter_expression("PK = :ticket") + .send() + .await?; + + // Assert 1 item was returned + let items = query12_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("ticket1")); + assert_eq!(items[0]["TicketNumber"], ss("ticket_001")); + Ok(()) +} + +async fn run_query13( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 13: Get ticket history by assignee email + // Key condition: PK=AssigneeEmail + // Filter condition: PK=ticketNumber + let query13_values = HashMap::from([ + entry(":assigneeemail", "AE-able@gmail.com"), + entry(":ticket", "T-ticket_001"), + ]); + + let query13_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-2") + .set_expression_attribute_values(Some(query13_values)) + .key_condition_expression("PK2 = :assigneeemail") + .filter_expression("PK = :ticket") + .send() + .await?; + + // Assert 1 item was returned + let items = query13_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("ticket1")); + assert_eq!(items[0]["Subject"], ss("Bad bug")); + Ok(()) +} + +async fn run_query14( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 14: Get employees by city.building.floor.desk + // Key condition: PK3=city SK3 begins_with(building.floor.desk) + + let query14_values = HashMap::from([ + entry(":city", "C-Seattle"), + entry(":location", "B-44~F-12~D-3"), + ]); + + let query14_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-3") + .set_expression_attribute_values(Some(query14_values)) + .key_condition_expression("PK3 = :city AND begins_with(SK3, :location)") + .send() + .await?; + + // Assert 1 item was returned + let items = query14_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("employee1")); + assert_eq!(items[0]["EmployeeID"], ss("emp_001")); + assert_eq!(items[0]["Location"].as_m().unwrap()["Desk"], ss("3")); + Ok(()) +} + +async fn run_query15( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 15: Get employees by manager email + // Key condition: PK2 = ManagerEmail + + let query15_values = HashMap::from([entry(":manageremail", "ME-zorro@gmail.com")]); + + let query15_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-2") + .set_expression_attribute_values(Some(query15_values)) + .key_condition_expression("PK2 = :manageremail") + .send() + .await?; + + // Assert 4 items returned: + // Expected to be `employee1`, `employee2`, `employee3`, and `employee4` + let items = query15_response.items.unwrap(); + assert_eq!(items.len(), 4); + + let mut found_known_value_item_query15 = false; + for item in &items { + if item["partition_key"] == ss("employee1") { + found_known_value_item_query15 = true; + assert_eq!(item["EmployeeID"], ss("emp_001")); + assert_eq!(item["Location"].as_m().unwrap()["Desk"], ss("3")); + } + } + + assert!(found_known_value_item_query15); + Ok(()) +} + +async fn run_query16( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 16: Get assigned tickets by assignee email + // Key condition: PK2 = AssigneeEmail + + let query16_values = HashMap::from([entry(":assigneeemail", "AE-able@gmail.com")]); + + let query16_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-2") + .set_expression_attribute_values(Some(query16_values)) + .key_condition_expression("PK2 = :assigneeemail") + .send() + .await?; + + // Assert 2 items were returned + let items = query16_response.items.unwrap(); + assert_eq!(items.len(), 2); + + // Expected to be `ticket1` and `ticket4` + assert!( + ((items[0]["partition_key"] == ss("ticket1")) + && (items[1]["partition_key"] == ss("ticket4"))) + || ((items[0]["partition_key"] == ss("ticket4")) + && (items[1]["partition_key"] == ss("ticket1"))) + ); + Ok(()) +} + +async fn run_query17( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 17: Get tickets updated within the last 24 hours + // Key condition: PK3 = Severity, SK3 > 24 hours ago + // (For the sake of this example, we will assume + // the date is 2022-10-08T09:30:00, such that "24 hours ago" + // is 2022-10-07T09:30:00, and that our sample ticket record + // with TicketModTime=2022-10-07T14:32:25 will be returned.) + + let query17_values = HashMap::from([ + entry(":severity", "S-3"), + entry(":yesterday", "M-2022-10-07T09:30:00"), + ]); + + let query17_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-3") + .set_expression_attribute_values(Some(query17_values)) + .key_condition_expression("PK3 = :severity AND SK3 > :yesterday") + .send() + .await?; + + // Assert 3 items returned: + // Expected to be `ticket1`, `ticket2`, and `ticket4` + + let items = query17_response.items.unwrap(); + assert_eq!(items.len(), 3); + + let mut found_known_value_item_query17 = false; + for item in &items { + if item["partition_key"] == ss("ticket1") { + found_known_value_item_query17 = true; + assert_eq!(item["TicketNumber"], ss("ticket_001")); + } + } + + assert!(found_known_value_item_query17); + Ok(()) +} + +async fn run_query18( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 18: Get projects by status, start and target date + // Key condition: PK1 = Status, SK1 > StartDate + // Filter condition: TargetDelivery < TargetDate + + let query18_values = HashMap::from([ + entry(":status", "PSts-Pending"), + entry(":startdate", "PS-2022-01-01"), + entry(":target", "2025-01-01"), + ]); + + let query18_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-1") + .set_expression_attribute_values(Some(query18_values)) + .key_condition_expression("PK1 = :status AND SK1 > :startdate") + .filter_expression("ProjectTarget < :target") + .send() + .await?; + + // Assert 1 item was returned + let items = query18_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("project1")); + assert_eq!(items[0]["ProjectName"], ss("project_001")); + Ok(()) +} + +async fn run_query19( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 19: Get projects by name + // Key condition: PK = ProjectName, SK = ProjectName + + let query19_values = HashMap::from([entry(":projectname", "P-project_001")]); + + let query19_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-0") + .set_expression_attribute_values(Some(query19_values)) + .key_condition_expression("PK = :projectname AND SK = :projectname") + .send() + .await?; + + // Assert 1 item was returned + let items = query19_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("project1")); + assert_eq!(items[0]["ProjectName"], ss("project_001")); + Ok(()) +} + +async fn run_query20( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 20: Get Project History by date range (against timecard record) + // Key condition: PK = ProjectName, SK between(date1, date2) + + let query20_values = HashMap::from([ + entry(":projectname", "P-project_002"), + entry(":date1", "TC-2022-01-01"), + entry(":date2", "TC-2023-01-01"), + ]); + + let query20_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-0") + .set_expression_attribute_values(Some(query20_values)) + .key_condition_expression("PK = :projectname AND SK BETWEEN :date1 AND :date2") + .send() + .await?; + + // Assert 2 items returned: + // Expected to be `timecard1` and `timecard2` + let items = query20_response.items.unwrap(); + assert_eq!(items.len(), 2); + + assert!( + ((items[0]["partition_key"] == ss("timecard1")) + && (items[1]["partition_key"] == ss("timecard2"))) + || ((items[0]["partition_key"] == ss("timecard2")) + && (items[1]["partition_key"] == ss("timecard1"))) + ); + Ok(()) +} + +async fn run_query21( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 21: Get Project History by role + // Key condition: PK = ProjectName + // Filter condition: role=rolename + + let query21_names = HashMap::from([ + ("#role".to_string(), "Role".to_string()), // Role is a reserved word + ]); + + let query21_values = HashMap::from([ + entry(":projectname", "P-project_002"), + entry(":role", "SDE3"), + ]); + + let query21_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-0") + .set_expression_attribute_values(Some(query21_values)) + .set_expression_attribute_names(Some(query21_names)) + .key_condition_expression("PK = :projectname") + .filter_expression("#role = :role") + .send() + .await?; + + // Assert 1 item was returned: `timecard1` + let items = query21_response.items.unwrap(); + assert_eq!(items.len(), 1); + + // Known value test: Assert some properties the item + assert_eq!(items[0]["partition_key"], ss("timecard1")); + assert_eq!(items[0]["ProjectName"], ss("project_002")); + Ok(()) +} + +async fn run_query22( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 22: Get reservations by building ID + // Key condition: PK = Building ID + + let query22_values = HashMap::from([entry(":building", "B-SEA33")]); + + let query22_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-0") + .set_expression_attribute_values(Some(query22_values)) + .key_condition_expression("PK = :building") + .send() + .await?; + + // Assert 2 items returned: + // Expected to be `reservation1` and `reservation2` + let items = query22_response.items.unwrap(); + assert_eq!(items.len(), 2); + + assert!( + ((items[0]["partition_key"] == ss("reservation1")) + && (items[1]["partition_key"] == ss("reservation2"))) + || ((items[0]["partition_key"] == ss("reservation2")) + && (items[1]["partition_key"] == ss("reservation1"))) + ); + Ok(()) +} + +async fn run_query23( + ddb_table_name: &str, + ddb: &mut aws_sdk_dynamodb::Client, +) -> Result<(), crate::BoxError> { + // Query 23: Get reservations by building ID and time range + // Key condition: PK = Building ID, SK between(date1, date2) + // Filter condition: Duration > 0 + + let query23_names = HashMap::from([ + ("#duration".to_string(), "Duration".to_string()), // Duration is a reserved word + ]); + + let query23_values = HashMap::from([ + entry(":building", "B-SEA33"), + entry(":date1", "MS-2022-07-01"), + entry(":date2", "MS-2022-07-08"), + entry(":zero", "0"), + ]); + + let query23_response = ddb + .query() + .table_name(ddb_table_name) + .index_name("GSI-0") + .set_expression_attribute_values(Some(query23_values)) + .set_expression_attribute_names(Some(query23_names)) + .key_condition_expression("PK = :building AND SK BETWEEN :date1 AND :date2") + .filter_expression("#duration > :zero") + .send() + .await?; + + // Assert 2 items returned: + // Expected to be `reservation1` and `reservation2` + let items = query23_response.items.unwrap(); + assert_eq!(items.len(), 2); + + assert!( + ((items[0]["partition_key"] == ss("reservation1")) + && (items[1]["partition_key"] == ss("reservation2"))) + || ((items[0]["partition_key"] == ss("reservation2")) + && (items[1]["partition_key"] == ss("reservation1"))) + ); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/compound_beacon_searchable_encryption.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/compound_beacon_searchable_encryption.rs new file mode 100644 index 000000000..c78f14576 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/compound_beacon_searchable_encryption.rs @@ -0,0 +1,328 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconKeySource; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconVersion; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::CompoundBeacon; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::EncryptedPart; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SearchConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SingleKeyStore; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::StandardBeacon; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_keyStore::client as keystore_client; +use aws_db_esdk::aws_cryptography_keyStore::types::key_store_config::KeyStoreConfig; +use aws_db_esdk::aws_cryptography_keyStore::types::KmsConfiguration; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::client as transform_client; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + This example demonstrates how to set up a compound beacon on encrypted attributes, + put an item with the beacon, and query against that beacon. + This example follows a use case of a database that stores unit inspection information. + This is an extension of the "BasicSearchableEncryptionExample" in this directory. + This example uses the same situation (storing unit inspection information) + and the same table schema. + However, this example uses a different Global Secondary Index (GSI) + that is based on a compound beacon configuration composed of + the `last4` and `unit` attributes. + + Running this example requires access to a DDB table with the + following key configuration: + - Partition key is named "work_id" with type (S) + - Sort key is named "inspection_time" with type (S) + This table must have a Global Secondary Index (GSI) configured named "last4UnitCompound-index": + - Partition key is named "aws_dbe_b_last4UnitCompound" with type (S) + + In this example for storing unit inspection information, this schema is utilized for the data: + - "work_id" stores a unique identifier for a unit inspection work order (v4 UUID) + - "inspection_date" stores an ISO 8601 date for the inspection (YYYY-MM-DD) + - "inspector_id_last4" stores the last 4 digits of the ID of the inspector performing the work + - "unit" stores a 12-digit serial number for the unit being inspected + + The example requires the following ordered input command line parameters: + 1. DDB table name for table to put/query data from + 2. Branch key ID for a branch key that was previously created in your key store. See the + CreateKeyStoreKeyExample. + 3. Branch key wrapping KMS key ARN for the KMS key used to create the branch key with ID + provided in arg 2 + 4. Branch key DDB table ARN for the DDB table representing the branch key store +*/ + +const GSI_NAME: &str = "last4UnitCompound-index"; + +pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::UNIT_INSPECTION_TEST_DDB_TABLE_NAME; + let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN; + let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME; + + // 1. Create Beacons. + // These are the same beacons as in the "BasicSearchableEncryptionExample" in this directory. + // See that file to see details on beacon construction and parameters. + // While we will not directly query against these beacons, + // you must create standard beacons on encrypted fields + // that we wish to use in compound beacons. + let last4_beacon = StandardBeacon::builder() + .name("inspector_id_last4") + .length(10) + .build()?; + + let unit_beacon = StandardBeacon::builder().name("unit").length(30).build()?; + + let standard_beacon_list = vec![last4_beacon, unit_beacon]; + + // 2. Define encrypted parts. + // Encrypted parts define the beacons that can be used to construct a compound beacon, + // and how the compound beacon prefixes those beacon values. + + // A encrypted part must receive: + // - name: Name of a standard beacon + // - prefix: Any string. This is plaintext that prefixes the beaconized value in the compound beacon. + // Prefixes must be unique across the configuration, and must not be a prefix of another prefix; + // i.e. for all configured prefixes, the first N characters of a prefix must not equal another prefix. + // In practice, it is suggested to have a short value distinguishable from other parts served on the prefix. + // For this example, we will choose "L-" as the prefix for "Last 4 digits of inspector ID". + // With this prefix and the standard beacon's bit length definition (10), the beaconized + // version of the inspector ID's last 4 digits will appear as + // `L-000` to `L-3ff` inside a compound beacon. + + // For this example, we will choose "U-" as the prefix for "unit". + // With this prefix and the standard beacon's bit length definition (30), a unit beacon will appear + // as `U-00000000` to `U-3fffffff` inside a compound beacon. + let encrypted_parts_list = vec![ + EncryptedPart::builder() + .name("inspector_id_last4") + .prefix("L-") + .build()?, + EncryptedPart::builder().name("unit").prefix("U-").build()?, + ]; + + // 3. Define compound beacon. + // A compound beacon allows one to serve multiple beacons or attributes from a single index. + // A compound beacon must receive: + // - name: The name of the beacon. Compound beacon values will be written to `aws_ddb_e_[name]`. + // - split: A character separating parts in a compound beacon + // A compound beacon may also receive: + // - encrypted: A list of encrypted parts. This is effectively a list of beacons. We provide the list + // that we created above. + // - constructors: A list of constructors. This is an ordered list of possible ways to create a beacon. + // We have not defined any constructors here; see the complex example for how to do this. + // The client will provide a default constructor, which will write a compound beacon as: + // all signed parts in the order they are added to the signed list; + // all encrypted parts in order they are added to the encrypted list; all parts required. + // In this example, we expect compound beacons to be written as + // `L-XXX.U-YYYYYYYY`, since our encrypted list looks like + // [last4EncryptedPart, unitEncryptedPart]. + // - signed: A list of signed parts, i.e. plaintext attributes. This would be provided if we + // wanted to use plaintext values as part of constructing our compound beacon. We do not + // provide this here; see the Complex example for an example. + let compound_beacon_list = vec![CompoundBeacon::builder() + .name("last4UnitCompound") + .split(".") + .encrypted(encrypted_parts_list) + .build()?]; + + // 4. Configure the Keystore + // These are the same constructions as in the Basic example, which describes these in more detail. + + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let key_store_config = KeyStoreConfig::builder() + .kms_client(aws_sdk_kms::Client::new(&sdk_config)) + .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config)) + .ddb_table_name(branch_key_ddb_table_name) + .logical_key_store_name(branch_key_ddb_table_name) + .kms_configuration(KmsConfiguration::KmsKeyArn( + branch_key_wrapping_kms_key_arn.to_string(), + )) + .build()?; + + let key_store = keystore_client::Client::from_conf(key_store_config)?; + + // 5. Create BeaconVersion. + // This is similar to the Basic example, except we have also provided a compoundBeaconList. + // We must also continue to provide all of the standard beacons that compose a compound beacon list. + let beacon_version = BeaconVersion::builder() + .standard_beacons(standard_beacon_list) + .compound_beacons(compound_beacon_list) + .version(1) // MUST be 1 + .key_store(key_store.clone()) + .key_source(BeaconKeySource::Single( + SingleKeyStore::builder() + // `keyId` references a beacon key. + // For every branch key we create in the keystore, + // we also create a beacon key. + // This beacon key is not the same as the branch key, + // but is created with the same ID as the branch key. + .key_id(branch_key_id) + .cache_ttl(6000) + .build()?, + )) + .build()?; + let beacon_versions = vec![beacon_version]; + + // 6. Create a Hierarchical Keyring + // This is the same configuration as in the Basic example. + + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + let kms_keyring = mpl + .create_aws_kms_hierarchical_keyring() + .branch_key_id(branch_key_id) + .key_store(key_store) + .ttl_seconds(6000) + .send() + .await?; + + // 7. Configure which attributes are encrypted and/or signed when writing new items. + let attribute_actions_on_encrypt = HashMap::from([ + ("work_id".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("inspection_date".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ( + "inspector_id_last4".to_string(), + CryptoAction::EncryptAndSign, + ), // Beaconized attributes must be encrypted + ("unit".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted + ]); + + // We do not need to define a crypto action on last4UnitCompound. + // We only need to define crypto actions on attributes that we pass to PutItem. + + // 8. Create the DynamoDb Encryption configuration for the table we will be writing to. + // The beaconVersions are added to the search configuration. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("work_id") + .sort_key_name("inspection_date") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(kms_keyring) + .search( + SearchConfig::builder() + .write_version(1) // MUST be 1 + .versions(beacon_versions) + .build()?, + ) + .build()?; + + // 9. Create config + let encryption_config = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 10. Create an item with both attributes used in the compound beacon. + let item = HashMap::from([ + ( + "work_id".to_string(), + AttributeValue::S("9ce39272-8068-4efd-a211-cd162ad65d4c".to_string()), + ), + ( + "inspection_date".to_string(), + AttributeValue::S("2023-06-13".to_string()), + ), + ( + "inspector_id_last4".to_string(), + AttributeValue::S("5678".to_string()), + ), + ( + "unit".to_string(), + AttributeValue::S("011899988199".to_string()), + ), + ]); + + // 11. If developing or debugging, verify config by checking compound beacon values directly + let trans = transform_client::Client::from_conf(encryption_config.clone())?; + let resolve_output = trans + .resolve_attributes() + .table_name(ddb_table_name) + .item(item.clone()) + .version(1) + .send() + .await?; + + // Verify that there are no virtual fields + assert_eq!(resolve_output.virtual_fields.unwrap().len(), 0); + + // Verify that CompoundBeacons has the expected value + let compound_beacons = resolve_output.compound_beacons.unwrap(); + assert_eq!(compound_beacons.len(), 1); + assert_eq!( + compound_beacons["last4UnitCompound"], + "L-5678.U-011899988199" + ); + // Note : the compound beacon actually stored in the table is not "L-5678.U-011899988199" + // but rather something like "L-abc.U-123", as both parts are EncryptedParts + // and therefore the text is replaced by the associated beacon + + // 12. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(encryption_config)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 13. Write the item to the table + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item.clone())) + .send() + .await?; + + // 14. Query for the item we just put. + let expression_attribute_values = HashMap::from([ + // This query expression takes a few factors into consideration: + // - The configured prefix for the last 4 digits of an inspector ID is "L-"; + // the prefix for the unit is "U-" + // - The configured split character, separating component parts, is "." + // - The default constructor adds encrypted parts in the order they are in the encrypted list, which + // configures `last4` to come before `unit`` + // NOTE: We did not need to create a compound beacon for this query. This query could have also been + // done by querying on the partition and sort key, as was done in the Basic example. + // This is intended to be a simple example to demonstrate how one might set up a compound beacon. + // For examples where compound beacons are required, see the Complex example. + // The most basic extension to this example that would require a compound beacon would add a third + // part to the compound beacon, then query against three parts. + ( + ":value".to_string(), + AttributeValue::S("L-5678.U-011899988199".to_string()), + ), + ]); + + // GSIs are sometimes a little bit delayed, so we retry if the query comes up empty. + for _i in 0..10 { + let query_response = ddb + .query() + .table_name(ddb_table_name) + .index_name(GSI_NAME) + .key_condition_expression("last4UnitCompound = :value") + .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .send() + .await?; + + // if no results, sleep and try again + if query_response.items.is_none() || query_response.items.as_ref().unwrap().is_empty() { + std::thread::sleep(std::time::Duration::from_millis(20)); + continue; + } + + let attribute_values = query_response.items.unwrap(); + // Validate only 1 item was returned: the item we just put + assert_eq!(attribute_values.len(), 1); + let returned_item = &attribute_values[0]; + // Validate the item has the expected attributes + assert_eq!( + returned_item["inspector_id_last4"], + AttributeValue::S("5678".to_string()) + ); + assert_eq!( + returned_item["unit"], + AttributeValue::S("011899988199".to_string()) + ); + break; + } + println!("compound_beacon_searchable_encryption successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/mod.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/mod.rs new file mode 100644 index 000000000..5dcd38898 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/mod.rs @@ -0,0 +1,8 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +pub mod basic_searchable_encryption; +pub mod beacon_styles_searchable_encryption; +pub mod complexexample; +pub mod compound_beacon_searchable_encryption; +pub mod virtual_beacon_searchable_encryption; diff --git a/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/virtual_beacon_searchable_encryption.rs b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/virtual_beacon_searchable_encryption.rs new file mode 100644 index 000000000..6de40463e --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/searchableencryption/virtual_beacon_searchable_encryption.rs @@ -0,0 +1,437 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_utils; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconKeySource; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::BeaconVersion; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::DynamoDbTableEncryptionConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::GetPrefix; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SearchConfig; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::SingleKeyStore; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::StandardBeacon; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::VirtualField; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::VirtualPart; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_dynamoDb::types::VirtualTransform; +use aws_db_esdk::aws_cryptography_dbEncryptionSdk_structuredEncryption::types::CryptoAction; +use aws_db_esdk::aws_cryptography_keyStore::client as keystore_client; +use aws_db_esdk::aws_cryptography_keyStore::types::key_store_config::KeyStoreConfig; +use aws_db_esdk::aws_cryptography_keyStore::types::KmsConfiguration; +use aws_db_esdk::aws_cryptography_materialProviders::client as mpl_client; +use aws_db_esdk::aws_cryptography_materialProviders::types::material_providers_config::MaterialProvidersConfig; +use aws_db_esdk::client as transform_client; +use aws_db_esdk::intercept::DbEsdkInterceptor; +use aws_db_esdk::DynamoDbTablesEncryptionConfig; +use aws_sdk_dynamodb::types::AttributeValue; +use std::collections::HashMap; + +/* + This example demonstrates how to set up a virtual field from two DDB + attributes, create a standard beacon with that field, put an item with + that beacon, and query against that beacon. + + A virtual field is a field consisting of a transformation of one or more attributes in a DDB item. + Virtual fields are useful in querying against encrypted fields that only have a handful of + possible values. They allow you to take fields with few possible values, concatenate + them to other fields, then query against the combined field. This enables using these types of + fields in queries while making it infeasible to identify which beacon values encode + the few possible distinct plaintexts. This is explained in more detail below. + Virtual fields are not stored in the DDB table. However, they are used to construct + a beacon, the value of which is stored. + + For more information on virtual fields, see + https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/beacons.html#virtual-field + + For our example, we will construct a virtual field + from two DDB attributes `state` and `hasTestResult` as `state`+prefix(`hasTestResult`, 1). + We will then create a beacon out of this virtual field and use it to search. + + This example follows a use case of a database that stores customer test result metadata. + Records are indexed by `customer_id` and store a `state` attribute, representing the + US state or territory where the customer lives, and a `hasTestResult` boolean attribute, + representing whether the customer has a "test result" available. (Maybe this represents + some medical test result, and this table stores "result available" metadata.) We assume + that values in these fields are uniformly distributed across all possible values for + these fields (56 for `state`, 2 for `hasTestResult`), and are uniformly distributed across + customer IDs. + + The motivation behind this example is to demonstrate how and why one would use a virtual beacon. + In this example, our table stores records with an encrypted boolean `hasTestResult` attribute. + We would like to be able to query for customers in a given state with a `true` hasTestResult + attribute. + + To be able to execute this query securely and efficiently, we want the following + properties on our table: + 1. Hide the distribution of `hasTestResult` attribute values (i.e. it should be infeasible + to determine the percentage of `true`s to `false`s across the dataset from beaconized + values) + 2. Query against a combination of whether `hasTestResult` is true/false and the `state` field + We cannot achieve these properties with a standard beacon on a true/false attribute. Following + the guidance to choose a beacon length: + https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html + For a boolean value (in our case, whether `hasTestResult` is true or false), the acceptable + bounds for beacon length are either 0 or 1. This corresponds to either not storing a beacon + (length 0), or effectively storing another boolean attribute (length 1). With + length 0, this beacon is useless for searching (violating property 2); with length 1, this + beacon may not hide the attribute (violating property 1). + In addition, choosing a longer beacon length does not help us. + Each attribute value is mapped to a distinct beacon. + Since booleans only have 2 possible attribute values, we will still only have 2 possible + beacon values, though those values may be longer. A longer beacon provides no advantages over + beacon of length 1 in this situation. + + A compound beacon also does not help. + To (over)simplify, a compound beacon is a concatenation of standard beacons, + i.e. beacon(`state`)+beacon(`hasTestResult`). + The `hasTestResult` beacon is still visible, so we would still have the problems above. + + To achieve these properties, we instead construct a virtual field and use that in our beacon, + i.e. beacon(`state`+`hasTestResult`). Assuming these fields are well-distributed across + customer IDs and possible values, this gives us both desired properties; we can query against + both attributes while hiding information from the underlying data. This is demonstrated in more + detail below. + + Running this example requires access to a DDB table with the + following primary key configuration: + - Partition key is named "customer_id" with type (S) + - Sort key is named "create_time" with type (S) + This table must have a Global Secondary Index (GSI) configured named "stateAndHasTestResult-index": + - Partition key is named "aws_dbe_b_stateAndHasTestResult" with type (S) + + In this example for storing customer location data, this schema is utilized for the data: + - "customer_id" stores a unique customer identifier + - "create_time" stores a Unix timestamp + - "state" stores an encrypted 2-letter US state or territory abbreviation + (https://www.faa.gov/air_traffic/publications/atpubs/cnt_html/appendix_a.html) + - "hasTestResult" is not part of the schema, but is an attribute utilized in this example. + It stores a boolean attribute (false/true) indicating whether this customer has a test result + available. + + The example requires the following ordered input command line parameters: + 1. DDB table name for table to put/query data from + 2. Branch key ID for a branch key that was previously created in your key store. See the + CreateKeyStoreKeyExample. + 2. Branch key wrapping KMS key ARN for the KMS key used to create the branch key + 3. Branch key DDB table name for the DDB table representing the branch key store +*/ + +const GSI_NAME: &str = "stateAndHasTestResult-index"; + +pub async fn put_and_query_with_beacon(branch_key_id: &str) -> Result<(), crate::BoxError> { + let ddb_table_name = test_utils::SIMPLE_BEACON_TEST_DDB_TABLE_NAME; + let branch_key_wrapping_kms_key_arn = test_utils::TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN; + let branch_key_ddb_table_name = test_utils::TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME; + + // 1. Construct a length-1 prefix virtual transform. + // `hasTestResult` is a binary attribute, containing either `true` or `false`. + // As an example to demonstrate virtual transforms, we will truncate the value + // of `hasTestResult` in the virtual field to the length-1 prefix of the binary value, i.e.: + // - "true" -> "t" + // - "false -> "f" + // This is not necessary. This is done as a demonstration of virtual transforms. + // Virtual transform operations treat all attributes as strings + // (i.e. the boolean value `true` is interpreted as a string "true"), + // so its length-1 prefix is just "t". + + let length1_prefix_virtual_transform_list = vec![VirtualTransform::Prefix( + GetPrefix::builder().length(1).build()?, + )]; + + // 2. Construct the VirtualParts required for the VirtualField + let has_test_result_part = VirtualPart::builder() + .loc("hasTestResult") + .trans(length1_prefix_virtual_transform_list) + .build()?; + + let state_part = VirtualPart::builder().loc("state").build()?; + // Note that we do not apply any transform to the `state` attribute, + // and the virtual field will read in the attribute as-is. + + // 3. Construct the VirtualField from the VirtualParts + // Note that the order that virtual parts are added to the virtualPartList + // dictates the order in which they are concatenated to build the virtual field. + // You must add virtual parts in the same order on write as you do on read. + let virtual_part_list = vec![state_part, has_test_result_part]; + + let state_and_has_test_result_field = VirtualField::builder() + .name("stateAndHasTestResult") + .parts(virtual_part_list) + .build()?; + + let virtual_field_list = vec![state_and_has_test_result_field]; + + // 4. Configure our beacon. + // The virtual field is assumed to hold a US 2-letter state abbreviation + // (56 possible values = 50 states + 6 territories) concatenated with a binary attribute + // (2 possible values: true/false hasTestResult field), we expect a population size of + // 56 * 2 = 112 possible values. + // We will also assume that these values are reasonably well-distributed across + // customer IDs. In practice, this will not be true. We would expect + // more populous states to appear more frequently in the database. + // A more complex analysis would show that a stricter upper bound + // is necessary to account for this by hiding information from the + // underlying distribution. + // + // This link provides guidance for choosing a beacon length: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/choosing-beacon-length.html + // We follow the guidance in the link above to determine reasonable bounds for beacon length: + // - min: log(sqrt(112))/log(2) ~= 3.4, round down to 3 + // - max: log((112/2))/log(2) ~= 5.8, round up to 6 + // You will somehow need to round results to a nearby integer. + // We choose to round to the nearest integer; you might consider a different rounding approach. + // Rounding up will return fewer expected "false positives" in queries, + // leading to fewer decrypt calls and better performance, + // but it is easier to identify which beacon values encode distinct plaintexts. + // Rounding down will return more expected "false positives" in queries, + // leading to more decrypt calls and worse performance, + // but it is harder to identify which beacon values encode distinct plaintexts. + // We can choose a beacon length between 3 and 6: + // - Closer to 3, we expect more "false positives" to be returned, + // making it harder to identify which beacon values encode distinct plaintexts, + // but leading to more decrypt calls and worse performance + // - Closer to 6, we expect fewer "false positives" returned in queries, + // leading to fewer decrypt calls and better performance, + // but it is easier to identify which beacon values encode distinct plaintexts. + // As an example, we will choose 5. + // Values stored in aws_dbe_b_stateAndHasTestResult will be 5 bits long (0x00 - 0x1f) + // There will be 2^5 = 32 possible HMAC values. + // With a well-distributed dataset (112 values), for a particular beacon we expect + // (112/32) = 3.5 combinations of abbreviation + true/false attribute + // sharing that beacon value. + let standard_beacon_list = vec![StandardBeacon::builder() + .name("stateAndHasTestResult") + .length(5) + .build()?]; + + // 5. Configure Keystore. + // This example expects that you have already set up a KeyStore with a single branch key. + // See the "CreateKeyStoreTableExample" and "CreateKeyStoreKeyExample" files for how to do this. + // After you create a branch key, you should persist its ID for use in this example. + let sdk_config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; + let key_store_config = KeyStoreConfig::builder() + .kms_client(aws_sdk_kms::Client::new(&sdk_config)) + .ddb_client(aws_sdk_dynamodb::Client::new(&sdk_config)) + .ddb_table_name(branch_key_ddb_table_name) + .logical_key_store_name(branch_key_ddb_table_name) + .kms_configuration(KmsConfiguration::KmsKeyArn( + branch_key_wrapping_kms_key_arn.to_string(), + )) + .build()?; + + let key_store = keystore_client::Client::from_conf(key_store_config)?; + + // 6. Create BeaconVersion. + // The BeaconVersion inside the list holds the list of beacons on the table. + // The BeaconVersion also stores information about the keystore. + // BeaconVersion must be provided: + // - keyStore: The keystore configured in the previous step. + // - keySource: A configuration for the key source. + // For simple use cases, we can configure a 'singleKeySource' which + // statically configures a single beaconKey. That is the approach this example takes. + // For use cases where you want to use different beacon keys depending on the data + // (for example if your table holds data for multiple tenants, and you want to use + // a different beacon key per tenant), look into configuring a MultiKeyStore: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/searchable-encryption-multitenant.html + // We also provide our standard beacon list and virtual fields here. + let beacon_version = BeaconVersion::builder() + .standard_beacons(standard_beacon_list) + .virtual_fields(virtual_field_list) + .version(1) // MUST be 1 + .key_store(key_store.clone()) + .key_source(BeaconKeySource::Single( + SingleKeyStore::builder() + // `keyId` references a beacon key. + // For every branch key we create in the keystore, + // we also create a beacon key. + // This beacon key is not the same as the branch key, + // but is created with the same ID as the branch key. + .key_id(branch_key_id) + .cache_ttl(6000) + .build()?, + )) + .build()?; + let beacon_versions = vec![beacon_version]; + + // 7. Create a Hierarchical Keyring + // This is a KMS keyring that utilizes the keystore table. + // This config defines how items are encrypted and decrypted. + // NOTE: You should configure this to use the same keystore as your search config. + let mpl_config = MaterialProvidersConfig::builder().build()?; + let mpl = mpl_client::Client::from_conf(mpl_config)?; + let kms_keyring = mpl + .create_aws_kms_hierarchical_keyring() + .branch_key_id(branch_key_id) + .key_store(key_store) + .ttl_seconds(6000) + .send() + .await?; + + // 8. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Any attributes that will be used in beacons must be configured as ENCRYPT_AND_SIGN. + let attribute_actions_on_encrypt = HashMap::from([ + ("customer_id".to_string(), CryptoAction::SignOnly), // Our partition attribute must be SIGN_ONLY + ("create_time".to_string(), CryptoAction::SignOnly), // Our sort attribute must be SIGN_ONLY + ("state".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted + ("hasTestResult".to_string(), CryptoAction::EncryptAndSign), // Beaconized attributes must be encrypted + ]); + + // 9. Create the DynamoDb Encryption configuration for the table we will be writing to. + // The beaconVersions are added to the search configuration. + let table_config = DynamoDbTableEncryptionConfig::builder() + .logical_table_name(ddb_table_name) + .partition_key_name("customer_id") + .sort_key_name("create_time") + .attribute_actions_on_encrypt(attribute_actions_on_encrypt) + .keyring(kms_keyring) + .search( + SearchConfig::builder() + .write_version(1) // MUST be 1 + .versions(beacon_versions) + .build()?, + ) + .build()?; + + // 10. Create config + let encryption_config = DynamoDbTablesEncryptionConfig::builder() + .table_encryption_configs(HashMap::from([(ddb_table_name.to_string(), table_config)])) + .build()?; + + // 11. Create test items + + // Create item with hasTestResult=true + let item_with_has_test_result = HashMap::from([ + ( + "customer_id".to_string(), + AttributeValue::S("ABC-123".to_string()), + ), + ( + "create_time".to_string(), + AttributeValue::N("1681495205".to_string()), + ), + ("state".to_string(), AttributeValue::S("CA".to_string())), + ("hasTestResult".to_string(), AttributeValue::Bool(true)), + ]); + + // Create item with hasTestResult=false + let item_with_no_has_test_result = HashMap::from([ + ( + "customer_id".to_string(), + AttributeValue::S("DEF-456".to_string()), + ), + ( + "create_time".to_string(), + AttributeValue::N("1681495205".to_string()), + ), + ("state".to_string(), AttributeValue::S("CA".to_string())), + ("hasTestResult".to_string(), AttributeValue::Bool(false)), + ]); + + // 12. If developing or debugging, verify config by checking virtual field values directly + let trans = transform_client::Client::from_conf(encryption_config.clone())?; + let resolve_output = trans + .resolve_attributes() + .table_name(ddb_table_name) + .item(item_with_has_test_result.clone()) + .version(1) + .send() + .await?; + + // CompoundBeacons is empty because we have no Compound Beacons configured + assert_eq!(resolve_output.compound_beacons.unwrap().len(), 0); + + // Verify that VirtualFields has the expected value + let virtual_fields = resolve_output.virtual_fields.unwrap(); + assert_eq!(virtual_fields.len(), 1); + assert_eq!(virtual_fields["stateAndHasTestResult"], "CAt"); + + // 13. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&sdk_config) + .interceptor(DbEsdkInterceptor::new(encryption_config)) + .build(); + let ddb = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + // 14. Put two items into our table using the above client. + // The two items will differ only in their `customer_id` attribute (primary key) + // and their `hasTestResult` attribute. + // We will query against these items to demonstrate how to use our setup above + // to query against our `stateAndHasTestResult` beacon. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + // Since our configuration includes a beacon on a virtual field named + // `stateAndHasTestResult`, the client will add an attribute + // to the item with name `aws_dbe_b_stateAndHasTestResult`. + // Its value will be an HMAC truncated to as many bits as the + // beacon's `length` parameter; i.e. 5. + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item_with_has_test_result.clone())) + .send() + .await?; + + ddb.put_item() + .table_name(ddb_table_name) + .set_item(Some(item_with_no_has_test_result.clone())) + .send() + .await?; + + // 15. Query by stateAndHasTestResult attribute. + // Note that we are constructing the query as if we were querying on plaintext values. + // However, the DDB encryption client will detect that this attribute name has a beacon configured. + // The client will add the beaconized attribute name and attribute value to the query, + // and transform the query to use the beaconized name and value. + // Internally, the client will query for and receive all items with a matching HMAC value in the beacon field. + // This may include a number of "false positives" with different ciphertext, but the same truncated HMAC. + // e.g. if truncate(HMAC("CAt"), 5) == truncate(HMAC("DCf"), 5), the query will return both items. + // The client will decrypt all returned items to determine which ones have the expected attribute values, + // and only surface items with the correct plaintext to the user. + // This procedure is internal to the client and is abstracted away from the user; + // e.g. the user will only see "CAt" and never "DCf", though the actual query returned both. + let expression_attribute_values = HashMap::from([ + // We are querying for the item with `state`="CA" and `hasTestResult`=`true`. + // Since we added virtual parts as `state` then `hasTestResult`, + // we must write our query expression in the same order. + // We constructed our virtual field as `state`+`hasTestResult`, + // so we add the two parts in that order. + // Since we also created a virtual transform that truncated `hasTestResult` + // to its length-1 prefix, i.e. "true" -> "t", + // we write that field as its length-1 prefix in the query. + ( + ":stateAndHasTestResult".to_string(), + AttributeValue::S("CAt".to_string()), + ), + ]); + + // GSIs are sometimes a little bit delayed, so we retry if the query comes up empty. + for _i in 0..10 { + let query_response = ddb + .query() + .table_name(ddb_table_name) + .index_name(GSI_NAME) + .key_condition_expression("stateAndHasTestResult = :stateAndHasTestResult") + .set_expression_attribute_values(Some(expression_attribute_values.clone())) + .send() + .await?; + + // if no results, sleep and try again + if query_response.items.is_none() || query_response.items.as_ref().unwrap().is_empty() { + std::thread::sleep(std::time::Duration::from_millis(20)); + continue; + } + + let attribute_values = query_response.items.unwrap(); + // Validate only 1 item was returned: the item we just put + assert_eq!(attribute_values.len(), 1); + let returned_item = &attribute_values[0]; + // Validate the item has the expected attributes + assert_eq!(returned_item["state"], AttributeValue::S("CA".to_string())); + assert_eq!(returned_item["hasTestResult"], AttributeValue::Bool(true)); + break; + } + println!("virtual_beacon_searchable_encryption successful."); + Ok(()) +} diff --git a/DynamoDbEncryption/runtimes/rust/examples/test_utils.rs b/DynamoDbEncryption/runtimes/rust/examples/test_utils.rs new file mode 100644 index 000000000..01b1eb012 --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/examples/test_utils.rs @@ -0,0 +1,46 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +pub const TEST_KEYSTORE_NAME: &str = "KeyStoreDdbTable"; +pub const TEST_LOGICAL_KEYSTORE_NAME: &str = "KeyStoreDdbTable"; + +pub const TEST_KEYSTORE_KMS_KEY_ID: &str = + "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"; + +pub const TEST_AWS_ACCOUNT_ID: &str = "658956600833"; + +pub const TEST_AWS_REGION: &str = "us-west-2"; + +// These are public KMS Keys that MUST only be used for testing, and MUST NOT be used for any production data +pub const TEST_KMS_KEY_ID: &str = + "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"; + +pub const TEST_MRK_KEY_ID: &str = + "arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + +pub const TEST_KMS_RSA_KEY_ID: &str = + "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6"; + +pub const TEST_MRK_REPLICA_KEY_ID_US_EAST_1: &str = + "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + +pub const TEST_MRK_REPLICA_KEY_ID_EU_WEST_1: &str = + "arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + +// Our tests require access to DDB Table with this name +pub const TEST_DDB_TABLE_NAME: &str = "DynamoDbEncryptionInterceptorTestTableCS"; +pub const TEST_COMPLEX_TABLE_NAME: &str = "ComplexBeaconTestTableCS"; + +// Our tests require access to DDB Tables with these name +pub const SIMPLE_BEACON_TEST_DDB_TABLE_NAME: &str = "SimpleBeaconTestTable"; +pub const UNIT_INSPECTION_TEST_DDB_TABLE_NAME: &str = "UnitInspectionTestTableCS"; + +// The branch key must have been created using this KMS key +// Note: This is a public resource that anyone can access. +// This MUST NOT be used to encrypt any production data. +pub const TEST_BRANCH_KEY_WRAPPING_KMS_KEY_ARN: &str = + "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"; + +// Our tests require access to DDB Table with this name configured as a branch keystore +pub const TEST_BRANCH_KEYSTORE_DDB_TABLE_NAME: &str = "KeyStoreDdbTable"; +pub const TEST_COMPLEX_DDB_TABLE_NAME: &str = "ComplexBeaconTestTable"; diff --git a/DynamoDbEncryption/runtimes/rust/src/intercept.rs b/DynamoDbEncryption/runtimes/rust/src/intercept.rs new file mode 100644 index 000000000..50395a38d --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/src/intercept.rs @@ -0,0 +1,183 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![deny(warnings, unconditional_panic)] +#![deny(nonstandard_style)] +#![deny(clippy::all)] + +use aws_sdk_dynamodb::{ + config::{ + ConfigBag, Intercept, RuntimeComponents, + interceptors::{BeforeSerializationInterceptorContextMut, FinalizerInterceptorContextMut}, + }, + error::BoxError, +}; +use aws_smithy_runtime_api::client::interceptors::context::Input; +use aws_smithy_types::config_bag::{Storable, StoreReplace}; + +#[macro_export] +macro_rules! modify_request { + ($cfg:ident,$request:ident,$self:ident,$transform:ident) => {{ + // store the original request + $cfg.interceptor_state() + .store_put(OriginalRequest(Input::erase($request.clone()))); + + // transform the request + let result = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + $self + .client + .$transform() + .sdk_input($request.clone()) + .send() + .await + }) + }); + match result { + Ok(x) => *$request = x.transformed_input.unwrap(), + Err(x) => { + let s = format!("{:?}", x); + return Err(s.into()); + } + }; + }}; +} + +#[macro_export] +macro_rules! modify_response { + ($cfg:ident,$type:ty,$response:ident,$self:ident,$transform:ident) => {{ + // retrieve the original request + let original = $cfg + .load::() + .expect("we put this in ourselves"); + let original = original + .0 + .downcast_ref::<$type>() + .expect("we know this type corresponds to the output type"); + + // transform the response + let result = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + $self + .client + .$transform() + .original_input(original.clone()) + .sdk_output($response.clone()) + .send() + .await + }) + }); + match result { + Ok(x) => *$response = x.transformed_output.unwrap(), + Err(x) => { + let s = format!("{:?}", x); + return Err(s.into()); + } + }; + }}; +} + +#[derive(Debug)] +pub struct DbEsdkInterceptor { + client: crate::client::Client, +} + +impl DbEsdkInterceptor { + pub fn new( + config: crate::types::dynamo_db_tables_encryption_config::DynamoDbTablesEncryptionConfig, + ) -> Self { + let client = crate::client::Client::from_conf(config).unwrap(); // FIXME + DbEsdkInterceptor { client } + } +} + +unsafe impl Sync for DbEsdkInterceptor {} +unsafe impl Send for DbEsdkInterceptor {} + +#[derive(Debug)] +struct OriginalRequest(Input); + +impl Storable for OriginalRequest { + type Storer = StoreReplace; +} + +impl Intercept for DbEsdkInterceptor { + fn name(&self) -> &'static str { + "DbEsdkInterceptor" + } + + fn modify_before_serialization( + &self, + // https://docs.rs/aws-smithy-runtime-api/latest/aws_smithy_runtime_api/client/interceptors/context/struct.BeforeSerializationInterceptorContextMut.html + context: &mut BeforeSerializationInterceptorContextMut, + _rc: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + if let Some(batch_execute_statement_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, batch_execute_statement_request, self, batch_execute_statement_input_transform); + } else if let Some(batch_get_item_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, batch_get_item_request, self, batch_get_item_input_transform); + } else if let Some(batch_write_item_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, batch_write_item_request, self, batch_write_item_input_transform); + } else if let Some(delete_item_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, delete_item_request, self, delete_item_input_transform); + } else if let Some(execute_statement_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, execute_statement_request, self, execute_statement_input_transform); + } else if let Some(execute_transaction_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, execute_transaction_request, self, execute_transaction_input_transform); + } else if let Some(get_item_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, get_item_request, self, get_item_input_transform); + } else if let Some(put_item_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, put_item_request, self, put_item_input_transform); + } else if let Some(query_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, query_request, self, query_input_transform); + } else if let Some(scan_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, scan_request, self, scan_input_transform); + } else if let Some(transact_get_items_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, transact_get_items_request, self, transact_get_items_input_transform); + } else if let Some(transact_write_items_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, transact_write_items_request, self, transact_write_items_input_transform); + } else if let Some(update_item_request) = context.input_mut().downcast_mut::() { + modify_request!(cfg, update_item_request, self, update_item_input_transform); + } + Ok(()) + } + + fn modify_before_attempt_completion( + &self, + context: &mut FinalizerInterceptorContextMut, + _rc: &RuntimeComponents, + cfg: &mut ConfigBag, + ) -> Result<(), BoxError> { + if let Some(Ok(output)) = context.output_or_error_mut() { + if let Some(batch_execute_statement_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::batch_execute_statement::BatchExecuteStatementInput, batch_execute_statement_response, self, batch_execute_statement_output_transform); + } else if let Some(batch_get_item_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::batch_get_item::BatchGetItemInput, batch_get_item_response, self, batch_get_item_output_transform); + } else if let Some(batch_write_item_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::batch_write_item::BatchWriteItemInput, batch_write_item_response, self, batch_write_item_output_transform); + } else if let Some(delete_item_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::delete_item::DeleteItemInput, delete_item_response, self, delete_item_output_transform); + } else if let Some(execute_statement_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::execute_statement::ExecuteStatementInput, execute_statement_response, self, execute_statement_output_transform); + } else if let Some(execute_transaction_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::execute_transaction::ExecuteTransactionInput, execute_transaction_response, self, execute_transaction_output_transform); + } else if let Some(get_item_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::get_item::GetItemInput, get_item_response, self, get_item_output_transform); + } else if let Some(put_item_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::put_item::PutItemInput, put_item_response, self, put_item_output_transform); + } else if let Some(query_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::query::QueryInput, query_response, self, query_output_transform); + } else if let Some(scan_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::scan::ScanInput, scan_response, self, scan_output_transform); + } else if let Some(transact_get_items_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::transact_get_items::TransactGetItemsInput, transact_get_items_response, self, transact_get_items_output_transform); + } else if let Some(transact_write_items_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::transact_write_items::TransactWriteItemsInput, transact_write_items_response, self, transact_write_items_output_transform); + } else if let Some(update_item_response) = output.downcast_mut::() { + modify_response!(cfg, aws_sdk_dynamodb::operation::update_item::UpdateItemInput, update_item_response, self, update_item_output_transform); + } + } + Ok(()) + } +} diff --git a/DynamoDbEncryption/runtimes/rust/src/lib.rs b/DynamoDbEncryption/runtimes/rust/src/lib.rs new file mode 100644 index 000000000..451e83e2c --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/src/lib.rs @@ -0,0 +1,67 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![allow(warnings, unconditional_panic)] +#![allow(nonstandard_style)] + +pub mod client; +pub mod conversions; +pub mod deps; +/// Common errors and error handling utilities. +pub mod error; +/// All operations that this crate can perform. +pub mod operation; +pub mod types; + +#[cfg(feature = "wrapped-client")] +pub mod wrapped; + +mod standard_library_conversions; +mod standard_library_externs; + +pub use client::Client; +pub use types::dynamo_db_tables_encryption_config::DynamoDbTablesEncryptionConfig; + +pub use crate::deps::aws_cryptography_dbEncryptionSdk_dynamoDb; +pub use crate::deps::aws_cryptography_dbEncryptionSdk_dynamoDb_itemEncryptor; +pub use crate::deps::aws_cryptography_dbEncryptionSdk_structuredEncryption; +pub use crate::deps::aws_cryptography_keyStore; +pub use crate::deps::aws_cryptography_materialProviders; +pub use crate::deps::aws_cryptography_primitives; + +pub(crate) mod implementation_from_dafny; +pub(crate) use crate::implementation_from_dafny::r#_Wrappers_Compile; +pub(crate) use crate::implementation_from_dafny::software; +pub(crate) use crate::implementation_from_dafny::AesKdfCtr; +pub(crate) use crate::implementation_from_dafny::ConcurrentCall; +pub(crate) use crate::implementation_from_dafny::DafnyLibraries; +pub(crate) use crate::implementation_from_dafny::ExternDigest; +pub(crate) use crate::implementation_from_dafny::ExternRandom; +pub(crate) use crate::implementation_from_dafny::Signature; +pub(crate) use crate::implementation_from_dafny::Time; +pub(crate) use crate::implementation_from_dafny::_LocalCMC_Compile; +pub(crate) use crate::implementation_from_dafny::_StormTracker_Compile; +pub(crate) use crate::implementation_from_dafny::ECDH; +pub(crate) use crate::implementation_from_dafny::HMAC; +pub(crate) use crate::implementation_from_dafny::UTF8; +pub(crate) use crate::implementation_from_dafny::UUID; + +pub mod aes_gcm; +pub mod aes_kdf_ctr; +pub mod concurrent_call; +pub mod dafny_libraries; +pub mod ddb; +pub mod digest; +pub mod ecdh; +pub mod ecdsa; +pub mod hmac; +pub mod intercept; +pub mod kms; +pub mod local_cmc; +pub mod random; +pub mod rsa; +pub mod sets; +pub mod software_externs; +pub mod storm_tracker; +pub mod time; +pub mod uuid; diff --git a/DynamoDbEncryption/runtimes/rust/src/software_externs.rs b/DynamoDbEncryption/runtimes/rust/src/software_externs.rs new file mode 100644 index 000000000..e5461844c --- /dev/null +++ b/DynamoDbEncryption/runtimes/rust/src/software_externs.rs @@ -0,0 +1,99 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![deny(warnings, unconditional_panic)] +#![deny(nonstandard_style)] +#![deny(clippy::all)] +#![allow(non_snake_case)] + +pub mod software { + pub mod amazon { + pub mod cryptography { + pub mod internaldafny { + pub mod StormTrackingCMC { + pub use crate::storm_tracker::internal_StormTrackingCMC::*; + } + pub mod SynchronizedLocalCMC { + pub use crate::local_cmc::internal_SynchronizedLocalCMC::*; + } + } + pub mod dbencryptionsdk { + pub mod dynamodb { + pub mod itemencryptor { + pub mod internaldafny { + pub mod legacy { + use crate::software::amazon::cryptography::dbencryptionsdk::dynamodb::itemencryptor::internaldafny::types::Error as DafnyError; + use crate::software::amazon::cryptography::dbencryptionsdk::dynamodb::internaldafny::types::LegacyPolicy; + use ::std::rc::Rc; + type Legacy = ::dafny_runtime::Object; + + fn error(s: &str) -> Rc { + Rc::new(DafnyError::DynamoDbItemEncryptorException { + message: + dafny_runtime::dafny_runtime_conversions::unicode_chars_false::string_to_dafny_string(s), + }) + } + pub struct InternalLegacyOverride { + pub r#__i_policy: Rc, + } + fn fail_override() -> Rc< + crate::_Wrappers_Compile::Result< + Rc>, + Rc, + >, + > { + Rc::new(crate::_Wrappers_Compile::Result::Failure { + error: error("Legacy configuration unsupported."), + }) + } + fn success_override() -> Rc< + crate::_Wrappers_Compile::Result< + Rc>, + Rc, + >, + > { + Rc::new(crate::_Wrappers_Compile::Result::Success { + value: Rc::new(crate::_Wrappers_Compile::Option::None {}), + }) + } + + impl InternalLegacyOverride { + pub fn Build( + config: &Rc, + ) -> Rc< + crate::_Wrappers_Compile::Result< + Rc>, + Rc, + >, + > { + match &**config.legacyOverride() { + crate::_Wrappers_Compile::Option::Some{value} => { + match &**value.policy() { + LegacyPolicy::FORBID_LEGACY_ENCRYPT_FORBID_LEGACY_DECRYPT{} => success_override(), + _ => fail_override() + } + } + crate::_Wrappers_Compile::Option::None{} => success_override() + } + } + pub fn EncryptItem(&self, _input: &Rc) -> Rc, Rc>>{ + todo!("InternalLLegacyOverride::EncryptItem") + } + pub fn DecryptItem(&self, _input: &Rc) -> Rc, Rc>>{ + todo!("InternalLLegacyOverride::DecryptItem") + } + pub fn IsLegacyInput( + &self, + _input: &Rc, + ) -> bool { + false + } + } + } + } + } + } + } + } + } +} diff --git a/Examples/runtimes/rust/README.md b/Examples/runtimes/rust/README.md new file mode 100644 index 000000000..e4fbc8d1f --- /dev/null +++ b/Examples/runtimes/rust/README.md @@ -0,0 +1 @@ +For Rust examples, see /DynamoDbEncryption/runtimes/rust/examples/ diff --git a/README.md b/README.md index 85b1cf86d..73375e7e6 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ You need an Amazon Web Services (AWS) account to use the DB-ESDK for DynamoDB as - Java - .NET - Dafny +- Rust # Contributing diff --git a/TestVectors/Makefile b/TestVectors/Makefile index 786fe9fb6..ecd473c25 100644 --- a/TestVectors/Makefile +++ b/TestVectors/Makefile @@ -2,11 +2,14 @@ # SPDX-License-Identifier: Apache-2.0 CORES=2 +TRANSPILE_TESTS_IN_RUST=1 include ../SharedMakefile.mk PROJECT_SERVICES := \ - DDBEncryption \ + DDBEncryption + +MAIN_SERVICE_FOR_RUST := DDBEncryption SMITHY_MODEL_ROOT := $(PROJECT_ROOT)/DynamoDbEncryption/dafny/DynamoDbEncryption/Model OUTPUT_LOCAL_SERVICE_DDBEncryption := --local-service-test @@ -25,6 +28,28 @@ PROJECT_DEPENDENCIES := \ submodules/MaterialProviders/TestVectorsAwsCryptographicMaterialProviders \ DynamoDbEncryption \ + +RUST_OTHER_FILES := \ + runtimes/rust/src/aes_gcm.rs \ + runtimes/rust/src/aes_kdf_ctr.rs \ + runtimes/rust/src/ddb.rs \ + runtimes/rust/src/concurrent_call.rs \ + runtimes/rust/src/create_client.rs \ + runtimes/rust/src/dafny_libraries.rs \ + runtimes/rust/src/digest.rs \ + runtimes/rust/src/ecdh.rs \ + runtimes/rust/src/ecdsa.rs \ + runtimes/rust/src/hmac.rs \ + runtimes/rust/src/kms.rs \ + runtimes/rust/src/local_cmc.rs \ + runtimes/rust/src/random.rs \ + runtimes/rust/src/rsa.rs \ + runtimes/rust/src/sets.rs \ + runtimes/rust/src/software_externs.rs \ + runtimes/rust/src/storm_tracker.rs \ + runtimes/rust/src/time.rs \ + runtimes/rust/src/uuid.rs + # Since we are packaging projects differently, we cannot make assumptions # about where the files are located. # This is here to get unblocked but should be removed once we have migrated packages @@ -55,9 +80,8 @@ SERVICE_DEPS_DDBEncryption := \ DynamoDbEncryption/dafny/DynamoDbEncryption \ submodules/MaterialProviders/TestVectorsAwsCryptographicMaterialProviders/dafny/TestVectorsAwsCryptographicMaterialProviders \ +transpile_implementation_rust: _remove_wrapped_client_rust -format_net: - pushd runtimes/net && dotnet format && popd - -clean: - rm -f runtimes/java/WriteTests1.json runtimes/java/decrypt.json runtimes/java/encrypt.json runtimes/net/*.json +_remove_wrapped_client_rust: + $(MAKE) _sed_file SED_FILE_PATH="runtimes/rust/src/deps/aws_cryptography_materialProviders.rs" \ +SED_BEFORE_STRING=' \#\[cfg(feature = "wrapped-client")\]' SED_AFTER_STRING='\/\/ Removed cfg(feature = "wrapped-client")' diff --git a/TestVectors/README.md b/TestVectors/README.md index b592c4c9c..aed9ae4cf 100644 --- a/TestVectors/README.md +++ b/TestVectors/README.md @@ -7,7 +7,7 @@ This validates the Database Encryption SDK's cross-version compatibility. ### Development Requirements -- Dafny 4.2.0: https://github.com/dafny-lang/dafny +- Dafny 4.9.0: https://github.com/dafny-lang/dafny The code that executes the test vectors is written in Dafny. You must install the Dafny runtime to compile the Dafny tests into Java. @@ -22,6 +22,9 @@ This validates the Database Encryption SDK's cross-version compatibility. 4. Run `make transpile_net` 5. Run `cd runtimes/net` 6. Run `dotnet run --framework net6.0` +7. Run `make transpile_rust` +8. Run `make polymorph_rust` +9. Run `make test_rust` ### Saving results for later diff --git a/TestVectors/dafny/DDBEncryption/src/CreateInterceptedDDBClient.dfy b/TestVectors/dafny/DDBEncryption/src/CreateInterceptedDDBClient.dfy index ade060ba5..7437cd58c 100644 --- a/TestVectors/dafny/DDBEncryption/src/CreateInterceptedDDBClient.dfy +++ b/TestVectors/dafny/DDBEncryption/src/CreateInterceptedDDBClient.dfy @@ -3,7 +3,7 @@ include "../Model/AwsCryptographyDynamoDbEncryptionTypesWrapped.dfy" -module CreateInterceptedDDBClient { +module {:extern} CreateInterceptedDDBClient { import opened Wrappers import AwsCryptographyDbEncryptionSdkDynamoDbTypes import ComAmazonawsDynamodbTypes diff --git a/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy b/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy index 650e9fd21..d4eb1f144 100644 --- a/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy +++ b/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy @@ -1377,4 +1377,4 @@ module {:options "-functionSyntax:4"} JsonConfig { var num :- StrToNat(hash.N); return Success(Record(num, item)); } -} \ No newline at end of file +} diff --git a/TestVectors/dafny/DDBEncryption/src/TestVectors.dfy b/TestVectors/dafny/DDBEncryption/src/TestVectors.dfy index 647249e30..c0f494301 100644 --- a/TestVectors/dafny/DDBEncryption/src/TestVectors.dfy +++ b/TestVectors/dafny/DDBEncryption/src/TestVectors.dfy @@ -96,6 +96,12 @@ module {:options "-functionSyntax:4"} DdbEncryptionTestVectors { return; } Validate(); + // Because of Dafny-Rust's lack of modules, there is no way to mae an interceptor for the wrapped DB-ESDK client. + // So we create runtimes/rust/SkipLocal.txt to skip those tests that need the wrapped client. + var skipLocal := FileIO.ReadBytesFromFile("SkipLocal.txt"); + if skipLocal.Success? { + return; + } StringOrdering(); BasicIoTest(); RunIoTests(); diff --git a/TestVectors/project.properties b/TestVectors/project.properties index 02e193a7d..55d988fe1 100644 --- a/TestVectors/project.properties +++ b/TestVectors/project.properties @@ -1,4 +1,4 @@ # This file stores the top level dafny version information. # All elements of the project need to agree on this version. -dafnyVersion=4.8.0 -dafnyRuntimeJavaVersion=4.8.0 +dafnyVersion=4.9.0 +dafnyRuntimeJavaVersion=4.9.0 diff --git a/TestVectors/runtimes/net/Generated/DDBEncryption/TypeConversion.cs b/TestVectors/runtimes/net/Generated/DDBEncryption/TypeConversion.cs index 009ce943e..346af372e 100644 --- a/TestVectors/runtimes/net/Generated/DDBEncryption/TypeConversion.cs +++ b/TestVectors/runtimes/net/Generated/DDBEncryption/TypeConversion.cs @@ -1293,13 +1293,15 @@ public static AWS.Cryptography.MaterialProviders.StormTrackingCache FromDafny_N3 converted.GraceInterval = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_graceInterval(concrete._graceInterval); converted.FanOut = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M6_fanOut(concrete._fanOut); converted.InFlightTTL = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_inFlightTTL(concrete._inFlightTTL); - converted.SleepMilli = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(concrete._sleepMilli); return converted; + converted.SleepMilli = (int)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(concrete._sleepMilli); + if (concrete._timeUnits.is_Some) converted.TimeUnits = (AWS.Cryptography.MaterialProviders.TimeUnits)FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(concrete._timeUnits); return converted; } public static software.amazon.cryptography.materialproviders.internaldafny.types._IStormTrackingCache ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache(AWS.Cryptography.MaterialProviders.StormTrackingCache value) { value.Validate(); int? var_entryPruningTailSize = value.IsSetEntryPruningTailSize() ? value.EntryPruningTailSize : (int?)null; - return new software.amazon.cryptography.materialproviders.internaldafny.types.StormTrackingCache(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_entryCapacity(value.EntryCapacity), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M20_entryPruningTailSize(var_entryPruningTailSize), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_gracePeriod(value.GracePeriod), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_graceInterval(value.GraceInterval), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M6_fanOut(value.FanOut), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_inFlightTTL(value.InFlightTTL), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(value.SleepMilli)); + AWS.Cryptography.MaterialProviders.TimeUnits var_timeUnits = value.IsSetTimeUnits() ? value.TimeUnits : (AWS.Cryptography.MaterialProviders.TimeUnits)null; + return new software.amazon.cryptography.materialproviders.internaldafny.types.StormTrackingCache(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_entryCapacity(value.EntryCapacity), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M20_entryPruningTailSize(var_entryPruningTailSize), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_gracePeriod(value.GracePeriod), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M13_graceInterval(value.GraceInterval), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M6_fanOut(value.FanOut), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M11_inFlightTTL(value.InFlightTTL), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M10_sleepMilli(value.SleepMilli), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(var_timeUnits)); } public static AWS.Cryptography.MaterialProviders.ICryptographicMaterialsCache FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S36_CryptographicMaterialsCacheReference(software.amazon.cryptography.materialproviders.internaldafny.types.ICryptographicMaterialsCache value) { @@ -1496,6 +1498,14 @@ public static int ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_S { return ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S14_CountingNumber(value); } + public static AWS.Cryptography.MaterialProviders.TimeUnits FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(Wrappers_Compile._IOption value) + { + return value.is_None ? (AWS.Cryptography.MaterialProviders.TimeUnits)null : FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits(value.Extract()); + } + public static Wrappers_Compile._IOption ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S18_StormTrackingCache__M9_timeUnits(AWS.Cryptography.MaterialProviders.TimeUnits value) + { + return value == null ? Wrappers_Compile.Option.create_None() : Wrappers_Compile.Option.create_Some(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits((AWS.Cryptography.MaterialProviders.TimeUnits)value)); + } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S23_StringSetAttributeValue__M6_member(Dafny.ISequence value) { return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S20_StringAttributeValue(value); @@ -1552,6 +1562,18 @@ public static int ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S14_C { return value; } + public static AWS.Cryptography.MaterialProviders.TimeUnits FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits(software.amazon.cryptography.materialproviders.internaldafny.types._ITimeUnits value) + { + if (value.is_Seconds) return AWS.Cryptography.MaterialProviders.TimeUnits.Seconds; + if (value.is_Milliseconds) return AWS.Cryptography.MaterialProviders.TimeUnits.Milliseconds; + throw new System.ArgumentException("Invalid AWS.Cryptography.MaterialProviders.TimeUnits value"); + } + public static software.amazon.cryptography.materialproviders.internaldafny.types._ITimeUnits ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_TimeUnits(AWS.Cryptography.MaterialProviders.TimeUnits value) + { + if (AWS.Cryptography.MaterialProviders.TimeUnits.Seconds.Equals(value)) return software.amazon.cryptography.materialproviders.internaldafny.types.TimeUnits.create_Seconds(); + if (AWS.Cryptography.MaterialProviders.TimeUnits.Milliseconds.Equals(value)) return software.amazon.cryptography.materialproviders.internaldafny.types.TimeUnits.create_Milliseconds(); + throw new System.ArgumentException("Invalid AWS.Cryptography.MaterialProviders.TimeUnits value"); + } public static System.Exception FromDafny_CommonError(software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types._IError value) { switch (value) @@ -1560,6 +1582,10 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyDbEncryptionSdkStructuredEncryption ); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_AwsCryptographyKeyStore dafnyVal: + return AWS.Cryptography.KeyStore.TypeConversion.FromDafny_CommonError( + dafnyVal._AwsCryptographyKeyStore + ); case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_AwsCryptographyMaterialProviders dafnyVal: return AWS.Cryptography.MaterialProviders.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyMaterialProviders @@ -1581,6 +1607,8 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph new string(dafnyVal.dtor_message.Elements)); case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_Opaque dafnyVal: return new OpaqueError(dafnyVal._obj); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_OpaqueWithText dafnyVal: + return new OpaqueWithTextError(dafnyVal._obj, dafnyVal._obj.ToString()); default: // The switch MUST be complete for _IError, so `value` MUST NOT be an _IError. (How did you get here?) return new OpaqueError(); diff --git a/TestVectors/runtimes/rust/.gitignore b/TestVectors/runtimes/rust/.gitignore new file mode 100644 index 000000000..b5d9045cf --- /dev/null +++ b/TestVectors/runtimes/rust/.gitignore @@ -0,0 +1,39 @@ +*.pem +*.json +Cargo.lock +src/aes_gcm.rs +src/aes_kdf_ctr.rs +src/client +src/client.rs +src/concurrent_call.rs +src/conversions +src/conversions.rs +src/dafny_libraries.rs +src/ddb.rs +src/deps +src/deps.rs +src/digest.rs +src/ecdh.rs +src/ecdsa.rs +src/error +src/error.rs +src/hmac.rs +src/implementation_from_dafny.rs +src/kms.rs +src/local_cmc.rs +src/operation +src/operation.rs +src/random.rs +src/rsa.rs +src/sets.rs +src/software_externs.rs +src/standard_library_conversions.rs +src/standard_library_externs.rs +src/storm_tracker.rs +src/time.rs +src/types +src/types.rs +src/uuid.rs +src/wrapped +src/wrapped.rs +target diff --git a/TestVectors/runtimes/rust/Cargo.toml b/TestVectors/runtimes/rust/Cargo.toml new file mode 100644 index 000000000..ec9a73e9c --- /dev/null +++ b/TestVectors/runtimes/rust/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "aws-db-esdk-test-vectors" +version = "0.1.0" +edition = "2021" +rust-version = "1.80.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aws-config = "1.5.10" +aws-lc-rs = "1.11.1" +aws-lc-sys = "0.23.1" +aws-sdk-dynamodb = "1.54.0" +aws-sdk-kms = "1.50.0" +aws-smithy-runtime-api = {version = "1.7.3", features = ["client"] } +aws-smithy-types = "1.2.9" +chrono = "0.4.38" +dafny_runtime = { path = "../../../submodules/MaterialProviders/smithy-dafny/TestModels/dafny-dependencies/dafny_runtime_rust"} +dashmap = "6.1.0" +pem = "3.0.4" +tokio = {version = "1.41.1", features = ["full"] } +uuid = { version = "1.11.0", features = ["v4"] } diff --git a/TestVectors/runtimes/rust/SkipLocal.txt b/TestVectors/runtimes/rust/SkipLocal.txt new file mode 100644 index 000000000..2e8dda198 --- /dev/null +++ b/TestVectors/runtimes/rust/SkipLocal.txt @@ -0,0 +1 @@ +Rust can't deal with wrapped DDB Client yet. diff --git a/TestVectors/runtimes/rust/copy_externs.sh b/TestVectors/runtimes/rust/copy_externs.sh new file mode 100755 index 000000000..8a13fc3ea --- /dev/null +++ b/TestVectors/runtimes/rust/copy_externs.sh @@ -0,0 +1,24 @@ +#!/bin/bash -eu + +cd $( dirname ${BASH_SOURCE[0]} ) + +SRC=../../../submodules/MaterialProviders/AwsCryptographicMaterialProviders/runtimes/rust/src/ + +cp $SRC/aes_gcm.rs src +cp $SRC/aes_kdf_ctr.rs src +cp $SRC/concurrent_call.rs src +cp $SRC/dafny_libraries.rs src +cp $SRC/ddb.rs src +cp $SRC/digest.rs src +cp $SRC/ecdh.rs src +cp $SRC/ecdsa.rs src +cp $SRC/hmac.rs src +cp $SRC/kms.rs src +cp $SRC/local_cmc.rs src +cp $SRC/random.rs src +cp $SRC/rsa.rs src +cp $SRC/sets.rs src +cp ../../../DynamoDbEncryption/runtimes/rust/src/software_externs.rs src +cp $SRC/storm_tracker.rs src +cp $SRC/time.rs src +cp $SRC/uuid.rs src diff --git a/TestVectors/runtimes/rust/src/create_client.rs b/TestVectors/runtimes/rust/src/create_client.rs new file mode 100644 index 000000000..b4f7b477f --- /dev/null +++ b/TestVectors/runtimes/rust/src/create_client.rs @@ -0,0 +1,64 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::rc::Rc; +use dafny_runtime::Object; +use crate::implementation_from_dafny::software::amazon::cryptography::services::dynamodb::internaldafny::types::IDynamoDBClient; +use crate::implementation_from_dafny::software::amazon::cryptography::dbencryptionsdk::dynamodb::internaldafny::types::Error; +use crate::implementation_from_dafny::_Wrappers_Compile; +use std::sync::LazyLock; +// use crate::intercept::DbEsdkInterceptor; + +static DAFNY_TOKIO_RUNTIME: LazyLock = LazyLock::new(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() +}); + +pub mod _CreateInterceptedDDBClient_Compile { +pub struct _default {} +} +impl _CreateInterceptedDDBClient_Compile::_default { + + pub fn CreateInterceptedDDBClient(config : &Rc) + -> Rc<_Wrappers_Compile::Result, Rc>> + { + let shared_config = DAFNY_TOKIO_RUNTIME.block_on(aws_config::load_defaults( + aws_config::BehaviorVersion::v2024_03_28())); + + let shared_config = shared_config + .to_builder() + .endpoint_url("http://localhost:8000") + .build(); + + let dynamo_config = aws_sdk_dynamodb::config::Builder::from(&shared_config) +// .interceptor(DbEsdkInterceptor::new(table_configs)) + .build(); + let inner = aws_sdk_dynamodb::Client::from_conf(dynamo_config); + + let client = crate::deps::com_amazonaws_dynamodb::client::Client { inner }; + let dafny_client = ::dafny_runtime::upcast_object()(::dafny_runtime::object::new(client)); + std::rc::Rc::new(crate::r#_Wrappers_Compile::Result::Success { + value: dafny_client, + }) +} + pub fn CreateVanillaDDBClient() + -> Rc<_Wrappers_Compile::Result, Rc>> + { + let shared_config = DAFNY_TOKIO_RUNTIME.block_on(aws_config::load_defaults( + aws_config::BehaviorVersion::v2024_03_28())); + + let shared_config = shared_config + .to_builder() + .endpoint_url("http://localhost:8000") + .build(); + let inner = aws_sdk_dynamodb::Client::new(&shared_config); + let client = crate::deps::com_amazonaws_dynamodb::client::Client { inner }; + let dafny_client = ::dafny_runtime::upcast_object()(::dafny_runtime::object::new(client)); + std::rc::Rc::new(crate::r#_Wrappers_Compile::Result::Success { + value: dafny_client, + }) + } + +} diff --git a/TestVectors/runtimes/rust/src/lib.rs b/TestVectors/runtimes/rust/src/lib.rs new file mode 100644 index 000000000..e43f4a1db --- /dev/null +++ b/TestVectors/runtimes/rust/src/lib.rs @@ -0,0 +1,65 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#![allow(warnings, unconditional_panic)] +#![allow(nonstandard_style)] + +pub mod client; +pub mod conversions; +pub mod deps; +/// Common errors and error handling utilities. +pub mod error; +/// All operations that this crate can perform. +pub mod operation; +pub mod types; + +#[cfg(feature = "wrapped-client")] +pub mod wrapped; + +mod standard_library_conversions; +mod standard_library_externs; + +pub use client::Client; + +pub use crate::deps::aws_cryptography_dbEncryptionSdk_structuredEncryption; +pub use crate::deps::aws_cryptography_keyStore; +pub use crate::deps::aws_cryptography_materialProviders; +pub use crate::deps::aws_cryptography_primitives; + +pub(crate) mod implementation_from_dafny; +pub(crate) use crate::implementation_from_dafny::r#_Wrappers_Compile; +pub(crate) use crate::implementation_from_dafny::software; +pub(crate) use crate::implementation_from_dafny::AesKdfCtr; +pub(crate) use crate::implementation_from_dafny::ConcurrentCall; +pub(crate) use crate::implementation_from_dafny::DafnyLibraries; +pub(crate) use crate::implementation_from_dafny::ExternDigest; +pub(crate) use crate::implementation_from_dafny::ExternRandom; +pub(crate) use crate::implementation_from_dafny::Signature; +pub(crate) use crate::implementation_from_dafny::Time; +pub(crate) use crate::implementation_from_dafny::_LocalCMC_Compile; +pub(crate) use crate::implementation_from_dafny::_StormTracker_Compile; +pub(crate) use crate::implementation_from_dafny::ECDH; +pub(crate) use crate::implementation_from_dafny::HMAC; +pub(crate) use crate::implementation_from_dafny::UTF8; +pub(crate) use crate::implementation_from_dafny::UUID; + +pub mod aes_gcm; +pub mod aes_kdf_ctr; +pub mod concurrent_call; +pub mod dafny_libraries; +pub mod ddb; +pub mod digest; +pub mod ecdh; +pub mod ecdsa; +pub mod hmac; +pub mod kms; +pub mod local_cmc; +pub mod random; +pub mod rsa; +pub mod sets; +pub mod software_externs; +pub mod storm_tracker; +pub mod time; +pub mod uuid; + +pub mod create_client; diff --git a/project.properties b/project.properties index 421c38f5a..e4924edf8 100644 --- a/project.properties +++ b/project.properties @@ -1,6 +1,6 @@ projectJavaVersion=3.7.0-SNAPSHOT -mplDependencyJavaVersion=1.7.2-SNAPSHOT -dafnyVersion=4.8.0 -dafnyVerifyVersion=4.8.0 -dafnyRuntimeJavaVersion=4.8.0 +mplDependencyJavaVersion=1.8.0-SNAPSHOT +dafnyVersion=4.9.0 +dafnyVerifyVersion=4.9.0 +dafnyRuntimeJavaVersion=4.9.0 smithyDafnyJavaConversionVersion=0.1.1 diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders index 139f903e9..370be9c25 160000 --- a/submodules/MaterialProviders +++ b/submodules/MaterialProviders @@ -1 +1 @@ -Subproject commit 139f903e9ebfbb77ac6008f2b81b73565915835e +Subproject commit 370be9c25900681687b36f022f0e3ba740c933b9 diff --git a/submodules/smithy-dafny b/submodules/smithy-dafny index c47e0832b..c06e2c0fb 160000 --- a/submodules/smithy-dafny +++ b/submodules/smithy-dafny @@ -1 +1 @@ -Subproject commit c47e0832b9dfafaa112593dd493728823804cc9b +Subproject commit c06e2c0fba84fdfa630b518b184cf4d6826b7622