From 9297e1bad3860301bcf52b73466ec4b762e4f817 Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:20:43 -0800 Subject: [PATCH 1/3] feat!: Integrate ESDK-Java with AWS Cryptographic Material Providers Library (MPL) for Keyring and CMM Support. (#1864) feat!: Integrate ESDK-Java with AWS Cryptographic Material Providers Library (MPL) for Keyring and CMM Support. New Features: The AWS ESDK for Java now incorporates the AWS Cryptographic Material Providers Library (MPL), enabling the use of Keyrings and Cryptographic Materials Managers (CMMs). BREAKING CHANGE: This feature update includes a breaking change that requires AWS SDK v2 Java as a hard dependency. --- .github/workflows/ci.yml | 31 + .gitmodules | 3 + aws-encryption-sdk-specification | 2 +- codebuild/ci/release-ci.yml | 21 + codebuild/ci/vectors-ci.yml | 29 + codebuild/ci/vectors-generator.yml | 53 ++ codebuild/release/release-prod.yml | 19 + codebuild/release/release-staging.yml | 19 + .../aws-kms-mrk-aware-multi-keyrings.java | 104 --- .../aws-kms-mrk-aware-symmetric-keyring.java | 226 ----- ...re-symmetric-region-discovery-keyring.java | 125 --- pom.xml | 39 +- .../keyrings/AwsKmsRsaKeyringExample.java | 224 +++++ .../BasicEncryptionKeyringExample.java | 89 ++ .../DiscoveryDecryptionKeyringExample.java | 130 +++ ...ryMultiRegionDecryptionKeyringExample.java | 161 ++++ .../EscrowedEncryptKeyringExample.java | 234 +++++ .../keyrings/FileStreamingKeyringExample.java | 124 +++ .../keyrings/MultiKeyringExample.java | 160 ++++ .../MultipleCmkEncryptKeyringExample.java | 128 +++ .../keyrings/RawAesKeyringExample.java | 115 +++ .../keyrings/RawRsaKeyringExample.java | 149 ++++ .../RequiredEncryptionContextCMMExample.java | 110 +++ .../SetCommitmentPolicyKeyringExample.java | 102 +++ .../SetEncryptionAlgorithmKeyringExample.java | 98 ++ ...thRequiredEncryptionContextCMMExample.java | 124 +++ .../AwsKmsHierarchicalKeyringExample.java | 333 +++++++ .../hierarchical/CreateBranchKeyId.java | 45 + .../ExampleBranchKeyIdSupplier.java | 44 + .../hierarchical/VersionBranchKeyId.java | 43 + .../{ => v2}/BasicEncryptionExample.java | 2 +- .../BasicMultiRegionKeyEncryptionExample.java | 2 +- .../{ => v2}/DiscoveryDecryptionExample.java | 2 +- ...DiscoveryMultiRegionDecryptionExample.java | 2 +- .../{ => v2}/EscrowedEncryptExample.java | 2 +- .../{ => v2}/FileStreamingExample.java | 2 +- .../{ => v2}/MultipleCmkEncryptExample.java | 2 +- .../{ => v2}/RestrictRegionExample.java | 2 +- .../{ => v2}/SetCommitmentPolicyExample.java | 12 +- .../SetEncryptionAlgorithmExample.java | 2 +- .../{ => v2}/SimpleDataKeyCachingExample.java | 2 +- .../amazonaws/encryptionsdk/AwsCrypto.java | 839 +++++++++++++++++- .../amazonaws/encryptionsdk/CMMHandler.java | 100 +++ .../encryptionsdk/CommitmentPolicy.java | 23 +- .../encryptionsdk/CryptoAlgorithm.java | 407 +++------ .../encryptionsdk/CryptoInputStream.java | 5 +- .../encryptionsdk/CryptoOutputStream.java | 5 +- .../amazonaws/encryptionsdk/CryptoResult.java | 9 +- .../DefaultCryptoMaterialsManager.java | 1 + .../internal/DecryptionHandler.java | 228 ++++- .../internal/EncryptionHandler.java | 56 +- .../internal/TrailingSignatureAlgorithm.java | 60 +- .../model/DecryptionMaterials.java | 18 + .../model/DecryptionMaterialsHandler.java | 76 ++ .../model/DecryptionMaterialsRequest.java | 18 + .../model/EncryptionMaterials.java | 2 + .../model/EncryptionMaterialsHandler.java | 105 +++ .../AwsKmsHierarchicalKeyringExampleTest.java | 26 + .../keyrings/AwsKmsRsaKeyringExampleTest.java | 15 + .../BasicEncryptionKeyringExampleTest.java | 15 + ...DiscoveryDecryptionKeyringExampleTest.java | 19 + ...ltiRegionDecryptionKeyringExampleTest.java | 19 + .../keyrings/MultiKeyringExampleTest.java | 15 + .../MultipleCmkEncryptKeyringExampleTest.java | 16 + .../keyrings/RawAesKeyringExampleTest.java | 17 + .../keyrings/RawRsaKeyringExampleTest.java | 19 + ...quiredEncryptionContextCMMExampleTest.java | 16 + ...SetCommitmentPolicyKeyringExampleTest.java | 16 + ...EncryptionAlgorithmKeyringExampleTest.java | 16 + ...quiredEncryptionContextCMMExampleTest.java | 47 + .../{ => v2}/BasicEncryptionExampleTest.java | 2 +- ...icMultiRegionKeyEncryptionExampleTest.java | 2 +- .../DiscoveryDecryptionExampleTest.java | 2 +- ...overyMultiRegionDecryptionExampleTest.java | 2 +- .../MultipleCmkEncryptExampleTest.java | 2 +- .../{ => v2}/RestrictRegionExampleTest.java | 2 +- .../SetCommitmentPolicyExampleTest.java | 2 +- .../SetEncryptionAlgorithmExampleTest.java | 2 +- .../SimpleDataKeyCachingExampleTest.java | 2 +- .../encryptionsdk/AllTestsSuite.java | 53 +- .../AwsCryptoIntegrationTest.java | 613 +++++++++++++ .../encryptionsdk/CryptoAlgorithmTest.java | 4 +- .../encryptionsdk/CryptoInputStreamTest.java | 465 ++++++++++ .../encryptionsdk/CryptoOutputStreamTest.java | 72 ++ .../encryptionsdk/DecryptionMethod.java | 141 ++- .../EncryptionContextCMMTest.java | 818 +++++++++++++++++ .../com/amazonaws/encryptionsdk/Fixtures.java | 116 +++ .../encryptionsdk/ParsedCiphertextTest.java | 4 +- .../encryptionsdk/TestVectorGenerator.java | 570 ++++++++++++ .../encryptionsdk/TestVectorRunner.java | 134 ++- .../internal/DecryptionHandlerTest.java | 4 +- .../internal/EncryptionHandlerTest.java | 70 +- .../encryptionsdk/jce/JceMasterKeyTest.java | 6 +- .../encryptionsdk/kms/KMSTestFixtures.java | 7 + .../MaxEncryptedDataKeysIntegrationTest.java | 7 +- src/test/resources/keys.json | 214 +++++ submodules/MaterialProviders | 1 + 97 files changed, 7765 insertions(+), 876 deletions(-) create mode 100644 codebuild/ci/vectors-generator.yml delete mode 100644 compliance_exceptions/aws-kms-mrk-aware-multi-keyrings.java delete mode 100644 compliance_exceptions/aws-kms-mrk-aware-symmetric-keyring.java delete mode 100644 compliance_exceptions/aws-kms-mrk-aware-symmetric-region-discovery-keyring.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/AwsKmsRsaKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/BasicEncryptionKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/DiscoveryDecryptionKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/DiscoveryMultiRegionDecryptionKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/EscrowedEncryptKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/FileStreamingKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/MultiKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/MultipleCmkEncryptKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/RawAesKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/RawRsaKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/RequiredEncryptionContextCMMExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/SetCommitmentPolicyKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/SetEncryptionAlgorithmKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/StreamingWithRequiredEncryptionContextCMMExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/AwsKmsHierarchicalKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/CreateBranchKeyId.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/ExampleBranchKeyIdSupplier.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/VersionBranchKeyId.java rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/BasicEncryptionExample.java (98%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/BasicMultiRegionKeyEncryptionExample.java (99%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/DiscoveryDecryptionExample.java (99%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/DiscoveryMultiRegionDecryptionExample.java (99%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/EscrowedEncryptExample.java (99%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/FileStreamingExample.java (99%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/MultipleCmkEncryptExample.java (99%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/RestrictRegionExample.java (99%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/SetCommitmentPolicyExample.java (99%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/SetEncryptionAlgorithmExample.java (99%) rename src/examples/java/com/amazonaws/crypto/examples/{ => v2}/SimpleDataKeyCachingExample.java (98%) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/CMMHandler.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsHandler.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsHandler.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/AwsKmsHierarchicalKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/AwsKmsRsaKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/BasicEncryptionKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/DiscoveryDecryptionKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/DiscoveryMultiRegionDecryptionKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/MultiKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/MultipleCmkEncryptKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/RawAesKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/RawRsaKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/RequiredEncryptionContextCMMExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/SetCommitmentPolicyKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/SetEncryptionAlgorithmKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/keyrings/StreamingWithRequiredEncryptionContextCMMExampleTest.java rename src/test/java/com/amazonaws/crypto/examples/{ => v2}/BasicEncryptionExampleTest.java (89%) rename src/test/java/com/amazonaws/crypto/examples/{ => v2}/BasicMultiRegionKeyEncryptionExampleTest.java (91%) rename src/test/java/com/amazonaws/crypto/examples/{ => v2}/DiscoveryDecryptionExampleTest.java (91%) rename src/test/java/com/amazonaws/crypto/examples/{ => v2}/DiscoveryMultiRegionDecryptionExampleTest.java (93%) rename src/test/java/com/amazonaws/crypto/examples/{ => v2}/MultipleCmkEncryptExampleTest.java (90%) rename src/test/java/com/amazonaws/crypto/examples/{ => v2}/RestrictRegionExampleTest.java (92%) rename src/test/java/com/amazonaws/crypto/examples/{ => v2}/SetCommitmentPolicyExampleTest.java (89%) rename src/test/java/com/amazonaws/crypto/examples/{ => v2}/SetEncryptionAlgorithmExampleTest.java (90%) rename src/test/java/com/amazonaws/crypto/examples/{ => v2}/SimpleDataKeyCachingExampleTest.java (96%) create mode 100644 src/test/java/com/amazonaws/encryptionsdk/AwsCryptoIntegrationTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/EncryptionContextCMMTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/Fixtures.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/TestVectorGenerator.java create mode 100644 src/test/resources/keys.json create mode 160000 submodules/MaterialProviders diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98a79a5d..53d2817f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,6 +58,37 @@ jobs: env-vars-for-codebuild: JAVA_ENV_VERSION env: JAVA_ENV_VERSION: ${{ matrix.platform.distribution }}${{ matrix.version }} + generateTestVectors: + name: Generate Vectors + runs-on: ubuntu-latest + strategy: + max-parallel: 1 + fail-fast: true + matrix: + platform: + - distribution: openjdk + image: "aws/codebuild/standard:3.0" + - distribution: corretto + image: "aws/codebuild/amazonlinux2-x86_64-standard:3.0" # Corretto only runs on AL2 + version: [ 8, 11 ] + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: ${{ secrets.CI_AWS_ROLE_ARN }} + aws-region: us-west-2 + role-duration-seconds: 3600 + - name: Generate Test Vectors + uses: aws-actions/aws-codebuild-run-build@v1 + timeout-minutes: 60 + with: + project-name: AWS-ESDK-Java-CI + buildspec-override: codebuild/ci/vectors-generator.yml + compute-type-override: BUILD_GENERAL1_LARGE + image-override: ${{ matrix.platform.image }} + env-vars-for-codebuild: JAVA_ENV_VERSION + env: + JAVA_ENV_VERSION: ${{ matrix.platform.distribution }}${{ matrix.version }} releaseCI: name: Release CI runs-on: ubuntu-latest diff --git a/.gitmodules b/.gitmodules index b90e3bf8..f4916f35 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "aws-encryption-sdk-specification"] path = aws-encryption-sdk-specification url = https://github.com/awslabs/aws-encryption-sdk-specification.git +[submodule "submodules/MaterialProviders"] + path = submodules/MaterialProviders + url = https://github.com/aws/aws-cryptographic-material-providers-library-java.git diff --git a/aws-encryption-sdk-specification b/aws-encryption-sdk-specification index c35fbd91..bfbe06dd 160000 --- a/aws-encryption-sdk-specification +++ b/aws-encryption-sdk-specification @@ -1 +1 @@ -Subproject commit c35fbd91b28303d69813119088c44b5006395eb4 +Subproject commit bfbe06dd7333fc27aad0e311c5dfff9fec40c4f0 diff --git a/codebuild/ci/release-ci.yml b/codebuild/ci/release-ci.yml index 1f799295..d6b89a23 100644 --- a/codebuild/ci/release-ci.yml +++ b/codebuild/ci/release-ci.yml @@ -17,6 +17,16 @@ phases: install: runtime-versions: java: openjdk11 + commands: + - git submodule update --init submodules/MaterialProviders + # Get Dafny + - curl https://github.com/dafny-lang/dafny/releases/download/v4.2.0/dafny-4.2.0-x64-ubuntu-20.04.zip -L -o dafny.zip + - unzip -qq dafny.zip && rm dafny.zip + - export PATH="$PWD/dafny:$PATH" + # Get Gradle 7.6 + - curl https://services.gradle.org/distributions/gradle-7.6-all.zip -L -o gradle.zip + - unzip -qq gradle.zip && rm gradle.zip + - export PATH="$PWD/gradle-7.6/bin:$PATH" pre_build: commands: - export SETTINGS_FILE=$(pwd)/codebuild/release/settings.xml @@ -24,6 +34,17 @@ phases: - export CODEARTIFACT_REPO_URL=https://${DOMAIN}-${ACCOUNT}.d.codeartifact.${REGION}.amazonaws.com/maven/${REPOSITORY} - aws secretsmanager get-secret-value --region us-west-2 --secret-id Maven-GPG-Keys-CI --query SecretBinary --output text | base64 -d > ~/mvn_gpg.tgz - tar -xvf ~/mvn_gpg.tgz -C ~ + + # Build and deploy to maven local + - cd submodules/MaterialProviders + - git checkout $BRANCH + - cd TestVectorsAwsCryptographicMaterialProviders/ + # This works because `node` is installed by default on GHA runners + - CORES=$(node -e 'console.log(os.cpus().length)') + - make build_java CORES=$CORES + - ./runtimes/java/gradlew -p runtimes/java publishMavenLocalPublicationToMavenLocal + - cd $CODEBUILD_SRC_DIR + build: commands: - VERSION_HASH="$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)-$CODEBUILD_RESOLVED_SOURCE_VERSION-$GITHUB_EVENT_NAME" diff --git a/codebuild/ci/vectors-ci.yml b/codebuild/ci/vectors-ci.yml index ae4ff9e1..0ff26ab7 100644 --- a/codebuild/ci/vectors-ci.yml +++ b/codebuild/ci/vectors-ci.yml @@ -4,6 +4,35 @@ phases: install: runtime-versions: java: $JAVA_ENV_VERSION + commands: + - git submodule update --init submodules/MaterialProviders + # Get Dafny + - curl https://github.com/dafny-lang/dafny/releases/download/v4.2.0/dafny-4.2.0-x64-ubuntu-20.04.zip -L -o dafny.zip + - unzip -qq dafny.zip && rm dafny.zip + - export PATH="$PWD/dafny:$PATH" + # Get Gradle 7.6 + - curl https://services.gradle.org/distributions/gradle-7.6-all.zip -L -o gradle.zip + - unzip -qq gradle.zip && rm gradle.zip + - export PATH="$PWD/gradle-7.6/bin:$PATH" + pre_build: + commands: + # Assume Role to access non-prod resources + - TMP_ROLE=$(aws sts assume-role --role-arn "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Java-Role-us-west-2" --role-session-name "CB-TestVectorResources") + - export TMP_ROLE + - export AWS_ACCESS_KEY_ID=$(echo "${TMP_ROLE}" | jq -r '.Credentials.AccessKeyId') + - export AWS_SECRET_ACCESS_KEY=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') + - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') + - aws sts get-caller-identity + + # Build and deploy TestVectors to maven local + - cd submodules/MaterialProviders + - git checkout $BRANCH + - cd TestVectorsAwsCryptographicMaterialProviders/ + # This works because `node` is installed by default on GHA runners + - CORES=$(node -e 'console.log(os.cpus().length)') + - make build_java CORES=$CORES + - ./runtimes/java/gradlew -p runtimes/java publishMavenLocalPublicationToMavenLocal + - cd $CODEBUILD_SRC_DIR build: commands: - mvn install -T 8 -Dgpg.skip=true -ntp "-DtestVectorZip=file://$CODEBUILD_SRC_DIR/src/test/resources/aws-encryption-sdk-test-vectors/vectors/awses-decrypt/python-2.3.0.zip" diff --git a/codebuild/ci/vectors-generator.yml b/codebuild/ci/vectors-generator.yml new file mode 100644 index 00000000..f219a55c --- /dev/null +++ b/codebuild/ci/vectors-generator.yml @@ -0,0 +1,53 @@ +version: 0.2 + +phases: + install: + runtime-versions: + java: $JAVA_ENV_VERSION + commands: + - n 16 + # Install the Javascript ESDK run test vectors + - npm install -g @aws-crypto/integration-node + + - git submodule update --init submodules/MaterialProviders + # Get Dafny + - curl https://github.com/dafny-lang/dafny/releases/download/v4.2.0/dafny-4.2.0-x64-ubuntu-20.04.zip -L -o dafny.zip + - unzip -qq dafny.zip && rm dafny.zip + - export PATH="$PWD/dafny:$PATH" + # Get Gradle 7.6 + - curl https://services.gradle.org/distributions/gradle-7.6-all.zip -L -o gradle.zip + - unzip -qq gradle.zip && rm gradle.zip + - export PATH="$PWD/gradle-7.6/bin:$PATH" + pre_build: + commands: + # Assume Role to access non-prod resources + - TMP_ROLE=$(aws sts assume-role --role-arn "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Java-Role-us-west-2" --role-session-name "CB-TestVectorResources") + - export TMP_ROLE + - export AWS_ACCESS_KEY_ID=$(echo "${TMP_ROLE}" | jq -r '.Credentials.AccessKeyId') + - export AWS_SECRET_ACCESS_KEY=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') + - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') + - aws sts get-caller-identity + + # Build and deploy to maven local + - cd submodules/MaterialProviders + - git checkout $BRANCH + - cd TestVectorsAwsCryptographicMaterialProviders/ + # This works because `node` is installed by default on GHA runners + - CORES=$(node -e 'console.log(os.cpus().length)') + - make build_java CORES=$CORES + - ./runtimes/java/gradlew -p runtimes/java publishMavenLocalPublicationToMavenLocal + - cd $CODEBUILD_SRC_DIR + build: + commands: + - export VECTORS_ZIP="$CODEBUILD_SRC_DIR/generated_vectors.zip" + # Generate test vectors by encrypting with Keyrings + # Ignore Testing coverage requirement by skipping jacoco + - mvn -B -ntp install -Dgpg.skip=true -Djacoco.skip=true "-Dtest=TestVectorGenerator" "-DzipFilePath=$VECTORS_ZIP" "-DkeysManifest=$CODEBUILD_SRC_DIR/src/test/resources/keys.json" + # Decrypt generated vectors with Javascript ESDK + - integration-node decrypt -v $VECTORS_ZIP + + - rm $VECTORS_ZIP + # Generate test vectors by encrypting with MasterKeys + - mvn -B -ntp install -Dgpg.skip=true -Djacoco.skip=true -Dmasterkey=true "-Dtest=TestVectorGenerator" "-DzipFilePath=$VECTORS_ZIP" "-DkeysManifest=$CODEBUILD_SRC_DIR/src/test/resources/keys.json" + # Decrypt generated vectors with Javascript ESDK + - integration-node decrypt -v $VECTORS_ZIP diff --git a/codebuild/release/release-prod.yml b/codebuild/release/release-prod.yml index 1e519f39..05c98ce9 100644 --- a/codebuild/release/release-prod.yml +++ b/codebuild/release/release-prod.yml @@ -16,12 +16,31 @@ phases: install: runtime-versions: java: corretto11 + commands: + - git submodule update --init submodules/MaterialProviders + # Get Dafny + - curl https://github.com/dafny-lang/dafny/releases/download/v4.2.0/dafny-4.2.0-x64-ubuntu-20.04.zip -L -o dafny.zip + - unzip -qq dafny.zip && rm dafny.zip + - export PATH="$PWD/dafny:$PATH" + # Get Gradle 7.6 + - curl https://services.gradle.org/distributions/gradle-7.6-all.zip -L -o gradle.zip + - unzip -qq gradle.zip && rm gradle.zip + - export PATH="$PWD/gradle-7.6/bin:$PATH" pre_build: commands: - git checkout $BRANCH - export SETTINGS_FILE=$(pwd)/codebuild/release/settings.xml - aws secretsmanager get-secret-value --region us-west-2 --secret-id Maven-GPG-Keys-Release --query SecretBinary --output text | base64 -d > ~/mvn_gpg.tgz - tar -xvf ~/mvn_gpg.tgz -C ~ + # Build and deploy TestVectorsAwsCryptographicMaterialProviders to maven local + - cd submodules/MaterialProviders + - git checkout $BRANCH + - cd TestVectorsAwsCryptographicMaterialProviders/ + # This works because `node` is installed by default on GHA runners + - CORES=$(node -e 'console.log(os.cpus().length)') + - make build_java CORES=$CORES + - ./runtimes/java/gradlew -p runtimes/java publishMavenLocalPublicationToMavenLocal + - cd $CODEBUILD_SRC_DIR build: commands: - | diff --git a/codebuild/release/release-staging.yml b/codebuild/release/release-staging.yml index e9bb1142..c53cf1b6 100644 --- a/codebuild/release/release-staging.yml +++ b/codebuild/release/release-staging.yml @@ -18,6 +18,16 @@ phases: install: runtime-versions: java: corretto11 + commands: + - git submodule update --init submodules/MaterialProviders + # Get Dafny + - curl https://github.com/dafny-lang/dafny/releases/download/v4.2.0/dafny-4.2.0-x64-ubuntu-20.04.zip -L -o dafny.zip + - unzip -qq dafny.zip && rm dafny.zip + - export PATH="$PWD/dafny:$PATH" + # Get Gradle 7.6 + - curl https://services.gradle.org/distributions/gradle-7.6-all.zip -L -o gradle.zip + - unzip -qq gradle.zip && rm gradle.zip + - export PATH="$PWD/gradle-7.6/bin:$PATH" pre_build: commands: - export SETTINGS_FILE=$(pwd)/codebuild/release/settings.xml @@ -25,6 +35,15 @@ phases: - export CODEARTIFACT_REPO_URL=https://${DOMAIN}-${ACCOUNT}.d.codeartifact.${REGION}.amazonaws.com/maven/${REPOSITORY} - aws secretsmanager get-secret-value --region us-west-2 --secret-id Maven-GPG-Keys-Release --query SecretBinary --output text | base64 -d > ~/mvn_gpg.tgz - tar -xvf ~/mvn_gpg.tgz -C ~ + # Build and deploy TestVectorsAwsCryptographicMaterialProviders to maven local + - cd submodules/MaterialProviders + - git checkout $BRANCH + - cd TestVectorsAwsCryptographicMaterialProviders/ + # This works because `node` is installed by default on GHA runners + - CORES=$(node -e 'console.log(os.cpus().length)') + - make build_java CORES=$CORES + - ./runtimes/java/gradlew -p runtimes/java publishMavenLocalPublicationToMavenLocal + - cd $CODEBUILD_SRC_DIR build: commands: - VERSION_HASH="$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)-$CODEBUILD_RESOLVED_SOURCE_VERSION" diff --git a/compliance_exceptions/aws-kms-mrk-aware-multi-keyrings.java b/compliance_exceptions/aws-kms-mrk-aware-multi-keyrings.java deleted file mode 100644 index e40f72c6..00000000 --- a/compliance_exceptions/aws-kms-mrk-aware-multi-keyrings.java +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// The AWS Encryption SDK - Java does not implement -// any of the Keyring interface at this time. - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-multi-keyrings.txt#2.5 -//= type=exception -//# The caller MUST provide: -//# -//# * A set of Region strings -//# -//# * An optional discovery filter that is an AWS partition and a set of -//# AWS accounts -//# -//# * An optional method that can take a region string and return an AWS -//# KMS client e.g. a regional client supplier -//# -//# * An optional list of AWS KMS grant tokens -//# -//# If an empty set of Region is provided this function MUST fail. If -//# any element of the set of regions is null or an empty string this -//# function MUST fail. If a regional client supplier is not passed, -//# then a default MUST be created that takes a region string and -//# generates a default AWS SDK client for the given region. -//# -//# A set of AWS KMS clients MUST be created by calling regional client -//# supplier for each region in the input set of regions. -//# -//# Then a set of AWS KMS MRK Aware Symmetric Region Discovery Keyring -//# (aws-kms-mrk-aware-symmetric-region-discovery-keyring.md) MUST be -//# created for each AWS KMS client by initializing each keyring with -//# -//# * The AWS KMS client -//# -//# * The input discovery filter -//# -//# * The input AWS KMS grant tokens -//# -//# Then a Multi-Keyring (../multi-keyring.md#inputs) MUST be initialize -//# by using this set of discovery keyrings as the child keyrings -//# (../multi-keyring.md#child-keyrings). This Multi-Keyring MUST be -//# this functions output. - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-multi-keyrings.txt#2.6 -//= type=exception -//# The caller MUST provide: -//# -//# * An optional AWS KMS key identifiers to use as the generator. -//# -//# * An optional set of AWS KMS key identifiers to us as child -//# keyrings. -//# -//# * An optional method that can take a region string and return an AWS -//# KMS client e.g. a regional client supplier -//# -//# * An optional list of AWS KMS grant tokens -//# -//# If any of the AWS KMS key identifiers is null or an empty string this -//# function MUST fail. At least one non-null or non-empty string AWS -//# KMS key identifiers exists in the input this function MUST fail. All -//# AWS KMS identifiers are passed to Assert AWS KMS MRK are unique (aws- -//# kms-mrk-are-unique.md#Implementation) and the function MUST return -//# success otherwise this MUST fail. If a regional client supplier is -//# not passed, then a default MUST be created that takes a region string -//# and generates a default AWS SDK client for the given region. -//# -//# If there is a generator input then the generator keyring MUST be a -//# AWS KMS MRK Aware Symmetric Keyring (aws-kms-mrk-aware-symmetric- -//# keyring.md) initialized with -//# -//# * The generator input. -//# -//# * The AWS KMS client that MUST be created by the regional client -//# supplier when called with the region part of the generator ARN or -//# a signal for the AWS SDK to select the default region. -//# -//# * The input list of AWS KMS grant tokens -//# -//# If there is a set of child identifiers then a set of AWS KMS MRK -//# Aware Symmetric Keyring (aws-kms-mrk-aware-symmetric-keyring.md) MUST -//# be created for each AWS KMS key identifier by initialized each -//# keyring with -//# -//# * AWS KMS key identifier. -//# -//# * The AWS KMS client that MUST be created by the regional client -//# supplier when called with the region part of the AWS KMS key -//# identifier or a signal for the AWS SDK to select the default -//# region. -//# -//# * The input list of AWS KMS grant tokens -//# -//# NOTE: The AWS Encryption SDK SHOULD NOT attempt to evaluate its own -//# default region. -//# -//# Then a Multi-Keyring (../multi-keyring.md#inputs) MUST be initialize -//# by using this generator keyring as the generator keyring (../multi- -//# keyring.md#generator-keyring) and this set of child keyrings as the -//# child keyrings (../multi-keyring.md#child-keyrings). This Multi- -//# Keyring MUST be this functions output. - - - diff --git a/compliance_exceptions/aws-kms-mrk-aware-symmetric-keyring.java b/compliance_exceptions/aws-kms-mrk-aware-symmetric-keyring.java deleted file mode 100644 index e4937758..00000000 --- a/compliance_exceptions/aws-kms-mrk-aware-symmetric-keyring.java +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// The AWS Encryption SDK - Java does not implement -// any of the Keyring interface at this time. - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.5 -//= type=exception -//# MUST implement the AWS Encryption SDK Keyring interface (../keyring- -//# interface.md#interface) - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.6 -//= type=exception -//# On initialization the caller MUST provide: -//# -//# * An AWS KMS key identifier -//# -//# * An AWS KMS SDK client -//# -//# * An optional list of Grant Tokens -//# -//# The AWS KMS key identifier MUST NOT be null or empty. The AWS KMS -//# key identifier MUST be a valid identifier (aws-kms-key-arn.md#a- -//# valid-aws-kms-identifier). The AWS KMS SDK client MUST NOT be null. - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7 -//= type=exception -//# OnEncrypt MUST take encryption materials (structures.md#encryption- -//# materials) as input. -//# -//# If the input encryption materials (structures.md#encryption- -//# materials) do not contain a plaintext data key OnEncrypt MUST attempt -//# to generate a new plaintext data key by calling AWS KMS -//# GenerateDataKey (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_GenerateDataKey.html). -//# -//# If the keyring calls AWS KMS GenerateDataKeys, it MUST use the -//# configured AWS KMS client to make the call. The keyring MUST call -//# AWS KMS GenerateDataKeys with a request constructed as follows: -//# -//# * "KeyId": this keyring's KMS key identifier. -//# -//# * "NumberOfBytes": the key derivation input length (algorithm- -//# suites.md#key-derivation-input-length) specified by the algorithm -//# suite (algorithm-suites.md) included in the input encryption -//# materials (structures.md#encryption-materials). -//# -//# * "EncryptionContext": the encryption context -//# (structures.md#encryption-context) included in the input -//# encryption materials (structures.md#encryption-materials). -//# -//# * "GrantTokens": this keyring's grant tokens -//# (https://docs.aws.amazon.com/kms/latest/developerguide/ -//# concepts.html#grant_token) -//# -//# If the call to AWS KMS GenerateDataKey -//# (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_GenerateDataKey.html) does not succeed, OnEncrypt MUST NOT modify -//# the encryption materials (structures.md#encryption-materials) and -//# MUST fail. -//# -//# If the Generate Data Key call succeeds, OnEncrypt MUST verify that -//# the response "Plaintext" length matches the specification of the -//# algorithm suite (algorithm-suites.md)'s Key Derivation Input Length -//# field. The Generate Data Key response's "KeyId" MUST be A valid AWS -//# KMS key ARN (aws-kms-key-arn.md#identifying-an-aws-kms-multi-region- -//# key). If verified, OnEncrypt MUST do the following with the response -//# from AWS KMS GenerateDataKey -//# (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_GenerateDataKey.html): -//# -//# * set the plaintext data key on the encryption materials -//# (structures.md#encryption-materials) as the response "Plaintext". -//# -//# * append a new encrypted data key (structures.md#encrypted-data-key) -//# to the encrypted data key list in the encryption materials -//# (structures.md#encryption-materials), constructed as follows: -//# -//# - the ciphertext (structures.md#ciphertext) is the response -//# "CiphertextBlob". -//# -//# - the key provider id (structures.md#key-provider-id) is "aws- -//# kms". -//# -//# - the key provider information (structures.md#key-provider- -//# information) is the response "KeyId". -//# -//# * OnEncrypt MUST output the modified encryption materials -//# (structures.md#encryption-materials) -//# -//# Given a plaintext data key in the encryption materials -//# (structures.md#encryption-materials), OnEncrypt MUST attempt to -//# encrypt the plaintext data key using the configured AWS KMS key -//# identifier. -//# -//# The keyring MUST call AWS KMS Encrypt -//# (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_Encrypt.html) using the configured AWS KMS client. The keyring -//# MUST AWS KMS Encrypt call with a request constructed as follows: -//# -//# * "KeyId": The configured AWS KMS key identifier. -//# -//# * "PlaintextDataKey": the plaintext data key in the encryption -//# materials (structures.md#encryption-materials). -//# -//# * "EncryptionContext": the encryption context -//# (structures.md#encryption-context) included in the input -//# encryption materials (structures.md#encryption-materials). -//# -//# * "GrantTokens": this keyring's grant tokens -//# (https://docs.aws.amazon.com/kms/latest/developerguide/ -//# concepts.html#grant_token) -//# -//# If the call to AWS KMS Encrypt -//# (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_Encrypt.html) does not succeed, OnEncrypt MUST fail. -//# -//# If the Encrypt call succeeds The response's "KeyId" MUST be A valid -//# AWS KMS key ARN (aws-kms-key-arn.md#identifying-an-aws-kms-multi- -//# region-key). If verified, OnEncrypt MUST do the following with the -//# response from AWS KMS Encrypt -//# (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_Encrypt.html): -//# -//# * append a new encrypted data key (structures.md#encrypted-data-key) -//# to the encrypted data key list in the encryption materials -//# (structures.md#encryption-materials), constructed as follows: -//# -//# - The ciphertext (structures.md#ciphertext) is the response -//# "CiphertextBlob". -//# -//# - The key provider id (structures.md#key-provider-id) is "aws- -//# kms". -//# -//# - The key provider information (structures.md#key-provider- -//# information) is the response "KeyId". Note that the "KeyId" in -//# the response is always in key ARN format. -//# -//# If all Encrypt calls succeed, OnEncrypt MUST output the modified -//# encryption materials (structures.md#encryption-materials). - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.8 -//= type=exception -//# OnDecrypt MUST take decryption materials (structures.md#decryption- -//# materials) and a list of encrypted data keys -//# (structures.md#encrypted-data-key) as input. -//# -//# If the decryption materials (structures.md#decryption-materials) -//# already contained a valid plaintext data key OnDecrypt MUST -//# immediately return the unmodified decryption materials -//# (structures.md#decryption-materials). -//# -//# The set of encrypted data keys MUST first be filtered to match this -//# keyring's configuration. For the encrypted data key to match -//# -//# * Its provider ID MUST exactly match the value "aws-kms". -//# -//# * The provider info MUST be a valid AWS KMS ARN (aws-kms-key- -//# arn.md#a-valid-aws-kms-arn) with a resource type of "key" or -//# OnDecrypt MUST fail. -//# -//# * The the function AWS KMS MRK Match for Decrypt (aws-kms-mrk-match- -//# for-decrypt.md#implementation) called with the configured AWS KMS -//# key identifier and the provider info MUST return "true". -//# -//# For each encrypted data key in the filtered set, one at a time, the -//# OnDecrypt MUST attempt to decrypt the data key. If this attempt -//# results in an error, then these errors MUST be collected. -//# -//# To attempt to decrypt a particular encrypted data key -//# (structures.md#encrypted-data-key), OnDecrypt MUST call AWS KMS -//# Decrypt (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_Decrypt.html) with the configured AWS KMS client. -//# -//# When calling AWS KMS Decrypt -//# (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_Decrypt.html), the keyring MUST call with a request constructed -//# as follows: -//# -//# * "KeyId": The configured AWS KMS key identifier. -//# -//# * "CiphertextBlob": the encrypted data key ciphertext -//# (structures.md#ciphertext). -//# -//# * "EncryptionContext": the encryption context -//# (structures.md#encryption-context) included in the input -//# decryption materials (structures.md#decryption-materials). -//# -//# * "GrantTokens": this keyring's grant tokens -//# (https://docs.aws.amazon.com/kms/latest/developerguide/ -//# concepts.html#grant_token) -//# -//# If the call to AWS KMS Decrypt -//# (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_Decrypt.html) succeeds OnDecrypt verifies -//# -//# * The "KeyId" field in the response MUST equal the configured AWS -//# KMS key identifier. -//# -//# * The length of the response's "Plaintext" MUST equal the key -//# derivation input length (algorithm-suites.md#key-derivation-input- -//# length) specified by the algorithm suite (algorithm-suites.md) -//# included in the input decryption materials -//# (structures.md#decryption-materials). -//# -//# If the response does not satisfies these requirements then an error -//# MUST be collected and the next encrypted data key in the filtered set -//# MUST be attempted. -//# -//# If the response does satisfies these requirements then OnDecrypt MUST -//# do the following with the response: -//# -//# * set the plaintext data key on the decryption materials -//# (structures.md#decryption-materials) as the response "Plaintext". -//# -//# * immediately return the modified decryption materials -//# (structures.md#decryption-materials). -//# -//# If OnDecrypt fails to successfully decrypt any encrypted data key -//# (structures.md#encrypted-data-key), then it MUST yield an error that -//# includes all the collected errors. - - - - - diff --git a/compliance_exceptions/aws-kms-mrk-aware-symmetric-region-discovery-keyring.java b/compliance_exceptions/aws-kms-mrk-aware-symmetric-region-discovery-keyring.java deleted file mode 100644 index f57c07fe..00000000 --- a/compliance_exceptions/aws-kms-mrk-aware-symmetric-region-discovery-keyring.java +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// The AWS Encryption SDK - Java does not implement -// any of the Keyring interface at this time. - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.5 -//= type=exception -//# MUST implement that AWS Encryption SDK Keyring interface (../keyring- -//# interface.md#interface) - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.6 -//= type=exception -//# On initialization the caller MUST provide: -//# -//# * An AWS KMS client -//# -//# * An optional discovery filter that is an AWS partition and a set of -//# AWS accounts -//# -//# * An optional list of AWS KMS grant tokens -//# -//# The keyring MUST know what Region the AWS KMS client is in. It -//# SHOULD obtain this information directly from the client as opposed to -//# having an additional parameter. However if it can not, then it MUST -//# NOT create the client itself. It SHOULD have a Region parameter and -//# SHOULD try to identify mismatched configurations. i.e. The client is -//# in Region A and the Region parameter is B. - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.7 -//= type=exception -//# This function MUST fail. - -//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-region-discovery-keyring.txt#2.8 -//= type=exception -//# OnDecrypt MUST take decryption materials (structures.md#decryption- -//# materials) and a list of encrypted data keys -//# (structures.md#encrypted-data-key) as input. -//# -//# If the decryption materials (structures.md#decryption-materials) -//# already contained a valid plaintext data key OnDecrypt MUST -//# immediately return the unmodified decryption materials -//# (structures.md#decryption-materials). -//# -//# The set of encrypted data keys MUST first be filtered to match this -//# keyring's configuration. For the encrypted data key to match -//# -//# * Its provider ID MUST exactly match the value "aws-kms". -//# -//# * The provider info MUST be a valid AWS KMS ARN (aws-kms-key- -//# arn.md#a-valid-aws-kms-arn) with a resource type of "key" or -//# OnDecrypt MUST fail. -//# -//# * If a discovery filter is configured, its partition and the -//# provider info partition MUST match. -//# -//# * If a discovery filter is configured, its set of accounts MUST -//# contain the provider info account. -//# -//# * If the provider info is not identified as a multi-Region key (aws- -//# kms-key-arn.md#identifying-an-aws-kms-multi-region-key), then the -//# provider info's Region MUST match the AWS KMS client region. -//# -//# For each encrypted data key in the filtered set, one at a time, the -//# OnDecrypt MUST attempt to decrypt the data key. If this attempt -//# results in an error, then these errors are collected. -//# -//# To attempt to decrypt a particular encrypted data key -//# (structures.md#encrypted-data-key), OnDecrypt MUST call AWS KMS -//# Decrypt (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_Decrypt.html) with the configured AWS KMS client. -//# -//# When calling AWS KMS Decrypt -//# (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_Decrypt.html), the keyring MUST call with a request constructed -//# as follows: -//# -//# * "KeyId": If the provider info's resource type is "key" and its -//# resource is a multi-Region key then a new ARN MUST be created -//# where the region part MUST equal the AWS KMS client region and -//# every other part MUST equal the provider info. Otherwise it MUST -//# be the provider info. -//# -//# * "CiphertextBlob": The encrypted data key ciphertext -//# (structures.md#ciphertext). -//# -//# * "EncryptionContext": The encryption context -//# (structures.md#encryption-context) included in the input -//# decryption materials (structures.md#decryption-materials). -//# -//# * "GrantTokens": this keyring's grant tokens -//# (https://docs.aws.amazon.com/kms/latest/developerguide/ -//# concepts.html#grant_token) -//# -//# If the call to AWS KMS Decrypt -//# (https://docs.aws.amazon.com/kms/latest/APIReference/ -//# API_Decrypt.html) succeeds OnDecrypt verifies -//# -//# * The "KeyId" field in the response MUST equal the requested "KeyId" -//# -//# * The length of the response's "Plaintext" MUST equal the key -//# derivation input length (algorithm-suites.md#key-derivation-input- -//# length) specified by the algorithm suite (algorithm-suites.md) -//# included in the input decryption materials -//# (structures.md#decryption-materials). -//# -//# If the response does not satisfies these requirements then an error -//# is collected and the next encrypted data key in the filtered set MUST -//# be attempted. -//# -//# Since the response does satisfies these requirements then OnDecrypt -//# MUST do the following with the response: -//# -//# * set the plaintext data key on the decryption materials -//# (structures.md#decryption-materials) as the response "Plaintext". -//# -//# * immediately return the modified decryption materials -//# (structures.md#decryption-materials). -//# -//# If OnDecrypt fails to successfully decrypt any encrypted data key -//# (structures.md#encrypted-data-key), then it MUST yield an error that -//# includes all collected errors. - - - diff --git a/pom.xml b/pom.xml index 22e5d207..01d41440 100644 --- a/pom.xml +++ b/pom.xml @@ -62,8 +62,37 @@ software.amazon.awssdk kms - 2.18.38 + 2.20.91 + + + + software.amazon.cryptography + aws-cryptographic-material-providers + 1.0.2 + + + + software.amazon.cryptography + TestAwsCryptographicMaterialProviders + 1.0-SNAPSHOT + true + test + + + + software.amazon.smithy.dafny + conversion + 0.1 true + test + + + + org.dafny + DafnyRuntime + 4.2.0 + true + test @@ -117,6 +146,14 @@ commons-lang3 3.13.0 + + + commons-io + commons-io + 2.11.0 + test + + diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/AwsKmsRsaKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/AwsKmsRsaKeyringExample.java new file mode 100644 index 00000000..e07387ea --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/AwsKmsRsaKeyringExample.java @@ -0,0 +1,224 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.EncryptionAlgorithmSpec; +import software.amazon.awssdk.services.kms.model.GetPublicKeyRequest; +import software.amazon.awssdk.services.kms.model.GetPublicKeyResponse; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsRsaKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; + +/** + * Encrypts and then decrypts data using an AWS KMS RSA Keyring. This keyring uses a KMS RSA key + * pair to encrypt and decrypt data. The client uses the downloaded public key to encrypt data and + * uses the private key to decrypt content, by calling KMS' decrypt API. + * + *

Arguments: + * + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your AWS KMS customer master + * key (CMK), see 'Viewing Keys' at + * http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ */ +public class AwsKmsRsaKeyringExample { + static String DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME = "KmsRsaKeyringExamplePublicKey.pem"; + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String rsaKeyArn = KMSTestFixtures.US_WEST_2_KMS_RSA_KEY_ID; + String rsaPublicKeyFilename; + if (args.length == 2) { + rsaPublicKeyFilename = args[1]; + } else { + rsaPublicKeyFilename = DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME; + } + + // 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 (shouldGetNewPublicKey(rsaPublicKeyFilename)) { + writePublicKeyPemForRsaKey(rsaKeyArn, rsaPublicKeyFilename); + } + + encryptAndDecryptWithKeyring(rsaKeyArn, rsaPublicKeyFilename); + } + + public static void encryptAndDecryptWithKeyring(final String rsaKeyArn) { + String rsaPublicKeyFilename = DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME; + + if (shouldGetNewPublicKey(rsaPublicKeyFilename)) { + writePublicKeyPemForRsaKey(rsaKeyArn, rsaPublicKeyFilename); + } + + encryptAndDecryptWithKeyring(rsaKeyArn, rsaPublicKeyFilename); + } + + public static void encryptAndDecryptWithKeyring( + final String rsaKeyArn, String rsaPublicKeyFilename) { + // 0. 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. + ByteBuffer publicKeyUtf8EncodedByteBuffer; + try { + publicKeyUtf8EncodedByteBuffer = + ByteBuffer.wrap(Files.readAllBytes(Paths.get(rsaPublicKeyFilename))); + } catch (IOException e) { + throw new RuntimeException("IOException while reading public key from file", e); + } + + // 1. Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + // Specify algorithmSuite without asymmetric signing here + // + // ALG_AES_128_GCM_IV12_TAG16_NO_KDF("0x0014"), + // ALG_AES_192_GCM_IV12_TAG16_NO_KDF("0x0046"), + // ALG_AES_256_GCM_IV12_TAG16_NO_KDF("0x0078"), + // ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256("0x0114"), + // ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA256("0x0146"), + // ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256("0x0178") + .withEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256) + .withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt) + .build(); + + // 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 + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsRsaKeyringInput createAwsKmsRsaKeyringInput = + CreateAwsKmsRsaKeyringInput.builder() + .kmsClient(KmsClient.create()) + .kmsKeyId(rsaKeyArn) + .publicKey(publicKeyUtf8EncodedByteBuffer) + .encryptionAlgorithm(EncryptionAlgorithmSpec.RSAES_OAEP_SHA_256) + .build(); + IKeyring awsKmsRsaKeyring = matProv.CreateAwsKmsRsaKeyring(createAwsKmsRsaKeyringInput); + + // 3. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(awsKmsRsaKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData( + awsKmsRsaKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 6. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + static boolean shouldGetNewPublicKey() { + return shouldGetNewPublicKey(DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME); + } + + static boolean shouldGetNewPublicKey(String rsaPublicKeyFilename) { + // Check if a public key file already exists + File publicKeyFile = new File(rsaPublicKeyFilename); + + // If a public key file already exists: do not overwrite existing file + if (publicKeyFile.exists()) { + return false; + } + + // If file is not present, generate a new key pair + return true; + } + + static void writePublicKeyPemForRsaKey(String rsaKeyArn) { + writePublicKeyPemForRsaKey(rsaKeyArn, DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME); + } + + static void writePublicKeyPemForRsaKey(String rsaKeyArn, String rsaPublicKeyFilename) { + // Safety check: Validate file is not present + File publicKeyFile = new File(rsaPublicKeyFilename); + if (publicKeyFile.exists()) { + throw new IllegalStateException("getRsaPublicKey will not overwrite existing PEM files"); + } + + // 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. + KmsClient getterForPublicKey = KmsClient.create(); + GetPublicKeyResponse response = + getterForPublicKey.getPublicKey(GetPublicKeyRequest.builder().keyId(rsaKeyArn).build()); + byte[] publicKeyByteArray = response.publicKey().asByteArray(); + + StringWriter publicKeyStringWriter = new StringWriter(); + PemWriter publicKeyPemWriter = new PemWriter(publicKeyStringWriter); + try { + publicKeyPemWriter.writeObject(new PemObject("PUBLIC KEY", publicKeyByteArray)); + publicKeyPemWriter.close(); + } catch (IOException e) { + throw new RuntimeException("IOException while writing public key PEM", e); + } + ByteBuffer publicKeyUtf8EncodedByteBufferToWrite = + StandardCharsets.UTF_8.encode(publicKeyStringWriter.toString()); + + try { + FileChannel fc = new FileOutputStream(rsaPublicKeyFilename).getChannel(); + fc.write(publicKeyUtf8EncodedByteBufferToWrite); + fc.close(); + } catch (FileNotFoundException e) { + throw new RuntimeException("FileNotFoundException while opening public key FileChannel", e); + } catch (IOException e) { + throw new RuntimeException("IOException while writing public key or closing FileChannel", e); + } + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/BasicEncryptionKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/BasicEncryptionKeyringExample.java new file mode 100644 index 00000000..ae54dbdb --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/BasicEncryptionKeyringExample.java @@ -0,0 +1,89 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +/** + * Encrypts and then decrypts data using an AWS KMS Keyring. + * + *

Arguments: + * + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your AWS KMS customer master + * key (CMK), see 'Viewing Keys' at + * http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ */ +public class BasicEncryptionKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyArn = args[0]; + + encryptAndDecryptWithKeyring(keyArn); + } + + public static void encryptAndDecryptWithKeyring(final String keyArn) { + // 1. Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. Create the AWS KMS keyring. + // We create a multi keyring, as this interface creates the KMS client for us automatically. + final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMultiKeyringInput keyringInput = + CreateAwsKmsMultiKeyringInput.builder().generator(keyArn).build(); + final IKeyring kmsKeyring = materialProviders.CreateAwsKmsMultiKeyring(keyringInput); + + // 3. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(kmsKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData( + kmsKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 6. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/DiscoveryDecryptionKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/DiscoveryDecryptionKeyringExample.java new file mode 100644 index 00000000..dda291c2 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/DiscoveryDecryptionKeyringExample.java @@ -0,0 +1,130 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsDiscoveryMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.DiscoveryFilter; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +/** + * Encrypts and then decrypts data using an Aws Kms Discovery Keyring. Discovery mode is useful when + * you use an alias to identify a CMK when encrypting and the underlying key ARN might vary in each + * AWS Region. + * + *

Arguments: + * + *

    + *
  1. Key Name: An identifier for the AWS KMS customer master key (CMK) to use. For example, a + * key ARN or a key alias. For help finding the Amazon Resource Name (ARN) of your AWS KMS + * customer master key (CMK), see 'Viewing Keys' at + * http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
  2. Partition: The partition of the AWS KMS customer master key, which is usually "aws." A + * partition is a group of regions. The partition is the second element in the key ARN, e.g. + * "arn" in "aws:aws: ..." + *
  3. Account ID: The identifier for the account of the AWS KMS customer master key. + *
+ */ +public class DiscoveryDecryptionKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyName = args[0]; + final String partition = args[1]; + final String accountId = args[2]; + final String region = args[3]; + + encryptAndDecryptWithKeyring(keyName, partition, accountId, region); + } + + public static void encryptAndDecryptWithKeyring( + final String keyName, final String partition, final String accountId, final String region) { + // 1. Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. Create the AWS KMS keyring. + // We create a multi keyring, as this interface creates the KMS client for us automatically. + final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMultiKeyringInput encryptingInput = + CreateAwsKmsMultiKeyringInput.builder().generator(keyName).build(); + final IKeyring encryptingKmsKeyring = + materialProviders.CreateAwsKmsMultiKeyring(encryptingInput); + + // 3. Create an encryption context + // + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(encryptingKmsKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. 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 + final DiscoveryFilter discoveryFilter = + DiscoveryFilter.builder() + .accountIds(Collections.singletonList(accountId)) + .partition(partition) + .build(); + + // 6. Construct a discovery keyring. + final CreateAwsKmsDiscoveryMultiKeyringInput decryptingInput = + CreateAwsKmsDiscoveryMultiKeyringInput.builder() + .discoveryFilter(discoveryFilter) + .regions(Collections.singletonList(region)) + .build(); + final IKeyring decryptingKeyring = + materialProviders.CreateAwsKmsDiscoveryMultiKeyring(decryptingInput); + + // 7. Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData( + decryptingKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 8. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/DiscoveryMultiRegionDecryptionKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/DiscoveryMultiRegionDecryptionKeyringExample.java new file mode 100644 index 00000000..e8984392 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/DiscoveryMultiRegionDecryptionKeyringExample.java @@ -0,0 +1,161 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.kmssdkv2.AwsKmsMrkAwareMasterKey; +import com.amazonaws.encryptionsdk.kmssdkv2.AwsKmsMrkAwareMasterKeyProvider; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkDiscoveryMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.DiscoveryFilter; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +/** + * Encrypts and then decrypts data using an Aws Kms Multi-Region Discovery Keyring. Discovery mode + * is useful when you can't or don't want to specify a CMK on decrypt. + * + *

Arguments: + * + *

    + *
  1. Key Name: A key identifier for the AWS KMS customer master key (CMK). For example, a key + * ARN or a key alias. For details, see "Key identifiers" at + * https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id + *
  2. Partition: The partition of the AWS KMS customer master key, which is usually "aws." A + * partition is a group of regions. The partition is the second element in the key ARN, e.g. + * "arn" in "aws:aws: ..." For details, see: + * https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-syntax + *
  3. Account ID: The identifier for the account of the AWS KMS customer master key. + *
+ */ +public class DiscoveryMultiRegionDecryptionKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyName = args[0]; + final String partition = args[1]; + final String accountId = args[2]; + final String discoveryMrkRegion = args[3]; + + encryptAndDecryptWithKeyring(keyName, partition, accountId, discoveryMrkRegion); + } + + static void encryptAndDecryptWithKeyring( + final String keyName, + final String partition, + final String accountId, + final String discoveryMrkRegion) { + // 1. Instantiate the SDK + // This builds the AwsCrypto client with + // the RequireEncryptRequireDecrypt commitment policy, + // which decrypts only with committing algorithm suites. + // This is the default commitment policy + // if you build the client with `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. Instantiate an AWS KMS multi region optimized master key provider + // in strict mode using buildStrict(). + // In this example we are using + // two related multi region keys. + // we will encrypt with + // the encrypting in the encrypting region first. + // In strict mode, + // the AWS KMS multi region optimized master key provider encrypts + // and decrypts only by using the key indicated + // by key arn passed to `buildStrict`. + // To encrypt with this master key provider, + // use an AWS KMS key ARN to identify the CMKs. + // In strict mode, the decrypt operation requires a key ARN. + final AwsKmsMrkAwareMasterKeyProvider encryptingKeyProvider = + AwsKmsMrkAwareMasterKeyProvider.builder().buildStrict(keyName); + + // 2. Create the AWS KMS keyring. + // We create a multi keyring, as this interface creates the KMS client for us automatically. + final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMultiKeyringInput encryptingInput = + CreateAwsKmsMultiKeyringInput.builder().generator(keyName).build(); + final IKeyring encryptingKmsKeyring = + materialProviders.CreateAwsKmsMultiKeyring(encryptingInput); + + // 3. Create an encryption context + // Most encrypted data + // should have an associated encryption context + // to protect integrity. + // This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(encryptingKeyProvider, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. 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 + final DiscoveryFilter discoveryFilter = + DiscoveryFilter.builder() + .accountIds(Collections.singletonList(accountId)) + .partition(partition) + .build(); + + // 6. Construct a discovery keyring + // with a Discovery Mrk Region + // and with a discovery filter. + // + // In discovery mode, the AWS KMS multi region optimized keyring + // attempts to decrypt only by using AWS KMS keys indicated in the encrypted message. + // By configuring the master key provider with a Discovery Mrk Region, + // this keyring will only attempt to decrypt + // with AWS KMS multi-Region keys in the Discovery Mrk Region. + // If the Discovery Mrk Region is not configured, + // it is limited to the Region configured for the AWS SDK. + final CreateAwsKmsMrkDiscoveryMultiKeyringInput decryptingInput = + CreateAwsKmsMrkDiscoveryMultiKeyringInput.builder() + .discoveryFilter(discoveryFilter) + .regions(Collections.singletonList(discoveryMrkRegion)) + .build(); + final IKeyring decryptingKeyring = + materialProviders.CreateAwsKmsMrkDiscoveryMultiKeyring(decryptingInput); + + // 7. Decrypt the data + // Even though the message was encrypted with an AWS KMS key in one region + // the keyring will attempt to decrypt with the discoveryMrkRegion. + final CryptoResult decryptResult = + crypto.decryptData( + decryptingKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 8. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/EscrowedEncryptKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/EscrowedEncryptKeyringExample.java new file mode 100644 index 00000000..70bc90bf --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/EscrowedEncryptKeyringExample.java @@ -0,0 +1,234 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoOutputStream; +import com.amazonaws.util.IOUtils; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateRawRsaKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.materialproviders.model.PaddingScheme; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Collections; + +/** + * Encrypts a file using both AWS KMS Key and an asymmetric key pair. + * + *

Arguments: + * + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your AWS KMS customer master + * key (CMK), see 'Viewing Keys' at + * http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
  2. Name of file containing plaintext data to encrypt + *
+ * + *

You might use AWS Key Management Service (AWS KMS) for most encryption and decryption + * operations, but still want the option of decrypting your data offline independently of AWS KMS. + * This sample demonstrates one way to do this. + * + *

The sample encrypts data under both an AWS KMS customer master key (CMK) and an "escrowed" RSA + * key pair so that either key alone can decrypt it. You might commonly use the AWS KMS CMK for + * decryption. However, at any time, you can use the private RSA key to decrypt the ciphertext + * independent of AWS KMS. + * + *

This sample uses the RawRsaKeyring to generate a RSA public-private key pair and saves the key + * pair in memory. In practice, you would store the private key in a secure offline location, such + * as an offline HSM, and distribute the public key to your development team. + */ +public class EscrowedEncryptKeyringExample { + private static ByteBuffer publicEscrowKey; + private static ByteBuffer privateEscrowKey; + + public static void main(final String[] args) throws Exception { + // This sample generates a new random key for each operation. + // In practice, you would distribute the public key and save the private key in secure + // storage. + generateEscrowKeyPair(); + + final String kmsArn = args[0]; + final String fileName = args[1]; + + standardEncrypt(kmsArn, fileName); + standardDecrypt(kmsArn, fileName); + + escrowDecrypt(fileName); + } + + private static void standardEncrypt(final String kmsArn, final String fileName) throws Exception { + // Encrypt with the KMS CMK and the escrowed public key + // 1. Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. Create the AWS KMS keyring. + // We create a multi keyring, as this interface creates the KMS client for us automatically. + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMultiKeyringInput keyringInput = + CreateAwsKmsMultiKeyringInput.builder().generator(kmsArn).build(); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMultiKeyring(keyringInput); + + // 3. Create the Raw Rsa Keyring with Public Key. + final CreateRawRsaKeyringInput encryptingKeyringInput = + CreateRawRsaKeyringInput.builder() + .keyName("Escrow") + .keyNamespace("Escrow") + .paddingScheme(PaddingScheme.OAEP_SHA512_MGF1) + .publicKey(publicEscrowKey) + .build(); + IKeyring rsaPublicKeyring = matProv.CreateRawRsaKeyring(encryptingKeyringInput); + + // 4. Create the multi-keyring. + final CreateMultiKeyringInput createMultiKeyringInput = + CreateMultiKeyringInput.builder() + .generator(kmsKeyring) + .childKeyrings(Collections.singletonList(rsaPublicKeyring)) + .build(); + IKeyring multiKeyring = matProv.CreateMultiKeyring(createMultiKeyringInput); + + // 5. Encrypt the file + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + final FileInputStream in = new FileInputStream(fileName); + final FileOutputStream out = new FileOutputStream(fileName + ".encrypted"); + final CryptoOutputStream encryptingStream = crypto.createEncryptingStream(multiKeyring, out); + + IOUtils.copy(in, encryptingStream); + in.close(); + encryptingStream.close(); + } + + private static void standardDecrypt(final String kmsArn, final String fileName) throws Exception { + // Decrypt with the AWS KMS CMK and the escrow public key. You can use a combined provider, + // as shown here, or just the AWS KMS master key provider. + + // 1. Instantiate the SDK. + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. Create the AWS KMS keyring. + // We create a multi keyring, as this interface creates the KMS client for us automatically. + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMultiKeyringInput keyringInput = + CreateAwsKmsMultiKeyringInput.builder().generator(kmsArn).build(); + IKeyring kmsKeyring = matProv.CreateAwsKmsMultiKeyring(keyringInput); + + // 3. Create the Raw Rsa Keyring with Public Key. + final CreateRawRsaKeyringInput encryptingKeyringInput = + CreateRawRsaKeyringInput.builder() + .keyName("Escrow") + .keyNamespace("Escrow") + .paddingScheme(PaddingScheme.OAEP_SHA512_MGF1) + .publicKey(publicEscrowKey) + .build(); + IKeyring rsaPublicKeyring = matProv.CreateRawRsaKeyring(encryptingKeyringInput); + + // 4. Create the multi-keyring. + final CreateMultiKeyringInput createMultiKeyringInput = + CreateMultiKeyringInput.builder() + .generator(kmsKeyring) + .childKeyrings(Collections.singletonList(rsaPublicKeyring)) + .build(); + IKeyring multiKeyring = matProv.CreateMultiKeyring(createMultiKeyringInput); + + // 5. Decrypt the file + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + final FileInputStream in = new FileInputStream(fileName + ".encrypted"); + final FileOutputStream out = new FileOutputStream(fileName + ".decrypted"); + // Since we are using a signing algorithm suite, we avoid streaming decryption directly to the + // output file, + // to ensure that the trailing signature is verified before writing any untrusted plaintext to + // disk. + final ByteArrayOutputStream plaintextBuffer = new ByteArrayOutputStream(); + final CryptoOutputStream decryptingStream = + crypto.createDecryptingStream(multiKeyring, plaintextBuffer); + IOUtils.copy(in, decryptingStream); + in.close(); + decryptingStream.close(); + final ByteArrayInputStream plaintextReader = + new ByteArrayInputStream(plaintextBuffer.toByteArray()); + IOUtils.copy(plaintextReader, out); + out.close(); + } + + private static void escrowDecrypt(final String fileName) throws Exception { + // You can decrypt the stream using only the private key. + // This method does not call AWS KMS. + + // 1. Instantiate the SDK + final AwsCrypto crypto = AwsCrypto.standard(); + + // 2. Create the Raw Rsa Keyring with Private Key. + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateRawRsaKeyringInput encryptingKeyringInput = + CreateRawRsaKeyringInput.builder() + .keyName("Escrow") + .keyNamespace("Escrow") + .paddingScheme(PaddingScheme.OAEP_SHA512_MGF1) + .publicKey(publicEscrowKey) + .privateKey(privateEscrowKey) + .build(); + IKeyring escrowPrivateKeyring = matProv.CreateRawRsaKeyring(encryptingKeyringInput); + + // 3. Decrypt the file + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + final FileInputStream in = new FileInputStream(fileName + ".encrypted"); + final FileOutputStream out = new FileOutputStream(fileName + ".deescrowed"); + final CryptoOutputStream decryptingStream = + crypto.createDecryptingStream(escrowPrivateKeyring, out); + IOUtils.copy(in, decryptingStream); + in.close(); + decryptingStream.close(); + } + + private static void generateEscrowKeyPair() throws GeneralSecurityException { + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + kg.initialize(4096); // Escrow keys should be very strong + final KeyPair keyPair = kg.generateKeyPair(); + publicEscrowKey = RawRsaKeyringExample.getPEMPublicKey(keyPair.getPublic()); + privateEscrowKey = RawRsaKeyringExample.getPEMPrivateKey(keyPair.getPrivate()); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/FileStreamingKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/FileStreamingKeyringExample.java new file mode 100644 index 00000000..c35746cb --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/FileStreamingKeyringExample.java @@ -0,0 +1,124 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.CryptoInputStream; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.util.IOUtils; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.AesWrappingAlg; +import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.Collections; +import java.util.Map; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +/** + * Encrypts and then decrypts a file under a random key. + * + *

Arguments: + * + *

    + *
  1. Name of file containing plaintext data to encrypt + *
+ * + *

This program demonstrates using a standard Java {@link SecretKey} object as a {@link IKeyring} + * to encrypt and decrypt streaming data. + */ +public class FileStreamingKeyringExample { + private static String srcFile; + + public static void main(String[] args) throws IOException { + srcFile = args[0]; + + // In this example, we generate a random key. In practice, + // you would get a key from an existing store + SecretKey cryptoKey = retrieveEncryptionKey(); + + // Create a Raw Aes Keyring using the random key and an AES-GCM encryption algorithm + final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateRawAesKeyringInput keyringInput = + CreateRawAesKeyringInput.builder() + .wrappingKey(ByteBuffer.wrap(cryptoKey.getEncoded())) + .keyNamespace("Example") + .keyName("RandomKey") + .wrappingAlg(AesWrappingAlg.ALG_AES128_GCM_IV12_TAG16) + .build(); + final IKeyring keyring = materialProviders.CreateRawAesKeyring(keyringInput); + + // Instantiate the SDK. + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + // This also chooses to encrypt with an algorithm suite that doesn't include signing for faster + // decryption, + // since this use case assumes that the contexts that encrypt and decrypt are equally trusted. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .withEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) + .build(); + + // Create an encryption context to identify this ciphertext + Map context = Collections.singletonMap("Example", "FileStreaming"); + + // Because the file might be to large to load into memory, we stream the data, instead of + // loading it all at once. + FileInputStream in = new FileInputStream(srcFile); + CryptoInputStream encryptingStream = + crypto.createEncryptingStream(keyring, in, context); + + FileOutputStream out = new FileOutputStream(srcFile + ".encrypted"); + IOUtils.copy(encryptingStream, out); + encryptingStream.close(); + out.close(); + + // Decrypt the file. Verify the encryption context before returning the plaintext. + // Since we encrypted using an unsigned algorithm suite, we can use the recommended + // createUnsignedMessageDecryptingStream method that only accepts unsigned messages. + in = new FileInputStream(srcFile + ".encrypted"); + CryptoInputStream decryptingStream = + crypto.createUnsignedMessageDecryptingStream(keyring, in); + // Does it contain the expected encryption context? + if (!"FileStreaming" + .equals(decryptingStream.getCryptoResult().getEncryptionContext().get("Example"))) { + throw new IllegalStateException("Bad encryption context"); + } + + // Write the plaintext data to disk. + out = new FileOutputStream(srcFile + ".decrypted"); + IOUtils.copy(decryptingStream, out); + decryptingStream.close(); + out.close(); + } + + /** + * In practice, this key would be saved in a secure location. For this demo, we generate a new + * random key for each operation. + */ + private static SecretKey retrieveEncryptionKey() { + SecureRandom rnd = new SecureRandom(); + byte[] rawKey = new byte[16]; // 128 bits + rnd.nextBytes(rawKey); + return new SecretKeySpec(rawKey, "AES"); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/MultiKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/MultiKeyringExample.java new file mode 100644 index 00000000..eea846da --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/MultiKeyringExample.java @@ -0,0 +1,160 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.AesWrappingAlg; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +/** + * This example creates a new multi-keyring which takes in multiple keyrings and uses them to + * encrypt and decrypt data. This example keyring consisting of an AWS KMS keyring (labeled the + * "generator keyring") and a raw AES keyring (labeled as the only "child keyring"). Data encrypted + * with a multi-keyring can be decrypted with any of its component keyrings. + * + *

Arguments: + * + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your AWS KMS customer master + * key (CMK), see 'Viewing Keys' at + * http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ * + *

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. + */ +public class MultiKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyArn = args[0]; + + // Generate a new AES key + ByteBuffer aesKeyBytes = generateAesKeyBytes(); + + encryptAndDecryptWithKeyring(keyArn, aesKeyBytes); + } + + public static void encryptAndDecryptWithKeyring(String keyArn, ByteBuffer aesKeyBytes) { + // 1. Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. Create the raw AES keyring. + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateRawAesKeyringInput createRawAesKeyringInput = + CreateRawAesKeyringInput.builder() + .keyName("my-aes-key-name") + .keyNamespace("my-key-namespace") + .wrappingKey(aesKeyBytes) + .wrappingAlg(AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16) + .build(); + IKeyring rawAesKeyring = matProv.CreateRawAesKeyring(createRawAesKeyringInput); + + // 3. Create the AWS KMS keyring. + // We create a multi keyring, as this interface creates the KMS client for us automatically. + final CreateAwsKmsMultiKeyringInput kmsMultiKeyringInput = + CreateAwsKmsMultiKeyringInput.builder().generator(keyArn).build(); + IKeyring kmsKeyring = matProv.CreateAwsKmsMultiKeyring(kmsMultiKeyringInput); + + // 4. 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. + final CreateMultiKeyringInput createMultiKeyringInput = + CreateMultiKeyringInput.builder() + .generator(kmsKeyring) + .childKeyrings(Collections.singletonList(rawAesKeyring)) + .build(); + final IKeyring multiKeyring = matProv.CreateMultiKeyring(createMultiKeyringInput); + + // 5. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 6. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(multiKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 7. Decrypt the data with the Multi Keyring that originally encrypted this data + final CryptoResult decryptResult = + crypto.decryptData( + multiKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 8. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // 9. Now show that the encrypted message can also be decrypted by child keyrings + // configured with either CMK. + final CryptoResult aesKeyringDecryptResult = + crypto.decryptData(rawAesKeyring, ciphertext, encryptionContext); + final CryptoResult kmsKeyringDecryptResult = + crypto.decryptData(kmsKeyring, ciphertext, encryptionContext); + + // 10. Verify that the decrypted plaintext matches the original plaintext for each decryption + assert Arrays.equals(aesKeyringDecryptResult.getResult(), EXAMPLE_DATA); + assert Arrays.equals(kmsKeyringDecryptResult.getResult(), EXAMPLE_DATA); + } + + public static ByteBuffer generateAesKeyBytes() { + // This example uses BouncyCastle's KeyGenerator to generate the key bytes. + // 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. + KeyGenerator aesGen; + try { + aesGen = KeyGenerator.getInstance("AES"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("No such algorithm", e); + } + aesGen.init(256, new SecureRandom()); + SecretKey encryptionKey = aesGen.generateKey(); + ByteBuffer encryptionKeyByteBuffer = ByteBuffer.wrap(encryptionKey.getEncoded()); + return encryptionKeyByteBuffer; + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/MultipleCmkEncryptKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/MultipleCmkEncryptKeyringExample.java new file mode 100644 index 00000000..a5811bd8 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/MultipleCmkEncryptKeyringExample.java @@ -0,0 +1,128 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +/** + * This example creates a new multi-keyring which takes in multiple keyrings and uses them to + * encrypt and decrypt data. This example keyring consisting of an AWS KMS keyring (labeled the + * "generator keyring") and another AWS KMS keyring (labeled as the only "child keyring"). Data + * encrypted with a multi-keyring can be decrypted with any of its component keyrings. + * + *

Arguments: + * + *

    + *
  1. Key ARN 1: For help finding the Amazon Resource Name (ARN) of your AWS KMS customer master + * key (CMK), see 'Viewing Keys' at + * http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
  2. Key ARN 2: For help finding the Amazon Resource Name (ARN) of your AWS KMS customer master + * key (CMK), see 'Viewing Keys' at + * http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ */ +public class MultipleCmkEncryptKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyArn1 = args[0]; + final String keyArn2 = args[1]; + + encryptAndDecryptWithKeyring(keyArn1, keyArn2); + } + + public static void encryptAndDecryptWithKeyring(final String keyArn1, final String keyArn2) { + // Instantiate the SDK. + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. 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. + final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMultiKeyringInput encryptingInput = + CreateAwsKmsMultiKeyringInput.builder() + .generator(keyArn1) + .kmsKeyIds(Arrays.asList(keyArn1, keyArn2)) + .build(); + final IKeyring multiCmkKeyring = materialProviders.CreateAwsKmsMultiKeyring(encryptingInput); + + // 3. Create the child keyrings + // Instantiate an AWS KMS Keyring that are configured with keyArn1 and keyArn2 + // separately. + // These will be used later in this example to show that the encrypted messages created by + // multiCmkKeyring + // can be decrypted by AWS KMS Keyrings that are configured with either CMK. + final IKeyring singleCMKKeyring1 = + materialProviders.CreateAwsKmsMultiKeyring( + CreateAwsKmsMultiKeyringInput.builder().generator(keyArn1).build()); + final IKeyring singleCMKKeyring2 = + materialProviders.CreateAwsKmsMultiKeyring( + CreateAwsKmsMultiKeyringInput.builder().generator(keyArn1).build()); + + // 4. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 5. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(multiCmkKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 6. Decrypt the data with the multi-keyring that originally encrypted this data + final CryptoResult decryptResult = + crypto.decryptData( + multiCmkKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 8. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // 9. Now show that the encrypted message can also be decrypted by child keyrings + // configured with either CMK. + final CryptoResult singleCmkDecryptResult1 = + crypto.decryptData(singleCMKKeyring1, ciphertext, encryptionContext); + final CryptoResult singleCmkDecryptResult2 = + crypto.decryptData(singleCMKKeyring2, ciphertext, encryptionContext); + + // 10. Verify that the decrypted plaintext matches the original plaintext for each decryption + assert Arrays.equals(singleCmkDecryptResult1.getResult(), EXAMPLE_DATA); + assert Arrays.equals(singleCmkDecryptResult2.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/RawAesKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/RawAesKeyringExample.java new file mode 100644 index 00000000..c4e0140e --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/RawAesKeyringExample.java @@ -0,0 +1,115 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.AesWrappingAlg; +import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +/** + * Encrypts and then decrypts data using an 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). + */ +public class RawAesKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + // Generate a new AES key + ByteBuffer aesKeyBytes = generateAesKeyBytes(); + + encryptAndDecryptWithKeyring(aesKeyBytes); + } + + public static void encryptAndDecryptWithKeyring(final ByteBuffer aesKeyBytes) { + // 1. Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. Create the Raw Aes Keyring. + final CreateRawAesKeyringInput keyringInput = + CreateRawAesKeyringInput.builder() + .keyName("my-aes-key-name") + .keyNamespace("my-key-namespace") + .wrappingKey(aesKeyBytes) + .wrappingAlg(AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16) + .build(); + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final IKeyring rawAesKeyring = matProv.CreateRawAesKeyring(keyringInput); + + // 3. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(rawAesKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData( + rawAesKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 6. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + public static ByteBuffer generateAesKeyBytes() { + // This example uses BouncyCastle's KeyGenerator to generate the key bytes. + // 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. + KeyGenerator aesGen; + try { + aesGen = KeyGenerator.getInstance("AES"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("No such algorithm", e); + } + aesGen.init(256, new SecureRandom()); + SecretKey encryptionKey = aesGen.generateKey(); + ByteBuffer encryptionKeyByteBuffer = ByteBuffer.wrap(encryptionKey.getEncoded()); + return encryptionKeyByteBuffer; + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/RawRsaKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/RawRsaKeyringExample.java new file mode 100644 index 00000000..5daacce1 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/RawRsaKeyringExample.java @@ -0,0 +1,149 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateRawRsaKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.materialproviders.model.PaddingScheme; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; + +/** + * Encrypts and then decrypts data using an Raw Rsa Keyring. This example takes in Rsa Key Pair. If + * this example is run through the class' main method, it will create a new key pair. 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). + */ +public class RawRsaKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + KeyPair keyPair = generateKeyPair(); + ByteBuffer publicKeyBytes = getPEMPublicKey(keyPair.getPublic()); + ByteBuffer privateKeyBytes = getPEMPrivateKey(keyPair.getPrivate()); + + encryptAndDecryptWithKeyring(publicKeyBytes, privateKeyBytes); + } + + public static void encryptAndDecryptWithKeyring( + final ByteBuffer publicKeyBytes, final ByteBuffer privateKeyBytes) { + // 1. Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. Create the Raw Rsa Keyring with Public Key for Encryption. + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateRawRsaKeyringInput encryptingKeyringInput = + CreateRawRsaKeyringInput.builder() + .keyName("rsa-key") + .keyNamespace("rsa-keyring") + .paddingScheme(PaddingScheme.PKCS1) + .publicKey(publicKeyBytes) + .build(); + final IKeyring encryptingKeyring = matProv.CreateRawRsaKeyring(encryptingKeyringInput); + + // 3. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(encryptingKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Create the Raw Rsa Keyring with Private Key for Decryption. + final CreateRawRsaKeyringInput decryptingKeyringInput = + CreateRawRsaKeyringInput.builder() + .keyName("rsa-key") + .keyNamespace("rsa-keyring") + .paddingScheme(PaddingScheme.PKCS1) + .privateKey(privateKeyBytes) + .build(); + final IKeyring decryptingKeyring = matProv.CreateRawRsaKeyring(decryptingKeyringInput); + + // 6. Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData( + decryptingKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 7. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + public static KeyPair generateKeyPair() { + KeyPairGenerator rsaGen; + try { + rsaGen = KeyPairGenerator.getInstance("RSA"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("No such algorithm", e); + } + rsaGen.initialize(2048, new SecureRandom()); + return rsaGen.generateKeyPair(); + } + + public static ByteBuffer getPEMPublicKey(PublicKey publicKey) { + StringWriter publicKeyStringWriter = new StringWriter(); + PemWriter publicKeyPemWriter = new PemWriter(publicKeyStringWriter); + try { + publicKeyPemWriter.writeObject(new PemObject("PUBLIC KEY", publicKey.getEncoded())); + publicKeyPemWriter.close(); + } catch (IOException e) { + throw new RuntimeException("IOException while writing public key PEM", e); + } + return StandardCharsets.UTF_8.encode(publicKeyStringWriter.toString()); + } + + public static ByteBuffer getPEMPrivateKey(PrivateKey privateKey) { + StringWriter privateKeyStringWriter = new StringWriter(); + PemWriter privateKeyPemWriter = new PemWriter(privateKeyStringWriter); + try { + privateKeyPemWriter.writeObject(new PemObject("PRIVATE KEY", privateKey.getEncoded())); + privateKeyPemWriter.close(); + } catch (IOException e) { + throw new RuntimeException("IOException while writing private key PEM", e); + } + return StandardCharsets.UTF_8.encode(privateKeyStringWriter.toString()); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/RequiredEncryptionContextCMMExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/RequiredEncryptionContextCMMExample.java new file mode 100644 index 00000000..974d1744 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/RequiredEncryptionContextCMMExample.java @@ -0,0 +1,110 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.materialproviders.ICryptographicMaterialsManager; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateDefaultCryptographicMaterialsManagerInput; +import software.amazon.cryptography.materialproviders.model.CreateRequiredEncryptionContextCMMInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Demonstrate an encrypt/decrypt cycle using a Required Encryption Context CMM. + * A required encryption context CMM asks for required keys in the encryption context field + * on encrypt such that they will not be stored on the message, but WILL be included in the header signature. + * On decrypt the client MUST supply the key/value pair(s) that were not stored to successfully decrypt the message. + */ +public class RequiredEncryptionContextCMMExample { + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyArn = args[0]; + + encryptAndDecryptWithKeyring(keyArn); + } + + public static void encryptAndDecryptWithKeyring(final String keyArn) { + // 1. Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // 2. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("key1", "value1"); + encryptionContext.put("key2", "value2"); + encryptionContext.put("requiredKey1", "requiredValue1"); + encryptionContext.put("requiredKey2", "requiredValue2"); + + // 3. Create list of required encryption context keys. + // This is a list of keys that must be present in the encryption context. + final List requiredEncryptionContextKeys = + Arrays.asList("requiredKey1", "requiredKey2"); + + // 4. Create the AWS KMS keyring. + final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsKeyringInput keyringInput = + CreateAwsKmsKeyringInput.builder().kmsKeyId(keyArn).kmsClient(KmsClient.create()).build(); + final IKeyring kmsKeyring = materialProviders.CreateAwsKmsKeyring(keyringInput); + + // 5. Create the required encryption context CMM. + final ICryptographicMaterialsManager cmm = + materialProviders.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(kmsKeyring).build()); + + final ICryptographicMaterialsManager requiredCMM = + materialProviders.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .requiredEncryptionContextKeys(requiredEncryptionContextKeys) + .underlyingCMM(cmm) + .build()); + + // 6. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(requiredCMM, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 7. Reproduce the encryption context. + // The reproduced encryption context MUST contain a value for + // every key in the configured required encryption context keys during encryption with + // Required Encryption Context CMM. + final Map reproducedEncryptionContext = new HashMap<>(); + reproducedEncryptionContext.put("requiredKey1", "requiredValue1"); + reproducedEncryptionContext.put("requiredKey2", "requiredValue2"); + + // 8. Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData(requiredCMM, ciphertext, reproducedEncryptionContext); + + // 9. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/SetCommitmentPolicyKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/SetCommitmentPolicyKeyringExample.java new file mode 100644 index 00000000..2f80f6f8 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/SetCommitmentPolicyKeyringExample.java @@ -0,0 +1,102 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +/** + * Configures a client with a specific commitment policy, then encrypts and decrypts data using an + * AWS KMS Keyring. + * + *

This configuration should only be used as part of a migration from version 1.x to 2.x, or for + * advanced users with specialized requirements. We recommend that AWS Encryption SDK users use the + * default commitment policy whenever possible. + * + *

Arguments: + * + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your AWS KMS customer master + * key (CMK), see 'Viewing Keys' at + * http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ */ +public class SetCommitmentPolicyKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyArn = args[0]; + + encryptAndDecryptWithKeyrings(keyArn); + } + + public static void encryptAndDecryptWithKeyrings(final String keyArn) { + // 1. Instantiate the SDK with a specific commitment policy + // + // `withCommitmentPolicy(CommitmentPolicy)` configures the client with + // a commitment policy that dictates whether the client is required to encrypt + // using committing algorithms and whether the client must require that the messages + // it decrypts were encrypted using committing algorithms. + // In this example, we set the commitment policy to `ForbidEncryptAllowDecrypt`. + // This policy enforces that the client writes using non-committing algorithms, + // and allows decrypting of messages created with committing algorithms. + // + // If this value is not set, the client is configured to use our recommended default: + // `RequireEncryptRequireDecrypt`. + // This policy enforces that the client uses committing algorithms + // to encrypt and enforces that the client only decrypts messages created with committing + // algorithms. + // We recommend using the default whenever possible. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt) + .build(); + + // 2. Create the AWS KMS keyring. + // We create a multi keyring, as this interface creates the KMS client for us automatically. + final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final IKeyring kmsKeyring = + materialProviders.CreateAwsKmsMultiKeyring( + CreateAwsKmsMultiKeyringInput.builder().generator(keyArn).build()); + + // 3. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(kmsKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData( + kmsKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 6. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/SetEncryptionAlgorithmKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/SetEncryptionAlgorithmKeyringExample.java new file mode 100644 index 00000000..3f9c73c5 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/SetEncryptionAlgorithmKeyringExample.java @@ -0,0 +1,98 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +/** + * Configures a client with a specific encryption algorithm, then encrypts and decrypts data using + * that encryption algorithm and an Aws Kms Keyring. + * + *

Arguments: + * + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your AWS KMS customer master + * key (CMK), see 'Viewing Keys' at + * http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ */ +public class SetEncryptionAlgorithmKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyArn = args[0]; + + encryptAndDecryptWithKeyring(keyArn); + } + + public static void encryptAndDecryptWithKeyring(final String keyArn) { + // 1. Instantiate the SDK with the algorithm for encryption + // + // `withEncryptionAlgorithm(cryptoAlgorithm)` configures the client to encrypt + // using a specified encryption algorithm. + // This example sets the encryption algorithm to + // `CryptoAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY`, + // which is an algorithm that does not contain message signing. + // + // If this value is not set, the client encrypts with the recommended default: + // `CryptoAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384`. + // We recommend using the default whenever possible. + // For a description of our supported algorithms, see + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/supported-algorithms.html + // + // You can update the encryption algorithm after constructing the client + // by using `crypto.setEncryptionAlgorithm(CryptoAlgorithm)`. + final AwsCrypto crypto = + AwsCrypto.builder() + .withEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) + .build(); + + // 2. Create the AWS KMS keyring. + // We create a multi keyring, as this interface creates the KMS client for us automatically. + final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMultiKeyringInput encryptingInput = + CreateAwsKmsMultiKeyringInput.builder().generator(keyArn).build(); + final IKeyring kmsKeyring = materialProviders.CreateAwsKmsMultiKeyring(encryptingInput); + + // 3. Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(kmsKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData( + kmsKeyring, + ciphertext, + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method + encryptionContext); + + // 6. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/StreamingWithRequiredEncryptionContextCMMExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/StreamingWithRequiredEncryptionContextCMMExample.java new file mode 100644 index 00000000..5ca259ba --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/StreamingWithRequiredEncryptionContextCMMExample.java @@ -0,0 +1,124 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoInputStream; +import com.amazonaws.util.IOUtils; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.materialproviders.ICryptographicMaterialsManager; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateDefaultCryptographicMaterialsManagerInput; +import software.amazon.cryptography.materialproviders.model.CreateRequiredEncryptionContextCMMInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Demonstrate an encrypt/decrypt cycle using a Required Encryption Context CMM using an Input Stream as Input. + * A required encryption context CMM asks for required keys in the encryption context field + * on encrypt such that they will not be stored on the message, but WILL be included in the header signature. + * On decrypt the client MUST supply the key/value pair(s) that were not stored to successfully decrypt the message. + */ +public class StreamingWithRequiredEncryptionContextCMMExample { + public static void main(final String[] args) throws IOException { + final String srcFile = args[0]; + final String keyArn = args[1]; + + encryptAndDecryptWithKeyring(srcFile, keyArn); + } + + public static void encryptAndDecryptWithKeyring(final String srcFile, final String keyArn) throws IOException { + // Instantiate the SDK. + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + // This also chooses to encrypt with an algorithm suite that doesn't include signing for faster + // decryption, + // since this use case assumes that the contexts that encrypt and decrypt are equally trusted. + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // Create an encryption context + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("key1", "value1"); + encryptionContext.put("key2", "value2"); + encryptionContext.put("requiredKey1", "requiredValue1"); + encryptionContext.put("requiredKey2", "requiredValue2"); + + // Create list of required encryption context keys. + // This is a list of keys that must be present in the encryption context. + final List requiredEncryptionContextKeys = + Arrays.asList("requiredKey1", "requiredKey2"); + + // Create the AWS KMS keyring. + final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsKeyringInput keyringInput = + CreateAwsKmsKeyringInput.builder().kmsKeyId(keyArn).kmsClient(KmsClient.create()).build(); + final IKeyring kmsKeyring = materialProviders.CreateAwsKmsKeyring(keyringInput); + + // Create the required encryption context CMM. + final ICryptographicMaterialsManager cmm = + materialProviders.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(kmsKeyring).build()); + + final ICryptographicMaterialsManager requiredCMM = + materialProviders.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .requiredEncryptionContextKeys(requiredEncryptionContextKeys) + .underlyingCMM(cmm) + .build()); + + // Because the file might be too large to load into memory, we stream the data, instead of + // loading it all at once. + FileInputStream in = new FileInputStream(srcFile); + CryptoInputStream encryptingStream = + crypto.createEncryptingStream(requiredCMM, in, encryptionContext); + + FileOutputStream out = new FileOutputStream(srcFile + ".encrypted"); + IOUtils.copy(encryptingStream, out); + encryptingStream.close(); + out.close(); + + // Decrypt the file. + in = new FileInputStream(srcFile + ".encrypted"); + CryptoInputStream decryptingStream = + crypto.createDecryptingStream(cmm, in, encryptionContext); + + // Write the plaintext data to disk. + out = new FileOutputStream(srcFile + ".decrypted"); + IOUtils.copy(decryptingStream, out); + decryptingStream.close(); + out.close(); + + File file1 = new File(srcFile); + File file2 = new File(srcFile + ".decrypted"); + assertTrue(org.apache.commons.io.FileUtils.contentEquals(file1, file2)); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/AwsKmsHierarchicalKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/AwsKmsHierarchicalKeyringExample.java new file mode 100644 index 00000000..6e2572a1 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/AwsKmsHierarchicalKeyringExample.java @@ -0,0 +1,333 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings.hierarchical; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.keystore.KeyStore; +import software.amazon.cryptography.keystore.model.CreateKeyInput; +import software.amazon.cryptography.keystore.model.KMSConfiguration; +import software.amazon.cryptography.keystore.model.KeyStoreConfig; +import software.amazon.cryptography.materialproviders.IBranchKeyIdSupplier; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CacheType; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsHierarchicalKeyringInput; +import software.amazon.cryptography.materialproviders.model.DefaultCache; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * This example sets up 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 KMS Key. + * + *

Establishing a key hierarchy like this has two benefits: + * + *

First, by caching the branch key material, and only calling KMS to re-establish authentication + * regularly according to your configured TTL, you limit how often you need to call 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. Additionally, KMS Cloudtrail + * cannot be used to distinguish Encrypt and Decrypt calls, and you cannot restrict who has + * Encryption rights from who has Decryption rights since they both ONLY need KMS:Decrypt. 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 facilitates cryptographic isolation of a tenant's data in a + * multi-tenant data store. Each tenant can have a unique Branch Key, that is only used to protect + * the tenant's data. You can either statically configure a single branch key to ensure you are + * restricting access to a single tenant, or you can implement an interface that selects the Branch + * Key based on the Encryption Context. + * + *

This example demonstrates configuring a Hierarchical Keyring with a Branch Key ID Supplier to + * encrypt and decrypt data for two separate tenants. + * + *

This example requires access to the DDB Table where you are storing the Branch Keys. 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. You need the following access on this key: - + * GenerateDataKeyWithoutPlaintext - Decrypt + */ +public class AwsKmsHierarchicalKeyringExample { + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void encryptAndDecryptWithKeyring( + String keyStoreTableName, String logicalKeyStoreName, String kmsKeyId) { + // Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // Configure your KeyStore resource. + // This SHOULD be the same configuration that you used + // to initially create and populate your KeyStore. + final KeyStore keystore = + KeyStore.builder() + .KeyStoreConfig( + KeyStoreConfig.builder() + .ddbClient(DynamoDbClient.create()) + .ddbTableName(keyStoreTableName) + .logicalKeyStoreName(logicalKeyStoreName) + .kmsClient(KmsClient.create()) + .kmsConfiguration(KMSConfiguration.builder().kmsKeyArn(kmsKeyId).build()) + .build()) + .build(); + + // Call CreateKey to create two new active branch keys + final String branchKeyIdA = + keystore.CreateKey(CreateKeyInput.builder().build()).branchKeyIdentifier(); + final String branchKeyIdB = + keystore.CreateKey(CreateKeyInput.builder().build()).branchKeyIdentifier(); + + // Create a branch key supplier that maps the branch key id to a more readable format + final IBranchKeyIdSupplier branchKeyIdSupplier = + new ExampleBranchKeyIdSupplier(branchKeyIdA, branchKeyIdB); + + // 4. Create the Hierarchical Keyring. + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsHierarchicalKeyringInput keyringInput = + CreateAwsKmsHierarchicalKeyringInput.builder() + .keyStore(keystore) + .branchKeyIdSupplier(branchKeyIdSupplier) + .ttlSeconds(600) + .cache( + CacheType.builder() // OPTIONAL + .Default(DefaultCache.builder().entryCapacity(100).build()) + .build()) + .build(); + final IKeyring hierarchicalKeyring = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput); + + // The Branch Key Id supplier uses the encryption context to determine which branch key id will + // be used to encrypt data. + // Create encryption context for TenantA + Map encryptionContextA = new HashMap<>(); + encryptionContextA.put("tenant", "TenantA"); + encryptionContextA.put("encryption", "context"); + encryptionContextA.put("is not", "secret"); + encryptionContextA.put("but adds", "useful metadata"); + encryptionContextA.put("that can help you", "be confident that"); + encryptionContextA.put("the data you are handling", "is what you think it is"); + + // Create encryption context for TenantB + Map encryptionContextB = new HashMap<>(); + encryptionContextB.put("tenant", "TenantB"); + encryptionContextB.put("encryption", "context"); + encryptionContextB.put("is not", "secret"); + encryptionContextB.put("but adds", "useful metadata"); + encryptionContextB.put("that can help you", "be confident that"); + encryptionContextB.put("the data you are handling", "is what you think it is"); + + // Encrypt the data for encryptionContextA & encryptionContextB + final CryptoResult encryptResultA = + crypto.encryptData(hierarchicalKeyring, EXAMPLE_DATA, encryptionContextA); + final CryptoResult encryptResultB = + crypto.encryptData(hierarchicalKeyring, EXAMPLE_DATA, encryptionContextB); + + // To attest that TenantKeyB cannot decrypt a message written by TenantKeyA + // let's construct more restrictive hierarchical keyrings. + final CreateAwsKmsHierarchicalKeyringInput keyringInputA = + CreateAwsKmsHierarchicalKeyringInput.builder() + .keyStore(keystore) + .branchKeyId(branchKeyIdA) + .ttlSeconds(600) + .cache( + CacheType.builder() // OPTIONAL + .Default(DefaultCache.builder().entryCapacity(100).build()) + .build()) + .build(); + final IKeyring hierarchicalKeyringA = matProv.CreateAwsKmsHierarchicalKeyring(keyringInputA); + + final CreateAwsKmsHierarchicalKeyringInput keyringInputB = + CreateAwsKmsHierarchicalKeyringInput.builder() + .keyStore(keystore) + .branchKeyId(branchKeyIdB) + .ttlSeconds(600) + .cache( + CacheType.builder() // OPTIONAL + .Default(DefaultCache.builder().entryCapacity(100).build()) + .build()) + .build(); + final IKeyring hierarchicalKeyringB = matProv.CreateAwsKmsHierarchicalKeyring(keyringInputB); + + boolean decryptFailed = false; + // Try to use keyring for Tenant B to decrypt a message encrypted with Tenant A's key + // Expected to fail. + try { + crypto.decryptData(hierarchicalKeyringB, encryptResultA.getResult()); + } catch (Exception e) { + decryptFailed = true; + } + assert decryptFailed == true; + + decryptFailed = false; + // Try to use keyring for Tenant A to decrypt a message encrypted with Tenant B's key + // Expected to fail. + try { + crypto.decryptData(hierarchicalKeyringA, encryptResultB.getResult()); + } catch (Exception e) { + decryptFailed = true; + } + assert decryptFailed == true; + + // Decrypt your encrypted data using the same keyring you used on encrypt. + final CryptoResult decryptResultA = + crypto.decryptData(hierarchicalKeyring, encryptResultA.getResult()); + assert Arrays.equals(decryptResultA.getResult(), EXAMPLE_DATA); + + final CryptoResult decryptResultB = + crypto.decryptData(hierarchicalKeyring, encryptResultB.getResult()); + assert Arrays.equals(decryptResultB.getResult(), EXAMPLE_DATA); + } + + public static void encryptAndDecryptWithKeyringThreadSafe( + String keyStoreTableName, String logicalKeyStoreName, String kmsKeyId) { + // Instantiate the SDK + // This builds the AwsCrypto client with the RequireEncryptRequireDecrypt commitment policy, + // which enforces that this client only encrypts using committing algorithm suites and enforces + // that this client will only decrypt encrypted messages that were created with a committing + // algorithm suite. + // This is the default commitment policy if you build the client with + // `AwsCrypto.builder().build()` + // or `AwsCrypto.standard()`. + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // Configure your KeyStore resource. + // This SHOULD be the same configuration that you used + // to initially create and populate your KeyStore. + final KeyStore keystore = + KeyStore.builder() + .KeyStoreConfig( + KeyStoreConfig.builder() + .ddbClient(DynamoDbClient.create()) + .ddbTableName(keyStoreTableName) + .logicalKeyStoreName(logicalKeyStoreName) + .kmsClient(KmsClient.create()) + .kmsConfiguration(KMSConfiguration.builder().kmsKeyArn(kmsKeyId).build()) + .build()) + .build(); + + // Call CreateKey to create two new active branch keys + final String branchKeyIdA = + keystore.CreateKey(CreateKeyInput.builder().build()).branchKeyIdentifier(); + final String branchKeyIdB = + keystore.CreateKey(CreateKeyInput.builder().build()).branchKeyIdentifier(); + + // Create a branch key supplier that maps the branch key id to a more readable format + final IBranchKeyIdSupplier branchKeyIdSupplier = + new ExampleBranchKeyIdSupplier(branchKeyIdA, branchKeyIdB); + + // 4. Create the Hierarchical Keyring. + final MaterialProviders matProv = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + + final CreateAwsKmsHierarchicalKeyringInput keyringInput = + CreateAwsKmsHierarchicalKeyringInput.builder() + .keyStore(keystore) + .branchKeyIdSupplier(branchKeyIdSupplier) + .ttlSeconds(600) + .cache( + CacheType.builder() // OPTIONAL + .Default(DefaultCache.builder().entryCapacity(100).build()) + .build()) + .build(); + final IKeyring hierarchicalKeyring = matProv.CreateAwsKmsHierarchicalKeyring(keyringInput); + + // The Branch Key Id supplier uses the encryption context to determine which branch key id will + // be used to encrypt data. + // Create encryption context for TenantA + Map encryptionContextA = new HashMap<>(); + encryptionContextA.put("tenant", "TenantA"); + encryptionContextA.put("encryption", "context"); + encryptionContextA.put("is not", "secret"); + encryptionContextA.put("but adds", "useful metadata"); + encryptionContextA.put("that can help you", "be confident that"); + encryptionContextA.put("the data you are handling", "is what you think it is"); + + // Create encryption context for TenantB + Map encryptionContextB = new HashMap<>(); + encryptionContextB.put("tenant", "TenantB"); + encryptionContextB.put("encryption", "context"); + encryptionContextB.put("is not", "secret"); + encryptionContextB.put("but adds", "useful metadata"); + encryptionContextB.put("that can help you", "be confident that"); + encryptionContextB.put("the data you are handling", "is what you think it is"); + + final int numThreads = 1000; + final ConcurrentHashMap sharedMap = new ConcurrentHashMap<>(); + AtomicInteger counter = new AtomicInteger(0); + + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + + for (int i = 0; i < numThreads; i++) { + final int threadNumber = i; + executor.execute( + () -> { + // Encrypt the data for encryptionContextA & encryptionContextB + final CryptoResult encryptResultA = + crypto.encryptData(hierarchicalKeyring, EXAMPLE_DATA, encryptionContextA); + final CryptoResult encryptResultB = + crypto.encryptData(hierarchicalKeyring, EXAMPLE_DATA, encryptionContextB); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + final CryptoResult decryptResultA = + crypto.decryptData(hierarchicalKeyring, encryptResultA.getResult()); + assert Arrays.equals(decryptResultA.getResult(), EXAMPLE_DATA); + + final CryptoResult decryptResultB = + crypto.decryptData(hierarchicalKeyring, encryptResultB.getResult()); + assert Arrays.equals(decryptResultB.getResult(), EXAMPLE_DATA); + + // Increment the counter + counter.incrementAndGet(); + }); + } + + executor.shutdown(); + + while (!executor.isTerminated()) { + // Wait for all threads to finish. + } + + System.out.println("All threads have completed."); + + // Ensure thread safety by checking the map's size + if (counter.get() == numThreads) { + System.out.println("Thread safety maintained."); + } else { + System.out.println("Thread safety not maintained."); + } + } + + public static void main(final String[] args) { + if (args.length <= 0) { + throw new IllegalArgumentException( + "To run this example, include the keyStoreTableName, logicalKeyStoreName, and kmsKeyId in args"); + } + final String keyStoreTableName = args[0]; + final String logicalKeyStoreName = args[1]; + final String kmsKeyId = args[2]; + encryptAndDecryptWithKeyring(keyStoreTableName, logicalKeyStoreName, kmsKeyId); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/CreateBranchKeyId.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/CreateBranchKeyId.java new file mode 100644 index 00000000..3c680a5f --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/CreateBranchKeyId.java @@ -0,0 +1,45 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings.hierarchical; + +import static com.amazonaws.encryptionsdk.kms.KMSTestFixtures.TEST_KEYSTORE_KMS_KEY_ID; +import static com.amazonaws.encryptionsdk.kms.KMSTestFixtures.TEST_KEYSTORE_NAME; +import static com.amazonaws.encryptionsdk.kms.KMSTestFixtures.TEST_LOGICAL_KEYSTORE_NAME; + +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.keystore.KeyStore; +import software.amazon.cryptography.keystore.model.CreateKeyInput; +import software.amazon.cryptography.keystore.model.CreateKeyOutput; +import software.amazon.cryptography.keystore.model.KMSConfiguration; +import software.amazon.cryptography.keystore.model.KeyStoreConfig; + +public class CreateBranchKeyId { + public static String createBranchKeyId() { + // Create an AWS KMS Configuration to use with your KeyStore. + // The KMS Configuration MUST have the right access to the resources in the KeyStore. + final KMSConfiguration kmsConfig = + KMSConfiguration.builder().kmsKeyArn(TEST_KEYSTORE_KMS_KEY_ID).build(); + + // Configure your KeyStore resource. + // This SHOULD be the same configuration that you used + // to initially create and populate your KeyStore. + final KeyStoreConfig keystoreConfig = + KeyStoreConfig.builder() + .ddbClient(DynamoDbClient.create()) + .ddbTableName(TEST_KEYSTORE_NAME) + .logicalKeyStoreName(TEST_LOGICAL_KEYSTORE_NAME) + .kmsClient(KmsClient.create()) + .kmsConfiguration(kmsConfig) + .build(); + + // Create a KeyStore + final KeyStore keystore = KeyStore.builder().KeyStoreConfig(keystoreConfig).build(); + + // Create a branch key identifier with the AWS KMS Key configured in the KeyStore Configuration. + final CreateKeyOutput branchKeyId = keystore.CreateKey(CreateKeyInput.builder().build()); + + return branchKeyId.branchKeyIdentifier(); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/ExampleBranchKeyIdSupplier.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/ExampleBranchKeyIdSupplier.java new file mode 100644 index 00000000..07e99abb --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/ExampleBranchKeyIdSupplier.java @@ -0,0 +1,44 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings.hierarchical; + +import software.amazon.cryptography.materialproviders.IBranchKeyIdSupplier; +import software.amazon.cryptography.materialproviders.model.GetBranchKeyIdInput; +import software.amazon.cryptography.materialproviders.model.GetBranchKeyIdOutput; + +import java.util.Map; + +// Use the encryption contexts to define friendly names for each branch key +public class ExampleBranchKeyIdSupplier implements IBranchKeyIdSupplier { + private static String branchKeyIdForTenantA; + private static String branchKeyIdForTenantB; + + public ExampleBranchKeyIdSupplier(String tenant1Id, String tenant2Id) { + this.branchKeyIdForTenantA = tenant1Id; + this.branchKeyIdForTenantB = tenant2Id; + } + + @Override + public GetBranchKeyIdOutput GetBranchKeyId(GetBranchKeyIdInput input) { + + Map encryptionContext = input.encryptionContext(); + + if (!encryptionContext.containsKey("tenant")) { + throw new IllegalArgumentException( + "EncryptionContext invalid, does not contain expected tenant key value pair."); + } + + String tenantKeyId = encryptionContext.get("tenant"); + String branchKeyId; + + if (tenantKeyId.equals("TenantA")) { + branchKeyId = branchKeyIdForTenantA; + } else if (tenantKeyId.equals("TenantB")) { + branchKeyId = branchKeyIdForTenantB; + } else { + throw new IllegalArgumentException("Item does not contain valid tenant ID"); + } + return GetBranchKeyIdOutput.builder().branchKeyId(branchKeyId).build(); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/VersionBranchKeyId.java b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/VersionBranchKeyId.java new file mode 100644 index 00000000..8803ae2c --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyrings/hierarchical/VersionBranchKeyId.java @@ -0,0 +1,43 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings.hierarchical; + +import static com.amazonaws.encryptionsdk.kms.KMSTestFixtures.TEST_KEYSTORE_KMS_KEY_ID; +import static com.amazonaws.encryptionsdk.kms.KMSTestFixtures.TEST_KEYSTORE_NAME; +import static com.amazonaws.encryptionsdk.kms.KMSTestFixtures.TEST_LOGICAL_KEYSTORE_NAME; + +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.keystore.KeyStore; +import software.amazon.cryptography.keystore.model.KMSConfiguration; +import software.amazon.cryptography.keystore.model.KeyStoreConfig; +import software.amazon.cryptography.keystore.model.VersionKeyInput; + +public class VersionBranchKeyId { + public static void versionBranchKeyId(String branchKeyId) { + // Create an AWS KMS Configuration to use with your KeyStore. + // The KMS Configuration MUST have the right access to the resources in the KeyStore. + final KMSConfiguration kmsConfig = + KMSConfiguration.builder().kmsKeyArn(TEST_KEYSTORE_KMS_KEY_ID).build(); + + // Configure your KeyStore resource. + // This SHOULD be the same configuration that you used + // to initially create and populate your KeyStore. + final KeyStoreConfig keystoreConfig = + KeyStoreConfig.builder() + .ddbClient(DynamoDbClient.create()) + .ddbTableName(TEST_KEYSTORE_NAME) + .logicalKeyStoreName(TEST_LOGICAL_KEYSTORE_NAME) + .kmsClient(KmsClient.create()) + .kmsConfiguration(kmsConfig) + .build(); + + // Create a KeyStore + final KeyStore keystore = KeyStore.builder().KeyStoreConfig(keystoreConfig).build(); + + // To version a branch key you MUST have access to kms:ReEncrypt* and + // kms:GenerateDataKeyWithoutPlaintext + keystore.VersionKey(VersionKeyInput.builder().branchKeyIdentifier(branchKeyId).build()); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/BasicEncryptionExample.java similarity index 98% rename from src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/BasicEncryptionExample.java index bed6c681..053d7df6 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/BasicEncryptionExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import java.nio.charset.StandardCharsets; import java.util.Arrays; diff --git a/src/examples/java/com/amazonaws/crypto/examples/BasicMultiRegionKeyEncryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/BasicMultiRegionKeyEncryptionExample.java similarity index 99% rename from src/examples/java/com/amazonaws/crypto/examples/BasicMultiRegionKeyEncryptionExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/BasicMultiRegionKeyEncryptionExample.java index dd6e7a35..58e9c0e7 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/BasicMultiRegionKeyEncryptionExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/BasicMultiRegionKeyEncryptionExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.CommitmentPolicy; diff --git a/src/examples/java/com/amazonaws/crypto/examples/DiscoveryDecryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/DiscoveryDecryptionExample.java similarity index 99% rename from src/examples/java/com/amazonaws/crypto/examples/DiscoveryDecryptionExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/DiscoveryDecryptionExample.java index 167bc90f..f6ac45bc 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/DiscoveryDecryptionExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/DiscoveryDecryptionExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import java.nio.charset.StandardCharsets; import java.util.Arrays; diff --git a/src/examples/java/com/amazonaws/crypto/examples/DiscoveryMultiRegionDecryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/DiscoveryMultiRegionDecryptionExample.java similarity index 99% rename from src/examples/java/com/amazonaws/crypto/examples/DiscoveryMultiRegionDecryptionExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/DiscoveryMultiRegionDecryptionExample.java index 529b1192..30b195ea 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/DiscoveryMultiRegionDecryptionExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/DiscoveryMultiRegionDecryptionExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.CommitmentPolicy; diff --git a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/EscrowedEncryptExample.java similarity index 99% rename from src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/EscrowedEncryptExample.java index e04c35d0..403642db 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/EscrowedEncryptExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/FileStreamingExample.java similarity index 99% rename from src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/FileStreamingExample.java index 7399cba2..a9ae1360 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/FileStreamingExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import java.io.FileInputStream; import java.io.FileOutputStream; diff --git a/src/examples/java/com/amazonaws/crypto/examples/MultipleCmkEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/MultipleCmkEncryptExample.java similarity index 99% rename from src/examples/java/com/amazonaws/crypto/examples/MultipleCmkEncryptExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/MultipleCmkEncryptExample.java index cae7f81c..07d8cd33 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/MultipleCmkEncryptExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/MultipleCmkEncryptExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import java.nio.charset.StandardCharsets; import java.util.Arrays; diff --git a/src/examples/java/com/amazonaws/crypto/examples/RestrictRegionExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/RestrictRegionExample.java similarity index 99% rename from src/examples/java/com/amazonaws/crypto/examples/RestrictRegionExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/RestrictRegionExample.java index 91d1f379..900fcd93 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/RestrictRegionExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/RestrictRegionExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import java.nio.charset.StandardCharsets; import java.util.Arrays; diff --git a/src/examples/java/com/amazonaws/crypto/examples/SetCommitmentPolicyExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/SetCommitmentPolicyExample.java similarity index 99% rename from src/examples/java/com/amazonaws/crypto/examples/SetCommitmentPolicyExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/SetCommitmentPolicyExample.java index e0dec906..b66f465f 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/SetCommitmentPolicyExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/SetCommitmentPolicyExample.java @@ -1,12 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.CommitmentPolicy; @@ -14,6 +9,11 @@ import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKey; import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKeyProvider; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + /** *

* Configures a client with a specific commitment policy, then diff --git a/src/examples/java/com/amazonaws/crypto/examples/SetEncryptionAlgorithmExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/SetEncryptionAlgorithmExample.java similarity index 99% rename from src/examples/java/com/amazonaws/crypto/examples/SetEncryptionAlgorithmExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/SetEncryptionAlgorithmExample.java index b49d6bb9..7eb15d40 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/SetEncryptionAlgorithmExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/SetEncryptionAlgorithmExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import java.nio.charset.StandardCharsets; import java.util.Arrays; diff --git a/src/examples/java/com/amazonaws/crypto/examples/SimpleDataKeyCachingExample.java b/src/examples/java/com/amazonaws/crypto/examples/v2/SimpleDataKeyCachingExample.java similarity index 98% rename from src/examples/java/com/amazonaws/crypto/examples/SimpleDataKeyCachingExample.java rename to src/examples/java/com/amazonaws/crypto/examples/v2/SimpleDataKeyCachingExample.java index 633850a7..4437a010 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/SimpleDataKeyCachingExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/v2/SimpleDataKeyCachingExample.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import java.nio.charset.StandardCharsets; import java.util.Collections; diff --git a/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java b/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java index 5268935b..701af85e 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java +++ b/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java @@ -5,15 +5,26 @@ import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.BadCiphertextException; -import com.amazonaws.encryptionsdk.internal.*; +import com.amazonaws.encryptionsdk.internal.DecryptionHandler; +import com.amazonaws.encryptionsdk.internal.EncryptionHandler; +import com.amazonaws.encryptionsdk.internal.LazyMessageCryptoHandler; +import com.amazonaws.encryptionsdk.internal.MessageCryptoHandler; +import com.amazonaws.encryptionsdk.internal.ProcessingSummary; +import com.amazonaws.encryptionsdk.internal.SignaturePolicy; +import com.amazonaws.encryptionsdk.internal.Utils; import com.amazonaws.encryptionsdk.model.CiphertextHeaders; -import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterialsHandler; import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; +import software.amazon.cryptography.materialproviders.ICryptographicMaterialsManager; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateDefaultCryptographicMaterialsManagerInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; /** * Provides the primary entry-point to the AWS Encryption SDK. All encryption and decryption @@ -45,9 +56,9 @@ * should be used to encrypt the {@code DataKeys} by calling {@link * MasterKeyProvider#getMasterKeysForEncryption(MasterKeyRequest)} . When more than one {@code * MasterKey} is returned, the first {@code MasterKeys} is used to create the {@code DataKeys} by - * calling {@link MasterKey#generateDataKey(CryptoAlgorithm,java.util.Map)} . All of the other + * calling {@link MasterKey#generateDataKey(CryptoAlgorithm, java.util.Map)} . All of the other * {@code MasterKeys} are then used to re-encrypt that {@code DataKey} with {@link - * MasterKey#encryptDataKey(CryptoAlgorithm,java.util.Map,DataKey)} . This list of {@link + * MasterKey#encryptDataKey(CryptoAlgorithm, java.util.Map, DataKey)} . This list of {@link * EncryptedDataKey EncryptedDataKeys} (the same {@code DataKey} possibly encrypted multiple times) * is stored in the {@link com.amazonaws.encryptionsdk.model.CiphertextHeaders}. * @@ -78,6 +89,7 @@ public class AwsCrypto { private static final CommitmentPolicy DEFAULT_COMMITMENT_POLICY = CommitmentPolicy.RequireEncryptRequireDecrypt; private final CommitmentPolicy commitmentPolicy_; + private final MaterialProviders materialProviders_; /** * The maximum number of encrypted data keys to unwrap (resp. wrap) on decrypt (resp. encrypt), if @@ -109,6 +121,7 @@ private AwsCrypto(Builder builder) { encryptionAlgorithm_ = builder.encryptionAlgorithm_; encryptionFrameSize_ = builder.encryptionFrameSize_; maxEncryptedDataKeys_ = builder.maxEncryptedDataKeys_; + materialProviders_ = builder.materialProviders_; } public static class Builder { @@ -116,6 +129,7 @@ public static class Builder { private int encryptionFrameSize_ = getDefaultFrameSize(); private CommitmentPolicy commitmentPolicy_; private int maxEncryptedDataKeys_ = CiphertextHeaders.NO_MAX_ENCRYPTED_DATA_KEYS; + private MaterialProviders materialProviders_ = null; private Builder() {} @@ -124,6 +138,7 @@ private Builder(final AwsCrypto client) { encryptionFrameSize_ = client.encryptionFrameSize_; commitmentPolicy_ = client.commitmentPolicy_; maxEncryptedDataKeys_ = client.maxEncryptedDataKeys_; + materialProviders_ = client.materialProviders_; } /** @@ -140,6 +155,17 @@ public Builder withEncryptionAlgorithm(CryptoAlgorithm encryptionAlgorithm) { return this; } + /** + * Sets the {@link MaterialProviders} for cryptographic operations. + * + * @param materialProviders The {@link MaterialProviders} + * @return The Builder, for method chaining + */ + public Builder withMaterialProviders(MaterialProviders materialProviders) { + this.materialProviders_ = materialProviders; + return this; + } + /** * Sets the frame size of the encrypted messages that the Aws Crypto client produces. The Aws * Crypto client will use the last frame size set with either {@link @@ -181,6 +207,12 @@ public Builder withMaxEncryptedDataKeys(int maxEncryptedDataKeys) { } public AwsCrypto build() { + if (materialProviders_ == null) { + materialProviders_ = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + } return new AwsCrypto(this); } } @@ -262,6 +294,7 @@ public int getEncryptionFrameSize() { *

This method is equivalent to calling {@link #estimateCiphertextSize(CryptoMaterialsManager, * int, Map)} with a {@link DefaultCryptoMaterialsManager} based on the given provider. */ + @Deprecated public > long estimateCiphertextSize( final MasterKeyProvider provider, final int plaintextSize, @@ -270,10 +303,31 @@ public > long estimateCiphertextSize( new DefaultCryptoMaterialsManager(provider), plaintextSize, encryptionContext); } + /** + * Returns the best estimate for the output length of encrypting a plaintext with the provided + * {@code plaintextSize} and {@code encryptionContext}. The actual ciphertext may be shorter. + * + *

This method is equivalent to calling {@link + * #estimateCiphertextSize(ICryptographicMaterialsManager, int, Map)} with a {@link + * ICryptographicMaterialsManager} based on the given keyring. + */ + public > long estimateCiphertextSize( + final IKeyring keyring, + final int plaintextSize, + final Map encryptionContext) { + CreateDefaultCryptographicMaterialsManagerInput input = + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(keyring).build(); + return estimateCiphertextSize( + materialProviders_.CreateDefaultCryptographicMaterialsManager(input), + plaintextSize, + encryptionContext); + } + /** * Returns the best estimate for the output length of encrypting a plaintext with the provided * {@code plaintextSize} and {@code encryptionContext}. The actual ciphertext may be shorter. */ + @Deprecated public long estimateCiphertextSize( CryptoMaterialsManager materialsManager, final int plaintextSize, @@ -294,7 +348,37 @@ public long estimateCiphertextSize( final MessageCryptoHandler cryptoHandler = new EncryptionHandler( getEncryptionFrameSize(), - checkAlgorithm(materialsManager.getMaterialsForEncrypt(request)), + checkAlgorithm(new CMMHandler(materialsManager).getMaterialsForEncrypt(request)), + commitmentPolicy_); + + return cryptoHandler.estimateOutputSize(plaintextSize); + } + + /** + * Returns the best estimate for the output length of encrypting a plaintext with the provided + * {@code plaintextSize} and {@code encryptionContext}. The actual ciphertext may be shorter. + */ + public long estimateCiphertextSize( + ICryptographicMaterialsManager materialsManager, + final int plaintextSize, + final Map encryptionContext) { + EncryptionMaterialsRequest request = + EncryptionMaterialsRequest.newBuilder() + .setContext(encryptionContext) + .setRequestedAlgorithm(getEncryptionAlgorithm()) + // We're not actually encrypting any data, so don't consume any bytes from the cache's + // limits. We do need to + // pass /something/ though, or the cache will be bypassed (as it'll assume this is a + // streaming encrypt of + // unknown size). + .setPlaintextSize(0) + .setCommitmentPolicy(commitmentPolicy_) + .build(); + + final MessageCryptoHandler cryptoHandler = + new EncryptionHandler( + getEncryptionFrameSize(), + checkAlgorithm(new CMMHandler(materialsManager).getMaterialsForEncrypt(request)), commitmentPolicy_); return cryptoHandler.estimateOutputSize(plaintextSize); @@ -304,20 +388,98 @@ public long estimateCiphertextSize( * Returns the equivalent to calling {@link #estimateCiphertextSize(MasterKeyProvider, int, Map)} * with an empty {@code encryptionContext}. */ + @Deprecated public > long estimateCiphertextSize( final MasterKeyProvider provider, final int plaintextSize) { return estimateCiphertextSize(provider, plaintextSize, EMPTY_MAP); } + /** + * Returns the equivalent to calling {@link #estimateCiphertextSize(IKeyring, int, Map)} with an + * empty {@code encryptionContext}. + */ + public > long estimateCiphertextSize( + final IKeyring keyring, final int plaintextSize) { + return estimateCiphertextSize(keyring, plaintextSize, EMPTY_MAP); + } + /** * Returns the equivalent to calling {@link #estimateCiphertextSize(CryptoMaterialsManager, int, * Map)} with an empty {@code encryptionContext}. */ + @Deprecated public long estimateCiphertextSize( final CryptoMaterialsManager materialsManager, final int plaintextSize) { return estimateCiphertextSize(materialsManager, plaintextSize, EMPTY_MAP); } + /** + * Returns the equivalent to calling {@link + * #estimateCiphertextSize(ICryptographicMaterialsManager, int, Map)} with an empty {@code + * encryptionContext}. + */ + public long estimateCiphertextSize( + final ICryptographicMaterialsManager materialsManager, final int plaintextSize) { + return estimateCiphertextSize(materialsManager, plaintextSize, EMPTY_MAP); + } + + /** + * Returns the equivalent to calling {@link #encryptData(IKeyring, byte[], Map)} with an empty + * {@code encryptionContext}. + */ + public > CryptoResult encryptData( + final IKeyring keyring, final byte[] plaintext) { + return encryptData(keyring, plaintext, EMPTY_MAP); + } + + /** + * Returns an encrypted form of {@code plaintext} that has been protected with {@link DataKey + * DataKeys} that are in turn protected by {@link IKeyring Keyrings} provided by {@code keyring}. + * + *

This method is equivalent to calling {@link #encryptData(ICryptographicMaterialsManager, + * byte[], Map)} + */ + public CryptoResult encryptData( + final IKeyring keyring, final byte[] plaintext, final Map encryptionContext) { + Utils.assertNonNull(keyring, "keyring"); + return encryptData(createDefaultCMM(keyring), plaintext, encryptionContext); + } + + /** + * Returns the equivalent to calling {@link #encryptData(ICryptographicMaterialsManager, byte[], + * Map)} with an empty {@code encryptionContext}. + */ + public CryptoResult encryptData( + final ICryptographicMaterialsManager materialsManager, final byte[] plaintext) { + return encryptData(materialsManager, plaintext, EMPTY_MAP); + } + + /** + * Returns an encrypted form of {@code plaintext} that has been protected with {@link DataKey + * DataKeys} that are in turn protected by the given CryptoMaterialsProvider. + */ + public CryptoResult encryptData( + final ICryptographicMaterialsManager materialsManager, + final byte[] plaintext, + final Map encryptionContext) { + Utils.assertNonNull(materialsManager, "materialsManager"); + + EncryptionMaterialsRequest request = + EncryptionMaterialsRequest.newBuilder() + .setContext(encryptionContext) + .setRequestedAlgorithm(getEncryptionAlgorithm()) + .setPlaintext(plaintext) + .setCommitmentPolicy(commitmentPolicy_) + .build(); + CMMHandler cmmHandler = new CMMHandler(materialsManager); + EncryptionMaterialsHandler encryptionMaterials = + checkMaxEncryptedDataKeys(checkAlgorithm(cmmHandler.getMaterialsForEncrypt(request))); + final MessageCryptoHandler cryptoHandler = + new EncryptionHandler(getEncryptionFrameSize(), encryptionMaterials, commitmentPolicy_); + + return encryptData(cryptoHandler, plaintext); + } + /** * Returns an encrypted form of {@code plaintext} that has been protected with {@link DataKey * DataKeys} that are in turn protected by {@link MasterKey MasterKeys} provided by {@code @@ -326,10 +488,12 @@ public long estimateCiphertextSize( *

This method is equivalent to calling {@link #encryptData(CryptoMaterialsManager, byte[], * Map)} using a {@link DefaultCryptoMaterialsManager} based on the given provider. */ + @Deprecated public > CryptoResult encryptData( final MasterKeyProvider provider, final byte[] plaintext, final Map encryptionContext) { + Utils.assertNonNull(provider, "provider"); //noinspection unchecked return (CryptoResult) encryptData(new DefaultCryptoMaterialsManager(provider), plaintext, encryptionContext); @@ -339,6 +503,7 @@ public > CryptoResult encryptData( * Returns an encrypted form of {@code plaintext} that has been protected with {@link DataKey * DataKeys} that are in turn protected by the given CryptoMaterialsProvider. */ + @Deprecated public CryptoResult encryptData( CryptoMaterialsManager materialsManager, final byte[] plaintext, @@ -351,11 +516,18 @@ public > CryptoResult encryptData( .setCommitmentPolicy(commitmentPolicy_) .build(); - EncryptionMaterials encryptionMaterials = - checkMaxEncryptedDataKeys(checkAlgorithm(materialsManager.getMaterialsForEncrypt(request))); + EncryptionMaterialsHandler encryptionMaterials = + checkMaxEncryptedDataKeys( + checkAlgorithm( + new EncryptionMaterialsHandler(materialsManager.getMaterialsForEncrypt(request)))); final MessageCryptoHandler cryptoHandler = new EncryptionHandler(getEncryptionFrameSize(), encryptionMaterials, commitmentPolicy_); + return encryptData(cryptoHandler, plaintext); + } + + private > CryptoResult encryptData( + MessageCryptoHandler cryptoHandler, byte[] plaintext) { final int outSizeEstimate = cryptoHandler.estimateOutputSize(plaintext.length); final byte[] out = new byte[outSizeEstimate]; int outLen = @@ -365,13 +537,18 @@ public > CryptoResult encryptData( final byte[] outBytes = Utils.truncate(out, outLen); //noinspection unchecked - return new CryptoResult(outBytes, cryptoHandler.getMasterKeys(), cryptoHandler.getHeaders()); + return new CryptoResult( + outBytes, + cryptoHandler.getMasterKeys(), + cryptoHandler.getHeaders(), + cryptoHandler.getEncryptionContext()); } /** * Returns the equivalent to calling {@link #encryptData(MasterKeyProvider, byte[], Map)} with an * empty {@code encryptionContext}. */ + @Deprecated public > CryptoResult encryptData( final MasterKeyProvider provider, final byte[] plaintext) { return encryptData(provider, plaintext, EMPTY_MAP); @@ -381,6 +558,7 @@ public > CryptoResult encryptData( * Returns the equivalent to calling {@link #encryptData(CryptoMaterialsManager, byte[], Map)} * with an empty {@code encryptionContext}. */ + @Deprecated public CryptoResult encryptData( final CryptoMaterialsManager materialsManager, final byte[] plaintext) { return encryptData(materialsManager, plaintext, EMPTY_MAP); @@ -429,7 +607,8 @@ public > CryptoResult encryptString( return new CryptoResult<>( Utils.encodeBase64String(ctBytes.getResult()), ctBytes.getMasterKeys(), - ctBytes.getHeaders()); + ctBytes.getHeaders(), + ctBytes.getEncryptionContext()); } /** @@ -471,11 +650,10 @@ public > CryptoResult encryptString( * usable {@link DataKey} in the ciphertext and then decrypts the ciphertext using that {@code * DataKey}. */ + @Deprecated public > CryptoResult decryptData( final MasterKeyProvider provider, final byte[] ciphertext) { - return decryptData( - Utils.assertNonNull(provider, "provider"), - new ParsedCiphertext(ciphertext, maxEncryptedDataKeys_)); + return decryptData(provider, new ParsedCiphertext(ciphertext, maxEncryptedDataKeys_)); } /** @@ -486,15 +664,15 @@ public > CryptoResult decryptData( * @param ciphertext the ciphertext to attempt to decrypt. * @return the {@link CryptoResult} with the decrypted data. */ + @Deprecated public CryptoResult decryptData( final CryptoMaterialsManager materialsManager, final byte[] ciphertext) { - return decryptData( - Utils.assertNonNull(materialsManager, "materialsManager"), - new ParsedCiphertext(ciphertext, maxEncryptedDataKeys_)); + return decryptData(materialsManager, new ParsedCiphertext(ciphertext, maxEncryptedDataKeys_)); } /** @see #decryptData(MasterKeyProvider, byte[]) */ @SuppressWarnings("unchecked") + @Deprecated public > CryptoResult decryptData( final MasterKeyProvider provider, final ParsedCiphertext ciphertext) { Utils.assertNonNull(provider, "provider"); @@ -503,6 +681,7 @@ public > CryptoResult decryptData( } /** @see #decryptData(CryptoMaterialsManager, byte[]) */ + @Deprecated public CryptoResult decryptData( final CryptoMaterialsManager materialsManager, final ParsedCiphertext ciphertext) { Utils.assertNonNull(materialsManager, "materialsManager"); @@ -515,6 +694,11 @@ public > CryptoResult decryptData( SignaturePolicy.AllowEncryptAllowDecrypt, maxEncryptedDataKeys_); + return decryptData(cryptoHandler, ciphertext); + } + + private CryptoResult decryptData( + final MessageCryptoHandler cryptoHandler, final ParsedCiphertext ciphertext) { final byte[] ciphertextBytes = ciphertext.getCiphertext(); final int contentLen = ciphertextBytes.length - ciphertext.getOffset(); final int outSizeEstimate = cryptoHandler.estimateOutputSize(contentLen); @@ -531,7 +715,119 @@ public > CryptoResult decryptData( final byte[] outBytes = Utils.truncate(out, outLen); //noinspection unchecked - return new CryptoResult(outBytes, cryptoHandler.getMasterKeys(), cryptoHandler.getHeaders()); + return new CryptoResult( + outBytes, + cryptoHandler.getMasterKeys(), + cryptoHandler.getHeaders(), + cryptoHandler.getEncryptionContext()); + } + + /** + * Decrypts the provided ciphertext by delegating to the provided materialsManager to obtain the + * decrypted {@link DataKey}. + * + * @param keyring the {@link IKeyring} to use for decryption operations. + * @param ciphertext the ciphertext to attempt to decrypt. + * @return the {@link CryptoResult} with the decrypted data. + */ + public CryptoResult decryptData(final IKeyring keyring, final byte[] ciphertext) { + return decryptData(keyring, new ParsedCiphertext(ciphertext, maxEncryptedDataKeys_)); + } + + /** + * Decrypts the provided ciphertext by delegating to the provided materialsManager to obtain the + * decrypted {@link DataKey}. + * + * @param keyring the {@link IKeyring} to use for decryption operations. + * @param ciphertext the ciphertext to attempt to decrypt. + * @param encryptionContext The encryption context MUST contain a value for every key in the + * configured required encryption context keys during encryption with Required Encryption + * Context CMM. + * @return the {@link CryptoResult} with the decrypted data. + */ + public CryptoResult decryptData( + final IKeyring keyring, + final byte[] ciphertext, + final Map encryptionContext) { + return decryptData( + keyring, new ParsedCiphertext(ciphertext, maxEncryptedDataKeys_), encryptionContext); + } + + /** @see #decryptData(IKeyring, byte[]) */ + public CryptoResult decryptData( + final IKeyring keyring, final ParsedCiphertext ciphertext) { + return decryptData(keyring, ciphertext, EMPTY_MAP); + } + + /** @see #decryptData(IKeyring, byte[], Map) */ + private CryptoResult decryptData( + IKeyring keyring, ParsedCiphertext ciphertext, Map encryptionContext) { + //noinspection unchecked + Utils.assertNonNull(keyring, "keyring"); + + return decryptData(createDefaultCMM(keyring), ciphertext, encryptionContext); + } + + /** + * Decrypts the provided ciphertext by delegating to the provided materialsManager to obtain the + * decrypted {@link DataKey}. + * + * @param materialsManager the {@link ICryptographicMaterialsManager} to use for decryption + * operations. + * @param ciphertext the ciphertext to attempt to decrypt. + * @return the {@link CryptoResult} with the decrypted data. + */ + public CryptoResult decryptData( + final ICryptographicMaterialsManager materialsManager, final byte[] ciphertext) { + return decryptData(materialsManager, new ParsedCiphertext(ciphertext, maxEncryptedDataKeys_)); + } + + /** + * Decrypts the provided ciphertext by delegating to the provided materialsManager to obtain the + * decrypted {@link DataKey}. + * + * @param materialsManager the {@link ICryptographicMaterialsManager} to use for decryption + * operations. + * @param ciphertext the ciphertext to attempt to decrypt. + * @param encryptionContext The encryption context MUST contain a value for every key in the + * configured required encryption context keys during encryption with Required Encryption + * Context CMM. + * @return the {@link CryptoResult} with the decrypted data. + */ + public CryptoResult decryptData( + final ICryptographicMaterialsManager materialsManager, + final byte[] ciphertext, + final Map encryptionContext) { + return decryptData( + materialsManager, + new ParsedCiphertext(ciphertext, maxEncryptedDataKeys_), + encryptionContext); + } + + /** @see #decryptData(ICryptographicMaterialsManager, byte[]) */ + public CryptoResult decryptData( + final ICryptographicMaterialsManager materialsManager, final ParsedCiphertext ciphertext) { + + return decryptData(materialsManager, ciphertext, EMPTY_MAP); + } + + /** @see #decryptData(ICryptographicMaterialsManager, byte[]) */ + public CryptoResult decryptData( + final ICryptographicMaterialsManager materialsManager, + final ParsedCiphertext ciphertext, + final Map encryptionContext) { + Utils.assertNonNull(materialsManager, "materialsManager"); + + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + materialsManager, + ciphertext, + commitmentPolicy_, + SignaturePolicy.AllowEncryptAllowDecrypt, + maxEncryptedDataKeys_, + encryptionContext); + + return decryptData(cryptoHandler, ciphertext); } /** @@ -581,7 +877,8 @@ public > CryptoResult decryptString( return new CryptoResult( new String(ptBytes.getResult(), StandardCharsets.UTF_8), ptBytes.getMasterKeys(), - ptBytes.getHeaders()); + ptBytes.getHeaders(), + ptBytes.getEncryptionContext()); } /** @@ -591,6 +888,7 @@ public > CryptoResult decryptString( * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherOutputStream */ + @Deprecated public > CryptoOutputStream createEncryptingStream( final MasterKeyProvider provider, final OutputStream os, @@ -600,6 +898,20 @@ public > CryptoOutputStream createEncryptingStream( createEncryptingStream(new DefaultCryptoMaterialsManager(provider), os, encryptionContext); } + /** + * Returns a {@link CryptoOutputStream} which encrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + * + * @see #encryptData(IKeyring, byte[], Map) + * @see javax.crypto.CipherOutputStream + */ + public > CryptoOutputStream createEncryptingStream( + final IKeyring keyring, final OutputStream os, final Map encryptionContext) { + //noinspection unchecked + return (CryptoOutputStream) + createEncryptingStream(createDefaultCMM(keyring), os, encryptionContext); + } + /** * Returns a {@link CryptoOutputStream} which encrypts the data prior to passing it onto the * underlying {@link OutputStream}. @@ -607,32 +919,69 @@ public > CryptoOutputStream createEncryptingStream( * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherOutputStream */ + @Deprecated public CryptoOutputStream createEncryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os, final Map encryptionContext) { return new CryptoOutputStream<>( - os, getEncryptingStreamHandler(materialsManager, encryptionContext)); + os, getEncryptingStreamHandler(new CMMHandler(materialsManager), encryptionContext)); + } + + /** + * Returns a {@link CryptoOutputStream} which encrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + * + * @see #encryptData(IKeyring, byte[], Map) + * @see javax.crypto.CipherOutputStream + */ + public CryptoOutputStream createEncryptingStream( + final ICryptographicMaterialsManager materialsManager, + final OutputStream os, + final Map encryptionContext) { + return new CryptoOutputStream<>( + os, getEncryptingStreamHandler(new CMMHandler(materialsManager), encryptionContext)); } /** * Returns the equivalent to calling {@link #createEncryptingStream(MasterKeyProvider, * OutputStream, Map)} with an empty {@code encryptionContext}. */ + @Deprecated public > CryptoOutputStream createEncryptingStream( final MasterKeyProvider provider, final OutputStream os) { return createEncryptingStream(provider, os, EMPTY_MAP); } + /** + * Returns the equivalent to calling {@link #createEncryptingStream(IKeyring, OutputStream, Map)} + * with an empty {@code encryptionContext}. + */ + public > CryptoOutputStream createEncryptingStream( + final IKeyring keyring, final OutputStream os) { + return createEncryptingStream(keyring, os, EMPTY_MAP); + } + /** * Returns the equivalent to calling {@link #createEncryptingStream(CryptoMaterialsManager, * OutputStream, Map)} with an empty {@code encryptionContext}. */ + @Deprecated public CryptoOutputStream createEncryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os) { return createEncryptingStream(materialsManager, os, EMPTY_MAP); } + /** + * Returns the equivalent to calling {@link + * #createEncryptingStream(ICryptographicMaterialsManager, OutputStream, Map)} with an empty + * {@code encryptionContext}. + */ + public CryptoOutputStream createEncryptingStream( + final ICryptographicMaterialsManager materialsManager, final OutputStream os) { + return createEncryptingStream(materialsManager, os, EMPTY_MAP); + } + /** * Returns a {@link CryptoInputStream} which encrypts the data after reading it from the * underlying {@link InputStream}. @@ -640,6 +989,7 @@ public CryptoOutputStream createEncryptingStream( * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherInputStream */ + @Deprecated public > CryptoInputStream createEncryptingStream( final MasterKeyProvider provider, final InputStream is, @@ -649,6 +999,20 @@ public > CryptoInputStream createEncryptingStream( createEncryptingStream(new DefaultCryptoMaterialsManager(provider), is, encryptionContext); } + /** + * Returns a {@link CryptoInputStream} which encrypts the data after reading it from the + * underlying {@link InputStream}. + * + * @see #encryptData(IKeyring, byte[], Map) + * @see javax.crypto.CipherInputStream + */ + public > CryptoInputStream createEncryptingStream( + final IKeyring keyring, final InputStream is, final Map encryptionContext) { + ICryptographicMaterialsManager materialsManager = createDefaultCMM(keyring); + //noinspection unchecked + return (CryptoInputStream) createEncryptingStream(materialsManager, is, encryptionContext); + } + /** * Returns a {@link CryptoInputStream} which encrypts the data after reading it from the * underlying {@link InputStream}. @@ -656,12 +1020,30 @@ public > CryptoInputStream createEncryptingStream( * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherInputStream */ + @Deprecated public CryptoInputStream createEncryptingStream( CryptoMaterialsManager materialsManager, final InputStream is, final Map encryptionContext) { final MessageCryptoHandler cryptoHandler = - getEncryptingStreamHandler(materialsManager, encryptionContext); + getEncryptingStreamHandler(new CMMHandler(materialsManager), encryptionContext); + + return new CryptoInputStream<>(is, cryptoHandler); + } + + /** + * Returns a {@link CryptoInputStream} which encrypts the data after reading it from the + * underlying {@link InputStream}. + * + * @see #encryptData(IKeyring, byte[], Map) + * @see javax.crypto.CipherInputStream + */ + public CryptoInputStream createEncryptingStream( + ICryptographicMaterialsManager materialsManager, + final InputStream is, + final Map encryptionContext) { + final MessageCryptoHandler cryptoHandler = + getEncryptingStreamHandler(new CMMHandler(materialsManager), encryptionContext); return new CryptoInputStream<>(is, cryptoHandler); } @@ -670,17 +1052,38 @@ public CryptoInputStream createEncryptingStream( * Returns the equivalent to calling {@link #createEncryptingStream(MasterKeyProvider, * InputStream, Map)} with an empty {@code encryptionContext}. */ + @Deprecated public > CryptoInputStream createEncryptingStream( final MasterKeyProvider provider, final InputStream is) { return createEncryptingStream(provider, is, EMPTY_MAP); } /** - * Returns the equivalent to calling {@link #createEncryptingStream(CryptoMaterialsManager, + * Returns the equivalent to calling {@link #createEncryptingStream(MasterKeyProvider, * InputStream, Map)} with an empty {@code encryptionContext}. */ - public CryptoInputStream createEncryptingStream( - final CryptoMaterialsManager materialsManager, final InputStream is) { + public > CryptoInputStream createEncryptingStream( + final IKeyring keyring, final InputStream is) { + return createEncryptingStream(keyring, is, EMPTY_MAP); + } + + /** + * Returns the equivalent to calling {@link #createEncryptingStream(CryptoMaterialsManager, + * InputStream, Map)} with an empty {@code encryptionContext}. + */ + @Deprecated + public CryptoInputStream createEncryptingStream( + final CryptoMaterialsManager materialsManager, final InputStream is) { + return createEncryptingStream(materialsManager, is, EMPTY_MAP); + } + + /** + * Returns the equivalent to calling {@link + * #createEncryptingStream(ICryptographicMaterialsManager, InputStream, Map)} with an empty {@code + * encryptionContext}. + */ + public CryptoInputStream createEncryptingStream( + final ICryptographicMaterialsManager materialsManager, final InputStream is) { return createEncryptingStream(materialsManager, is, EMPTY_MAP); } @@ -691,6 +1094,7 @@ public CryptoInputStream createEncryptingStream( * @see #decryptData(MasterKeyProvider, byte[]) * @see javax.crypto.CipherOutputStream */ + @Deprecated public > CryptoOutputStream createUnsignedMessageDecryptingStream( final MasterKeyProvider provider, final OutputStream os) { final MessageCryptoHandler cryptoHandler = @@ -702,6 +1106,43 @@ public > CryptoOutputStream createUnsignedMessageDecry return new CryptoOutputStream(os, cryptoHandler); } + /** + * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. This version only accepts unsigned messages. + * + * @see #decryptData(IKeyring, byte[]) + * @see javax.crypto.CipherOutputStream + */ + public > CryptoOutputStream createUnsignedMessageDecryptingStream( + final IKeyring keyring, final OutputStream os) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + createDefaultCMM(keyring), + commitmentPolicy_, + SignaturePolicy.AllowEncryptForbidDecrypt, + maxEncryptedDataKeys_); + return new CryptoOutputStream(os, cryptoHandler); + } + + /** + * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. This version only accepts unsigned messages. + * + * @see #decryptData(IKeyring, byte[], Map) + * @see javax.crypto.CipherOutputStream + */ + public > CryptoOutputStream createUnsignedMessageDecryptingStream( + final IKeyring keyring, final OutputStream os, final Map encryptionContext) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + createDefaultCMM(keyring), + commitmentPolicy_, + SignaturePolicy.AllowEncryptForbidDecrypt, + maxEncryptedDataKeys_, + encryptionContext); + return new CryptoOutputStream(os, cryptoHandler); + } + /** * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the * underlying {@link InputStream}. This version only accepts unsigned messages. @@ -709,6 +1150,7 @@ public > CryptoOutputStream createUnsignedMessageDecry * @see #decryptData(MasterKeyProvider, byte[]) * @see javax.crypto.CipherInputStream */ + @Deprecated public > CryptoInputStream createUnsignedMessageDecryptingStream( final MasterKeyProvider provider, final InputStream is) { final MessageCryptoHandler cryptoHandler = @@ -720,6 +1162,43 @@ public > CryptoInputStream createUnsignedMessageDecryp return new CryptoInputStream(is, cryptoHandler); } + /** + * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. This version only accepts unsigned messages. + * + * @see #decryptData(IKeyring, byte[]) + * @see javax.crypto.CipherInputStream + */ + public > CryptoInputStream createUnsignedMessageDecryptingStream( + final IKeyring keyring, final InputStream is) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + createDefaultCMM(keyring), + commitmentPolicy_, + SignaturePolicy.AllowEncryptForbidDecrypt, + maxEncryptedDataKeys_); + return new CryptoInputStream(is, cryptoHandler); + } + + /** + * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. This version only accepts unsigned messages. + * + * @see #decryptData(IKeyring, byte[], Map) + * @see javax.crypto.CipherInputStream + */ + public > CryptoInputStream createUnsignedMessageDecryptingStream( + final IKeyring keyring, final InputStream is, final Map encryptionContext) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + createDefaultCMM(keyring), + commitmentPolicy_, + SignaturePolicy.AllowEncryptForbidDecrypt, + maxEncryptedDataKeys_, + encryptionContext); + return new CryptoInputStream(is, cryptoHandler); + } + /** * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the * underlying {@link OutputStream}. This version only accepts unsigned messages. @@ -727,6 +1206,7 @@ public > CryptoInputStream createUnsignedMessageDecryp * @see #decryptData(CryptoMaterialsManager, byte[]) * @see javax.crypto.CipherOutputStream */ + @Deprecated public CryptoOutputStream createUnsignedMessageDecryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os) { final MessageCryptoHandler cryptoHandler = @@ -738,6 +1218,45 @@ public CryptoOutputStream createUnsignedMessageDecryptingStream( return new CryptoOutputStream(os, cryptoHandler); } + /** + * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. This version only accepts unsigned messages. + * + * @see #decryptData(ICryptographicMaterialsManager, byte[]) + * @see javax.crypto.CipherOutputStream + */ + public CryptoOutputStream createUnsignedMessageDecryptingStream( + final ICryptographicMaterialsManager materialsManager, final OutputStream os) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + materialsManager, + commitmentPolicy_, + SignaturePolicy.AllowEncryptForbidDecrypt, + maxEncryptedDataKeys_); + return new CryptoOutputStream(os, cryptoHandler); + } + + /** + * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. This version only accepts unsigned messages. + * + * @see #decryptData(ICryptographicMaterialsManager, byte[], Map) + * @see javax.crypto.CipherOutputStream + */ + public CryptoOutputStream createUnsignedMessageDecryptingStream( + final ICryptographicMaterialsManager materialsManager, + final OutputStream os, + final Map encryptionContext) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + materialsManager, + commitmentPolicy_, + SignaturePolicy.AllowEncryptForbidDecrypt, + maxEncryptedDataKeys_, + encryptionContext); + return new CryptoOutputStream(os, cryptoHandler); + } + /** * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the * underlying {@link InputStream}. This version only accepts unsigned messages. @@ -745,6 +1264,7 @@ public CryptoOutputStream createUnsignedMessageDecryptingStream( * @see #encryptData(CryptoMaterialsManager, byte[], Map) * @see javax.crypto.CipherInputStream */ + @Deprecated public CryptoInputStream createUnsignedMessageDecryptingStream( final CryptoMaterialsManager materialsManager, final InputStream is) { final MessageCryptoHandler cryptoHandler = @@ -756,6 +1276,45 @@ public CryptoInputStream createUnsignedMessageDecryptingStream( return new CryptoInputStream(is, cryptoHandler); } + /** + * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. This version only accepts unsigned messages. + * + * @see #encryptData(ICryptographicMaterialsManager, byte[]) + * @see javax.crypto.CipherInputStream + */ + public CryptoInputStream createUnsignedMessageDecryptingStream( + final ICryptographicMaterialsManager materialsManager, final InputStream is) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + materialsManager, + commitmentPolicy_, + SignaturePolicy.AllowEncryptForbidDecrypt, + maxEncryptedDataKeys_); + return new CryptoInputStream(is, cryptoHandler); + } + + /** + * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. This version only accepts unsigned messages. + * + * @see #encryptData(ICryptographicMaterialsManager, byte[], Map) + * @see javax.crypto.CipherInputStream + */ + public CryptoInputStream createUnsignedMessageDecryptingStream( + final ICryptographicMaterialsManager materialsManager, + final InputStream is, + final Map encryptionContext) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + materialsManager, + commitmentPolicy_, + SignaturePolicy.AllowEncryptForbidDecrypt, + maxEncryptedDataKeys_, + encryptionContext); + return new CryptoInputStream(is, cryptoHandler); + } + /** * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the * underlying {@link OutputStream}. @@ -771,6 +1330,7 @@ public CryptoInputStream createUnsignedMessageDecryptingStream( * @see #createUnsignedMessageDecryptingStream(MasterKeyProvider, OutputStream) * @see javax.crypto.CipherOutputStream */ + @Deprecated public > CryptoOutputStream createDecryptingStream( final MasterKeyProvider provider, final OutputStream os) { final MessageCryptoHandler cryptoHandler = @@ -782,6 +1342,58 @@ public > CryptoOutputStream createDecryptingStream( return new CryptoOutputStream(os, cryptoHandler); } + /** + * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + * + *

Note that if the encrypted message includes a trailing signature, by necessity it cannot be + * verified until after the decrypted plaintext has been released to the underlying {@link + * OutputStream}! This behavior can be avoided by using the non-streaming #decryptData(IKeyring, + * byte[]) method instead, or #createUnsignedMessageDecryptingStream(IKeyring, OutputStream) if + * you do not need to decrypt signed messages. + * + * @see #decryptData(IKeyring, byte[]) + * @see #createUnsignedMessageDecryptingStream(IKeyring, OutputStream) + * @see javax.crypto.CipherOutputStream + */ + public > CryptoOutputStream createDecryptingStream( + final IKeyring keyring, final OutputStream os) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + createDefaultCMM(keyring), + commitmentPolicy_, + SignaturePolicy.AllowEncryptAllowDecrypt, + maxEncryptedDataKeys_); + return new CryptoOutputStream(os, cryptoHandler); + } + + /** + * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + * + *

Note that if the encrypted message includes a trailing signature, by necessity it cannot be + * verified until after the decrypted plaintext has been released to the underlying {@link + * OutputStream}! This behavior can be avoided by using the non-streaming #decryptData(IKeyring, + * byte[], Map) method instead, or + * #createUnsignedMessageDecryptingStream(IKeyring, OutputStream, Map) if you do + * not need to decrypt signed messages. + * + * @see #decryptData(IKeyring, byte[], Map) + * @see #createUnsignedMessageDecryptingStream(IKeyring, OutputStream, Map) + * @see javax.crypto.CipherOutputStream + */ + public > CryptoOutputStream createDecryptingStream( + final IKeyring keyring, final OutputStream os, final Map encryptionContext) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + createDefaultCMM(keyring), + commitmentPolicy_, + SignaturePolicy.AllowEncryptAllowDecrypt, + maxEncryptedDataKeys_, + encryptionContext); + return new CryptoOutputStream(os, cryptoHandler); + } + /** * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the * underlying {@link InputStream}. @@ -796,6 +1408,7 @@ public > CryptoOutputStream createDecryptingStream( * @see #createUnsignedMessageDecryptingStream(MasterKeyProvider, InputStream) * @see javax.crypto.CipherInputStream */ + @Deprecated public > CryptoInputStream createDecryptingStream( final MasterKeyProvider provider, final InputStream is) { final MessageCryptoHandler cryptoHandler = @@ -807,6 +1420,57 @@ public > CryptoInputStream createDecryptingStream( return new CryptoInputStream(is, cryptoHandler); } + /** + * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. + * + *

Note that if the encrypted message includes a trailing signature, by necessity it cannot be + * verified until after the decrypted plaintext has been produced from the {@link InputStream}! + * This behavior can be avoided by using the non-streaming #decryptData(IKeyring, byte[]) method + * instead, or #createUnsignedMessageDecryptingStream(IKeyring, InputStream) if you do not need to + * decrypt signed messages. + * + * @see #decryptData(IKeyring, byte[]) + * @see #createUnsignedMessageDecryptingStream(IKeyring, InputStream) + * @see javax.crypto.CipherInputStream + */ + public > CryptoInputStream createDecryptingStream( + final IKeyring keyring, final InputStream is) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + createDefaultCMM(keyring), + commitmentPolicy_, + SignaturePolicy.AllowEncryptAllowDecrypt, + maxEncryptedDataKeys_); + return new CryptoInputStream(is, cryptoHandler); + } + + /** + * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. + * + *

Note that if the encrypted message includes a trailing signature, by necessity it cannot be + * verified until after the decrypted plaintext has been produced from the {@link InputStream}! + * This behavior can be avoided by using the non-streaming #decryptData(IKeyring, byte[], + * Map) method instead, or #createUnsignedMessageDecryptingStream(IKeyring, + * InputStream, Map) if you do not need to decrypt signed messages. + * + * @see #decryptData(IKeyring, byte[], Map) + * @see #createUnsignedMessageDecryptingStream(IKeyring, InputStream, Map) + * @see javax.crypto.CipherInputStream + */ + public > CryptoInputStream createDecryptingStream( + final IKeyring keyring, final InputStream is, final Map encryptionContext) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + createDefaultCMM(keyring), + commitmentPolicy_, + SignaturePolicy.AllowEncryptAllowDecrypt, + maxEncryptedDataKeys_, + encryptionContext); + return new CryptoInputStream(is, cryptoHandler); + } + /** * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the * underlying {@link OutputStream}. @@ -822,6 +1486,7 @@ public > CryptoInputStream createDecryptingStream( * @see #createUnsignedMessageDecryptingStream(CryptoMaterialsManager, OutputStream) * @see javax.crypto.CipherOutputStream */ + @Deprecated public CryptoOutputStream createDecryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os) { final MessageCryptoHandler cryptoHandler = @@ -833,6 +1498,62 @@ public CryptoOutputStream createDecryptingStream( return new CryptoOutputStream(os, cryptoHandler); } + /** + * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + * + *

Note that if the encrypted message includes a trailing signature, by necessity it cannot be + * verified until after the decrypted plaintext has been released to the underlying {@link + * OutputStream}! This behavior can be avoided by using the non-streaming + * #decryptData(ICryptographicMaterialsManager, byte[]) method instead, or + * #createUnsignedMessageDecryptingStream(ICryptographicMaterialsManager, OutputStream) if you do + * not need to decrypt signed messages. + * + * @see #decryptData(ICryptographicMaterialsManager, byte[]) + * @see #createUnsignedMessageDecryptingStream(ICryptographicMaterialsManager, OutputStream) + * @see javax.crypto.CipherOutputStream + */ + public CryptoOutputStream createDecryptingStream( + final ICryptographicMaterialsManager materialsManager, final OutputStream os) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + materialsManager, + commitmentPolicy_, + SignaturePolicy.AllowEncryptAllowDecrypt, + maxEncryptedDataKeys_); + return new CryptoOutputStream(os, cryptoHandler); + } + + /** + * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + * + *

Note that if the encrypted message includes a trailing signature, by necessity it cannot be + * verified until after the decrypted plaintext has been released to the underlying {@link + * OutputStream}! This behavior can be avoided by using the non-streaming + * #decryptData(ICryptographicMaterialsManager, byte[], Map) method instead, or + * #createUnsignedMessageDecryptingStream(ICryptographicMaterialsManager, OutputStream, + * Map) if you do not need to decrypt signed messages. + * + * @see #decryptData(ICryptographicMaterialsManager, byte[], Map) + * @see #createUnsignedMessageDecryptingStream(ICryptographicMaterialsManager, OutputStream, + * Map) + * @see javax.crypto.CipherOutputStream + */ + public CryptoOutputStream createDecryptingStream( + final ICryptographicMaterialsManager materialsManager, + final OutputStream os, + final Map encryptionContext) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + materialsManager, + commitmentPolicy_, + SignaturePolicy.AllowEncryptAllowDecrypt, + maxEncryptedDataKeys_, + encryptionContext); + return new CryptoOutputStream(os, cryptoHandler); + } + /** * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the * underlying {@link InputStream}. @@ -847,6 +1568,7 @@ public CryptoOutputStream createDecryptingStream( * @see #createUnsignedMessageDecryptingStream(CryptoMaterialsManager, InputStream) * @see javax.crypto.CipherInputStream */ + @Deprecated public CryptoInputStream createDecryptingStream( final CryptoMaterialsManager materialsManager, final InputStream is) { final MessageCryptoHandler cryptoHandler = @@ -858,9 +1580,65 @@ public CryptoInputStream createDecryptingStream( return new CryptoInputStream(is, cryptoHandler); } + /** + * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. + * + *

Note that if the encrypted message includes a trailing signature, by necessity it cannot be + * verified until after the decrypted plaintext has been produced from the {@link InputStream}! + * This behavior can be avoided by using the non-streaming + * #decryptData(ICryptographicMaterialsManager, byte[]) method instead, or + * #createUnsignedMessageDecryptingStream(ICryptographicMaterialsManager, InputStream) if you do + * not need to decrypt signed messages. + * + * @see #decryptData(ICryptographicMaterialsManager, byte[]) + * @see #createUnsignedMessageDecryptingStream(ICryptographicMaterialsManager, InputStream) + * @see javax.crypto.CipherInputStream + */ + public CryptoInputStream createDecryptingStream( + final ICryptographicMaterialsManager materialsManager, final InputStream is) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + materialsManager, + commitmentPolicy_, + SignaturePolicy.AllowEncryptAllowDecrypt, + maxEncryptedDataKeys_); + return new CryptoInputStream(is, cryptoHandler); + } + + /** + * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. + * + *

Note that if the encrypted message includes a trailing signature, by necessity it cannot be + * verified until after the decrypted plaintext has been produced from the {@link InputStream}! + * This behavior can be avoided by using the non-streaming + * #decryptData(ICryptographicMaterialsManager, byte[], Map) method instead, or + * #createUnsignedMessageDecryptingStream(ICryptographicMaterialsManager, InputStream, Map) if you do not need to decrypt signed messages. + * + * @see #decryptData(ICryptographicMaterialsManager, byte[], Map) + * @see #createUnsignedMessageDecryptingStream(ICryptographicMaterialsManager, InputStream, + * Map) + * @see javax.crypto.CipherInputStream + */ + public CryptoInputStream createDecryptingStream( + final ICryptographicMaterialsManager materialsManager, + final InputStream is, + final Map encryptionContext) { + final MessageCryptoHandler cryptoHandler = + DecryptionHandler.create( + materialsManager, + commitmentPolicy_, + SignaturePolicy.AllowEncryptAllowDecrypt, + maxEncryptedDataKeys_, + encryptionContext); + return new CryptoInputStream(is, cryptoHandler); + } + private MessageCryptoHandler getEncryptingStreamHandler( - CryptoMaterialsManager materialsManager, Map encryptionContext) { - Utils.assertNonNull(materialsManager, "materialsManager"); + CMMHandler cmmHandler, Map encryptionContext) { + Utils.assertNonNull(cmmHandler, "cmmHandler"); Utils.assertNonNull(encryptionContext, "encryptionContext"); EncryptionMaterialsRequest.Builder requestBuilder = @@ -879,12 +1657,18 @@ private MessageCryptoHandler getEncryptingStreamHandler( return new EncryptionHandler( getEncryptionFrameSize(), checkMaxEncryptedDataKeys( - checkAlgorithm(materialsManager.getMaterialsForEncrypt(requestBuilder.build()))), + checkAlgorithm(cmmHandler.getMaterialsForEncrypt(requestBuilder.build()))), commitmentPolicy_); }); } - private EncryptionMaterials checkAlgorithm(EncryptionMaterials result) { + private ICryptographicMaterialsManager createDefaultCMM(IKeyring keyring) { + CreateDefaultCryptographicMaterialsManagerInput input = + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(keyring).build(); + return materialProviders_.CreateDefaultCryptographicMaterialsManager(input); + } + + private EncryptionMaterialsHandler checkAlgorithm(EncryptionMaterialsHandler result) { if (encryptionAlgorithm_ != null && result.getAlgorithm() != encryptionAlgorithm_) { throw new AwsCryptoException( String.format( @@ -896,7 +1680,8 @@ private EncryptionMaterials checkAlgorithm(EncryptionMaterials result) { return result; } - private EncryptionMaterials checkMaxEncryptedDataKeys(EncryptionMaterials materials) { + private EncryptionMaterialsHandler checkMaxEncryptedDataKeys( + EncryptionMaterialsHandler materials) { if (maxEncryptedDataKeys_ > 0 && materials.getEncryptedDataKeys().size() > maxEncryptedDataKeys_) { throw new AwsCryptoException("Encrypted data keys exceed maxEncryptedDataKeys"); diff --git a/src/main/java/com/amazonaws/encryptionsdk/CMMHandler.java b/src/main/java/com/amazonaws/encryptionsdk/CMMHandler.java new file mode 100644 index 00000000..3b6e64b0 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/CMMHandler.java @@ -0,0 +1,100 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.encryptionsdk; + +import com.amazonaws.encryptionsdk.internal.Utils; +import com.amazonaws.encryptionsdk.model.DecryptionMaterialsHandler; +import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest; +import com.amazonaws.encryptionsdk.model.EncryptionMaterialsHandler; +import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import software.amazon.cryptography.materialproviders.ICryptographicMaterialsManager; +import software.amazon.cryptography.materialproviders.model.DecryptMaterialsInput; +import software.amazon.cryptography.materialproviders.model.DecryptMaterialsOutput; +import software.amazon.cryptography.materialproviders.model.EncryptedDataKey; +import software.amazon.cryptography.materialproviders.model.GetEncryptionMaterialsInput; +import software.amazon.cryptography.materialproviders.model.GetEncryptionMaterialsOutput; + +/** + * Handler to abstract the differences between the original {@link CryptoMaterialsManager} and the + * MPL's {@link ICryptographicMaterialsManager}. + */ +public class CMMHandler { + CryptoMaterialsManager cmm = null; + ICryptographicMaterialsManager mplCMM = null; + + public CMMHandler(CryptoMaterialsManager cmm) { + Utils.assertNonNull(cmm, "cmm"); + this.cmm = cmm; + } + + public CMMHandler(ICryptographicMaterialsManager mplCMM) { + Utils.assertNonNull(mplCMM, "mplCMM"); + this.mplCMM = mplCMM; + } + + public EncryptionMaterialsHandler getMaterialsForEncrypt(EncryptionMaterialsRequest request) { + if (cmm != null && mplCMM == null) { + return new EncryptionMaterialsHandler(cmm.getMaterialsForEncrypt(request)); + } else { + GetEncryptionMaterialsInput input = getEncryptionMaterialsRequestInput(request); + GetEncryptionMaterialsOutput output = mplCMM.GetEncryptionMaterials(input); + return new EncryptionMaterialsHandler(output.encryptionMaterials()); + } + } + + private GetEncryptionMaterialsInput getEncryptionMaterialsRequestInput( + EncryptionMaterialsRequest request) { + return GetEncryptionMaterialsInput.builder() + .encryptionContext(request.getContext()) + .algorithmSuiteId( + request.getRequestedAlgorithm() == null + ? null + : request.getRequestedAlgorithm().getAlgorithmSuiteId()) + .commitmentPolicy(request.getCommitmentPolicy().getMplCommitmentPolicy()) + .maxPlaintextLength(request.getPlaintextSize()) + .build(); + } + + public DecryptionMaterialsHandler decryptMaterials( + DecryptionMaterialsRequest request, CommitmentPolicy commitmentPolicy) { + if (cmm != null && mplCMM == null) { + return new DecryptionMaterialsHandler(cmm.decryptMaterials(request)); + } else { + DecryptMaterialsInput input = getDecryptMaterialsInput(request, commitmentPolicy); + DecryptMaterialsOutput output = mplCMM.DecryptMaterials(input); + return new DecryptionMaterialsHandler(output.decryptionMaterials()); + } + } + + private DecryptMaterialsInput getDecryptMaterialsInput( + DecryptionMaterialsRequest request, CommitmentPolicy commitmentPolicy) { + List keyBlobs = request.getEncryptedDataKeys(); + List edks = + new ArrayList<>(keyBlobs.size()); + for (KeyBlob keyBlob : keyBlobs) { + edks.add( + EncryptedDataKey.builder() + .keyProviderId(keyBlob.getProviderId()) + .keyProviderInfo( + ByteBuffer.wrap( + keyBlob.getProviderInformation(), 0, keyBlob.getProviderInformation().length)) + .ciphertext( + ByteBuffer.wrap( + keyBlob.getEncryptedDataKey(), 0, keyBlob.getEncryptedDataKey().length)) + .build()); + } + + return DecryptMaterialsInput.builder() + .encryptionContext(request.getEncryptionContext()) + .reproducedEncryptionContext(request.getReproducedEncryptionContext()) + .algorithmSuiteId(request.getAlgorithm().getAlgorithmSuiteId()) + .commitmentPolicy(commitmentPolicy.getMplCommitmentPolicy()) + .encryptedDataKeys(edks) + .build(); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/CommitmentPolicy.java b/src/main/java/com/amazonaws/encryptionsdk/CommitmentPolicy.java index 94477cb3..390671b1 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CommitmentPolicy.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CommitmentPolicy.java @@ -3,6 +3,8 @@ package com.amazonaws.encryptionsdk; +import software.amazon.cryptography.materialproviders.model.ESDKCommitmentPolicy; + /** * Governs how a AwsCrypto behaves during configuration, encryption, and decryption, with respect to * key commitment. @@ -13,18 +15,33 @@ public enum CommitmentPolicy { * is present on the ciphertext, then the key commitment must be valid. Key commitment will NOT be * included in ciphertext on encrypt. */ - ForbidEncryptAllowDecrypt, + ForbidEncryptAllowDecrypt(ESDKCommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT), /** * On encrypt, algorithm suite must support key commitment; On decrypt, if a key commitment is * present on the ciphertext, then the key commitment must be valid. Key commitment will be * included in ciphertext on encrypt. */ - RequireEncryptAllowDecrypt, + RequireEncryptAllowDecrypt(ESDKCommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT), /** * Algorithm suite must support key commitment. Key commitment will be included in ciphertext on * encrypt. Valid key commitment must be present in ciphertext on decrypt. */ - RequireEncryptRequireDecrypt; + RequireEncryptRequireDecrypt(ESDKCommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT); + + private final software.amazon.cryptography.materialproviders.model.CommitmentPolicy + mplCommitmentPolicy; + + CommitmentPolicy(ESDKCommitmentPolicy esdkCommitmentPolicy) { + this.mplCommitmentPolicy = + software.amazon.cryptography.materialproviders.model.CommitmentPolicy.builder() + .ESDK(esdkCommitmentPolicy) + .build(); + } + + public software.amazon.cryptography.materialproviders.model.CommitmentPolicy + getMplCommitmentPolicy() { + return mplCommitmentPolicy; + } /** Validates that an algorithm meets the Policy's On encrypt key commitment. */ public boolean algorithmAllowedForEncrypt(CryptoAlgorithm algorithm) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java index 82171865..49c01293 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoAlgorithm.java @@ -18,6 +18,11 @@ import java.util.Map; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.AlgorithmSuiteId; +import software.amazon.cryptography.materialproviders.model.AlgorithmSuiteInfo; +import software.amazon.cryptography.materialproviders.model.HKDF; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; /** * Describes the cryptographic algorithms available for use in this library. @@ -28,135 +33,57 @@ */ public enum CryptoAlgorithm { /** AES-GCM 128 */ - ALG_AES_128_GCM_IV12_TAG16_NO_KDF( - 1, 128, 12, 16, Constants.GCM_MAX_CONTENT_LEN, "AES", 16, 0x0014, "AES", 16, false), + ALG_AES_128_GCM_IV12_TAG16_NO_KDF(0x0014), /** AES-GCM 192 */ - ALG_AES_192_GCM_IV12_TAG16_NO_KDF( - 1, 128, 12, 16, Constants.GCM_MAX_CONTENT_LEN, "AES", 24, 0x0046, "AES", 24, false), + ALG_AES_192_GCM_IV12_TAG16_NO_KDF(0x0046), /** AES-GCM 256 */ - ALG_AES_256_GCM_IV12_TAG16_NO_KDF( - 1, 128, 12, 16, Constants.GCM_MAX_CONTENT_LEN, "AES", 32, 0x0078, "AES", 32, false), + ALG_AES_256_GCM_IV12_TAG16_NO_KDF(0x0078), /** AES-GCM 128 with HKDF-SHA256 */ - ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256( - 1, 128, 12, 16, Constants.GCM_MAX_CONTENT_LEN, "AES", 16, 0x0114, "HkdfSHA256", 16, true), - /** AES-GCM 192 */ - ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA256( - 1, 128, 12, 16, Constants.GCM_MAX_CONTENT_LEN, "AES", 24, 0x0146, "HkdfSHA256", 24, true), - /** AES-GCM 256 */ - ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256( - 1, 128, 12, 16, Constants.GCM_MAX_CONTENT_LEN, "AES", 32, 0x0178, "HkdfSHA256", 32, true), - - /** AES-GCM 128 with ECDSA (SHA256 with the secp256r1 curve) */ - ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256( - 1, - 128, - 12, - 16, - Constants.GCM_MAX_CONTENT_LEN, - "AES", - 16, - 0x0214, - "HkdfSHA256", - 16, - true, - "SHA256withECDSA", - 71), - /** AES-GCM 192 with ECDSA (SHA384 with the secp384r1 curve) */ - ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384( - 1, - 128, - 12, - 16, - Constants.GCM_MAX_CONTENT_LEN, - "AES", - 24, - 0x0346, - "HkdfSHA384", - 24, - true, - "SHA384withECDSA", - 103), - /** AES-GCM 256 with ECDSA (SHA384 with the secp384r1 curve) */ - ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384( - 1, - 128, - 12, - 16, - Constants.GCM_MAX_CONTENT_LEN, - "AES", - 32, - 0x0378, - "HkdfSHA384", - 32, - true, - "SHA384withECDSA", - 103), + ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256(0x0114), + /** AES-GCM 192 with HKDF-SHA256 */ + ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA256(0x0146), + /** AES-GCM 256 with HKDF-SHA256 */ + ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256(0x0178), + /** AES-GCM 128 with HKDF-SHA256 and ECDSA (SHA256 with the secp256r1 curve) */ + ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256(0x0214), + /** AES-GCM 192 with HKDF-SHA384 ECDSA (SHA384 with the secp384r1 curve) */ + ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384(0x0346), + /** AES-GCM 256 with HKDF-SHA384 and ECDSA (SHA384 with the secp384r1 curve) */ + ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384(0x0378), /** - * AES-GCM 256 with key commitment Note: 1.7.0 of this library only supports decryption of using - * this crypto algorithm and does not support encryption with this algorithm + * AES-GCM 256 with HKDF-SHA512 and key commitment Note: 1.7.0 of this library only supports + * decryption of using this crypto algorithm and does not support encryption with this algorithm */ - ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY( - 2, - 128, - 12, - 16, - Constants.GCM_MAX_CONTENT_LEN, - "AES", - 32, - 0x0478, - "HkdfSHA512", - 32, - true, - null, - 0, - "HkdfSHA512", - 32, - 32, - 32), + ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY(0x0478), /** - * AES-GCM 256 with ECDSA (SHA384 with the secp384r1 curve) and key commitment Note: 1.7.0 of this - * library only supports decryption of using this crypto algorithm and does not support encryption - * with this algorithm + * AES-GCM 256 with HKDF-SHA512, ECDSA (SHA384 with the secp384r1 curve) and key commitment Note: + * 1.7.0 of this library only supports decryption of using this crypto algorithm and does not + * support encryption with this algorithm */ - ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384( - 2, - 128, - 12, - 16, - Constants.GCM_MAX_CONTENT_LEN, - "AES", - 32, - 0x0578, - "HkdfSHA512", - 32, - true, - "SHA384withECDSA", - 103, - "HkdfSHA512", - 32, - 32, - 32); - - private final byte messageFormatVersion_; - private final int blockSizeBits_; - private final byte nonceLenBytes_; - private final int tagLenBytes_; - private final long maxContentLen_; - private final String keyAlgo_; - private final int keyLenBytes_; - private final short value_; - private final String trailingSigAlgo_; - private final short trailingSigLen_; - private final String dataKeyAlgo_; - private final int dataKeyLen_; - private final boolean safeToCache_; - private final String keyCommitmentAlgo_; - private final int commitmentLength_; - private final int commitmentNonceLength_; - private final int suiteDataLength_; - - private static final byte VERSION_1 = (byte) 1; - private static final byte VERSION_2 = (byte) 2; + ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384(0x0578); + + private final AlgorithmSuiteInfo info; + final MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + final MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + private final short value; + + CryptoAlgorithm(int value) { + if (value > Short.MAX_VALUE || value < Short.MIN_VALUE) { + throw new IllegalArgumentException("Invalid value " + value); + } + this.value = (short) value; + this.info = + materialProviders.GetAlgorithmSuiteInfo(ByteBuffer.allocate(2).putShort((short) value)); + } + + private static final String KEY_ALGORITHM = "AES"; + private static final String HKDF_SHA256 = "HkdfSHA256"; + private static final String HKDF_SHA384 = "HkdfSHA384"; + private static final String HKDF_SHA512 = "HkdfSHA512"; + private static final String ECDSA_P256 = "SHA256withECDSA"; + private static final String ECDSA_P384 = "SHA384withECDSA"; + private static final int BLOCK_SIZE_BYTES = 16; private static final int VERSION_1_MESSAGE_ID_LEN = 16; private static final int VERSION_2_MESSAGE_ID_LEN = 32; @@ -169,125 +96,8 @@ public enum CryptoAlgorithm { static { for (final CryptoAlgorithm s : EnumSet.allOf(CryptoAlgorithm.class)) { - ID_MAPPING.put(fieldsToLookupKey(s.messageFormatVersion_, s.value_), s); - } - } - - CryptoAlgorithm( - final int messageFormatVersion, - final int blockSizeBits, - final int nonceLenBytes, - final int tagLenBytes, - final long maxContentLen, - final String keyAlgo, - final int keyLenBytes, - final int value, - final String dataKeyAlgo, - final int dataKeyLen, - boolean safeToCache) { - this( - messageFormatVersion, - blockSizeBits, - nonceLenBytes, - tagLenBytes, - maxContentLen, - keyAlgo, - keyLenBytes, - value, - dataKeyAlgo, - dataKeyLen, - safeToCache, - null, - 0); - } - - CryptoAlgorithm( - final int messageFormatVersion, - final int blockSizeBits, - final int nonceLenBytes, - final int tagLenBytes, - final long maxContentLen, - final String keyAlgo, - final int keyLenBytes, - final int value, - final String dataKeyAlgo, - final int dataKeyLen, - boolean safeToCache, - final String trailingSignatureAlgo, - final int trailingSignatureLength) { - this( - messageFormatVersion, - blockSizeBits, - nonceLenBytes, - tagLenBytes, - maxContentLen, - keyAlgo, - keyLenBytes, - value, - dataKeyAlgo, - dataKeyLen, - safeToCache, - trailingSignatureAlgo, - trailingSignatureLength, - null, - 0, - 0, - 0); - } - - CryptoAlgorithm( - final int messageFormatVersion, - final int blockSizeBits, - final int nonceLenBytes, - final int tagLenBytes, - final long maxContentLen, - final String keyAlgo, - final int keyLenBytes, - final int value, - final String dataKeyAlgo, - final int dataKeyLen, - boolean safeToCache, - final String trailingSignatureAlgo, - final int trailingSignatureLength, - final String keyCommitmentAlgo, - final int commitmentLength, - final int commitmentNonceLength, - final int suiteDataLength) { - if ((messageFormatVersion & 0xFF) != messageFormatVersion) { - throw new IllegalArgumentException("Invalid messageFormatVersion: " + messageFormatVersion); - } - // All non-null key commitment algs must be the same as the kdf alg - if (keyCommitmentAlgo != null && !keyCommitmentAlgo.equals(dataKeyAlgo)) { - throw new IllegalArgumentException( - "Invalid keyCommitmentAlgo " - + keyCommitmentAlgo - + ". Must be equal to dataKeyAlgo " - + dataKeyAlgo - + "."); - } - messageFormatVersion_ = (byte) (messageFormatVersion & 0xFF); - blockSizeBits_ = blockSizeBits; - nonceLenBytes_ = (byte) nonceLenBytes; - tagLenBytes_ = tagLenBytes; - keyAlgo_ = keyAlgo; - keyLenBytes_ = keyLenBytes; - maxContentLen_ = maxContentLen; - safeToCache_ = safeToCache; - if (value > Short.MAX_VALUE || value < Short.MIN_VALUE) { - throw new IllegalArgumentException("Invalid value " + value); - } - value_ = (short) value; - dataKeyAlgo_ = dataKeyAlgo; - dataKeyLen_ = dataKeyLen; - trailingSigAlgo_ = trailingSignatureAlgo; - if (trailingSignatureLength > Short.MAX_VALUE || trailingSignatureLength < 0) { - throw new IllegalArgumentException("Invalid value " + trailingSignatureLength); + ID_MAPPING.put(fieldsToLookupKey(s.getMessageFormatVersion(), s.getValue()), s); } - trailingSigLen_ = (short) trailingSignatureLength; - keyCommitmentAlgo_ = keyCommitmentAlgo; - commitmentLength_ = commitmentLength; - commitmentNonceLength_ = commitmentNonceLength; - suiteDataLength_ = suiteDataLength; } private static int fieldsToLookupKey(final byte messageFormatVersion, final short algorithmId) { @@ -311,17 +121,21 @@ public static CryptoAlgorithm deserialize(final byte messageFormatVersion, final return ID_MAPPING.get(fieldsToLookupKey(messageFormatVersion, value)); } + public AlgorithmSuiteId getAlgorithmSuiteId() { + return info.id(); + } + /** Returns the length of the message Id in the header for this algorithm. */ public int getMessageIdLength() { // For now this is a derived value rather than stored explicitly - switch (messageFormatVersion_) { - case VERSION_1: + switch (info.messageVersion()) { + case 1: return VERSION_1_MESSAGE_ID_LEN; - case VERSION_2: + case 2: return VERSION_2_MESSAGE_ID_LEN; default: throw new UnsupportedOperationException( - "Support for version " + messageFormatVersion_ + " not yet built."); + "Support for version " + info.messageVersion() + " not yet built."); } } @@ -331,36 +145,36 @@ public int getMessageIdLength() { */ public byte[] getHeaderNonce() { // For now this is a derived value rather than stored explicitly - switch (messageFormatVersion_) { - case VERSION_1: + switch (info.messageVersion()) { + case 1: return null; - case VERSION_2: + case 2: // V2 explicitly uses an IV of 0 in the header - return new byte[nonceLenBytes_]; + return new byte[info.encrypt().AES_GCM().ivLength()]; default: throw new UnsupportedOperationException( - "Support for version " + messageFormatVersion_ + " not yet built."); + "Support for version " + info.messageVersion() + " not yet built."); } } /** Returns the message format version associated with this algorithm suite. */ public byte getMessageFormatVersion() { - return messageFormatVersion_; + return (byte) (info.messageVersion() & 0xFF); } /** Returns the block size of this algorithm in bytes. */ public int getBlockSize() { - return blockSizeBits_ / 8; + return BLOCK_SIZE_BYTES; } /** Returns the nonce length used in this algorithm in bytes. */ public byte getNonceLen() { - return nonceLenBytes_; + return (byte) info.encrypt().AES_GCM().ivLength(); } /** Returns the tag length used in this algorithm in bytes. */ public int getTagLen() { - return tagLenBytes_; + return info.encrypt().AES_GCM().tagLength(); } /** @@ -368,37 +182,65 @@ public int getTagLen() { * this algorithm. */ public long getMaxContentLen() { - return maxContentLen_; + return Constants.GCM_MAX_CONTENT_LEN; } /** Returns the algorithm used for encrypting the plaintext data. */ public String getKeyAlgo() { - return keyAlgo_; + return KEY_ALGORITHM; } /** Returns the length of the key used in this algorithm in bytes. */ public int getKeyLength() { - return keyLenBytes_; + return info.encrypt().AES_GCM().keyLength(); } /** Returns the value used to encode this algorithm in the ciphertext. */ public short getValue() { - return value_; + return value; } /** Returns the algorithm associated with the data key. */ public String getDataKeyAlgo() { - return dataKeyAlgo_; + if (info.kdf().HKDF() == null) { + return KEY_ALGORITHM; + } else { + String hmac = info.kdf().HKDF().hmac().name(); + switch (hmac) { + case "SHA_256": + return HKDF_SHA256; + case "SHA_384": + return HKDF_SHA384; + case "SHA_512": + return HKDF_SHA512; + default: + throw new UnsupportedOperationException( + "Support for Data Key Algorithm:" + hmac + " not yet built"); + } + } } /** Returns the length of the data key in bytes. */ public int getDataKeyLength() { - return dataKeyLen_; + return this.getKeyLength(); } /** Returns the algorithm used to calculate the trailing signature */ public String getTrailingSignatureAlgo() { - return trailingSigAlgo_; + if (info.signature().ECDSA() == null) { + return null; + } else { + String ecdsa = info.signature().ECDSA().curve().name(); + switch (ecdsa) { + case "ECDSA_P256": + return ECDSA_P256; + case "ECDSA_P384": + return ECDSA_P384; + default: + throw new UnsupportedOperationException( + "Support for Data Key Algorithm:" + ecdsa + " not yet built"); + } + } } /** @@ -407,7 +249,7 @@ public String getTrailingSignatureAlgo() { * cryptographic weaknesses, potentially even with only a single such use. */ public boolean isSafeToCache() { - return safeToCache_; + return (info.kdf().HKDF() != null); } /** @@ -415,11 +257,34 @@ public boolean isSafeToCache() { * signature may be shorter than this. */ public short getTrailingSignatureLength() { - return trailingSigLen_; + if (info.signature().ECDSA() == null) { + return 0; + } else { + String ecdsa = info.signature().ECDSA().curve().name(); + switch (ecdsa) { + case "ECDSA_P256": + return 71; + case "ECDSA_P384": + return 103; + default: + throw new UnsupportedOperationException( + "Support for Data Key Algorithm:" + ecdsa + " not yet built"); + } + } } public String getKeyCommitmentAlgo_() { - return keyCommitmentAlgo_; + HKDF keyCommitment = info.commitment().HKDF(); + if (keyCommitment == null) { + return null; + } + switch (keyCommitment.hmac().name()) { + case "SHA_512": + return "HkdfSHA512"; + default: + throw new UnsupportedOperationException( + "Support for Commitment Key Algorithm:" + info.commitment().HKDF() + " not yet built"); + } } /** @@ -427,19 +292,19 @@ public String getKeyCommitmentAlgo_() { * ensure key commitment. */ public boolean isCommitting() { - return keyCommitmentAlgo_ != null; + return info.commitment().HKDF() != null; } public int getCommitmentLength() { - return commitmentLength_; + return info.commitment().HKDF() == null ? 0 : info.commitment().HKDF().inputKeyLength(); } public int getCommitmentNonceLength() { - return commitmentNonceLength_; + return info.commitment().HKDF() == null ? 0 : info.commitment().HKDF().saltLength(); } public int getSuiteDataLength() { - return suiteDataLength_; + return info.commitment().HKDF() == null ? 0 : info.commitment().HKDF().outputKeyLength(); } public SecretKey getEncryptionKeyFromDataKey( @@ -453,14 +318,14 @@ public SecretKey getEncryptionKeyFromDataKey( } // We perform key derivation differently depending on the message format version - switch (messageFormatVersion_) { - case VERSION_1: + switch (info.messageVersion()) { + case 1: return getNonCommittedEncryptionKey(dataKey, headers); - case VERSION_2: + case 2: return getCommittedEncryptionKey(dataKey, headers); default: throw new UnsupportedOperationException( - "Support for message format version " + messageFormatVersion_ + " not yet built."); + "Support for message format version " + info.messageVersion() + " not yet built."); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java index 33ff8bd5..eacb0563 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java @@ -248,6 +248,9 @@ public CryptoResult, K> getCryptoResult() } //noinspection unchecked return new CryptoResult<>( - this, (List) cryptoHandler_.getMasterKeys(), cryptoHandler_.getHeaders()); + this, + (List) cryptoHandler_.getMasterKeys(), + cryptoHandler_.getHeaders(), + cryptoHandler_.getEncryptionContext()); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java index 69526d80..0204da28 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java @@ -166,6 +166,9 @@ public CryptoResult, K> getCryptoResult() { //noinspection unchecked return new CryptoResult<>( - this, (List) cryptoHandler_.getMasterKeys(), cryptoHandler_.getHeaders()); + this, + (List) cryptoHandler_.getMasterKeys(), + cryptoHandler_.getHeaders(), + cryptoHandler_.getEncryptionContext()); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoResult.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoResult.java index 16704413..61f38fc2 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CryptoResult.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoResult.java @@ -35,11 +35,15 @@ public class CryptoResult> { private final CiphertextHeaders headers_; /** Note, does not make a defensive copy of any of the data. */ - CryptoResult(final T result, final List masterKeys, final CiphertextHeaders headers) { + CryptoResult( + final T result, + final List masterKeys, + final CiphertextHeaders headers, + Map encryptionContext) { result_ = result; masterKeys_ = Collections.unmodifiableList(masterKeys); headers_ = headers; - encryptionContext_ = headers_.getEncryptionContextMap(); + encryptionContext_ = encryptionContext; } /** @@ -59,6 +63,7 @@ public T getResult() { * * @return */ + @Deprecated public List getMasterKeys() { return masterKeys_; } diff --git a/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java b/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java index 22980ad2..c0ef8197 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java +++ b/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java @@ -142,6 +142,7 @@ public DecryptionMaterials decryptMaterials(DecryptionMaterialsRequest request) return DecryptionMaterials.newBuilder() .setDataKey(dataKey) .setTrailingSignatureKey(pubKey) + .setEncryptionContext(request.getEncryptionContext()) .build(); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java b/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java index 05a31be9..591e8805 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java @@ -3,14 +3,22 @@ package com.amazonaws.encryptionsdk.internal; -import com.amazonaws.encryptionsdk.*; +import com.amazonaws.encryptionsdk.CMMHandler; +import com.amazonaws.encryptionsdk.CommitmentPolicy; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.CryptoMaterialsManager; +import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.DefaultCryptoMaterialsManager; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.ParsedCiphertext; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.BadCiphertextException; import com.amazonaws.encryptionsdk.model.CiphertextFooters; import com.amazonaws.encryptionsdk.model.CiphertextHeaders; import com.amazonaws.encryptionsdk.model.CiphertextType; import com.amazonaws.encryptionsdk.model.ContentType; -import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.DecryptionMaterialsHandler; import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; @@ -21,8 +29,10 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.crypto.Cipher; import javax.crypto.SecretKey; +import software.amazon.cryptography.materialproviders.ICryptographicMaterialsManager; /** * This class implements the CryptoHandler interface by providing methods for the decryption of @@ -33,7 +43,7 @@ * on the content type parsed in the ciphertext headers. */ public class DecryptionHandler> implements MessageCryptoHandler { - private final CryptoMaterialsManager materialsManager_; + private final CMMHandler cmmHandler_; private final CommitmentPolicy commitmentPolicy_; /** * The maximum number of encrypted data keys to parse, if positive. If zero, do not limit EDKs. @@ -54,6 +64,7 @@ public class DecryptionHandler> implements MessageCryptoH private Signature trailingSig_; private Map encryptionContext_ = null; + private Map reproducedEncryptionContext_ = Collections.emptyMap(); private byte[] unparsedBytes_ = new byte[0]; private boolean complete_ = false; @@ -67,15 +78,15 @@ public class DecryptionHandler> implements MessageCryptoH // CryptoMaterialsManager is not itself // genericized. private DecryptionHandler( - final CryptoMaterialsManager materialsManager, + final CMMHandler cmmHandler, final CommitmentPolicy commitmentPolicy, final SignaturePolicy signaturePolicy, final int maxEncryptedDataKeys) { - Utils.assertNonNull(materialsManager, "materialsManager"); + Utils.assertNonNull(cmmHandler, "cmmHandler"); Utils.assertNonNull(commitmentPolicy, "commitmentPolicy"); Utils.assertNonNull(signaturePolicy, "signaturePolicy"); - this.materialsManager_ = materialsManager; + this.cmmHandler_ = cmmHandler; this.commitmentPolicy_ = commitmentPolicy; this.maxEncryptedDataKeys_ = maxEncryptedDataKeys; this.signaturePolicy_ = signaturePolicy; @@ -84,17 +95,37 @@ private DecryptionHandler( } private DecryptionHandler( - final CryptoMaterialsManager materialsManager, + final CMMHandler cmmHandler, + final CommitmentPolicy commitmentPolicy, + final SignaturePolicy signaturePolicy, + final int maxEncryptedDataKeys, + final Map reproducedEncryptionContext) { + Utils.assertNonNull(cmmHandler, "cmmHandler"); + Utils.assertNonNull(commitmentPolicy, "commitmentPolicy"); + Utils.assertNonNull(signaturePolicy, "signaturePolicy"); + + this.cmmHandler_ = cmmHandler; + this.commitmentPolicy_ = commitmentPolicy; + this.maxEncryptedDataKeys_ = maxEncryptedDataKeys; + this.signaturePolicy_ = signaturePolicy; + this.reproducedEncryptionContext_ = reproducedEncryptionContext; + ciphertextHeaders_ = new CiphertextHeaders(); + ciphertextFooters_ = new CiphertextFooters(); + } + + private DecryptionHandler( + final CMMHandler cmmHandler, final CiphertextHeaders headers, final CommitmentPolicy commitmentPolicy, final SignaturePolicy signaturePolicy, - final int maxEncryptedDataKeys) + final int maxEncryptedDataKeys, + final Map reproducedEncryptionContext) throws AwsCryptoException { - Utils.assertNonNull(materialsManager, "materialsManager"); + Utils.assertNonNull(cmmHandler, "materialsManager"); Utils.assertNonNull(commitmentPolicy, "commitmentPolicy"); Utils.assertNonNull(signaturePolicy, "signaturePolicy"); - materialsManager_ = materialsManager; + cmmHandler_ = cmmHandler; ciphertextHeaders_ = headers; commitmentPolicy_ = commitmentPolicy; signaturePolicy_ = signaturePolicy; @@ -108,6 +139,7 @@ private DecryptionHandler( // deprecated. ciphertextBytesSupplied_ = headers.toByteArray().length; } + reproducedEncryptionContext_ = reproducedEncryptionContext; readHeaderFields(headers); updateTrailingSignature(headers); } @@ -239,7 +271,63 @@ public static DecryptionHandler create( final int maxEncryptedDataKeys) throws AwsCryptoException { return new DecryptionHandler( - materialsManager, commitmentPolicy, signaturePolicy, maxEncryptedDataKeys); + new CMMHandler(materialsManager), commitmentPolicy, signaturePolicy, maxEncryptedDataKeys); + } + + /** + * Create a decryption handler using the provided materials manager. + * + *

Note the methods in the provided materials manager are used in decrypting the encrypted data + * key parsed from the ciphertext headers. + * + * @param materialsManager the materials manager to use in decrypting the data key from the key + * blobs encoded in the provided ciphertext. + * @param commitmentPolicy The commitment policy to enforce during decryption + * @param signaturePolicy The signature policy to enforce during decryption + * @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during + * decryption; zero indicates no maximum + * @throws AwsCryptoException if the master key is null. + */ + public static DecryptionHandler create( + final ICryptographicMaterialsManager materialsManager, + final CommitmentPolicy commitmentPolicy, + final SignaturePolicy signaturePolicy, + final int maxEncryptedDataKeys) + throws AwsCryptoException { + return new DecryptionHandler( + new CMMHandler(materialsManager), commitmentPolicy, signaturePolicy, maxEncryptedDataKeys); + } + + /** + * Create a decryption handler using the provided materials manager. + * + *

Note the methods in the provided materials manager are used in decrypting the encrypted data + * key parsed from the ciphertext headers. + * + * @param materialsManager the materials manager to use in decrypting the data key from the key + * blobs encoded in the provided ciphertext. + * @param commitmentPolicy The commitment policy to enforce during decryption + * @param signaturePolicy The signature policy to enforce during decryption + * @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during + * decryption; zero indicates no maximum + * @param reproducedEncryptionContext The reproduced encryption context MUST contain a value for + * every key in the configured required encryption context keys during encryption with + * Required Encryption Context CMM. + * @throws AwsCryptoException if the master key is null. + */ + public static DecryptionHandler create( + final ICryptographicMaterialsManager materialsManager, + final CommitmentPolicy commitmentPolicy, + final SignaturePolicy signaturePolicy, + final int maxEncryptedDataKeys, + final Map reproducedEncryptionContext) + throws AwsCryptoException { + return new DecryptionHandler( + new CMMHandler(materialsManager), + commitmentPolicy, + signaturePolicy, + maxEncryptedDataKeys, + reproducedEncryptionContext); } /** @@ -272,7 +360,12 @@ public static DecryptionHandler create( final int maxEncryptedDataKeys) throws AwsCryptoException { return new DecryptionHandler( - materialsManager, headers, commitmentPolicy, signaturePolicy, maxEncryptedDataKeys); + new CMMHandler(materialsManager), + headers, + commitmentPolicy, + signaturePolicy, + maxEncryptedDataKeys, + Collections.emptyMap()); } /** @@ -300,7 +393,82 @@ public static DecryptionHandler create( final int maxEncryptedDataKeys) throws AwsCryptoException { return new DecryptionHandler( - materialsManager, headers, commitmentPolicy, signaturePolicy, maxEncryptedDataKeys); + new CMMHandler(materialsManager), + headers, + commitmentPolicy, + signaturePolicy, + maxEncryptedDataKeys, + Collections.emptyMap()); + } + + /** + * Create a decryption handler using the provided materials manager and already parsed {@code + * headers}. + * + *

Note the methods in the provided materials manager are used in decrypting the encrypted data + * key parsed from the ciphertext headers. + * + * @param materialsManager the materials manager to use in decrypting the data key from the key + * blobs encoded in the provided ciphertext. + * @param headers already parsed headers which will not be passed into {@link + * #processBytes(byte[], int, int, byte[], int)} + * @param commitmentPolicy The commitment policy to enforce during decryption + * @param signaturePolicy The signature policy to enforce during decryption + * @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during + * decryption; zero indicates no maximum + * @throws AwsCryptoException if the master key is null. + */ + public static DecryptionHandler create( + final ICryptographicMaterialsManager materialsManager, + final ParsedCiphertext headers, + final CommitmentPolicy commitmentPolicy, + final SignaturePolicy signaturePolicy, + final int maxEncryptedDataKeys) + throws AwsCryptoException { + return new DecryptionHandler( + new CMMHandler(materialsManager), + headers, + commitmentPolicy, + signaturePolicy, + maxEncryptedDataKeys, + Collections.emptyMap()); + } + + /** + * Create a decryption handler using the provided materials manager and already parsed {@code + * headers}. + * + *

Note the methods in the provided materials manager are used in decrypting the encrypted data + * key parsed from the ciphertext headers. + * + * @param materialsManager the materials manager to use in decrypting the data key from the key + * blobs encoded in the provided ciphertext. + * @param headers already parsed headers which will not be passed into {@link + * #processBytes(byte[], int, int, byte[], int)} + * @param commitmentPolicy The commitment policy to enforce during decryption + * @param signaturePolicy The signature policy to enforce during decryption + * @param maxEncryptedDataKeys The maximum number of encrypted data keys to unwrap during + * decryption; zero indicates no maximum + * @param reproducedEncryptionContext The reproduced encryption context MUST contain a value for + * every key in the configured required encryption context keys during encryption with + * Required Encryption Context CMM. + * @throws AwsCryptoException if the master key is null. + */ + public static DecryptionHandler create( + final ICryptographicMaterialsManager materialsManager, + final ParsedCiphertext headers, + final CommitmentPolicy commitmentPolicy, + final SignaturePolicy signaturePolicy, + final int maxEncryptedDataKeys, + final Map reproducedEncryptionContext) + throws AwsCryptoException { + return new DecryptionHandler( + new CMMHandler(materialsManager), + headers, + commitmentPolicy, + signaturePolicy, + maxEncryptedDataKeys, + reproducedEncryptionContext); } /** @@ -544,9 +712,13 @@ long getMaxInputLength() { * cipher. * * @param ciphertextHeaders the ciphertext headers object whose integrity needs to be checked. + * @param authOnlyEncryptionContext The authenticated only encryption context is all encryption + * context key-value pairs where the key exists in Required Encryption Context Keys. It is + * then serialized according to the message header Key Value Pairs. * @return true if the integrity of the header is intact; false otherwise. */ - private void verifyHeaderIntegrity(final CiphertextHeaders ciphertextHeaders) + private void verifyHeaderIntegrity( + final CiphertextHeaders ciphertextHeaders, byte[] authOnlyEncryptionContext) throws BadCiphertextException { final CipherHandler cipherHandler = new CipherHandler(decryptionKey_, Cipher.DECRYPT_MODE, cryptoAlgo_); @@ -555,7 +727,11 @@ private void verifyHeaderIntegrity(final CiphertextHeaders ciphertextHeaders) final byte[] headerTag = ciphertextHeaders.getHeaderTag(); cipherHandler.cipherData( ciphertextHeaders.getHeaderNonce(), - ciphertextHeaders.serializeAuthenticatedFields(), + // When verifying the header the AAD input to the authenticated encryption algorithm + // specified by the algorithm suite is the message header body and the serialized + // authenticated only encryption context. + org.bouncycastle.util.Arrays.concatenate( + ciphertextHeaders.serializeAuthenticatedFields(), authOnlyEncryptionContext), headerTag, 0, headerTag.length); @@ -609,16 +785,28 @@ private void readHeaderFields(final CiphertextHeaders ciphertextHeaders) { + "."); } - encryptionContext_ = ciphertextHeaders.getEncryptionContextMap(); - DecryptionMaterialsRequest request = DecryptionMaterialsRequest.newBuilder() .setAlgorithm(cryptoAlgo_) - .setEncryptionContext(encryptionContext_) + .setEncryptionContext(ciphertextHeaders.getEncryptionContextMap()) + .setReproducedEncryptionContext(reproducedEncryptionContext_) .setEncryptedDataKeys(ciphertextHeaders.getEncryptedKeyBlobs()) .build(); - DecryptionMaterials result = materialsManager_.decryptMaterials(request); + DecryptionMaterialsHandler result = cmmHandler_.decryptMaterials(request, commitmentPolicy_); + + encryptionContext_ = result.getEncryptionContext(); + + List reqKeys = result.getRequiredEncryptionContextKeys(); + Map reqEncryptionContext = + encryptionContext_.entrySet().stream() + .filter(x -> reqKeys.contains(x.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + // The authenticated only encryption context is all encryption context key-value pairs where the + // key exists in Required Encryption Context Keys. It is then serialized according to the + // message header Key Value Pairs. + final byte[] authOnlyEncryptionContext = + EncryptionContextSerializer.serialize(reqEncryptionContext); //noinspection unchecked dataKey_ = (DataKey) result.getDataKey(); @@ -657,7 +845,7 @@ private void readHeaderFields(final CiphertextHeaders ciphertextHeaders) { final short nonceLen = ciphertextHeaders.getNonceLength(); final int frameLen = ciphertextHeaders.getFrameLength(); - verifyHeaderIntegrity(ciphertextHeaders); + verifyHeaderIntegrity(ciphertextHeaders, authOnlyEncryptionContext); switch (contentType) { case FRAME: diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java b/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java index 4487419b..ea144af7 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java @@ -12,7 +12,7 @@ import com.amazonaws.encryptionsdk.model.CiphertextHeaders; import com.amazonaws.encryptionsdk.model.CiphertextType; import com.amazonaws.encryptionsdk.model.ContentType; -import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterialsHandler; import com.amazonaws.encryptionsdk.model.KeyBlob; import java.io.IOException; import java.security.GeneralSecurityException; @@ -24,12 +24,14 @@ import java.security.interfaces.ECPrivateKey; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.crypto.Cipher; import javax.crypto.SecretKey; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.util.Arrays; /** * This class implements the CryptoHandler interface by providing methods for the encryption of @@ -42,8 +44,9 @@ public class EncryptionHandler implements MessageCryptoHandler { private static final CiphertextType CIPHERTEXT_TYPE = CiphertextType.CUSTOMER_AUTHENTICATED_ENCRYPTED_DATA; - private final EncryptionMaterials encryptionMaterials_; - private final Map encryptionContext_; + private final EncryptionMaterialsHandler encryptionMaterials_; + private final Map storedEncryptionContext_; + private final Map reqEncryptionContext_; private final CryptoAlgorithm cryptoAlgo_; private final List masterKeys_; private final List keyBlobs_; @@ -73,13 +76,25 @@ public class EncryptionHandler implements MessageCryptoHandler { * @throws AwsCryptoException if the encryption context or master key is null. */ public EncryptionHandler( - int frameSize, EncryptionMaterials result, CommitmentPolicy commitmentPolicy) + int frameSize, EncryptionMaterialsHandler result, CommitmentPolicy commitmentPolicy) throws AwsCryptoException { Utils.assertNonNull(result, "result"); Utils.assertNonNull(commitmentPolicy, "commitmentPolicy"); this.encryptionMaterials_ = result; - this.encryptionContext_ = result.getEncryptionContext(); + + Map encryptionContext = result.getEncryptionContext(); + List reqKeys = result.getRequiredEncryptionContextKeys(); + Map> partitionedEncryptionContext = + encryptionContext.entrySet().stream() + .collect( + Collectors.partitioningBy( + entry -> reqKeys.contains(entry.getKey()), + Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + + storedEncryptionContext_ = partitionedEncryptionContext.get(false); + reqEncryptionContext_ = partitionedEncryptionContext.get(true); + if (!commitmentPolicy.algorithmAllowedForEncrypt(result.getAlgorithm())) { if (commitmentPolicy == CommitmentPolicy.ForbidEncryptAllowDecrypt) { throw new AwsCryptoException( @@ -139,10 +154,15 @@ public EncryptionHandler( // Construct the headers // Included here rather than as a sub-routine so we can set final variables. // This way we can avoid calculating the keys more times than we need. - final byte[] encryptionContextBytes = EncryptionContextSerializer.serialize(encryptionContext_); + // + // AAD: MUST be the serialization of the encryption context in the encryption materials, and + // this serialization MUST NOT contain any key value pairs listed in the encryption material's + // required encryption context keys. + final byte[] storedEncryptionContextBytes = + EncryptionContextSerializer.serialize(storedEncryptionContext_); final CiphertextHeaders unsignedHeaders = new CiphertextHeaders( - type_, cryptoAlgo_, encryptionContextBytes, keyBlobs_, contentType, frameSize); + type_, cryptoAlgo_, storedEncryptionContextBytes, keyBlobs_, contentType, frameSize); // We use a deterministic IV of zero for the header authentication. unsignedHeaders.setHeaderNonce(new byte[nonceLen_]); @@ -163,7 +183,12 @@ public EncryptionHandler( } } - ciphertextHeaders_ = signCiphertextHeaders(unsignedHeaders); + // The authenticated only encryption context is all encryption context key-value pairs where the + // key exists in Required Encryption Context Keys. It is then serialized according to the + // message header Key Value Pairs. + final byte[] reqEncryptionContextBytes = + EncryptionContextSerializer.serialize(reqEncryptionContext_); + ciphertextHeaders_ = signCiphertextHeaders(unsignedHeaders, reqEncryptionContextBytes); ciphertextHeaderBytes_ = ciphertextHeaders_.toByteArray(); byte[] messageId_ = ciphertextHeaders_.getMessageId(); @@ -350,7 +375,7 @@ public int estimateFinalOutputSize() { */ @Override public Map getEncryptionContext() { - return encryptionContext_; + return storedEncryptionContext_; } @Override @@ -397,9 +422,18 @@ private byte[] computeHeaderTag(final byte[] nonce, final byte[] aad) { return cipherHandler.cipherData(nonce, aad, new byte[0], 0, 0); } - private CiphertextHeaders signCiphertextHeaders(final CiphertextHeaders unsignedHeaders) { + private CiphertextHeaders signCiphertextHeaders( + final CiphertextHeaders unsignedHeaders, byte[] reqEncryptionContextBytes) { final byte[] headerFields = unsignedHeaders.serializeAuthenticatedFields(); - final byte[] headerTag = computeHeaderTag(unsignedHeaders.getHeaderNonce(), headerFields); + // The AAD MUST be the concatenation of the serialized message header body and the serialization + // of encryption context to only authenticate. The encryption context to only authenticate MUST + // be the encryption context in the encryption materials filtered to only contain key value + // pairs listed in the encryption material's required encryption context keys serialized + // according to the encryption context serialization specification. + final byte[] headerTag = + computeHeaderTag( + unsignedHeaders.getHeaderNonce(), + Arrays.concatenate(headerFields, reqEncryptionContextBytes)); unsignedHeaders.setHeaderTag(headerTag); diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/TrailingSignatureAlgorithm.java b/src/main/java/com/amazonaws/encryptionsdk/internal/TrailingSignatureAlgorithm.java index 875ea9a2..fba5eb62 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/TrailingSignatureAlgorithm.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/TrailingSignatureAlgorithm.java @@ -9,22 +9,26 @@ import com.amazonaws.encryptionsdk.CryptoAlgorithm; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECFieldFp; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidParameterSpecException; import java.util.Arrays; +import software.amazon.cryptography.materialproviders.model.AlgorithmSuiteInfo; /** * Provides a consistent interface across various trailing signature algorithms. @@ -32,7 +36,6 @@ *

NOTE: This is not a stable API and may undergo breaking changes in the future. */ public abstract class TrailingSignatureAlgorithm { - private TrailingSignatureAlgorithm() { /* Do not allow arbitrary subclasses */ } @@ -45,23 +48,27 @@ private TrailingSignatureAlgorithm() { public abstract PublicKey deserializePublicKey(String keyString); + public abstract PublicKey decompressPublicKey(byte[] decodedKey); + public abstract String serializePublicKey(PublicKey key); public abstract KeyPair generateKey() throws GeneralSecurityException; + public abstract PrivateKey privateKeyFromByteBuffer(ByteBuffer privateKey); + /* Standards for Efficient Cryptography over a prime field */ private static final String SEC_PRIME_FIELD_PREFIX = "secp"; private static final class ECDSASignatureAlgorithm extends TrailingSignatureAlgorithm { - private final ECGenParameterSpec ecSpec; - private final ECParameterSpec ecParameterSpec; - private final String messageDigestAlgorithm; - private final String hashAndSignAlgorithm; private static final String ELLIPTIC_CURVE_ALGORITHM = "EC"; /* Constants used by SEC-1 v2 point compression and decompression algorithms */ private static final BigInteger TWO = BigInteger.valueOf(2); private static final BigInteger THREE = BigInteger.valueOf(3); private static final BigInteger FOUR = BigInteger.valueOf(4); + private final ECGenParameterSpec ecSpec; + private final ECParameterSpec ecParameterSpec; + private final String messageDigestAlgorithm; + private final String hashAndSignAlgorithm; private ECDSASignatureAlgorithm( ECGenParameterSpec ecSpec, String messageDigestAlgorithm, String hashAndSignAlgorithm) { @@ -106,15 +113,28 @@ public String getHashAndSignAlgorithm() { /** * Decodes a compressed elliptic curve point as described in SEC-1 v2 section 2.3.4 * - * @param keyString The serialized and compressed public key + * @param keyString The serialized and compressed public key as Base64 Encoded String * @return The PublicKey * @see http://www.secg.org/sec1-v2.pdf */ @Override - public PublicKey deserializePublicKey(String keyString) { + public PublicKey deserializePublicKey(final String keyString) { notNull(keyString, "keyString is required"); - final byte[] decodedKey = Utils.decodeBase64String(keyString); + return decompressPublicKey(decodedKey); + } + + /** + * Decodes a compressed elliptic curve point as described in SEC-1 v2 section 2.3.4 + * + * @param decodedKey Byte Array of compressed elliptic curve point + * @return The PublicKey + * @see http://www.secg.org/sec1-v2.pdf + */ + @Override + public PublicKey decompressPublicKey(final byte[] decodedKey) { + notNull(decodedKey, "decodedKey is required"); + final BigInteger x = new BigInteger(1, Arrays.copyOfRange(decodedKey, 1, decodedKey.length)); final byte compressedY = decodedKey[0]; @@ -165,7 +185,7 @@ public PublicKey deserializePublicKey(String keyString) { * @see http://www.secg.org/sec1-v2.pdf */ @Override - public String serializePublicKey(PublicKey key) { + public String serializePublicKey(final PublicKey key) { notNull(key, "key is required"); isInstanceOf(ECPublicKey.class, key, "key must be an instance of ECPublicKey"); @@ -184,11 +204,24 @@ public String serializePublicKey(PublicKey key) { return encodeBase64String(compressedKey); } + @Override + public PrivateKey privateKeyFromByteBuffer(final ByteBuffer privateKey) { + notNull(privateKey, "privateKey is required"); + + BigInteger privateKeyValue = new BigInteger(1, privateKey.array()); + ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateKeyValue, ecParameterSpec); + try { + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return keyFactory.generatePrivate(privateKeySpec); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new IllegalStateException("Invalid algorithm", e); + } + } + @Override public KeyPair generateKey() throws GeneralSecurityException { KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ELLIPTIC_CURVE_ALGORITHM); keyGen.initialize(ecSpec, Utils.getSecureRandom()); - return keyGen.generateKeyPair(); } } @@ -196,6 +229,7 @@ public KeyPair generateKey() throws GeneralSecurityException { private static final ECDSASignatureAlgorithm SHA256_ECDSA_P256 = new ECDSASignatureAlgorithm( new ECGenParameterSpec(SEC_PRIME_FIELD_PREFIX + "256r1"), "SHA-256", "SHA256withECDSA"); + private static final ECDSASignatureAlgorithm SHA384_ECDSA_P384 = new ECDSASignatureAlgorithm( new ECGenParameterSpec(SEC_PRIME_FIELD_PREFIX + "384r1"), "SHA-384", "SHA384withECDSA"); @@ -212,4 +246,10 @@ public static TrailingSignatureAlgorithm forCryptoAlgorithm(CryptoAlgorithm algo throw new IllegalStateException("Algorithm does not support trailing signature"); } } + + public static TrailingSignatureAlgorithm forCryptoAlgorithm( + AlgorithmSuiteInfo algorithmSuiteInfo) { + notNull(algorithmSuiteInfo, "algorithmSuiteInfo is required"); + return forCryptoAlgorithm(CryptoAlgorithm.valueOf(algorithmSuiteInfo.id().ESDK().name())); + } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java index 4f137d3f..1394b0c9 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java @@ -2,14 +2,17 @@ import com.amazonaws.encryptionsdk.DataKey; import java.security.PublicKey; +import java.util.Map; public final class DecryptionMaterials { private final DataKey dataKey; private final PublicKey trailingSignatureKey; + private final Map encryptionContext; private DecryptionMaterials(Builder b) { dataKey = b.getDataKey(); trailingSignatureKey = b.getTrailingSignatureKey(); + encryptionContext = b.getEncryptionContext(); } public DataKey getDataKey() { @@ -20,6 +23,10 @@ public PublicKey getTrailingSignatureKey() { return trailingSignatureKey; } + public Map getEncryptionContext() { + return encryptionContext; + } + public static Builder newBuilder() { return new Builder(); } @@ -31,10 +38,12 @@ public Builder toBuilder() { public static final class Builder { private DataKey dataKey; private PublicKey trailingSignatureKey; + private Map encryptionContext; private Builder(DecryptionMaterials result) { this.dataKey = result.getDataKey(); this.trailingSignatureKey = result.getTrailingSignatureKey(); + this.encryptionContext = result.getEncryptionContext(); } private Builder() {} @@ -57,6 +66,15 @@ public Builder setTrailingSignatureKey(PublicKey trailingSignatureKey) { return this; } + public Map getEncryptionContext() { + return encryptionContext; + } + + public Builder setEncryptionContext(Map encryptionContext) { + this.encryptionContext = encryptionContext; + return this; + } + public DecryptionMaterials build() { return new DecryptionMaterials(this); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsHandler.java b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsHandler.java new file mode 100644 index 00000000..87984c13 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsHandler.java @@ -0,0 +1,76 @@ +package com.amazonaws.encryptionsdk.model; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.internal.TrailingSignatureAlgorithm; +import java.security.PublicKey; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +/** + * Handler to abstract the differences between the original {@link DecryptionMaterials} and the + * MPL's {@link software.amazon.cryptography.materialproviders.model.DecryptionMaterials}. + */ +public class DecryptionMaterialsHandler { + private DecryptionMaterials materials; + private software.amazon.cryptography.materialproviders.model.DecryptionMaterials mplMaterials; + + public DecryptionMaterialsHandler(DecryptionMaterials materials) { + this.materials = materials; + this.mplMaterials = null; + } + + public DecryptionMaterialsHandler( + software.amazon.cryptography.materialproviders.model.DecryptionMaterials mplMaterials) { + this.mplMaterials = mplMaterials; + this.materials = null; + } + + public DataKey getDataKey() { + if (materials != null) { + return materials.getDataKey(); + } else { + byte[] cacheDataKey = mplMaterials.plaintextDataKey().array(); + SecretKey key = + new SecretKeySpec( + cacheDataKey, + 0, + cacheDataKey.length, + CryptoAlgorithm.valueOf(mplMaterials.algorithmSuite().id().ESDK().name()) + .getDataKeyAlgo()); + return new DataKey<>(key, new byte[0], new byte[0], null); + } + } + + public PublicKey getTrailingSignatureKey() { + if (materials != null) { + return materials.getTrailingSignatureKey(); + } else { + if (mplMaterials.verificationKey() == null) { + return null; + } + // Converts ByteBuffer to ECPublicKey using the AlgorithmSuiteInfo + return TrailingSignatureAlgorithm.forCryptoAlgorithm(mplMaterials.algorithmSuite()) + .decompressPublicKey(mplMaterials.verificationKey().array()); + } + } + + public Map getEncryptionContext() { + if (materials != null) { + return materials.getEncryptionContext(); + } else { + return mplMaterials.encryptionContext(); + } + } + + public List getRequiredEncryptionContextKeys() { + if (materials != null) { + return Collections.emptyList(); + } else { + return mplMaterials.requiredEncryptionContextKeys(); + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsRequest.java b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsRequest.java index 102a76e4..e9d6c213 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsRequest.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsRequest.java @@ -10,11 +10,13 @@ public final class DecryptionMaterialsRequest { private final CryptoAlgorithm algorithm; private final Map encryptionContext; + private final Map reproducedEncryptionContext; private final List encryptedDataKeys; private DecryptionMaterialsRequest(Builder b) { this.algorithm = b.getAlgorithm(); this.encryptionContext = b.getEncryptionContext(); + this.reproducedEncryptionContext = b.getReproducedEncryptionContext(); this.encryptedDataKeys = b.getEncryptedDataKeys(); } @@ -26,6 +28,10 @@ public Map getEncryptionContext() { return encryptionContext; } + public Map getReproducedEncryptionContext() { + return reproducedEncryptionContext; + } + public List getEncryptedDataKeys() { return encryptedDataKeys; } @@ -49,11 +55,13 @@ public static DecryptionMaterialsRequest fromCiphertextHeaders(CiphertextHeaders public static final class Builder { private CryptoAlgorithm algorithm; private Map encryptionContext; + private Map reproducedEncryptionContext; private List encryptedDataKeys; private Builder(DecryptionMaterialsRequest request) { this.algorithm = request.getAlgorithm(); this.encryptionContext = request.getEncryptionContext(); + this.reproducedEncryptionContext = request.getReproducedEncryptionContext(); this.encryptedDataKeys = request.getEncryptedDataKeys(); } @@ -81,6 +89,16 @@ public Builder setEncryptionContext(Map encryptionContext) { return this; } + public Map getReproducedEncryptionContext() { + return reproducedEncryptionContext; + } + + public Builder setReproducedEncryptionContext(Map reproducedEncryptionContext) { + this.reproducedEncryptionContext = + Collections.unmodifiableMap(new HashMap<>(reproducedEncryptionContext)); + return this; + } + public List getEncryptedDataKeys() { return encryptedDataKeys; } diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java index 2d015648..bffc24bd 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java @@ -84,6 +84,7 @@ public PrivateKey getTrailingSignatureKey() { } /** Contains a list of all MasterKeys that could decrypt this message. */ + @Deprecated public List getMasterKeys() { return masterKeys; } @@ -180,6 +181,7 @@ public Builder setTrailingSignatureKey(PrivateKey trailingSignatureKey) { return this; } + @Deprecated public List getMasterKeys() { return masterKeys; } diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsHandler.java b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsHandler.java new file mode 100644 index 00000000..9633a7a9 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsHandler.java @@ -0,0 +1,105 @@ +package com.amazonaws.encryptionsdk.model; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.internal.TrailingSignatureAlgorithm; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import software.amazon.cryptography.materialproviders.model.EncryptedDataKey; + +/** + * Handler to abstract the differences between the original {@link EncryptionMaterials} and the + * MPL's {@link software.amazon.cryptography.materialproviders.model.EncryptionMaterials}. + */ +public class EncryptionMaterialsHandler { + EncryptionMaterials materials; + software.amazon.cryptography.materialproviders.model.EncryptionMaterials mplMaterials; + + public EncryptionMaterialsHandler(EncryptionMaterials materials) { + this.materials = materials; + } + + public EncryptionMaterialsHandler( + software.amazon.cryptography.materialproviders.model.EncryptionMaterials mplMaterials) { + this.mplMaterials = mplMaterials; + } + + public CryptoAlgorithm getAlgorithm() { + if (materials != null) { + return materials.getAlgorithm(); + } else { + return CryptoAlgorithm.valueOf(mplMaterials.algorithmSuite().id().ESDK().name()); + } + } + + public Map getEncryptionContext() { + if (materials != null) { + return materials.getEncryptionContext(); + } else { + return mplMaterials.encryptionContext(); + } + } + + public List getEncryptedDataKeys() { + if (materials != null) { + return materials.getEncryptedDataKeys(); + } else { + List edks = mplMaterials.encryptedDataKeys(); + List keyBlobs = new ArrayList<>(edks.size()); + for (EncryptedDataKey edk : edks) { + keyBlobs.add( + new KeyBlob( + edk.keyProviderId(), edk.keyProviderInfo().array(), edk.ciphertext().array())); + } + return keyBlobs; + } + } + + public SecretKey getCleartextDataKey() { + if (materials != null) { + return materials.getCleartextDataKey(); + } else { + byte[] cacheDataKey = mplMaterials.plaintextDataKey().array(); + CryptoAlgorithm cryptoAlgorithm = + CryptoAlgorithm.valueOf(mplMaterials.algorithmSuite().id().ESDK().name()); + return new SecretKeySpec( + cacheDataKey, 0, cacheDataKey.length, cryptoAlgorithm.getDataKeyAlgo()); + } + } + + public PrivateKey getTrailingSignatureKey() { + if (materials != null) { + return materials.getTrailingSignatureKey(); + } else { + if (mplMaterials.signingKey() == null) { + return null; + } + // Converts ByteBuffer to ECPrivateKey using the AlgorithmSuiteInfo + return TrailingSignatureAlgorithm.forCryptoAlgorithm(mplMaterials.algorithmSuite()) + .privateKeyFromByteBuffer(mplMaterials.signingKey()); + } + } + + public List getRequiredEncryptionContextKeys() { + if (materials != null) { + return Collections.emptyList(); + } else { + return mplMaterials.requiredEncryptionContextKeys(); + } + } + + @Deprecated + public List getMasterKeys() { + if (materials != null) { + return materials.getMasterKeys(); + } else { + // Return Empty List + return Collections.emptyList(); + } + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/AwsKmsHierarchicalKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/AwsKmsHierarchicalKeyringExampleTest.java new file mode 100644 index 00000000..be0aff67 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/AwsKmsHierarchicalKeyringExampleTest.java @@ -0,0 +1,26 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.crypto.examples.keyrings.hierarchical.AwsKmsHierarchicalKeyringExample; +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class AwsKmsHierarchicalKeyringExampleTest { + @Test + public void testEncryptAndDecrypt() { + AwsKmsHierarchicalKeyringExample.encryptAndDecryptWithKeyring( + KMSTestFixtures.TEST_KEYSTORE_NAME, + KMSTestFixtures.TEST_LOGICAL_KEYSTORE_NAME, + KMSTestFixtures.TEST_KEYSTORE_KMS_KEY_ID); + } + + @Test + public void testEncryptAndDecryptWithKeyringThreadSafe() { + AwsKmsHierarchicalKeyringExample.encryptAndDecryptWithKeyringThreadSafe( + KMSTestFixtures.TEST_KEYSTORE_NAME, + KMSTestFixtures.TEST_LOGICAL_KEYSTORE_NAME, + KMSTestFixtures.TEST_KEYSTORE_KMS_KEY_ID); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/AwsKmsRsaKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/AwsKmsRsaKeyringExampleTest.java new file mode 100644 index 00000000..22e687c2 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/AwsKmsRsaKeyringExampleTest.java @@ -0,0 +1,15 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class AwsKmsRsaKeyringExampleTest { + + @Test + public void testEncryptAndDecrypt() { + AwsKmsRsaKeyringExample.encryptAndDecryptWithKeyring(KMSTestFixtures.US_WEST_2_KMS_RSA_KEY_ID); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/BasicEncryptionKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/BasicEncryptionKeyringExampleTest.java new file mode 100644 index 00000000..5f6bfbdf --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/BasicEncryptionKeyringExampleTest.java @@ -0,0 +1,15 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class BasicEncryptionKeyringExampleTest { + + @Test + public void testEncryptAndDecrypt() { + BasicEncryptionKeyringExample.encryptAndDecryptWithKeyring(KMSTestFixtures.TEST_KEY_IDS[0]); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/DiscoveryDecryptionKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/DiscoveryDecryptionKeyringExampleTest.java new file mode 100644 index 00000000..260e2027 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/DiscoveryDecryptionKeyringExampleTest.java @@ -0,0 +1,19 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class DiscoveryDecryptionKeyringExampleTest { + + @Test + public void testEncryptAndDecrypt() { + DiscoveryDecryptionKeyringExample.encryptAndDecryptWithKeyring( + KMSTestFixtures.TEST_KEY_IDS[0], + KMSTestFixtures.PARTITION, + KMSTestFixtures.ACCOUNT_ID, + KMSTestFixtures.US_WEST_2); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/DiscoveryMultiRegionDecryptionKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/DiscoveryMultiRegionDecryptionKeyringExampleTest.java new file mode 100644 index 00000000..35eb46c5 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/DiscoveryMultiRegionDecryptionKeyringExampleTest.java @@ -0,0 +1,19 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class DiscoveryMultiRegionDecryptionKeyringExampleTest { + + @Test + public void testEncryptAndDecrypt() { + DiscoveryMultiRegionDecryptionKeyringExample.encryptAndDecryptWithKeyring( + KMSTestFixtures.US_EAST_1_MULTI_REGION_KEY_ID, + KMSTestFixtures.PARTITION, + KMSTestFixtures.ACCOUNT_ID, + KMSTestFixtures.US_WEST_2); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/MultiKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/MultiKeyringExampleTest.java new file mode 100644 index 00000000..5d1090f6 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/MultiKeyringExampleTest.java @@ -0,0 +1,15 @@ +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import java.nio.ByteBuffer; +import org.junit.Test; + +public class MultiKeyringExampleTest { + @Test + public void testEncryptAndDecrypt() { + // Generate a new AES key + ByteBuffer aesKeyBytes = MultiKeyringExample.generateAesKeyBytes(); + + MultiKeyringExample.encryptAndDecryptWithKeyring(KMSTestFixtures.TEST_KEY_IDS[0], aesKeyBytes); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/MultipleCmkEncryptKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/MultipleCmkEncryptKeyringExampleTest.java new file mode 100644 index 00000000..4c5e70e8 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/MultipleCmkEncryptKeyringExampleTest.java @@ -0,0 +1,16 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class MultipleCmkEncryptKeyringExampleTest { + + @Test + public void testEncryptAndDecrypt() { + MultipleCmkEncryptKeyringExample.encryptAndDecryptWithKeyring( + KMSTestFixtures.TEST_KEY_IDS[0], KMSTestFixtures.TEST_KEY_IDS[1]); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/RawAesKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/RawAesKeyringExampleTest.java new file mode 100644 index 00000000..7542e3b9 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/RawAesKeyringExampleTest.java @@ -0,0 +1,17 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import java.nio.ByteBuffer; +import org.junit.Test; + +public class RawAesKeyringExampleTest { + @Test + public void testRawAesKeyringExample() { + // Generate a new AES key + ByteBuffer aesKeyBytes = RawAesKeyringExample.generateAesKeyBytes(); + + RawAesKeyringExample.encryptAndDecryptWithKeyring(aesKeyBytes); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/RawRsaKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/RawRsaKeyringExampleTest.java new file mode 100644 index 00000000..60791325 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/RawRsaKeyringExampleTest.java @@ -0,0 +1,19 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import java.nio.ByteBuffer; +import java.security.KeyPair; +import org.junit.Test; + +public class RawRsaKeyringExampleTest { + @Test + public void testRawAesKeyringExample() { + KeyPair keyPair = RawRsaKeyringExample.generateKeyPair(); + ByteBuffer publicKeyBytes = RawRsaKeyringExample.getPEMPublicKey(keyPair.getPublic()); + ByteBuffer privateKeyBytes = RawRsaKeyringExample.getPEMPrivateKey(keyPair.getPrivate()); + + RawRsaKeyringExample.encryptAndDecryptWithKeyring(publicKeyBytes, privateKeyBytes); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/RequiredEncryptionContextCMMExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/RequiredEncryptionContextCMMExampleTest.java new file mode 100644 index 00000000..a0e434a8 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/RequiredEncryptionContextCMMExampleTest.java @@ -0,0 +1,16 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class RequiredEncryptionContextCMMExampleTest { + + @Test + public void testEncryptAndDecrypt() { + RequiredEncryptionContextCMMExample.encryptAndDecryptWithKeyring( + KMSTestFixtures.US_WEST_2_KEY_ID); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/SetCommitmentPolicyKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/SetCommitmentPolicyKeyringExampleTest.java new file mode 100644 index 00000000..e7a6b8af --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/SetCommitmentPolicyKeyringExampleTest.java @@ -0,0 +1,16 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class SetCommitmentPolicyKeyringExampleTest { + + @Test + public void testEncryptAndDecryptKeyrings() { + SetCommitmentPolicyKeyringExample.encryptAndDecryptWithKeyrings( + KMSTestFixtures.TEST_KEY_IDS[0]); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/SetEncryptionAlgorithmKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/SetEncryptionAlgorithmKeyringExampleTest.java new file mode 100644 index 00000000..f7c4632b --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/SetEncryptionAlgorithmKeyringExampleTest.java @@ -0,0 +1,16 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class SetEncryptionAlgorithmKeyringExampleTest { + + @Test + public void testEncryptAndDecrypt() { + SetEncryptionAlgorithmKeyringExample.encryptAndDecryptWithKeyring( + KMSTestFixtures.TEST_KEY_IDS[0]); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/keyrings/StreamingWithRequiredEncryptionContextCMMExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/keyrings/StreamingWithRequiredEncryptionContextCMMExampleTest.java new file mode 100644 index 00000000..4225114f --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/keyrings/StreamingWithRequiredEncryptionContextCMMExampleTest.java @@ -0,0 +1,47 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyrings; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.SecureRandom; +import org.junit.Test; + +public class StreamingWithRequiredEncryptionContextCMMExampleTest { + + @Test + public void testEncryptAndDecrypt() throws IOException { + // Create a temporary file for testing the example + final String srcFile = "RandomFile.txt"; + final File file = new File(srcFile); + file.createNewFile(); + String randomMessage = generateRandomMessage(1024); + + writeToFile(srcFile, randomMessage); + + StreamingWithRequiredEncryptionContextCMMExample.encryptAndDecryptWithKeyring( + srcFile, KMSTestFixtures.US_WEST_2_KEY_ID); + } + + private static String generateRandomMessage(int length) { + String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + StringBuilder randomMessage = new StringBuilder(); + + SecureRandom random = new SecureRandom(); + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(characters.length()); + randomMessage.append(characters.charAt(randomIndex)); + } + + return randomMessage.toString(); + } + + private static void writeToFile(String srcFile, String content) throws IOException { + Files.write(Paths.get(srcFile), content.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/BasicEncryptionExampleTest.java similarity index 89% rename from src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java rename to src/test/java/com/amazonaws/crypto/examples/v2/BasicEncryptionExampleTest.java index 20f7c79e..137e1c7f 100644 --- a/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/v2/BasicEncryptionExampleTest.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; import org.junit.Test; diff --git a/src/test/java/com/amazonaws/crypto/examples/BasicMultiRegionKeyEncryptionExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/BasicMultiRegionKeyEncryptionExampleTest.java similarity index 91% rename from src/test/java/com/amazonaws/crypto/examples/BasicMultiRegionKeyEncryptionExampleTest.java rename to src/test/java/com/amazonaws/crypto/examples/v2/BasicMultiRegionKeyEncryptionExampleTest.java index 634b2c47..4945a94c 100644 --- a/src/test/java/com/amazonaws/crypto/examples/BasicMultiRegionKeyEncryptionExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/v2/BasicMultiRegionKeyEncryptionExampleTest.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; import org.junit.Test; diff --git a/src/test/java/com/amazonaws/crypto/examples/DiscoveryDecryptionExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/DiscoveryDecryptionExampleTest.java similarity index 91% rename from src/test/java/com/amazonaws/crypto/examples/DiscoveryDecryptionExampleTest.java rename to src/test/java/com/amazonaws/crypto/examples/v2/DiscoveryDecryptionExampleTest.java index 1df65105..8a677fb4 100644 --- a/src/test/java/com/amazonaws/crypto/examples/DiscoveryDecryptionExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/v2/DiscoveryDecryptionExampleTest.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; import org.junit.Test; diff --git a/src/test/java/com/amazonaws/crypto/examples/DiscoveryMultiRegionDecryptionExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/DiscoveryMultiRegionDecryptionExampleTest.java similarity index 93% rename from src/test/java/com/amazonaws/crypto/examples/DiscoveryMultiRegionDecryptionExampleTest.java rename to src/test/java/com/amazonaws/crypto/examples/v2/DiscoveryMultiRegionDecryptionExampleTest.java index 77769a2e..2f54bf42 100644 --- a/src/test/java/com/amazonaws/crypto/examples/DiscoveryMultiRegionDecryptionExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/v2/DiscoveryMultiRegionDecryptionExampleTest.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; import org.junit.Test; diff --git a/src/test/java/com/amazonaws/crypto/examples/MultipleCmkEncryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/MultipleCmkEncryptExampleTest.java similarity index 90% rename from src/test/java/com/amazonaws/crypto/examples/MultipleCmkEncryptExampleTest.java rename to src/test/java/com/amazonaws/crypto/examples/v2/MultipleCmkEncryptExampleTest.java index 6ff8ee97..3f0dd834 100644 --- a/src/test/java/com/amazonaws/crypto/examples/MultipleCmkEncryptExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/v2/MultipleCmkEncryptExampleTest.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; import org.junit.Test; diff --git a/src/test/java/com/amazonaws/crypto/examples/RestrictRegionExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/RestrictRegionExampleTest.java similarity index 92% rename from src/test/java/com/amazonaws/crypto/examples/RestrictRegionExampleTest.java rename to src/test/java/com/amazonaws/crypto/examples/v2/RestrictRegionExampleTest.java index 6613b8ea..019d9b71 100644 --- a/src/test/java/com/amazonaws/crypto/examples/RestrictRegionExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/v2/RestrictRegionExampleTest.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; import org.junit.Test; diff --git a/src/test/java/com/amazonaws/crypto/examples/SetCommitmentPolicyExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/SetCommitmentPolicyExampleTest.java similarity index 89% rename from src/test/java/com/amazonaws/crypto/examples/SetCommitmentPolicyExampleTest.java rename to src/test/java/com/amazonaws/crypto/examples/v2/SetCommitmentPolicyExampleTest.java index 60df0462..f13b9fbd 100644 --- a/src/test/java/com/amazonaws/crypto/examples/SetCommitmentPolicyExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/v2/SetCommitmentPolicyExampleTest.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; import org.junit.Test; diff --git a/src/test/java/com/amazonaws/crypto/examples/SetEncryptionAlgorithmExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/SetEncryptionAlgorithmExampleTest.java similarity index 90% rename from src/test/java/com/amazonaws/crypto/examples/SetEncryptionAlgorithmExampleTest.java rename to src/test/java/com/amazonaws/crypto/examples/v2/SetEncryptionAlgorithmExampleTest.java index fe88e583..7b57fb40 100644 --- a/src/test/java/com/amazonaws/crypto/examples/SetEncryptionAlgorithmExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/v2/SetEncryptionAlgorithmExampleTest.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; import org.junit.Test; diff --git a/src/test/java/com/amazonaws/crypto/examples/SimpleDataKeyCachingExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/v2/SimpleDataKeyCachingExampleTest.java similarity index 96% rename from src/test/java/com/amazonaws/crypto/examples/SimpleDataKeyCachingExampleTest.java rename to src/test/java/com/amazonaws/crypto/examples/v2/SimpleDataKeyCachingExampleTest.java index 9bd2f0da..af026cdb 100644 --- a/src/test/java/com/amazonaws/crypto/examples/SimpleDataKeyCachingExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/v2/SimpleDataKeyCachingExampleTest.java @@ -1,7 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples; +package com.amazonaws.crypto.examples.v2; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java b/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java index e72a0b2e..9367233c 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java +++ b/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java @@ -3,21 +3,41 @@ package com.amazonaws.encryptionsdk; -import com.amazonaws.crypto.examples.BasicEncryptionExampleTest; -import com.amazonaws.crypto.examples.BasicMultiRegionKeyEncryptionExampleTest; -import com.amazonaws.crypto.examples.DiscoveryDecryptionExampleTest; -import com.amazonaws.crypto.examples.DiscoveryMultiRegionDecryptionExampleTest; -import com.amazonaws.crypto.examples.MultipleCmkEncryptExampleTest; -import com.amazonaws.crypto.examples.RestrictRegionExampleTest; -import com.amazonaws.crypto.examples.SetCommitmentPolicyExampleTest; -import com.amazonaws.crypto.examples.SetEncryptionAlgorithmExampleTest; -import com.amazonaws.crypto.examples.SimpleDataKeyCachingExampleTest; +import com.amazonaws.crypto.examples.keyrings.AwsKmsHierarchicalKeyringExampleTest; +import com.amazonaws.crypto.examples.keyrings.AwsKmsRsaKeyringExampleTest; +import com.amazonaws.crypto.examples.keyrings.BasicEncryptionKeyringExampleTest; +import com.amazonaws.crypto.examples.keyrings.DiscoveryDecryptionKeyringExampleTest; +import com.amazonaws.crypto.examples.keyrings.MultiKeyringExampleTest; +import com.amazonaws.crypto.examples.keyrings.RawAesKeyringExampleTest; +import com.amazonaws.crypto.examples.keyrings.RawRsaKeyringExampleTest; +import com.amazonaws.crypto.examples.keyrings.SetEncryptionAlgorithmKeyringExampleTest; +import com.amazonaws.crypto.examples.v2.BasicEncryptionExampleTest; +import com.amazonaws.crypto.examples.v2.BasicMultiRegionKeyEncryptionExampleTest; +import com.amazonaws.crypto.examples.v2.DiscoveryDecryptionExampleTest; +import com.amazonaws.crypto.examples.v2.DiscoveryMultiRegionDecryptionExampleTest; +import com.amazonaws.crypto.examples.v2.MultipleCmkEncryptExampleTest; +import com.amazonaws.crypto.examples.v2.RestrictRegionExampleTest; +import com.amazonaws.crypto.examples.v2.SetCommitmentPolicyExampleTest; +import com.amazonaws.crypto.examples.v2.SetEncryptionAlgorithmExampleTest; +import com.amazonaws.crypto.examples.v2.SimpleDataKeyCachingExampleTest; import com.amazonaws.encryptionsdk.caching.CacheIdentifierTests; import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManagerTest; import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCacheTest; import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCacheThreadStormTest; import com.amazonaws.encryptionsdk.caching.NullCryptoMaterialsCacheTest; -import com.amazonaws.encryptionsdk.internal.*; +import com.amazonaws.encryptionsdk.internal.AwsKmsCmkArnInfoTest; +import com.amazonaws.encryptionsdk.internal.BlockDecryptionHandlerTest; +import com.amazonaws.encryptionsdk.internal.BlockEncryptionHandlerTest; +import com.amazonaws.encryptionsdk.internal.CipherHandlerTest; +import com.amazonaws.encryptionsdk.internal.CommittedKeyTest; +import com.amazonaws.encryptionsdk.internal.DecryptionHandlerTest; +import com.amazonaws.encryptionsdk.internal.EncContextSerializerTest; +import com.amazonaws.encryptionsdk.internal.EncryptionHandlerTest; +import com.amazonaws.encryptionsdk.internal.FrameDecryptionHandlerTest; +import com.amazonaws.encryptionsdk.internal.FrameEncryptionHandlerTest; +import com.amazonaws.encryptionsdk.internal.PrimitivesParserTest; +import com.amazonaws.encryptionsdk.internal.UtilsTest; +import com.amazonaws.encryptionsdk.internal.VersionInfoTest; import com.amazonaws.encryptionsdk.jce.JceMasterKeyTest; import com.amazonaws.encryptionsdk.jce.KeyStoreProviderTest; import com.amazonaws.encryptionsdk.kms.AwsKmsMrkAwareMasterKeyProviderTest; @@ -59,6 +79,7 @@ CryptoInputStreamTest.class, CryptoOutputStreamTest.class, TestVectorRunner.class, + TestVectorGenerator.class, XCompatDecryptTest.class, DefaultCryptoMaterialsManagerTest.class, NullCryptoMaterialsCacheTest.class, @@ -86,7 +107,19 @@ SimpleDataKeyCachingExampleTest.class, SetEncryptionAlgorithmExampleTest.class, SetCommitmentPolicyExampleTest.class, + BasicEncryptionKeyringExampleTest.class, + DiscoveryDecryptionKeyringExampleTest.class, + MultiKeyringExampleTest.class, + MultipleCmkEncryptExampleTest.class, + RawAesKeyringExampleTest.class, + RawRsaKeyringExampleTest.class, + AwsKmsRsaKeyringExampleTest.class, + DiscoveryDecryptionKeyringExampleTest.class, + AwsKmsHierarchicalKeyringExampleTest.class, + SetCommitmentPolicyExampleTest.class, + SetEncryptionAlgorithmKeyringExampleTest.class, ParsedCiphertextTest.class, + AwsCryptoIntegrationTest.class, AwsKmsMrkAwareMasterKeyProviderTest.class, AwsKmsMrkAwareMasterKeyTest.class, VersionInfoTest.class, diff --git a/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoIntegrationTest.java b/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoIntegrationTest.java new file mode 100644 index 00000000..e414c50b --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoIntegrationTest.java @@ -0,0 +1,613 @@ +package com.amazonaws.encryptionsdk; + +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKey; +import com.amazonaws.encryptionsdk.kmssdkv2.KmsMasterKeyProvider; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; +import org.junit.Before; +import org.junit.Test; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.AesWrappingAlg; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateRawRsaKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.materialproviders.model.PaddingScheme; + +public class AwsCryptoIntegrationTest { + + private static SecretKey AES_KEY; + private static KeyPair RSA_KEY_PAIR; + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + @Before + public void setUp() throws NoSuchAlgorithmException { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); + AES_KEY = keyGen.generateKey(); + + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + RSA_KEY_PAIR = keyPairGen.generateKeyPair(); + } + + @Test + public void AwsKmsEncryptDecryptMasterKey() { + + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + KmsMasterKeyProvider keyProvider = + KmsMasterKeyProvider.builder().buildStrict(KMSTestFixtures.TEST_KEY_IDS[0]); + + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(keyProvider, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData(keyProvider, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void AwsKmsEncryptDecryptKeyring() { + // Instantiate the SDK + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + + // Create a AwsKmsKeyring + CreateAwsKmsKeyringInput nativeValue = + CreateAwsKmsKeyringInput.builder() + .kmsKeyId(KMSTestFixtures.TEST_KEY_IDS[0]) + .kmsClient(KmsClient.create()) + .build(); + IKeyring kmsKeyring = materialProviders.CreateAwsKmsKeyring(nativeValue); + + // Create an encryption context + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(kmsKeyring, EXAMPLE_DATA, encryptionContext); + + List masterKeys = encryptResult.getMasterKeys(); + // Assert CryptoResult returns empty list if keyrings are used. + assert masterKeys.size() == 0; + + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = crypto.decryptData(kmsKeyring, ciphertext); + assert masterKeys.size() == 0; + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void AwsKmsEncryptMasterKeyDecryptKeyring() { + // Instantiate the SDK + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + + KmsMasterKeyProvider keyProvider = + KmsMasterKeyProvider.builder().buildStrict(KMSTestFixtures.TEST_KEY_IDS[0]); + + CreateAwsKmsKeyringInput nativeValue = + CreateAwsKmsKeyringInput.builder() + .kmsKeyId(KMSTestFixtures.TEST_KEY_IDS[0]) + .kmsClient(KmsClient.create()) + .build(); + IKeyring kmsKeyring = materialProviders.CreateAwsKmsKeyring(nativeValue); + + // Create an encryption context + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(keyProvider, EXAMPLE_DATA, encryptionContext); + + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = crypto.decryptData(kmsKeyring, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void AwsKmsEncryptKeyringDecryptMasterKey() { + // Instantiate the SDK + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + + KmsMasterKeyProvider keyProvider = + KmsMasterKeyProvider.builder().buildStrict(KMSTestFixtures.TEST_KEY_IDS[0]); + + CreateAwsKmsKeyringInput nativeValue = + CreateAwsKmsKeyringInput.builder() + .kmsKeyId(KMSTestFixtures.TEST_KEY_IDS[0]) + .kmsClient(KmsClient.create()) + .build(); + IKeyring kmsKeyring = materialProviders.CreateAwsKmsKeyring(nativeValue); + + // Create an encryption context + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(kmsKeyring, EXAMPLE_DATA, encryptionContext); + + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData(keyProvider, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void RawAesEncryptDecryptMasterKey() { + + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + final JceMasterKey masterKey = + JceMasterKey.getInstance( + AES_KEY, "aws-raw-vectors-persistant", "aes-key", "AES/GCM/NoPadding"); + + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(masterKey, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData(masterKey, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void RawAesEncryptMasterKeyDecryptKeyring() { + // AWS Encryption SDK Client + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + + // Keyring + IKeyring keyring = + materialProviders.CreateRawAesKeyring( + CreateRawAesKeyringInput.builder() + .keyName("aes-key") + .keyNamespace("aws-raw-vectors-persistant") + .wrappingAlg(AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16) + .wrappingKey(ByteBuffer.wrap(AES_KEY.getEncoded())) + .build()); + + // MasterKey + final JceMasterKey masterKey = + JceMasterKey.getInstance( + AES_KEY, "aws-raw-vectors-persistant", "aes-key", "AES/GCM/NoPadding"); + + // Encryption Context + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(masterKey, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = crypto.decryptData(keyring, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void RawAesEncryptKeyringKeyDecryptMasterKey() { + // AWS Encryption SDK Client + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + + // Keyring + IKeyring keyring = + materialProviders.CreateRawAesKeyring( + CreateRawAesKeyringInput.builder() + .keyName("aes-key") + .keyNamespace("aws-raw-vectors-persistant") + .wrappingAlg(AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16) + .wrappingKey(ByteBuffer.wrap(AES_KEY.getEncoded())) + .build()); + + // MasterKey + final JceMasterKey masterKey = + JceMasterKey.getInstance( + AES_KEY, "aws-raw-vectors-persistant", "aes-key", "AES/GCM/NoPadding"); + + // Encryption Context + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(keyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData(masterKey, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void RawAesEncryptDecryptKeyring() { + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + + IKeyring keyring = + materialProviders.CreateRawAesKeyring( + CreateRawAesKeyringInput.builder() + .keyName("aes-key") + .keyNamespace("aws-raw-vectors-persistant") + .wrappingAlg(AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16) + .wrappingKey(ByteBuffer.wrap(AES_KEY.getEncoded())) + .build()); + + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(keyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = crypto.decryptData(keyring, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void RawRsaEncryptDecryptMasterKey() throws Exception { + // AWS Encryption SDK Client + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // MasterKey + final JceMasterKey masterKey = + JceMasterKey.getInstance( + RSA_KEY_PAIR.getPublic(), + RSA_KEY_PAIR.getPrivate(), + "rsa-keyring", + "rsa-key", + "RSA/ECB/PKCS1Padding"); + // Encryption Context + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(masterKey, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData(masterKey, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void RawRsaEncryptMasterKeyDecryptKeyring() throws Exception { + // AWS Encryption SDK Client + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + + // Keyring + IKeyring keyring = + materialProviders.CreateRawRsaKeyring( + CreateRawRsaKeyringInput.builder() + .keyName("rsa-key") + .keyNamespace("rsa-keyring") + .paddingScheme(PaddingScheme.PKCS1) + .privateKey(getPEMPrivateKey(RSA_KEY_PAIR.getPrivate())) + .build()); + + // MasterKey + final JceMasterKey masterKey = + JceMasterKey.getInstance( + RSA_KEY_PAIR.getPublic(), null, "rsa-keyring", "rsa-key", "RSA/ECB/PKCS1Padding"); + // Encryption Context + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(masterKey, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = crypto.decryptData(keyring, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void RawRsaEncryptKeyringKeyDecryptMasterKey() throws Exception { + // AWS Encryption SDK Client + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + + // Keyring + IKeyring keyring = + materialProviders.CreateRawRsaKeyring( + CreateRawRsaKeyringInput.builder() + .keyName("rsa-key") + .keyNamespace("rsa-keyring") + .paddingScheme(PaddingScheme.PKCS1) + .publicKey(getPEMPublicKey(RSA_KEY_PAIR.getPublic())) + .build()); + + // MasterKey + final JceMasterKey masterKey = + JceMasterKey.getInstance( + null, RSA_KEY_PAIR.getPrivate(), "rsa-keyring", "rsa-key", "RSA/ECB/PKCS1Padding"); + + // Encryption Context + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(keyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = + crypto.decryptData(masterKey, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void RawRsaEncryptDecryptKeyring() throws Exception { + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + + IKeyring keyring = + materialProviders.CreateRawRsaKeyring( + CreateRawRsaKeyringInput.builder() + .keyName("rsa-key") + .keyNamespace("rsa-keyring") + .paddingScheme(PaddingScheme.PKCS1) + .publicKey(getPEMPublicKey(RSA_KEY_PAIR.getPublic())) + .privateKey(getPEMPrivateKey(RSA_KEY_PAIR.getPrivate())) + .build()); + + final Map encryptionContext = + Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(keyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + final CryptoResult decryptResult = crypto.decryptData(keyring, ciphertext); + + // Verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + public static ByteBuffer getPEMPublicKey(PublicKey publicKey) { + StringWriter publicKeyStringWriter = new StringWriter(); + PemWriter publicKeyPemWriter = new PemWriter(publicKeyStringWriter); + try { + publicKeyPemWriter.writeObject(new PemObject("PUBLIC KEY", publicKey.getEncoded())); + publicKeyPemWriter.close(); + } catch (IOException e) { + throw new RuntimeException("IOException while writing public key PEM", e); + } + return StandardCharsets.UTF_8.encode(publicKeyStringWriter.toString()); + } + + public static ByteBuffer getPEMPrivateKey(PrivateKey privateKey) { + StringWriter privateKeyStringWriter = new StringWriter(); + PemWriter privateKeyPemWriter = new PemWriter(privateKeyStringWriter); + try { + privateKeyPemWriter.writeObject(new PemObject("PRIVATE KEY", privateKey.getEncoded())); + privateKeyPemWriter.close(); + } catch (IOException e) { + throw new RuntimeException("IOException while writing private key PEM", e); + } + return StandardCharsets.UTF_8.encode(privateKeyStringWriter.toString()); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/CryptoAlgorithmTest.java b/src/test/java/com/amazonaws/encryptionsdk/CryptoAlgorithmTest.java index fba98ae2..9b2530a6 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/CryptoAlgorithmTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/CryptoAlgorithmTest.java @@ -13,6 +13,7 @@ import com.amazonaws.encryptionsdk.internal.Utils; import com.amazonaws.encryptionsdk.model.CiphertextHeaders; import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterialsHandler; import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; import java.security.InvalidKeyException; import java.util.Collections; @@ -149,7 +150,8 @@ private ParsedCiphertext getTestHeaders(CryptoAlgorithm algo) { .getMaterialsForEncrypt(encryptionMaterialsRequest); final EncryptionHandler encryptionHandler = - new EncryptionHandler(frameSize_, encryptionMaterials, policy); + new EncryptionHandler( + frameSize_, new EncryptionMaterialsHandler(encryptionMaterials), policy); final byte[] in = new byte[0]; final int ciphertextLen = encryptionHandler.estimateOutputSize(in.length); diff --git a/src/test/java/com/amazonaws/encryptionsdk/CryptoInputStreamTest.java b/src/test/java/com/amazonaws/encryptionsdk/CryptoInputStreamTest.java index 342b1bf7..6bb3926d 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/CryptoInputStreamTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/CryptoInputStreamTest.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; @@ -37,11 +38,23 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.mockito.ArgumentCaptor; +import software.amazon.cryptography.materialproviders.ICryptographicMaterialsManager; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.AesWrappingAlg; +import software.amazon.cryptography.materialproviders.model.CreateDefaultCryptographicMaterialsManagerInput; +import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; @RunWith(Enclosed.class) public class CryptoInputStreamTest { private static final SecureRandom RND = new SecureRandom(); + private static final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); private static final MasterKey customerMasterKey; + private static final IKeyring keyring; private static final CommitmentPolicy commitmentPolicy = TestUtils.DEFAULT_TEST_COMMITMENT_POLICY; static { @@ -51,6 +64,14 @@ public class CryptoInputStreamTest { customerMasterKey = JceMasterKey.getInstance( new SecretKeySpec(rawKey, "AES"), "mockProvider", "mockKey", "AES/GCM/NoPadding"); + keyring = + materialProviders.CreateRawAesKeyring( + CreateRawAesKeyringInput.builder() + .keyName("mockKey") + .keyNamespace("mockProvider") + .wrappingAlg(AesWrappingAlg.ALG_AES128_GCM_IV12_TAG16) + .wrappingKey(ByteBuffer.wrap(new SecretKeySpec(rawKey, "AES").getEncoded())) + .build()); } private static void testRoundTrip( @@ -90,6 +111,15 @@ private static Callback encryptWithContext(Map encryptionContext }; } + private static Callback encryptWithContextKeyring(Map encryptionContext) { + return (awsCrypto, inStream, outStream) -> { + final InputStream cryptoStream = + awsCrypto.createEncryptingStream(keyring, inStream, encryptionContext); + + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream); + }; + } + private static Callback encryptWithoutContext() { return (awsCrypto, inStream, outStream) -> { final InputStream cryptoStream = @@ -99,6 +129,14 @@ private static Callback encryptWithoutContext() { }; } + private static Callback encryptWithoutContextKeyring() { + return (awsCrypto, inStream, outStream) -> { + final InputStream cryptoStream = awsCrypto.createEncryptingStream(keyring, inStream); + + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream); + }; + } + private static Callback basicDecrypt(int readLen) { return (awsCrypto, inStream, outStream) -> { final InputStream cryptoStream = @@ -108,6 +146,14 @@ private static Callback basicDecrypt(int readLen) { }; } + private static Callback basicDecryptKeyring(int readLen) { + return (awsCrypto, inStream, outStream) -> { + final InputStream cryptoStream = awsCrypto.createDecryptingStream(keyring, inStream); + + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream, readLen); + }; + } + private static Callback basicDecrypt() { return (awsCrypto, inStream, outStream) -> { final InputStream cryptoStream = @@ -117,6 +163,14 @@ private static Callback basicDecrypt() { }; } + private static Callback basicDecryptKeyring() { + return (awsCrypto, inStream, outStream) -> { + final InputStream cryptoStream = awsCrypto.createDecryptingStream(keyring, inStream); + + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream); + }; + } + @RunWith(Parameterized.class) public static class ParameterizedEncryptDecryptTest { private final CryptoAlgorithm cryptoAlg; @@ -208,6 +262,23 @@ public void encryptDecrypt() throws Exception { basicDecrypt(readLen), commitmentPolicy); } + + @Test + public void encryptDecryptWithKeyring() throws Exception { + final CommitmentPolicy commitmentPolicy = + cryptoAlg.isCommitting() + ? CommitmentPolicy.RequireEncryptRequireDecrypt + : CommitmentPolicy.ForbidEncryptAllowDecrypt; + testRoundTrip( + byteSize, + awsCrypto -> { + awsCrypto.setEncryptionAlgorithm(cryptoAlg); + awsCrypto.setEncryptionFrameSize(frameSize); + }, + encryptWithoutContextKeyring(), + basicDecryptKeyring(readLen), + commitmentPolicy); + } } public static class NonParameterized { @@ -228,6 +299,16 @@ public void doEncryptDecryptWithoutEncContext() throws Exception { CommitmentPolicy.RequireEncryptRequireDecrypt); } + @Test + public void doEncryptDecryptWithoutEncContextWithKeyring() throws Exception { + testRoundTrip( + 1_000_000, + awsCrypto -> {}, + encryptWithoutContextKeyring(), + basicDecryptKeyring(), + CommitmentPolicy.RequireEncryptRequireDecrypt); + } + @Test public void encryptBytesDecryptStream() throws Exception { Map encryptionContext = new HashMap<>(1); @@ -249,6 +330,27 @@ public void encryptBytesDecryptStream() throws Exception { CommitmentPolicy.RequireEncryptRequireDecrypt); } + @Test + public void encryptBytesDecryptStreamWithKeyring() throws Exception { + Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "encryptBytesDecryptStream"); + + testRoundTrip( + 1_000_000, + awsCrypto -> {}, + (AwsCrypto awsCrypto, InputStream inStream, OutputStream outStream) -> { + ByteArrayOutputStream inbuf = new ByteArrayOutputStream(); + TestIOUtils.copyInStreamToOutStream(inStream, inbuf); + + CryptoResult ciphertext = + awsCrypto.encryptData(keyring, inbuf.toByteArray(), encryptionContext); + + outStream.write(ciphertext.getResult()); + }, + basicDecryptKeyring(), + CommitmentPolicy.RequireEncryptRequireDecrypt); + } + @Test public void encryptStreamDecryptBytes() throws Exception { Map encryptionContext = new HashMap<>(1); @@ -269,6 +371,26 @@ public void encryptStreamDecryptBytes() throws Exception { CommitmentPolicy.RequireEncryptRequireDecrypt); } + @Test + public void encryptStreamDecryptBytesWithKeyring() throws Exception { + Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "encryptStreamDecryptBytes"); + testRoundTrip( + 1_000_000, + awsCrypto -> {}, + encryptWithContextKeyring(encryptionContext), + (AwsCrypto awsCrypto, InputStream inStream, OutputStream outStream) -> { + ByteArrayOutputStream inbuf = new ByteArrayOutputStream(); + TestIOUtils.copyInStreamToOutStream(inStream, inbuf); + + CryptoResult ciphertext = + awsCrypto.decryptData(keyring, inbuf.toByteArray()); + + outStream.write(ciphertext.getResult()); + }, + CommitmentPolicy.RequireEncryptRequireDecrypt); + } + @Test public void encryptOSDecryptIS() throws Exception { Map encryptionContext = new HashMap<>(1); @@ -286,6 +408,23 @@ public void encryptOSDecryptIS() throws Exception { CommitmentPolicy.RequireEncryptRequireDecrypt); } + @Test + public void encryptOSDecryptISWithKeyring() throws Exception { + Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "encryptOSDecryptIS"); + + testRoundTrip( + 1_000_000, + awsCrypto -> {}, + (awsCrypto, inStream, outStream) -> { + OutputStream cryptoOS = + awsCrypto.createEncryptingStream(keyring, outStream, encryptionContext); + TestIOUtils.copyInStreamToOutStream(inStream, cryptoOS); + }, + basicDecryptKeyring(), + CommitmentPolicy.RequireEncryptRequireDecrypt); + } + private void singleByteCopyLoop(InputStream is, OutputStream os) throws Exception { int rv; while (-1 != (rv = is.read())) { @@ -316,6 +455,25 @@ public void singleByteRead() throws Exception { CommitmentPolicy.RequireEncryptRequireDecrypt); } + @Test + public void singleByteReadWithKeyring() throws Exception { + Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "singleByteRead"); + + testRoundTrip( + 1_000_000, + awsCrypto -> {}, + (awsCrypto, inStream, outStream) -> { + InputStream is = awsCrypto.createEncryptingStream(keyring, inStream, encryptionContext); + singleByteCopyLoop(is, outStream); + }, + (awsCrypto, inStream, outStream) -> { + InputStream is = awsCrypto.createDecryptingStream(keyring, inStream); + singleByteCopyLoop(is, outStream); + }, + CommitmentPolicy.RequireEncryptRequireDecrypt); + } + @SuppressWarnings({"ConstantConditions", "ResultOfMethodCallIgnored"}) @Test(expected = NullPointerException.class) public void whenNullBufferPassed_andNoOffsetArgs_readThrowsNPE() @@ -330,6 +488,20 @@ public void whenNullBufferPassed_andNoOffsetArgs_readThrowsNPE() encryptionInStream.read(null); } + @SuppressWarnings({"ConstantConditions", "ResultOfMethodCallIgnored"}) + @Test(expected = NullPointerException.class) + public void whenNullBufferPassed_andNoOffsetArgs_readThrowsNPE_keyring() + throws BadCiphertextException, IOException { + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "nullReadBuffer"); + + final InputStream inStream = new ByteArrayInputStream(TestUtils.insecureRandomBytes(2048)); + final InputStream encryptionInStream = + encryptionClient_.createEncryptingStream(keyring, inStream, encryptionContext); + + encryptionInStream.read(null); + } + @SuppressWarnings({"ConstantConditions", "ResultOfMethodCallIgnored"}) @Test(expected = NullPointerException.class) public void whenNullBufferPassed_andOffsetArgsPassed_readThrowsNPE() @@ -344,6 +516,20 @@ public void whenNullBufferPassed_andOffsetArgsPassed_readThrowsNPE() encryptionInStream.read(null, 0, 0); } + @SuppressWarnings({"ConstantConditions", "ResultOfMethodCallIgnored"}) + @Test(expected = NullPointerException.class) + public void whenNullBufferPassed_andOffsetArgsPassed_readThrowsNPE_keyring() + throws BadCiphertextException, IOException { + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "nullReadBuffer2"); + + final InputStream inStream = new ByteArrayInputStream(TestUtils.insecureRandomBytes(2048)); + final InputStream encryptionInStream = + encryptionClient_.createEncryptingStream(keyring, inStream, encryptionContext); + + encryptionInStream.read(null, 0, 0); + } + @Test public void zeroReadLen() throws BadCiphertextException, IOException { final Map encryptionContext = new HashMap<>(1); @@ -358,6 +544,20 @@ public void zeroReadLen() throws BadCiphertextException, IOException { assertEquals(readLen, 0); } + @Test + public void zeroReadLen_keyring() throws BadCiphertextException, IOException { + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "zeroReadLen"); + + final InputStream inStream = new ByteArrayInputStream(TestUtils.insecureRandomBytes(2048)); + final InputStream encryptionInStream = + encryptionClient_.createEncryptingStream(keyring, inStream, encryptionContext); + + final byte[] tempBytes = new byte[0]; + final int readLen = encryptionInStream.read(tempBytes); + assertEquals(readLen, 0); + } + @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalArgumentException.class) public void negativeReadLen() throws BadCiphertextException, IOException { @@ -372,6 +572,20 @@ public void negativeReadLen() throws BadCiphertextException, IOException { encryptionInStream.read(tempBytes, 0, -1); } + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = IllegalArgumentException.class) + public void negativeReadLen_keyring() throws BadCiphertextException, IOException { + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "negativeReadLen"); + + final InputStream inStream = new ByteArrayInputStream(TestUtils.insecureRandomBytes(2048)); + final InputStream encryptionInStream = + encryptionClient_.createEncryptingStream(keyring, inStream, encryptionContext); + + final byte[] tempBytes = new byte[1]; + encryptionInStream.read(tempBytes, 0, -1); + } + @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = IllegalArgumentException.class) public void negativeReadOffset() throws BadCiphertextException, IOException { @@ -386,6 +600,20 @@ public void negativeReadOffset() throws BadCiphertextException, IOException { encryptionInStream.read(tempBytes, -1, tempBytes.length); } + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = IllegalArgumentException.class) + public void negativeReadOffset_keyring() throws BadCiphertextException, IOException { + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "negativeReadOffset"); + + final InputStream inStream = new ByteArrayInputStream(TestUtils.insecureRandomBytes(2048)); + final InputStream encryptionInStream = + encryptionClient_.createEncryptingStream(keyring, inStream, encryptionContext); + + byte[] tempBytes = new byte[1]; + encryptionInStream.read(tempBytes, -1, tempBytes.length); + } + @SuppressWarnings("ResultOfMethodCallIgnored") @Test(expected = ArrayIndexOutOfBoundsException.class) public void invalidReadOffset() throws BadCiphertextException, IOException { @@ -424,6 +652,44 @@ public void decryptEmptyFile() throws IOException { assertEquals(0, outStream.size()); } + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test(expected = ArrayIndexOutOfBoundsException.class) + public void invalidReadOffset_keyring() throws BadCiphertextException, IOException { + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "invalidReadOffset"); + + final InputStream inStream = new ByteArrayInputStream(TestUtils.insecureRandomBytes(2048)); + final InputStream encryptionInStream = + encryptionClient_.createEncryptingStream(keyring, inStream, encryptionContext); + + final byte[] tempBytes = new byte[100]; + encryptionInStream.read(tempBytes, tempBytes.length + 1, tempBytes.length); + } + + @Test + public void noOpStream_keyring() throws IOException { + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC", "noOpStream"); + + final InputStream inStream = new ByteArrayInputStream(TestUtils.insecureRandomBytes(2048)); + final InputStream encryptionInStream = + encryptionClient_.createEncryptingStream(keyring, inStream, encryptionContext); + + encryptionInStream.close(); + } + + @Test + public void decryptEmptyFile_keyring() throws IOException { + final InputStream inStream = new ByteArrayInputStream(new byte[0]); + final InputStream decryptionInStream = + encryptionClient_.createDecryptingStream(keyring, inStream); + final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + + TestIOUtils.copyInStreamToOutStream(decryptionInStream, outStream); + + assertEquals(0, outStream.size()); + } + @Test public void checkEncContext() throws Exception { Map setEncryptionContext = new HashMap<>(1); @@ -447,6 +713,29 @@ public void checkEncContext() throws Exception { CommitmentPolicy.RequireEncryptRequireDecrypt); } + @Test + public void checkEncContextKeyring() throws Exception { + Map setEncryptionContext = new HashMap<>(1); + setEncryptionContext.put("ENC", "checkEncContext"); + + testRoundTrip( + 4096, + awsCrypto -> {}, + encryptWithContextKeyring(setEncryptionContext), + (crypto, inStream, outStream) -> { + CryptoInputStream cis = crypto.createDecryptingStream(keyring, inStream); + TestIOUtils.copyInStreamToOutStream(cis, outStream); + + // Note that the crypto result might have additional entries in its context, so only + // check that + // the entries we set were present, not that the entire map is equal + CryptoResult, ?> cryptoResult = cis.getCryptoResult(); + setEncryptionContext.forEach( + (k, v) -> assertEquals(v, cryptoResult.getEncryptionContext().get(k))); + }, + CommitmentPolicy.RequireEncryptRequireDecrypt); + } + @Test public void checkKeyId() throws Exception { testRoundTrip( @@ -485,6 +774,25 @@ public void checkAvailable() throws IOException { assertEquals(byteSize, encryptionInStream.available()); } + @Test + public void checkAvailableKeyring() throws IOException { + final int byteSize = 128; + final byte[] inBytes = TestIOUtils.generateRandomPlaintext(byteSize); + final InputStream inStream = new ByteArrayInputStream(inBytes); + + final int frameSize = AwsCrypto.getDefaultFrameSize(); + encryptionClient_.setEncryptionFrameSize(frameSize); + + Map setEncryptionContext = new HashMap<>(1); + setEncryptionContext.put("ENC", "Streaming Test"); + + // encryption + final InputStream encryptionInStream = + encryptionClient_.createEncryptingStream(keyring, inStream, setEncryptionContext); + + assertEquals(byteSize, encryptionInStream.available()); + } + @Test public void whenGetResultCalledTooEarly_noExceptionThrown() throws Exception { testRoundTrip( @@ -516,6 +824,37 @@ public void whenGetResultCalledTooEarly_noExceptionThrown() throws Exception { CommitmentPolicy.RequireEncryptRequireDecrypt); } + @Test + public void whenGetResultCalledTooEarly_noExceptionThrown_keyring() throws Exception { + testRoundTrip( + 1024, + awsCrypto -> {}, + (awsCrypto, inStream, outStream) -> { + final CryptoInputStream cryptoStream = + awsCrypto.createEncryptingStream(keyring, inStream); + + // can invoke at any time on encrypt + cryptoStream.getCryptoResult(); + + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream); + + cryptoStream.getCryptoResult(); + }, + (awsCrypto, inStream, outStream) -> { + final CryptoInputStream cryptoStream = + awsCrypto.createDecryptingStream(keyring, inStream); + + // this will implicitly read the crypto headers + cryptoStream.getCryptoResult(); + + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream); + + // still works + cryptoStream.getCryptoResult(); + }, + CommitmentPolicy.RequireEncryptRequireDecrypt); + } + @Test(expected = BadCiphertextException.class) public void whenGetResultInvokedOnEmptyStream_exceptionThrown() throws IOException { final CryptoInputStream cryptoStream = @@ -525,6 +864,14 @@ public void whenGetResultInvokedOnEmptyStream_exceptionThrown() throws IOExcepti cryptoStream.getCryptoResult(); } + @Test(expected = BadCiphertextException.class) + public void whenGetResultInvokedOnEmptyStream_exceptionThrown_keyring() throws IOException { + final CryptoInputStream cryptoStream = + encryptionClient_.createDecryptingStream(keyring, new ByteArrayInputStream(new byte[0])); + + cryptoStream.getCryptoResult(); + } + @Test() public void encryptUsingCryptoMaterialsManager() throws Exception { RecordingMaterialsManager cmm = new RecordingMaterialsManager(customerMasterKey); @@ -542,6 +889,23 @@ public void encryptUsingCryptoMaterialsManager() throws Exception { commitmentPolicy); } + @Test() + public void encryptUsingMplCryptographicMaterialsManager() throws Exception { + ICryptographicMaterialsManager cmm = + materialProviders.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(keyring).build()); + testRoundTrip( + 1024, + awsCrypto -> {}, + (crypto, inStream, outStream) -> { + final CryptoInputStream cryptoStream = crypto.createEncryptingStream(cmm, inStream); + + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream); + }, + basicDecryptKeyring(), + commitmentPolicy); + } + @Test public void decryptUsingCryptoMaterialsManager() throws Exception { RecordingMaterialsManager cmm = new RecordingMaterialsManager(customerMasterKey); @@ -562,6 +926,23 @@ public void decryptUsingCryptoMaterialsManager() throws Exception { commitmentPolicy); } + @Test + public void decryptUsingMplCryptographicMaterialsManager() throws Exception { + ICryptographicMaterialsManager cmm = + materialProviders.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(keyring).build()); + + testRoundTrip( + 1024, + awsCrypto -> {}, + encryptWithoutContextKeyring(), + (crypto, inStream, outStream) -> { + final CryptoInputStream cryptoStream = crypto.createDecryptingStream(cmm, inStream); + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream); + }, + commitmentPolicy); + } + @Test public void whenStreamSizeSetEarly_streamSizePassedToCMM() throws Exception { CryptoMaterialsManager cmm = spy(new DefaultCryptoMaterialsManager(customerMasterKey)); @@ -592,6 +973,20 @@ public void whenStreamSizeSetEarly_andExceeded_exceptionThrown() throws Exceptio assertThrows(() -> is.read(new byte[65536])); } + @Test + public void whenStreamSizeSetEarly_andExceeded_exceptionThrown_mplCMM() throws Exception { + ICryptographicMaterialsManager cmm = + materialProviders.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(keyring).build()); + + CryptoInputStream is = + AwsCrypto.standard().createEncryptingStream(cmm, new ByteArrayInputStream(new byte[2])); + + is.setMaxInputLength(1); + + assertThrows(() -> is.read(new byte[65536])); + } + @Test public void whenStreamSizeSetLate_andExceeded_exceptionThrown() throws Exception { CryptoMaterialsManager cmm = spy(new DefaultCryptoMaterialsManager(customerMasterKey)); @@ -607,6 +1002,23 @@ public void whenStreamSizeSetLate_andExceeded_exceptionThrown() throws Exception }); } + @Test + public void whenStreamSizeSetLate_andExceeded_exceptionThrown_mplCMM() throws Exception { + ICryptographicMaterialsManager cmm = + materialProviders.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(keyring).build()); + + CryptoInputStream is = + AwsCrypto.standard().createEncryptingStream(cmm, new ByteArrayInputStream(new byte[2])); + + assertThrows( + () -> { + is.read(); + is.setMaxInputLength(1); + is.read(new byte[65536]); + }); + } + @Test public void whenStreamSizeSet_afterBeingExceeded_exceptionThrown() throws Exception { CryptoMaterialsManager cmm = spy(new DefaultCryptoMaterialsManager(customerMasterKey)); @@ -622,6 +1034,23 @@ public void whenStreamSizeSet_afterBeingExceeded_exceptionThrown() throws Except }); } + @Test + public void whenStreamSizeSet_afterBeingExceeded_exceptionThrown_mplCMM() throws Exception { + ICryptographicMaterialsManager cmm = + materialProviders.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(keyring).build()); + + CryptoInputStream is = + AwsCrypto.standard() + .createEncryptingStream(cmm, new ByteArrayInputStream(new byte[1024 * 1024])); + + assertThrows( + () -> { + is.read(); + is.setMaxInputLength(1); + }); + } + @Test public void whenStreamSizeNegative_setSizeThrows() throws Exception { CryptoInputStream is = @@ -631,6 +1060,15 @@ public void whenStreamSizeNegative_setSizeThrows() throws Exception { assertThrows(() -> is.setMaxInputLength(-1)); } + @Test + public void whenStreamSizeNegative_setSizeThrows_keyring() throws Exception { + CryptoInputStream is = + AwsCrypto.standard() + .createEncryptingStream(keyring, new ByteArrayInputStream(new byte[0])); + + assertThrows(() -> is.setMaxInputLength(-1)); + } + @Test public void whenStreamSizeSet_roundTripSucceeds() throws Exception { testRoundTrip( @@ -657,5 +1095,32 @@ public void whenStreamSizeSet_roundTripSucceeds() throws Exception { }, CommitmentPolicy.RequireEncryptRequireDecrypt); } + + @Test + public void whenStreamSizeSet_roundTripSucceeds_keyring() throws Exception { + testRoundTrip( + 1024, + awsCrypto -> {}, + (awsCrypto, inStream, outStream) -> { + final CryptoInputStream cryptoStream = + awsCrypto.createEncryptingStream(keyring, inStream); + + // we happen to know inStream is a ByteArrayInputStream which will give an accurate + // number + // of bytes remaining on .available() + cryptoStream.setMaxInputLength(inStream.available()); + + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream); + }, + (awsCrypto, inStream, outStream) -> { + final CryptoInputStream cryptoStream = + awsCrypto.createDecryptingStream(keyring, inStream); + + cryptoStream.setMaxInputLength(inStream.available()); + + TestIOUtils.copyInStreamToOutStream(cryptoStream, outStream); + }, + CommitmentPolicy.RequireEncryptRequireDecrypt); + } } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java b/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java index 466f7565..e0e6db8d 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; @@ -42,11 +43,21 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.mockito.ArgumentCaptor; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.AesWrappingAlg; +import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; @RunWith(Enclosed.class) public class CryptoOutputStreamTest { private static final SecureRandom RND = new SecureRandom(); + private static final MaterialProviders materialProviders = + MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); private static final MasterKey customerMasterKey; + private static final IKeyring keyring; private static final AtomicReference RANDOM_BUFFER = new AtomicReference<>(new byte[0]); private static final CommitmentPolicy commitmentPolicy = TestUtils.DEFAULT_TEST_COMMITMENT_POLICY; @@ -56,6 +67,14 @@ public class CryptoOutputStreamTest { customerMasterKey = JceMasterKey.getInstance( new SecretKeySpec(rawKey, "AES"), "mockProvider", "mockKey", "AES/GCM/NoPadding"); + keyring = + materialProviders.CreateRawAesKeyring( + CreateRawAesKeyringInput.builder() + .keyName("mockKey") + .keyNamespace("mockProvider") + .wrappingAlg(AesWrappingAlg.ALG_AES128_GCM_IV12_TAG16) + .wrappingKey(ByteBuffer.wrap(new SecretKeySpec(rawKey, "AES").getEncoded())) + .build()); } private static void testRoundTrip( @@ -122,6 +141,39 @@ private static Callback basicDecrypt() { }; } + private static Callback encryptWithContextKeyring(Map encryptionContext) { + return (awsCrypto, inStream, outStream) -> { + final OutputStream encryptionOutStream = + awsCrypto.createEncryptingStream(keyring, outStream, encryptionContext); + + TestIOUtils.copyInStreamToOutStream(inStream, encryptionOutStream); + }; + } + + private static Callback encryptWithoutContextKeyrin() { + return (awsCrypto, inStream, outStream) -> { + final OutputStream encryptionOutStream = awsCrypto.createEncryptingStream(keyring, outStream); + + TestIOUtils.copyInStreamToOutStream(inStream, encryptionOutStream); + }; + } + + private static Callback basicDecryptKeyrin(int readLen) { + return (awsCrypto, inStream, outStream) -> { + final OutputStream decryptionOutStream = awsCrypto.createDecryptingStream(keyring, outStream); + + TestIOUtils.copyInStreamToOutStream(inStream, decryptionOutStream, readLen); + }; + } + + private static Callback basicDecryptKeyrin() { + return (awsCrypto, inStream, outStream) -> { + final OutputStream decryptionOutStream = awsCrypto.createDecryptingStream(keyring, outStream); + + TestIOUtils.copyInStreamToOutStream(inStream, decryptionOutStream); + }; + } + @RunWith(Parameterized.class) public static class ParameterizedEncryptDecryptTest { private final CryptoAlgorithm cryptoAlg; @@ -207,6 +259,26 @@ public void encryptDecrypt() throws Exception { basicDecrypt(readLen), commitmentPolicy); } + + @Test + public void encryptDecryptWithKeyring() throws Exception { + final Map encryptionContext = new HashMap(1); + encryptionContext.put("ENC", "Streaming Test"); + final CommitmentPolicy commitmentPolicy = + cryptoAlg.isCommitting() + ? CommitmentPolicy.RequireEncryptRequireDecrypt + : CommitmentPolicy.ForbidEncryptAllowDecrypt; + + testRoundTrip( + byteSize, + awsCrypto -> { + awsCrypto.setEncryptionFrameSize(frameSize); + awsCrypto.setEncryptionAlgorithm(cryptoAlg); + }, + encryptWithContextKeyring(encryptionContext), + basicDecryptKeyrin(readLen), + commitmentPolicy); + } } public static class NonParameterized { diff --git a/src/test/java/com/amazonaws/encryptionsdk/DecryptionMethod.java b/src/test/java/com/amazonaws/encryptionsdk/DecryptionMethod.java index 7091fa6f..8d10bd63 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/DecryptionMethod.java +++ b/src/test/java/com/amazonaws/encryptionsdk/DecryptionMethod.java @@ -2,9 +2,14 @@ import com.amazonaws.encryptionsdk.internal.SignaturePolicy; import com.amazonaws.encryptionsdk.internal.TestIOUtils; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import software.amazon.cryptography.materialproviders.IKeyring; -enum DecryptionMethod { +public enum DecryptionMethod { OneShot { @Override public byte[] decryptMessage( @@ -12,10 +17,16 @@ public byte[] decryptMessage( throws IOException { return crypto.decryptData(masterKeyProvider, ciphertext).getResult(); } + + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + return crypto.decryptData(keyring, ciphertext).getResult(); + } }, // Note for the record that changing the readLen parameter of copyInStreamToOutStream has minimal - // effect on the actual data flow when copying from a CryptoInputStream: it will always read from - // the + // effect on the actual data flow when copying from a CryptoInputStream: it will always read + // from// the // underlying input stream with a fixed chunk size (4096 bytes at the time of writing this), // independently // of how many bytes its asked to read of the decryption result. It's still useful to vary the @@ -32,6 +43,15 @@ public byte[] decryptMessage( TestIOUtils.copyInStreamToOutStream(in, out, 1); return out.toByteArray(); } + + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = crypto.createDecryptingStream(keyring, new ByteArrayInputStream(ciphertext)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TestIOUtils.copyInStreamToOutStream(in, out, 1); + return out.toByteArray(); + } }, InputStreamSmallByteChunks { @Override @@ -44,6 +64,15 @@ public byte[] decryptMessage( TestIOUtils.copyInStreamToOutStream(in, out, SMALL_CHUNK_SIZE); return out.toByteArray(); } + + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = crypto.createDecryptingStream(keyring, new ByteArrayInputStream(ciphertext)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TestIOUtils.copyInStreamToOutStream(in, out, SMALL_CHUNK_SIZE); + return out.toByteArray(); + } }, InputStreamWholeMessageChunks { @Override @@ -56,6 +85,15 @@ public byte[] decryptMessage( TestIOUtils.copyInStreamToOutStream(in, out, ciphertext.length); return out.toByteArray(); } + + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = crypto.createDecryptingStream(keyring, new ByteArrayInputStream(ciphertext)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TestIOUtils.copyInStreamToOutStream(in, out, ciphertext.length); + return out.toByteArray(); + } }, UnsignedMessageInputStreamSingleByteChunks { @Override @@ -70,6 +108,17 @@ public byte[] decryptMessage( return out.toByteArray(); } + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = + crypto.createUnsignedMessageDecryptingStream( + keyring, new ByteArrayInputStream(ciphertext)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TestIOUtils.copyInStreamToOutStream(in, out, 1); + return out.toByteArray(); + } + @Override public SignaturePolicy signaturePolicy() { return SignaturePolicy.AllowEncryptForbidDecrypt; @@ -88,6 +137,17 @@ public byte[] decryptMessage( return out.toByteArray(); } + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = + crypto.createUnsignedMessageDecryptingStream( + keyring, new ByteArrayInputStream(ciphertext)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TestIOUtils.copyInStreamToOutStream(in, out, SMALL_CHUNK_SIZE); + return out.toByteArray(); + } + @Override public SignaturePolicy signaturePolicy() { return SignaturePolicy.AllowEncryptForbidDecrypt; @@ -106,6 +166,17 @@ public byte[] decryptMessage( return out.toByteArray(); } + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = + crypto.createUnsignedMessageDecryptingStream( + keyring, new ByteArrayInputStream(ciphertext)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + TestIOUtils.copyInStreamToOutStream(in, out, ciphertext.length); + return out.toByteArray(); + } + @Override public SignaturePolicy signaturePolicy() { return SignaturePolicy.AllowEncryptForbidDecrypt; @@ -122,6 +193,16 @@ public byte[] decryptMessage( TestIOUtils.copyInStreamToOutStream(in, decryptingOut, 1); return out.toByteArray(); } + + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = new ByteArrayInputStream(ciphertext); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStream decryptingOut = crypto.createDecryptingStream(keyring, out); + TestIOUtils.copyInStreamToOutStream(in, decryptingOut, 1); + return out.toByteArray(); + } }, OutputStreamSmallByteChunks { @Override @@ -134,6 +215,16 @@ public byte[] decryptMessage( TestIOUtils.copyInStreamToOutStream(in, decryptingOut, SMALL_CHUNK_SIZE); return out.toByteArray(); } + + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = new ByteArrayInputStream(ciphertext); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStream decryptingOut = crypto.createDecryptingStream(keyring, out); + TestIOUtils.copyInStreamToOutStream(in, decryptingOut, SMALL_CHUNK_SIZE); + return out.toByteArray(); + } }, OutputStreamWholeMessageChunks { @Override @@ -146,6 +237,16 @@ public byte[] decryptMessage( TestIOUtils.copyInStreamToOutStream(in, decryptingOut, ciphertext.length); return out.toByteArray(); } + + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = new ByteArrayInputStream(ciphertext); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStream decryptingOut = crypto.createDecryptingStream(keyring, out); + TestIOUtils.copyInStreamToOutStream(in, decryptingOut, ciphertext.length); + return out.toByteArray(); + } }, UnsignedMessageOutputStreamSingleByteChunks { @Override @@ -160,6 +261,16 @@ public byte[] decryptMessage( return out.toByteArray(); } + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = new ByteArrayInputStream(ciphertext); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStream decryptingOut = crypto.createUnsignedMessageDecryptingStream(keyring, out); + TestIOUtils.copyInStreamToOutStream(in, decryptingOut, 1); + return out.toByteArray(); + } + @Override public SignaturePolicy signaturePolicy() { return SignaturePolicy.AllowEncryptForbidDecrypt; @@ -178,6 +289,16 @@ public byte[] decryptMessage( return out.toByteArray(); } + @Override + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = new ByteArrayInputStream(ciphertext); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStream decryptingOut = crypto.createUnsignedMessageDecryptingStream(keyring, out); + TestIOUtils.copyInStreamToOutStream(in, decryptingOut, SMALL_CHUNK_SIZE); + return out.toByteArray(); + } + @Override public SignaturePolicy signaturePolicy() { return SignaturePolicy.AllowEncryptForbidDecrypt; @@ -196,6 +317,15 @@ public byte[] decryptMessage( return out.toByteArray(); } + public byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException { + InputStream in = new ByteArrayInputStream(ciphertext); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStream decryptingOut = crypto.createUnsignedMessageDecryptingStream(keyring, out); + TestIOUtils.copyInStreamToOutStream(in, decryptingOut, ciphertext.length); + return out.toByteArray(); + } + @Override public SignaturePolicy signaturePolicy() { return SignaturePolicy.AllowEncryptForbidDecrypt; @@ -210,6 +340,9 @@ public abstract byte[] decryptMessage( AwsCrypto crypto, MasterKeyProvider masterKeyProvider, byte[] ciphertext) throws IOException; + public abstract byte[] decryptMessage(AwsCrypto crypto, IKeyring keyring, byte[] ciphertext) + throws IOException; + public SignaturePolicy signaturePolicy() { return SignaturePolicy.AllowEncryptAllowDecrypt; } diff --git a/src/test/java/com/amazonaws/encryptionsdk/EncryptionContextCMMTest.java b/src/test/java/com/amazonaws/encryptionsdk/EncryptionContextCMMTest.java new file mode 100644 index 00000000..566dca77 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/EncryptionContextCMMTest.java @@ -0,0 +1,818 @@ +package com.amazonaws.encryptionsdk; + +import static com.amazonaws.encryptionsdk.TestUtils.assertThrows; +import static com.amazonaws.encryptionsdk.kms.KMSTestFixtures.TEST_KEYSTORE_KMS_KEY_ID; +import static com.amazonaws.encryptionsdk.kms.KMSTestFixtures.TEST_KEYSTORE_NAME; +import static com.amazonaws.encryptionsdk.kms.KMSTestFixtures.TEST_LOGICAL_KEYSTORE_NAME; + +import com.amazonaws.crypto.examples.keyrings.RawAesKeyringExample; +import com.amazonaws.crypto.examples.keyrings.RawRsaKeyringExample; +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.keystore.KeyStore; +import software.amazon.cryptography.keystore.model.CreateKeyInput; +import software.amazon.cryptography.keystore.model.KMSConfiguration; +import software.amazon.cryptography.keystore.model.KeyStoreConfig; +import software.amazon.cryptography.materialproviders.ICryptographicMaterialsManager; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.AesWrappingAlg; +import software.amazon.cryptography.materialproviders.model.CacheType; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsHierarchicalKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateDefaultCryptographicMaterialsManagerInput; +import software.amazon.cryptography.materialproviders.model.CreateMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateRawRsaKeyringInput; +import software.amazon.cryptography.materialproviders.model.CreateRequiredEncryptionContextCMMInput; +import software.amazon.cryptography.materialproviders.model.DefaultCache; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.materialproviders.model.PaddingScheme; + +public class EncryptionContextCMMTest { + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + MaterialProviders matProv; + + @Before + public void setUp() { + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + + matProv = MaterialProviders.builder().MaterialProvidersConfig(config).build(); + } + + @Test + public void TestReprEncryptionContextWithSameECHappyCase() { + // Instantiate the Client + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // get keyrings + final IKeyring rsaKeyring = getRsaKeyring(); + final IKeyring kmsKeyring = getAwsKmsKeyring(); + final IKeyring aesKeyring = getAesKeyring(); + final IKeyring hKeyring = getHierarchicalKeyring(); + + final IKeyring multiKeyring = + matProv.CreateMultiKeyring( + CreateMultiKeyringInput.builder() + .generator(aesKeyring) + .childKeyrings(Arrays.asList(kmsKeyring, rsaKeyring, hKeyring)) + .build()); + + // HAPPY CASE 1 + // Test supply same encryption context on encrypt and decrypt NO filtering + final Map encryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.AB); + + final CryptoResult encryptResult = + crypto.encryptData(multiKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Test RSA + CryptoResult decryptResult = + crypto.decryptData(rsaKeyring, ciphertext, encryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test KMS + decryptResult = crypto.decryptData(rsaKeyring, ciphertext, encryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test AES + decryptResult = crypto.decryptData(aesKeyring, ciphertext, encryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test Hierarchy Keyring + decryptResult = crypto.decryptData(hKeyring, ciphertext, encryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void TestRemoveOnEncryptAndSupplyOnDecryptHappyCase() { + // Instantiate the Client + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // get keyrings + final IKeyring rsaKeyring = getRsaKeyring(); + final IKeyring kmsKeyring = getAwsKmsKeyring(); + final IKeyring aesKeyring = getAesKeyring(); + final IKeyring hKeyring = getHierarchicalKeyring(); + + final IKeyring multiKeyring = + matProv.CreateMultiKeyring( + CreateMultiKeyringInput.builder() + .generator(aesKeyring) + .childKeyrings(Arrays.asList(kmsKeyring, rsaKeyring, hKeyring)) + .build()); + + // Happy Test Case 2 + // On Encrypt we will only write one encryption context key value to the header + // we will then supply only what we didn't write wth no required ec cmm, + // This test case is checking that the default cmm is doing the correct filtering by using + final Map encryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.AB); + final Map reproducedEncryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.A); + // These keys mean that we will not write these on the message but are required for message + // authentication on decrypt. + final List requiredECKeys = + Fixtures.generateEncryptionContextKeys(Fixtures.Variation.A); + + final ICryptographicMaterialsManager defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder() + .keyring(multiKeyring) + .build()); + final ICryptographicMaterialsManager reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + + // Encrypt with Required Encryption Context CMM + final CryptoResult encryptResult = + crypto.encryptData(reqCMM, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Test RSA + CryptoResult decryptResult = + crypto.decryptData(rsaKeyring, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test KMS + decryptResult = crypto.decryptData(rsaKeyring, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test AES + decryptResult = crypto.decryptData(aesKeyring, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test Hierarchy Keyring + decryptResult = crypto.decryptData(hKeyring, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void TestRemoveOnEncryptRemoveAndSupplyOnDecryptHappyCase() { + // Instantiate the Client + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // get keyrings + final IKeyring rsaKeyring = getRsaKeyring(); + final IKeyring kmsKeyring = getAwsKmsKeyring(); + final IKeyring aesKeyring = getAesKeyring(); + final IKeyring hKeyring = getHierarchicalKeyring(); + + final IKeyring multiKeyring = + matProv.CreateMultiKeyring( + CreateMultiKeyringInput.builder() + .generator(aesKeyring) + .childKeyrings(Arrays.asList(kmsKeyring, rsaKeyring, hKeyring)) + .build()); + + // HAPPY CASE 3 + // On Encrypt we will only write one encryption context key value to the header + // we will then supply only what we didn't write but included in the signature while we + // are configured with the required encryption context cmm + final Map encryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.AB); + final Map reproducedEncryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.A); + // These keys mean that we will not write these on the message but are required for message + // authentication on decrypt. + final List requiredECKeys = + Fixtures.generateEncryptionContextKeys(Fixtures.Variation.A); + + // Create Required EC CMM with the required EC Keys we want + ICryptographicMaterialsManager defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder() + .keyring(multiKeyring) + .build()); + ICryptographicMaterialsManager reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + + // Encrypt with Required Encryption Context CMM + final CryptoResult encryptResult = + crypto.encryptData(reqCMM, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Test RSA + defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(rsaKeyring).build()); + reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + CryptoResult decryptResult = + crypto.decryptData(reqCMM, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test KMS + defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(kmsKeyring).build()); + reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + decryptResult = crypto.decryptData(reqCMM, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test AES + defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(aesKeyring).build()); + reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + decryptResult = crypto.decryptData(reqCMM, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test Hierarchy Keyring + defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(hKeyring).build()); + reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + decryptResult = crypto.decryptData(reqCMM, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void TestRemoveOnDecryptIsBackwardsCompatibleHappyCase() { + // Instantiate the Client + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // get keyrings + final IKeyring rsaKeyring = getRsaKeyring(); + final IKeyring kmsKeyring = getAwsKmsKeyring(); + final IKeyring aesKeyring = getAesKeyring(); + final IKeyring hKeyring = getHierarchicalKeyring(); + + final IKeyring multiKeyring = + matProv.CreateMultiKeyring( + CreateMultiKeyringInput.builder() + .generator(aesKeyring) + .childKeyrings(Arrays.asList(kmsKeyring, rsaKeyring, hKeyring)) + .build()); + + // HAPPY CASE 4 + // On Encrypt we write all encryption context + // as if the message was encrypted before the feature existed. + // We will then have a required encryption context cmm + // that will require us to supply the encryption context on decrypt. + final Map encryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.AB); + final Map reproducedEncryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.A); + // These keys mean that we will not write these on the message but are required for message + // authentication on decrypt. + final List requiredECKeys = + Fixtures.generateEncryptionContextKeys(Fixtures.Variation.A); + + // Create Default CMM + ICryptographicMaterialsManager defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder() + .keyring(multiKeyring) + .build()); + + // Encrypt with Required Encryption Context CMM + final CryptoResult encryptResult = + crypto.encryptData(defaultCMM, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Test RSA + defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(rsaKeyring).build()); + ICryptographicMaterialsManager reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + CryptoResult decryptResult = + crypto.decryptData(reqCMM, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test KMS + defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(kmsKeyring).build()); + reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + decryptResult = crypto.decryptData(reqCMM, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test AES + defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(aesKeyring).build()); + reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + decryptResult = crypto.decryptData(reqCMM, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Test Hierarchy Keyring + defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(hKeyring).build()); + reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + decryptResult = crypto.decryptData(reqCMM, ciphertext, reproducedEncryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + @Test + public void TestDifferentECOnDecryptFailure() { + // encrypt {a, b} => decrypt {b:c} => fail + // encrypt {a, b} => decrypt {d} => fail + + // Instantiate the Client + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // get keyrings + final IKeyring rsaKeyring = getRsaKeyring(); + final IKeyring kmsKeyring = getAwsKmsKeyring(); + final IKeyring aesKeyring = getAesKeyring(); + final IKeyring hKeyring = getHierarchicalKeyring(); + + final IKeyring multiKeyring = + matProv.CreateMultiKeyring( + CreateMultiKeyringInput.builder() + .generator(aesKeyring) + .childKeyrings(Arrays.asList(kmsKeyring, rsaKeyring, hKeyring)) + .build()); + + // FAILURE CASE 1 + // Encrypt with and store all encryption context in header + // On Decrypt supply additional encryption context not stored in the header; this MUST fail + // On Decrypt supply mismatched encryption context key values; this MUST fail + final Map encryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.AB); + // Additional EC + final Map reproducedAdditionalEncryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.C); + // Mismatched EncryptionContext + final Map reproducedMismatchedEncryptionContext = + Fixtures.generateMismatchedEncryptionContext(Fixtures.Variation.AB); + + // Encrypt with Multi Keyring + final CryptoResult encryptResult = + crypto.encryptData(multiKeyring, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Test RSA Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(rsaKeyring, ciphertext, reproducedAdditionalEncryptionContext)); + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(rsaKeyring, ciphertext, reproducedMismatchedEncryptionContext)); + + // Test KMS Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(kmsKeyring, ciphertext, reproducedAdditionalEncryptionContext)); + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(kmsKeyring, ciphertext, reproducedMismatchedEncryptionContext)); + + // Test AES Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(aesKeyring, ciphertext, reproducedAdditionalEncryptionContext)); + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(aesKeyring, ciphertext, reproducedMismatchedEncryptionContext)); + + // Test Hierarchy Keyring Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(hKeyring, ciphertext, reproducedAdditionalEncryptionContext)); + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(hKeyring, ciphertext, reproducedMismatchedEncryptionContext)); + } + + @Test + public void TestRemoveECAndNotSupplyOnDecryptFailure() { + // encrypt remove(a) RSA {a, b} => decrypt => fail + // encrypt remove(a) KMS {a, b} => decrypt => fail + // encrypt remove(a) AES {a, b} => decrypt => fail + // encrypt remove(a) Hie {a, b} => decrypt => fail + + // Instantiate the Client + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // get keyrings + final IKeyring rsaKeyring = getRsaKeyring(); + final IKeyring kmsKeyring = getAwsKmsKeyring(); + final IKeyring aesKeyring = getAesKeyring(); + final IKeyring hKeyring = getHierarchicalKeyring(); + + final IKeyring multiKeyring = + matProv.CreateMultiKeyring( + CreateMultiKeyringInput.builder() + .generator(aesKeyring) + .childKeyrings(Arrays.asList(kmsKeyring, rsaKeyring, hKeyring)) + .build()); + + // FAILURE CASE 2 + // Encrypt will not store all Encryption Context, we will drop one entry but it will still get + // included in the + // header signture. + // Decrypt will not supply any reproduced Encryption Context; this MUST fail. + final Map encryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.AB); + // These keys mean that we will not write these on the message but are required for message + // authentication on decrypt. + final List requiredECKeys = + Fixtures.generateEncryptionContextKeys(Fixtures.Variation.A); + + // Create Required EC CMM with the required EC Keys we want + final ICryptographicMaterialsManager defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder() + .keyring(multiKeyring) + .build()); + final ICryptographicMaterialsManager reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + + // Encrypt with Multi Keyring + final CryptoResult encryptResult = + crypto.encryptData(reqCMM, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Test RSA Failure + assertThrows(RuntimeException.class, () -> crypto.decryptData(rsaKeyring, ciphertext)); + + // Test KMS Failure + assertThrows(RuntimeException.class, () -> crypto.decryptData(kmsKeyring, ciphertext)); + + // Test AES Failure + assertThrows(RuntimeException.class, () -> crypto.decryptData(aesKeyring, ciphertext)); + + // Test Hierarchy Keyring Failure + assertThrows(RuntimeException.class, () -> crypto.decryptData(hKeyring, ciphertext)); + } + + @Test + public void TestRemoveECAndSupplyMismatchedReprECFailure() { + // encrypt remove(a) RSA {a, b} => decrypt {a:c} => fail + // encrypt remove(a) KMS {a, b} => decrypt {a:c} => fail + // encrypt remove(a) AES {a, b} => decrypt {a:c} => fail + // encrypt remove(a) Hie {a, b} => decrypt {a:c} => fail + + // Instantiate the Client + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // Get keyrings + final IKeyring rsaKeyring = getRsaKeyring(); + final IKeyring kmsKeyring = getAwsKmsKeyring(); + final IKeyring aesKeyring = getAesKeyring(); + final IKeyring hKeyring = getHierarchicalKeyring(); + + final IKeyring multiKeyring = + matProv.CreateMultiKeyring( + CreateMultiKeyringInput.builder() + .generator(aesKeyring) + .childKeyrings(Arrays.asList(kmsKeyring, rsaKeyring, hKeyring)) + .build()); + + // FAILURE CASE 3 + // Encrypt will not store all Encryption Context, we will drop one entry but it will still get + // included in the + // header signture. + // Decrypt will supply the correct key but incorrect value; this MUST fail. + final Map encryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.AB); + // These keys mean that we will not write these on the message but are required for message + // authentication on decrypt. + final List requiredECKeys = + Fixtures.generateEncryptionContextKeys(Fixtures.Variation.A); + // this reproduced encryption context contains the key we didn't store, but it has the wrong + // value + final Map mismatchedReproducedEncryptionContext = + Fixtures.generateMismatchedEncryptionContext(Fixtures.Variation.A); + + // Create Required EC CMM with the required EC Keys we want + final ICryptographicMaterialsManager defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder() + .keyring(multiKeyring) + .build()); + final ICryptographicMaterialsManager reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + + // Encrypt with Multi Keyring + final CryptoResult encryptResult = + crypto.encryptData(reqCMM, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Test RSA Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(rsaKeyring, ciphertext, mismatchedReproducedEncryptionContext)); + + // Test KMS Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(kmsKeyring, ciphertext, mismatchedReproducedEncryptionContext)); + + // Test AES Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(aesKeyring, ciphertext, mismatchedReproducedEncryptionContext)); + + // Test Hierarchy Keyring Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(hKeyring, ciphertext, mismatchedReproducedEncryptionContext)); + } + + @Test + public void TestRemoveECAndSupplyWithMissingRequiredValueDecryptFailure() { + // encrypt remove(a) RSA {a, b} => decrypt remove(a) => fail + // encrypt remove(a) KMS {a, b} => decrypt remove(a) => fail + // encrypt remove(a) AES {a, b} => decrypt remove(a) => fail + // encrypt remove(a) Hie {a, b} => decrypt remove(a) => fail + + // Instantiate the Client + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // Get keyrings + final IKeyring rsaKeyring = getRsaKeyring(); + final IKeyring kmsKeyring = getAwsKmsKeyring(); + final IKeyring aesKeyring = getAesKeyring(); + final IKeyring hKeyring = getHierarchicalKeyring(); + + final IKeyring multiKeyring = + matProv.CreateMultiKeyring( + CreateMultiKeyringInput.builder() + .generator(aesKeyring) + .childKeyrings(Arrays.asList(kmsKeyring, rsaKeyring, hKeyring)) + .build()); + + // FAILURE CASE 4 + // Encrypt will not store all Encryption Context, we will drop one entry but it will still get + // included in the + // header signture. + // Decrypt will supply the correct key but incorrect value; this MUST fail. + final Map encryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.AB); + // These keys mean that we will not write these on the message but are required for message + // authentication on decrypt. + final List requiredECKeys = + Fixtures.generateEncryptionContextKeys(Fixtures.Variation.A); + // this reproduced encryption context does not contain the key that was dropped + final Map droppedRequiredKeyEncryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.B); + + // Create Required EC CMM with the required EC Keys we want + final ICryptographicMaterialsManager defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder() + .keyring(multiKeyring) + .build()); + final ICryptographicMaterialsManager reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + + // Encrypt with Multi Keyring + final CryptoResult encryptResult = + crypto.encryptData(reqCMM, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Test RSA Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(rsaKeyring, ciphertext, droppedRequiredKeyEncryptionContext)); + + // Test KMS Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(kmsKeyring, ciphertext, droppedRequiredKeyEncryptionContext)); + + // Test AES Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(aesKeyring, ciphertext, droppedRequiredKeyEncryptionContext)); + + // Test Hierarchy Keyring Failure + assertThrows( + RuntimeException.class, + () -> crypto.decryptData(hKeyring, ciphertext, droppedRequiredKeyEncryptionContext)); + } + + @Test + public void TestReservedEncryptionContextKeyFailure() { + // Instantiate the Client + final AwsCrypto crypto = AwsCrypto.builder().build(); + + // Get keyring + final IKeyring rsaKeyring = getRsaKeyring(); + + // FAILURE CASE 5 + // Although we are requesting that we remove a RESERVED key word from the encryption context + // The CMM instantiation will still succeed because the CMM is meant to work with different + // higher level + // encryption libraries who may have different reserved keys. Encryption will ultimately fail. + final Map encryptionContext = Fixtures.getReservedEncryptionContextMap(); + final List requiredECKeys = Fixtures.getReservedEncryptionContextKey(); + + // Create Required EC CMM with the required EC Keys we want + final ICryptographicMaterialsManager defaultCMM = + matProv.CreateDefaultCryptographicMaterialsManager( + CreateDefaultCryptographicMaterialsManagerInput.builder().keyring(rsaKeyring).build()); + // Create Required EC CMM with the required EC Keys we want + final ICryptographicMaterialsManager reqCMM = + matProv.CreateRequiredEncryptionContextCMM( + CreateRequiredEncryptionContextCMMInput.builder() + .underlyingCMM(defaultCMM) + .requiredEncryptionContextKeys(requiredECKeys) + .build()); + + // Encrypt with Multi Keyring + assertThrows( + RuntimeException.class, () -> crypto.encryptData(reqCMM, EXAMPLE_DATA, encryptionContext)); + } + + @Test + public void TestReproducedEncryptionContextOnDecrypt() { + final AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(CommitmentPolicy.RequireEncryptRequireDecrypt) + .build(); + + // Get Keyring + IKeyring rsaKeyring = getRsaKeyring(); + + // Encryption Context + final Map encryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.A); + // Additional EC + final Map reproducedAdditionalEncryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.C); + // Incorrect EncryptionContext + final Map reproducedIncorrectEncryptionContext = + Fixtures.generateEncryptionContext(Fixtures.Variation.AB); + // Mismatched EncryptionContext + final Map reproducedMismatchedEncryptionContext = + Fixtures.generateMismatchedEncryptionContext(Fixtures.Variation.AB); + + // Encrypt the data + final CryptoResult encryptResult = + crypto.encryptData(rsaKeyring, EXAMPLE_DATA, encryptionContext); + + final byte[] ciphertext = encryptResult.getResult(); + + // Decrypt the data + // We expect to fail because we use different encryption context than the one we used on + // encrypt. + Assert.assertThrows( + Exception.class, + () -> crypto.decryptData(rsaKeyring, ciphertext, reproducedAdditionalEncryptionContext)); + + // Decrypt the data + // We expect to fail because we pass more encryption context than was used on encrypt + assertThrows( + Exception.class, + () -> crypto.decryptData(rsaKeyring, ciphertext, reproducedIncorrectEncryptionContext)); + + // Decrypt the data + // We expect to fail because although the same key is present on the ec + // their value is different. + Assert.assertThrows( + Exception.class, + () -> crypto.decryptData(rsaKeyring, ciphertext, reproducedMismatchedEncryptionContext)); + + // Decrypt the data & Verify that the decrypted plaintext matches the original plaintext + // Since we store all encryption context we MUST succeed if no encryption context is + // supplied on decrypt + CryptoResult decryptResult = crypto.decryptData(rsaKeyring, ciphertext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + + // Decrypt the data & Verify that the decrypted plaintext matches the original plaintext + decryptResult = crypto.decryptData(rsaKeyring, ciphertext, encryptionContext); + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + private IKeyring getRsaKeyring() { + final KeyPair keyPair = RawRsaKeyringExample.generateKeyPair(); + final ByteBuffer publicKeyBytes = RawRsaKeyringExample.getPEMPublicKey(keyPair.getPublic()); + final ByteBuffer privateKeyBytes = RawRsaKeyringExample.getPEMPrivateKey(keyPair.getPrivate()); + + final CreateRawRsaKeyringInput encryptingKeyringInput = + CreateRawRsaKeyringInput.builder() + .keyName("rsa-key") + .keyNamespace("rsa-keyring") + .paddingScheme(PaddingScheme.PKCS1) + .publicKey(publicKeyBytes) + .privateKey(privateKeyBytes) + .build(); + return matProv.CreateRawRsaKeyring(encryptingKeyringInput); + } + + private IKeyring getAesKeyring() { + final ByteBuffer aesKeyBytes = RawAesKeyringExample.generateAesKeyBytes(); + final CreateRawAesKeyringInput keyringInput = + CreateRawAesKeyringInput.builder() + .keyName("my-aes-key-name") + .keyNamespace("my-key-namespace") + .wrappingKey(aesKeyBytes) + .wrappingAlg(AesWrappingAlg.ALG_AES256_GCM_IV12_TAG16) + .build(); + return matProv.CreateRawAesKeyring(keyringInput); + } + + private IKeyring getAwsKmsKeyring() { + final String keyArn = KMSTestFixtures.US_WEST_2_KEY_ID; + final CreateAwsKmsKeyringInput keyringInput = + CreateAwsKmsKeyringInput.builder().kmsKeyId(keyArn).kmsClient(KmsClient.create()).build(); + return matProv.CreateAwsKmsKeyring(keyringInput); + } + + private IKeyring getHierarchicalKeyring() { + final String keyStoreTableName = TEST_KEYSTORE_NAME; + final String logicalKeyStoreName = TEST_LOGICAL_KEYSTORE_NAME; + final String kmsKeyId = TEST_KEYSTORE_KMS_KEY_ID; + + final KeyStore keystore = + KeyStore.builder() + .KeyStoreConfig( + KeyStoreConfig.builder() + .ddbClient(DynamoDbClient.create()) + .ddbTableName(keyStoreTableName) + .logicalKeyStoreName(logicalKeyStoreName) + .kmsClient(KmsClient.create()) + .kmsConfiguration(KMSConfiguration.builder().kmsKeyArn(kmsKeyId).build()) + .build()) + .build(); + + final String branchKeyId = + keystore.CreateKey(CreateKeyInput.builder().build()).branchKeyIdentifier(); + + final CreateAwsKmsHierarchicalKeyringInput keyringInput = + CreateAwsKmsHierarchicalKeyringInput.builder() + .keyStore(keystore) + .branchKeyId(branchKeyId) + .ttlSeconds(600) + .cache( + CacheType.builder() // OPTIONAL + .Default(DefaultCache.builder().entryCapacity(100).build()) + .build()) + .build(); + return matProv.CreateAwsKmsHierarchicalKeyring(keyringInput); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/Fixtures.java b/src/test/java/com/amazonaws/encryptionsdk/Fixtures.java new file mode 100644 index 00000000..e477c406 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/Fixtures.java @@ -0,0 +1,116 @@ +package com.amazonaws.encryptionsdk; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Fixtures { + public enum Variation { + Empty, + A, + B, + AB, + BA, + C, + CE + } + + public static Map generateEncryptionContext(Variation v) { + Map encryptionContext = new HashMap<>(); + + switch (v) { + case A: + encryptionContext.put("keyA", "valA"); + break; + case B: + encryptionContext.put("keyB", "valB"); + break; + case AB: + case BA: + encryptionContext.put("keyA", "valA"); + encryptionContext.put("keyB", "valB"); + break; + case C: + encryptionContext.put("keyC", "valC"); + break; + case CE: + encryptionContext.put("keyC", "valC"); + encryptionContext.put("keyD", "valD"); + break; + } + + return encryptionContext; + } + + public static Map generateMismatchedEncryptionContext(Variation v) { + Map encryptionContext = new HashMap<>(); + + switch (v) { + case A: + encryptionContext.put("keyA", "valB"); + break; + case B: + encryptionContext.put("keyB", "valA"); + break; + case AB: + case BA: + encryptionContext.put("keyA", "valC"); + encryptionContext.put("keyB", "valD"); + break; + case C: + encryptionContext.put("keyC", "valA"); + break; + case CE: + encryptionContext.put("keyC", "valA"); + encryptionContext.put("keyD", "valB"); + break; + } + + return encryptionContext; + } + + public static List generateEncryptionContextKeys(Variation v) { + return Stream.of("keyA", "keyB", "keyC", "keyD") + .filter( + key -> { + if (v == Variation.Empty) { + return false; + } + if (v == Variation.A && !key.equals("keyA")) { + return false; + } + if (v == Variation.B && !key.equals("keyB")) { + return false; + } + if (v == Variation.AB && (!key.equals("keyA") && !key.equals("keyB"))) { + return false; + } + if (v == Variation.BA && (!key.equals("keyB") && !key.equals("keyA"))) { + return false; + } + if (v == Variation.C && !key.equals("keyC")) { + return false; + } + if (v == Variation.CE && (!key.equals("keyC") && !key.equals("keyD"))) { + return false; + } + return true; + }) + .collect(Collectors.toList()); + } + + public static Map getReservedEncryptionContextMap() { + Map encryptionContext = new HashMap<>(); + encryptionContext.put("aws-crypto-public-key", "not a real public key"); + return encryptionContext; + } + + public static List getReservedEncryptionContextKey() { + List ecKeys = new ArrayList(); + ecKeys.add("aws-crypto-public-key"); + return ecKeys; + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/ParsedCiphertextTest.java b/src/test/java/com/amazonaws/encryptionsdk/ParsedCiphertextTest.java index 2de6a4a2..8c03deea 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/ParsedCiphertextTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/ParsedCiphertextTest.java @@ -4,7 +4,9 @@ package com.amazonaws.encryptionsdk; import static com.amazonaws.encryptionsdk.TestUtils.assertThrows; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; diff --git a/src/test/java/com/amazonaws/encryptionsdk/TestVectorGenerator.java b/src/test/java/com/amazonaws/encryptionsdk/TestVectorGenerator.java new file mode 100644 index 00000000..eb2b1764 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/TestVectorGenerator.java @@ -0,0 +1,570 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.encryptionsdk; + +import static java.lang.String.format; + +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileAttribute; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.io.FileUtils; +import org.bouncycastle.util.encoders.Base64; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import software.amazon.awssdk.utils.ImmutableMap; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.materialproviderstestvectorkeys.KeyVectors; +import software.amazon.cryptography.materialproviderstestvectorkeys.model.GetKeyDescriptionInput; +import software.amazon.cryptography.materialproviderstestvectorkeys.model.GetKeyDescriptionOutput; +import software.amazon.cryptography.materialproviderstestvectorkeys.model.KeyVectorsConfig; +import software.amazon.cryptography.materialproviderstestvectorkeys.model.TestVectorKeyringInput; + +@RunWith(Parameterized.class) +public class TestVectorGenerator { + + private static final String encryptManifestList = + "https://raw.githubusercontent.com/awslabs/aws-crypto-tools-test-vector-framework/master/features/CANONICAL-GENERATED-MANIFESTS/0003-awses-message-encryption.v2.json"; + // We save the files in memory to avoid repeatedly retrieving them. This won't work if the + // plaintexts are too + // large or numerous + private static final Map cachedData = new HashMap<>(); + private static final ObjectMapper mapper = new ObjectMapper(); + private static EncryptionInterface encryption; + private static boolean isMasterKey; + private final String testName; + private final TestCase testCase; + + // Temp Test Vectors Directory + private static String tempTestVectorPath; + // Zip File Path + private static String zipFilePath; + + public TestVectorGenerator(final String testName, TestCase testCase) { + this.testName = testName; + this.testCase = testCase; + } + + // Zip Temp Folder and delete temp files + @AfterClass + public static void zip() throws IOException { + Path zipFile = Files.createFile(Paths.get(zipFilePath)); + + Path sourceDirPath = Paths.get(tempTestVectorPath); + try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(zipFile)); + Stream paths = Files.walk(sourceDirPath)) { + paths + .filter(path -> !Files.isDirectory(path)) + .forEach( + path -> { + ZipEntry zipEntry = new ZipEntry(sourceDirPath.relativize(path).toString()); + try { + zipOutputStream.putNextEntry(zipEntry); + Files.copy(path, zipOutputStream); + zipOutputStream.closeEntry(); + } catch (IOException e) { + throw new UncheckedIOException("Unable to Zip File", e); + } + }); + } + FileUtils.deleteQuietly(sourceDirPath.toFile()); + + // Teardown + cachedData.clear(); + } + + @Test + @SuppressWarnings("unchecked") + public void encrypt() throws Exception { + CryptoAlgorithm cryptoAlgorithm = getCryptoAlgorithm(testCase.algorithmId); + CommitmentPolicy commitmentPolicy = + cryptoAlgorithm.isCommitting() + ? CommitmentPolicy.RequireEncryptRequireDecrypt + : CommitmentPolicy.ForbidEncryptAllowDecrypt; + + AwsCrypto crypto = + AwsCrypto.builder() + .withCommitmentPolicy(commitmentPolicy) + .withEncryptionAlgorithm(cryptoAlgorithm) + .withEncryptionFrameSize(testCase.frameSize) + .build(); + + Callable ciphertext; + if (isMasterKey) { + ciphertext = + () -> + crypto + .encryptData( + testCase.masterKey, + cachedData.get(testCase.plaintext), + testCase.encryptionContext) + .getResult(); + } else { + ciphertext = + () -> + crypto + .encryptData( + testCase.keyring, + cachedData.get(testCase.plaintext), + testCase.encryptionContext) + .getResult(); + } + Files.write(Paths.get(tempTestVectorPath + "ciphertexts/" + testName), ciphertext.call()); + } + + private static CryptoAlgorithm getCryptoAlgorithm(String algorithmId) { + Integer algId = Integer.parseInt(algorithmId, 16); + for (CryptoAlgorithm cryptoAlgorithm : CryptoAlgorithm.values()) { + if (cryptoAlgorithm.getValue() == algId) { + return cryptoAlgorithm; + } + } + throw new IllegalArgumentException("Invalid AlgorithmId: " + algorithmId); + } + + @Parameterized.Parameters(name = "Compatibility Test: {0} - {1}") + @SuppressWarnings("unchecked") + public static Collection data() throws Exception { + final String interfaceOption = System.getProperty("masterkey"); + + if (interfaceOption != null && interfaceOption.equals("true")) { + isMasterKey = true; + encryption = EncryptionInterface.EncryptWithMasterKey; + } else { + encryption = EncryptionInterface.EncryptWithKeyring; + } + + final String encryptKeyManifest = System.getProperty("keysManifest"); + if (encryptKeyManifest == null) { + return Collections.emptyList(); + } + + zipFilePath = System.getProperty("zipFilePath"); + if (zipFilePath == null) { + return Collections.emptyList(); + } + + tempTestVectorPath = Files.createTempDirectory("java", new FileAttribute[0]).toString() + "/"; + createDirectories(tempTestVectorPath + "ciphertexts/"); + createDirectories(tempTestVectorPath + "plaintexts/"); + + File decryptManifest = new File(tempTestVectorPath + "manifest.json"); + File keyManifest = new File(tempTestVectorPath + "keys.json"); + + final Map manifest = mapper.readValue(new URL(encryptManifestList), Map.class); + mapper + .writerWithDefaultPrettyPrinter() + .writeValue(decryptManifest, createDecryptManifest(manifest)); + + try (InputStream in = new FileInputStream(encryptKeyManifest)) { + Files.copy(in, keyManifest.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + + final Map keysManifest = + mapper.readValue(new File(encryptKeyManifest), Map.class); + + cachePlaintext((Map) manifest.get("plaintexts")); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + KeyVectors keyVectors = + KeyVectors.builder() + .KeyVectorsConfig( + KeyVectorsConfig.builder().keyManifiestPath(keyManifest.toString()).build()) + .build(); + + final Map keys = parseKeyManifest(keysManifest); + final KmsMasterKeyProvider kmsProv = + KmsMasterKeyProvider.builder() + .withCredentials(new DefaultAWSCredentialsProviderChain()) + .buildDiscovery(); + + return ((Map>) manifest.get("tests")) + .entrySet().stream() + .map( + entry -> { + String testName = entry.getKey(); + TestCase testCase = + encryption.parseTest(entry, keys, kmsProv, materialProviders, keyVectors); + return new Object[] {testName, testCase}; + }) + .collect(Collectors.toList()); + } + + private static void createDirectories(String path) { + File directory = new File(path); + directory.mkdirs(); + } + + private enum EncryptionInterface { + EncryptWithMasterKey { + @Override + public TestCase parseTest( + Map.Entry> testEntry, + Map keys, + KmsMasterKeyProvider kmsProv, + MaterialProviders materialProviders, + KeyVectors keyVectors) { + return parseTestWithMasterkeys(testEntry, keys, kmsProv); + } + }, + EncryptWithKeyring { + @Override + public TestCase parseTest( + Map.Entry> testEntry, + Map keys, + KmsMasterKeyProvider kmsProv, + MaterialProviders materialProviders, + KeyVectors keyVectors) { + return parseTestWithKeyrings(testEntry, materialProviders, keyVectors); + } + }; + + public abstract TestCase parseTest( + Map.Entry> testEntry, + Map keys, + KmsMasterKeyProvider kmsProv, + MaterialProviders materialProviders, + KeyVectors keyVectors); + } + + private static void cachePlaintext(Map plaintexts) { + Random rd = new Random(); + plaintexts.forEach( + (key, value) -> { + byte[] plaintext = new byte[value]; + rd.nextBytes(plaintext); + try { + Files.write(new File(tempTestVectorPath + "plaintexts/" + key).toPath(), plaintext); + cachedData.put(key, plaintext); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + private static Map createDecryptManifest(Map encryptManifest) { + Map decryptManifest = new LinkedHashMap<>(); + + decryptManifest.put("manifest", ImmutableMap.of("type", "awses-decrypt", "version", 2)); + + decryptManifest.put( + "client", ImmutableMap.of("name", "aws/aws-encryption-sdk-java", "version", "2.2.0")); + + decryptManifest.put("keys", "file://keys.json"); + + Map> testScenarios = + ((LinkedHashMap>) encryptManifest.get("tests")) + .entrySet().stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> { + Map scenario = entry.getValue(); + return new LinkedHashMap() { + { + put("ciphertext", "file://ciphertexts/" + entry.getKey()); + put("master-keys", scenario.get("master-keys")); + put( + "result", + Collections.singletonMap( + "output", + Collections.singletonMap( + "plaintext", + "file://plaintexts/" + scenario.get("plaintext")))); + } + }; + })); + + decryptManifest.put("tests", testScenarios); + return decryptManifest; + } + + private static TestCase parseTestWithMasterkeys( + Map.Entry> testEntry, + Map keys, + KmsMasterKeyProvider kmsProv) { + + String testName = testEntry.getKey(); + Map data = testEntry.getValue(); + + String plaintext = (String) data.get("plaintext"); + String algorithmId = (String) data.get("algorithm"); + int frameSize = (int) data.get("frame-size"); + Map encryptionContext = (Map) data.get("encryption-context"); + + final List> mks = new ArrayList<>(); + + for (Map mkEntry : (List>) data.get("master-keys")) { + if (mkEntry.get("key").equals("rsa-4096-private")) { + mkEntry.replace("key", "rsa-4096-public"); + } + + final String type = mkEntry.get("type"); + final String keyName = mkEntry.get("key"); + final KeyEntry key = keys.get(keyName); + + if ("aws-kms".equals(type)) { + mks.add(kmsProv.getMasterKey(key.keyId)); + } else if ("raw".equals(type)) { + final String provId = mkEntry.get("provider-id"); + final String algorithm = mkEntry.get("encryption-algorithm"); + if ("aes".equals(algorithm)) { + mks.add( + JceMasterKey.getInstance( + (SecretKey) key.key, provId, key.keyId, "AES/GCM/NoPadding")); + } else if ("rsa".equals(algorithm)) { + String transformation = "RSA/ECB/"; + final String padding = mkEntry.get("padding-algorithm"); + if ("pkcs1".equals(padding)) { + transformation += "PKCS1Padding"; + } else if ("oaep-mgf1".equals(padding)) { + final String hashName = + mkEntry.get("padding-hash").replace("sha", "sha-").toUpperCase(); + transformation += "OAEPWith" + hashName + "AndMGF1Padding"; + } else { + throw new IllegalArgumentException("Unsupported padding:" + padding); + } + final PublicKey wrappingKey; + final PrivateKey unwrappingKey; + if (key.key instanceof PublicKey) { + wrappingKey = (PublicKey) key.key; + unwrappingKey = null; + } else { + wrappingKey = null; + unwrappingKey = (PrivateKey) key.key; + } + mks.add( + JceMasterKey.getInstance( + wrappingKey, unwrappingKey, provId, key.keyId, transformation)); + } else { + throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); + } + } else { + throw new IllegalArgumentException("Unsupported Key Type: " + type); + } + } + + MasterKeyProvider multiProvider = MultipleProviderFactory.buildMultiProvider(mks); + + return new TestCase( + testName, null, multiProvider, plaintext, algorithmId, frameSize, encryptionContext); + } + + private static TestCase parseTestWithKeyrings( + Map.Entry> testEntry, + MaterialProviders materialProviders, + KeyVectors keyVectors) { + String testName = testEntry.getKey(); + Map data = testEntry.getValue(); + + String plaintext = (String) data.get("plaintext"); + String algorithmId = (String) data.get("algorithm"); + int frameSize = (int) data.get("frame-size"); + Map encryptionContext = (Map) data.get("encryption-context"); + + List keyrings = new ArrayList<>(); + + ((List>) data.get("master-keys")) + .forEach( + mkEntry -> { + if (mkEntry.get("type").equals("raw") + && mkEntry.get("encryption-algorithm").equals("rsa")) { + if (mkEntry.get("key").equals("rsa-4096-private")) { + mkEntry.replace("key", "rsa-4096-public"); + } + mkEntry.putIfAbsent("padding-hash", "sha1"); + } + + try { + byte[] json = new ObjectMapper().writeValueAsBytes(mkEntry); + GetKeyDescriptionOutput output = + keyVectors.GetKeyDescription( + GetKeyDescriptionInput.builder().json(ByteBuffer.wrap(json)).build()); + + IKeyring testVectorKeyring = + keyVectors.CreateTestVectorKeyring( + TestVectorKeyringInput.builder() + .keyDescription(output.keyDescription()) + .build()); + + keyrings.add(testVectorKeyring); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + }); + + IKeyring primary = keyrings.remove(0); + IKeyring multiKeyring = + materialProviders.CreateMultiKeyring( + CreateMultiKeyringInput.builder().generator(primary).childKeyrings(keyrings).build()); + + return new TestCase( + testName, multiKeyring, null, plaintext, algorithmId, frameSize, encryptionContext); + } + + @SuppressWarnings("unchecked") + private static Map parseKeyManifest(final Map keysManifest) + throws GeneralSecurityException { + // check our type + final Map metaData = (Map) keysManifest.get("manifest"); + if (!"keys".equals(metaData.get("type"))) { + throw new IllegalArgumentException("Invalid manifest type: " + metaData.get("type")); + } + if (!Integer.valueOf(3).equals(metaData.get("version"))) { + throw new IllegalArgumentException("Invalid manifest version: " + metaData.get("version")); + } + + final Map result = new HashMap<>(); + + Map keys = (Map) keysManifest.get("keys"); + for (Map.Entry entry : keys.entrySet()) { + final String name = entry.getKey(); + final Map data = (Map) entry.getValue(); + + final String keyType = (String) data.get("type"); + final String encoding = (String) data.get("encoding"); + final String keyId = (String) data.get("key-id"); + final String material = (String) data.get("material"); // May be null + final String algorithm = (String) data.get("algorithm"); // May be null + + final KeyEntry keyEntry; + + final KeyFactory kf; + switch (keyType) { + case "symmetric": + if (!"base64".equals(encoding)) { + throw new IllegalArgumentException( + format("Key %s is symmetric but has encoding %s", keyId, encoding)); + } + keyEntry = + new KeyEntry( + name, + keyId, + keyType, + new SecretKeySpec(Base64.decode(material), algorithm.toUpperCase())); + break; + case "private": + kf = KeyFactory.getInstance(algorithm); + if (!"pem".equals(encoding)) { + throw new IllegalArgumentException( + format("Key %s is private but has encoding %s", keyId, encoding)); + } + byte[] pkcs8Key = parsePem(material); + keyEntry = + new KeyEntry( + name, keyId, keyType, kf.generatePrivate(new PKCS8EncodedKeySpec(pkcs8Key))); + break; + case "public": + kf = KeyFactory.getInstance(algorithm); + if (!"pem".equals(encoding)) { + throw new IllegalArgumentException( + format("Key %s is private but has encoding %s", keyId, encoding)); + } + byte[] x509Key = parsePem(material); + keyEntry = + new KeyEntry( + name, keyId, keyType, kf.generatePublic(new X509EncodedKeySpec(x509Key))); + break; + case "aws-kms": + keyEntry = new KeyEntry(name, keyId, keyType, null); + break; + default: + throw new IllegalArgumentException("Unsupported key type: " + keyType); + } + + result.put(name, keyEntry); + } + + return result; + } + + private static byte[] parsePem(String pem) { + final String stripped = pem.replaceAll("-+[A-Z ]+-+", ""); + return Base64.decode(stripped); + } + + private static class KeyEntry { + final String name; + final String keyId; + final String type; + final Key key; + + private KeyEntry(String name, String keyId, String type, Key key) { + this.name = name; + this.keyId = keyId; + this.type = type; + this.key = key; + } + } + + private static class TestCase { + private final String name; + private final IKeyring keyring; + private final MasterKeyProvider masterKey; + private final String plaintext; + private final String algorithmId; + private final int frameSize; + private final Map encryptionContext; + + public TestCase( + String name, + IKeyring keyring, + MasterKeyProvider multiProvider, + String plaintext, + String algorithmId, + int frameSize, + Map encryptionContext) { + this.name = name; + this.keyring = keyring; + this.masterKey = multiProvider; + this.plaintext = plaintext; + this.algorithmId = algorithmId; + this.frameSize = frameSize; + this.encryptionContext = encryptionContext; + } + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java index a89cde8e..8e27eb60 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java +++ b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java @@ -13,13 +13,20 @@ import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; import com.amazonaws.util.IOUtils; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.URL; -import java.security.*; +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; @@ -41,6 +48,15 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import software.amazon.awssdk.regions.Region; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.materialproviderstestvectorkeys.KeyVectors; +import software.amazon.cryptography.materialproviderstestvectorkeys.model.GetKeyDescriptionInput; +import software.amazon.cryptography.materialproviderstestvectorkeys.model.GetKeyDescriptionOutput; +import software.amazon.cryptography.materialproviderstestvectorkeys.model.KeyVectorsConfig; +import software.amazon.cryptography.materialproviderstestvectorkeys.model.TestVectorKeyringInput; @RunWith(Parameterized.class) public class TestVectorRunner { @@ -69,14 +85,22 @@ public void decrypt() throws Exception { AwsCrypto.builder() .withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt) .build(); - Callable decryptor = - () -> - decryptionMethod.decryptMessage( - crypto, testCase.mkpSupplier.get(), cachedData.get(testCase.ciphertextPath)); + Callable decryptor; + if (testCase.isKeyring) { + decryptor = + () -> + decryptionMethod.decryptMessage( + crypto, testCase.keyringSupplier.get(), cachedData.get(testCase.ciphertextPath)); + } else { + decryptor = + () -> + decryptionMethod.decryptMessage( + crypto, testCase.mkpSupplier.get(), cachedData.get(testCase.ciphertextPath)); + } testCase.matcher.Match(decryptor); } - @Parameterized.Parameters(name = "Compatibility Test: {0} - {2}") + @Parameterized.Parameters(name = "Compatibility Test: {0} - {3}") @SuppressWarnings("unchecked") public static Collection data() throws Exception { final String zipPath = System.getProperty("testVectorZip"); @@ -89,6 +113,13 @@ public static Collection data() throws Exception { try (JarFile jar = jarConnection.getJarFile()) { final Map manifest = readJsonMapFromJar(jar, "manifest.json"); + final Map keysManifest = readJsonMapFromJar(jar, "keys.json"); + + ObjectMapper objectMapper = new ObjectMapper(); + + // Create a temporary file and write the JSON string to it + File tempFile = File.createTempFile("keys", ".json"); + objectMapper.writeValue(tempFile, keysManifest); final Map metaData = (Map) manifest.get("manifest"); @@ -105,6 +136,16 @@ public static Collection data() throws Exception { final Map keys = parseKeyManifest(readJsonMapFromJar(jar, (String) manifest.get("keys"))); + KeyVectors keyVectors = + KeyVectors.builder() + .KeyVectorsConfig( + KeyVectorsConfig.builder().keyManifiestPath(tempFile.getPath()).build()) + .build(); + + MaterialProvidersConfig config = MaterialProvidersConfig.builder().build(); + MaterialProviders materialProviders = + MaterialProviders.builder().MaterialProvidersConfig(config).build(); + final KmsMasterKeyProvider kmsProvV1 = KmsMasterKeyProvider.builder() .withCredentials(new DefaultAWSCredentialsProviderChain()) @@ -122,11 +163,15 @@ public static Collection data() throws Exception { parseTest(testEntry.getKey(), testEntry.getValue(), keys, jar, kmsProvV1); TestCase testCaseV2 = parseTest(testEntry.getKey(), testEntry.getValue(), keys, jar, kmsProvV2); + TestCase testCaseKeyring = + parseTest( + testEntry.getKey(), testEntry.getValue(), keys, jar, materialProviders, keyVectors); for (DecryptionMethod decryptionMethod : DecryptionMethod.values()) { if (testCaseV1.signaturePolicy.equals(decryptionMethod.signaturePolicy())) { testCases.add(new Object[] {testName, testCaseV1, decryptionMethod}); testCases.add(new Object[] {testName + "-V2", testCaseV2, decryptionMethod}); + testCases.add(new Object[] {testName + "-Keyrings", testCaseKeyring, decryptionMethod}); } } } @@ -166,6 +211,71 @@ private static void cacheData(JarFile jar, String url) throws IOException { } } + @SuppressWarnings("unchecked") + private static TestCase parseTest( + String testName, + Map data, + Map keys, + JarFile jar, + MaterialProviders materialProviders, + KeyVectors keyVectors) + throws IOException { + final String ciphertextURL = (String) data.get("ciphertext"); + cacheData(jar, ciphertextURL); + + Supplier keyringSupplier = + () -> { + final List keyrings = new ArrayList<>(); + for (Map mkEntry : (List>) data.get("master-keys")) { + if (mkEntry.get("type").equals("raw") + && mkEntry.get("encryption-algorithm").equals("rsa")) { + mkEntry.putIfAbsent("padding-hash", "sha1"); + } + + try { + byte[] json = new ObjectMapper().writeValueAsBytes(mkEntry); + GetKeyDescriptionOutput output = + keyVectors.GetKeyDescription( + GetKeyDescriptionInput.builder().json(ByteBuffer.wrap(json)).build()); + + IKeyring testVectorKeyring = + keyVectors.CreateTestVectorKeyring( + TestVectorKeyringInput.builder() + .keyDescription(output.keyDescription()) + .build()); + + keyrings.add(testVectorKeyring); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + IKeyring multiKeyring = + materialProviders.CreateMultiKeyring( + CreateMultiKeyringInput.builder() + .generator(keyrings.get(0)) + .childKeyrings(keyrings) + .build()); + return multiKeyring; + }; + @SuppressWarnings("unchecked") + final Map resultSpec = (Map) data.get("result"); + final ResultMatcher matcher = parseResultMatcher(jar, resultSpec); + + String decryptionMethodSpec = (String) data.get("decryption-method"); + SignaturePolicy signaturePolicy = SignaturePolicy.AllowEncryptAllowDecrypt; + if (decryptionMethodSpec != null) { + if ("streaming-unsigned-only".equals(decryptionMethodSpec)) { + signaturePolicy = SignaturePolicy.AllowEncryptForbidDecrypt; + } else { + throw new IllegalArgumentException( + "Unsupported Decryption Method: " + decryptionMethodSpec); + } + } + + return new TestCase( + testName, ciphertextURL, true, null, keyringSupplier, matcher, signaturePolicy); + } + @SuppressWarnings("unchecked") private static TestCase parseTest( String testName, @@ -266,7 +376,8 @@ private static TestCase parseTest( } } - return new TestCase(testName, ciphertextURL, mkpSupplier, matcher, signaturePolicy); + return new TestCase( + testName, ciphertextURL, false, mkpSupplier, null, matcher, signaturePolicy); } @SuppressWarnings("unchecked") @@ -370,7 +481,8 @@ private static TestCase parseTest( } } - return new TestCase(testName, ciphertextURL, mkpSupplier, matcher, signaturePolicy); + return new TestCase( + testName, ciphertextURL, false, mkpSupplier, null, matcher, signaturePolicy); } private static ResultMatcher parseResultMatcher( @@ -492,19 +604,25 @@ private static class TestCase { private final String name; private final String ciphertextPath; private final ResultMatcher matcher; + private final boolean isKeyring; private final Supplier> mkpSupplier; + private final Supplier keyringSupplier; private final SignaturePolicy signaturePolicy; private TestCase( String name, String ciphertextPath, + boolean isKeyring, Supplier> mkpSupplier, + Supplier keyringSupplier, ResultMatcher matcher, SignaturePolicy signaturePolicy) { this.name = name; this.ciphertextPath = ciphertextPath; this.matcher = matcher; + this.isKeyring = isKeyring; this.mkpSupplier = mkpSupplier; + this.keyringSupplier = keyringSupplier; this.signaturePolicy = signaturePolicy; } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/DecryptionHandlerTest.java b/src/test/java/com/amazonaws/encryptionsdk/internal/DecryptionHandlerTest.java index 728673ec..1c003c13 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/internal/DecryptionHandlerTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/DecryptionHandlerTest.java @@ -22,6 +22,7 @@ import com.amazonaws.encryptionsdk.model.CiphertextHeaders; import com.amazonaws.encryptionsdk.model.CiphertextType; import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterialsHandler; import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; import java.util.ArrayList; @@ -461,7 +462,8 @@ private byte[] getTestMessage( .getMaterialsForEncrypt(encryptionMaterialsRequest); final EncryptionHandler encryptionHandler = - new EncryptionHandler(frameSize_, encryptionMaterials, policy); + new EncryptionHandler( + frameSize_, new EncryptionMaterialsHandler(encryptionMaterials), policy); // create the ciphertext headers by calling encryption handler. final byte[] in = new byte[0]; diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/EncryptionHandlerTest.java b/src/test/java/com/amazonaws/encryptionsdk/internal/EncryptionHandlerTest.java index 8c04373c..7ab9765b 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/internal/EncryptionHandlerTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/EncryptionHandlerTest.java @@ -17,6 +17,7 @@ import com.amazonaws.encryptionsdk.TestUtils; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterialsHandler; import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; import java.util.Arrays; import java.util.Collections; @@ -46,50 +47,63 @@ public void badArguments() { assertThrows( () -> new EncryptionHandler( - frameSize_, testResult.toBuilder().setAlgorithm(null).build(), commitmentPolicy)); + frameSize_, + new EncryptionMaterialsHandler(testResult.toBuilder().setAlgorithm(null).build()), + commitmentPolicy)); assertThrows( () -> new EncryptionHandler( frameSize_, - testResult.toBuilder().setEncryptionContext(null).build(), + new EncryptionMaterialsHandler( + testResult.toBuilder().setEncryptionContext(null).build()), commitmentPolicy)); assertThrows( () -> new EncryptionHandler( frameSize_, - testResult.toBuilder().setEncryptedDataKeys(null).build(), + new EncryptionMaterialsHandler( + testResult.toBuilder().setEncryptedDataKeys(null).build()), commitmentPolicy)); assertThrows( () -> new EncryptionHandler( frameSize_, - testResult.toBuilder().setEncryptedDataKeys(emptyList()).build(), + new EncryptionMaterialsHandler( + testResult.toBuilder().setEncryptedDataKeys(emptyList()).build()), commitmentPolicy)); assertThrows( () -> new EncryptionHandler( frameSize_, - testResult.toBuilder().setCleartextDataKey(null).build(), + new EncryptionMaterialsHandler( + testResult.toBuilder().setCleartextDataKey(null).build()), commitmentPolicy)); assertThrows( () -> new EncryptionHandler( - frameSize_, testResult.toBuilder().setMasterKeys(null).build(), commitmentPolicy)); + frameSize_, + new EncryptionMaterialsHandler(testResult.toBuilder().setMasterKeys(null).build()), + commitmentPolicy)); - assertThrows(() -> new EncryptionHandler(-1, testResult, commitmentPolicy)); + assertThrows( + () -> + new EncryptionHandler( + -1, new EncryptionMaterialsHandler(testResult), commitmentPolicy)); - assertThrows(() -> new EncryptionHandler(frameSize_, testResult, null)); + assertThrows( + () -> new EncryptionHandler(frameSize_, new EncryptionMaterialsHandler(testResult), null)); } @Test(expected = AwsCryptoException.class) public void invalidLenProcessBytes() { final EncryptionHandler encryptionHandler = - new EncryptionHandler(frameSize_, testResult, commitmentPolicy); + new EncryptionHandler( + frameSize_, new EncryptionMaterialsHandler(testResult), commitmentPolicy); final byte[] in = new byte[1]; final byte[] out = new byte[1]; @@ -99,7 +113,8 @@ public void invalidLenProcessBytes() { @Test(expected = AwsCryptoException.class) public void invalidOffsetProcessBytes() { final EncryptionHandler encryptionHandler = - new EncryptionHandler(frameSize_, testResult, commitmentPolicy); + new EncryptionHandler( + frameSize_, new EncryptionMaterialsHandler(testResult), commitmentPolicy); final byte[] in = new byte[1]; final byte[] out = new byte[1]; @@ -109,7 +124,8 @@ public void invalidOffsetProcessBytes() { @Test public void whenEncrypting_headerIVIsZero() throws Exception { final EncryptionHandler encryptionHandler = - new EncryptionHandler(frameSize_, testResult, commitmentPolicy); + new EncryptionHandler( + frameSize_, new EncryptionMaterialsHandler(testResult), commitmentPolicy); assertArrayEquals( new byte[encryptionHandler.getHeaders().getCryptoAlgoId().getNonceLen()], @@ -124,7 +140,9 @@ public void whenConstructWithForbidPolicyAndCommittingAlg_fails() throws Excepti AwsCryptoException.class, () -> new EncryptionHandler( - frameSize_, resultWithV2Alg, CommitmentPolicy.ForbidEncryptAllowDecrypt)); + frameSize_, + new EncryptionMaterialsHandler(resultWithV2Alg), + CommitmentPolicy.ForbidEncryptAllowDecrypt)); } @Test @@ -142,7 +160,10 @@ public void whenConstructWithForbidPolicyAndNonCommittingAlg_succeeds() throws E .getMaterialsForEncrypt(requestForMaterialsWithoutCommitment); EncryptionHandler handler = - new EncryptionHandler(frameSize_, materials, CommitmentPolicy.ForbidEncryptAllowDecrypt); + new EncryptionHandler( + frameSize_, + new EncryptionMaterialsHandler(materials), + CommitmentPolicy.ForbidEncryptAllowDecrypt); assertNotNull(handler); assertEquals(algorithm, handler.getHeaders().getCryptoAlgoId()); } @@ -158,12 +179,16 @@ public void whenConstructWithRequirePolicyAndNonCommittingAlg_fails() throws Exc AwsCryptoException.class, () -> new EncryptionHandler( - frameSize_, resultWithV1Alg, CommitmentPolicy.RequireEncryptRequireDecrypt)); + frameSize_, + new EncryptionMaterialsHandler(resultWithV1Alg), + CommitmentPolicy.RequireEncryptRequireDecrypt)); assertThrows( AwsCryptoException.class, () -> new EncryptionHandler( - frameSize_, resultWithV1Alg, CommitmentPolicy.RequireEncryptAllowDecrypt)); + frameSize_, + new EncryptionMaterialsHandler(resultWithV1Alg), + CommitmentPolicy.RequireEncryptAllowDecrypt)); } @Test @@ -184,7 +209,8 @@ public void whenConstructWithRequirePolicyAndCommittingAlg_succeeds() throws Exc CommitmentPolicy.RequireEncryptRequireDecrypt); for (CommitmentPolicy policy : requireWritePolicies) { - EncryptionHandler handler = new EncryptionHandler(frameSize_, materials, policy); + EncryptionHandler handler = + new EncryptionHandler(frameSize_, new EncryptionMaterialsHandler(materials), policy); assertNotNull(handler); assertEquals(algorithm, handler.getHeaders().getCryptoAlgoId()); } @@ -194,7 +220,8 @@ public void whenConstructWithRequirePolicyAndCommittingAlg_succeeds() throws Exc public void setMaxInputLength() { byte[] plaintext = "Don't warn the tadpoles".getBytes(); final EncryptionHandler encryptionHandler = - new EncryptionHandler(frameSize_, testResult, commitmentPolicy); + new EncryptionHandler( + frameSize_, new EncryptionMaterialsHandler(testResult), commitmentPolicy); encryptionHandler.setMaxInputLength(plaintext.length - 1); assertEquals(encryptionHandler.getMaxInputLength(), (long) plaintext.length - 1); @@ -210,7 +237,8 @@ public void setMaxInputLength() { public void setMaxInputLengthThrowsIfAlreadyOver() { byte[] plaintext = "Don't warn the tadpoles".getBytes(); final EncryptionHandler encryptionHandler = - new EncryptionHandler(frameSize_, testResult, commitmentPolicy); + new EncryptionHandler( + frameSize_, new EncryptionMaterialsHandler(testResult), commitmentPolicy); final byte[] out = new byte[1024]; encryptionHandler.processBytes(plaintext, 0, plaintext.length - 1, out, 0); assertFalse(encryptionHandler.isComplete()); @@ -224,7 +252,8 @@ public void setMaxInputLengthThrowsIfAlreadyOver() { @Test public void setMaxInputLengthAcceptsSmallerValue() { final EncryptionHandler encryptionHandler = - new EncryptionHandler(frameSize_, testResult, commitmentPolicy); + new EncryptionHandler( + frameSize_, new EncryptionMaterialsHandler(testResult), commitmentPolicy); encryptionHandler.setMaxInputLength(100); assertEquals(encryptionHandler.getMaxInputLength(), 100); @@ -235,7 +264,8 @@ public void setMaxInputLengthAcceptsSmallerValue() { @Test public void setMaxInputLengthIgnoresLargerValue() { final EncryptionHandler encryptionHandler = - new EncryptionHandler(frameSize_, testResult, commitmentPolicy); + new EncryptionHandler( + frameSize_, new EncryptionMaterialsHandler(testResult), commitmentPolicy); encryptionHandler.setMaxInputLength(10); assertEquals(encryptionHandler.getMaxInputLength(), 10); diff --git a/src/test/java/com/amazonaws/encryptionsdk/jce/JceMasterKeyTest.java b/src/test/java/com/amazonaws/encryptionsdk/jce/JceMasterKeyTest.java index fb1b242e..e2dc7b87 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/jce/JceMasterKeyTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/jce/JceMasterKeyTest.java @@ -1,6 +1,10 @@ package com.amazonaws.encryptionsdk.jce; -import java.security.*; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.junit.Test; diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KMSTestFixtures.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KMSTestFixtures.java index 641e0c78..3ff1d5cf 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/KMSTestFixtures.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KMSTestFixtures.java @@ -25,9 +25,16 @@ private KMSTestFixtures() { "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; public static final String US_WEST_2_MULTI_REGION_KEY_ID = "arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + public static final String US_WEST_2_KMS_RSA_KEY_ID = + "arn:aws:kms:us-west-2:370957321024:key/mrk-63d386cb70614ea59b32ad65c9315297"; + public static final String ACCOUNT_ID = "658956600833"; public static final String PARTITION = "aws"; public static final String US_WEST_2 = "us-west-2"; public static final String[] TEST_KEY_IDS = new String[] {US_WEST_2_KEY_ID, EU_CENTRAL_1_KEY_ID}; + public static final String TEST_KEYSTORE_NAME = "KeyStoreDdbTable"; + public static final String TEST_LOGICAL_KEYSTORE_NAME = "KeyStoreDdbTable"; + public static final String TEST_KEYSTORE_KMS_KEY_ID = + "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"; } diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/MaxEncryptedDataKeysIntegrationTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/MaxEncryptedDataKeysIntegrationTest.java index 05cacd95..d3c8af27 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/MaxEncryptedDataKeysIntegrationTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/MaxEncryptedDataKeysIntegrationTest.java @@ -3,8 +3,11 @@ package com.amazonaws.encryptionsdk.kms; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertArrayEquals; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.TestUtils; diff --git a/src/test/resources/keys.json b/src/test/resources/keys.json new file mode 100644 index 00000000..304dae5f --- /dev/null +++ b/src/test/resources/keys.json @@ -0,0 +1,214 @@ +{ + "manifest": { + "type": "keys", + "version": 3 + }, + "keys": { + "aes-128": { + "encrypt": true, + "decrypt": true, + "algorithm": "aes", + "type": "symmetric", + "bits": 128, + "encoding": "base64", + "material": "AAECAwQFBgcICRAREhMUFQ==", + "key-id": "aes-128" + }, + "aes-192": { + "encrypt": true, + "decrypt": true, + "algorithm": "aes", + "type": "symmetric", + "bits": 192, + "encoding": "base64", + "material": "AAECAwQFBgcICRAREhMUFRYXGBkgISIj", + "key-id": "aes-192" + }, + "aes-256": { + "encrypt": true, + "decrypt": true, + "algorithm": "aes", + "type": "symmetric", + "bits": 256, + "encoding": "base64", + "material": "AAECAwQFBgcICRAREhMUFRYXGBkgISIjJCUmJygpMDE=", + "key-id": "aes-256" + }, + "rsa-4096-private": { + "encrypt": true, + "decrypt": true, + "algorithm": "rsa", + "type": "private", + "bits": 4096, + "encoding": "pem", + "material": "-----BEGIN PRIVATE KEY-----\nMIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCztGg1gQ8AjCzz\n1VX6StqtW//jBt2ZQBoApaBa7FmLmdr0YlKaeEKSrItGbvA9tBjgsKhrn8gxTGQc\nuxgM92651jRCbQZyjE6W8kodijhGMXsfKJLfgPp2/I7gZ3dqrSZkejFIYLFb/uF/\nTfAQzNyJUldYdeFojSUPqevMgSAusTgv7dXYt4BCO9mxMp35tgyp5k4vazKJVUgB\nTw87AAYZUGugmi94Wb9JSnqUKI3QzaRN7JADZrHdBO1lIBryfCsjtTnZc7NWZ0yJ\nwmzLY+C5b3y17cy44N0rbjI2QciRhqZ4/9SZ/9ImyFQlB3lr9NSndcT4eE5YC6bH\nba0gOUK9lLXVy6TZ+nRZ4dSddoLX03mpYp+8cQpK6DO3L/PeUY/si0WGsXZfWokd\n4ACwvXWSOjotzjwqwTW8q9udbhUvIHfB02JW+ZQ07b209fBpHRDkZuveOTedTN2Q\nQei4dZDjWW5s4cIIE3dXXeaH8yC02ERIeN+aY6eHngSsP2xoDV3sKNN/yDbCqaMS\nq8ZJbo2rvOFxZHa2nWiV+VLugfO6Xj8jeGeR8vopvbEBZZpAq+Dea2xjY4+XMUQ/\nS1HlRwc9+nkJ5LVfODuE3q9EgJbqbiXe7YckWV3ZqQMybW+dLPxEJs9buOntgHFS\nRYmbKky0bti/ZoZlcZtS0zyjVxlqsQIDAQABAoICAEr3m/GWIXgNAkPGX9PGnmtr\n0dgX6SIhh7d1YOwNZV3DlYAV9HfUa5Fcwc1kQny7QRWbHOepBI7sW2dQ9buTDXIh\nVjPP37yxo6d89EZWfxtpUP+yoXL0D4jL257qCvtJuJZ6E00qaVMDhXbiQKABlo8C\n9sVEiABhwXBDZsctpwtTiykTgv6hrrPy2+H8R8MAm0/VcBCAG9kG5r8FCEmIvQKa\ndgvNxrfiWNZuZ6yfLmpJH54SbhG9Kb4WbCKfvh4ihqyi0btRdSM6fMeLgG9o/zrc\ns54B0kHeLOYNVo0j7FQpZBFeSIbmHfln4RKBh7ntrTke/Ejbh3NbiPvxWSP0P067\nSYWPkQpip2q0ION81wSQZ1haP2GewFFu4IEjG3DlqqpKKGLqXrmjMufnildVFpBx\nir+MgvgQfEBoGEx0aElyO7QuRYaEiXeb/BhMZeC5O65YhJrWSuTVizh3xgJWjgfV\naYwYgxN8SBXBhXLIVvnPhadTqsW1C/aevLOk110eSFWcHf+FCK781ykIzcpXoRGX\nOwWcZzC/fmSABS0yH56ow+I0tjdLIEEMhoa4/kkamioHOJ4yyB+W1DO6/DnMyQlx\ng7y2WsAaIEBoWUARy776k70xPPMtYAxzFXI9KhqRVrPfeaRZ+ojeyLyr3GQGyyoo\ncuGRdMUblsmODv4ixmOxAoIBAQDvkznvVYNdP3Eg5vQeLm/qsP6dLejLijBLeq9i\n7DZH2gRpKcflXZxCkRjsKDDE+fgDcBYEp2zYfRIVvgrxlTQZdaSG+GoDcbjbNQn3\ndjCCtOOACioN/vg2zFlX4Bs6Q+NaV7g5qP5SUaxUBjuHLe7Nc+ZkyheMHuNYVLvk\nHL/IoWyANpZYjMUU3xMbL/J29Gz7CPGr8Si28TihAHGfcNgn8S04OQZhTX+bU805\n/+7B4XW47Mthg/u7hlqFl+YIAaSJYvWkEaVP1A9I7Ve0aMDSMWwzTg9cle2uVaL3\n+PTzWY5coBlHKjqAg9ufhYSDhAqBd/JOSlv8RwcA3PDXJ6C/AoIBAQDABmXXYQky\n7phExXBvkLtJt2TBGjjwulf4R8TC6W5F51jJuoqY/mTqYcLcOn2nYGVwoFvPsy/Q\nCTjfODwJBXzbloXtYFR3PWAeL1Y6+7Cm+koMWIPJyVbD5Fzm+gZStM0GwP8FhDt2\nWt8fWEyXmoLdAy6RAwiEmCagEh8o+13oBfwnBllbz7TxaErsUuR+XVgl/iHwztdv\ncdJKyRgaFfWSh9aiO7EMV2rBGWsoX09SRvprPFAGx8Ffm7YcqIk34QXsQyc45Dyn\nCwkvypxHoaB3ot/48FeFm9IubApb/ctv+EgkBfL4S4bdwRXS1rt+0+QihBoFyP2o\nJ91cdm4hEWCPAoIBAQC6l11hFaYZo0bWDGsHcr2B+dZkzxPoKznQH76n+jeQoLIc\nwgjJkK4afm39yJOrZtEOxGaxu0CgIFFMk9ZsL/wC9EhvQt02z4TdXiLkFK5VrtMd\nr0zv16y06VWQhqBOMf/KJlX6uq9RqADi9HO6pkC+zc0cpPXQEWKaMmygju+kMG2U\nMm/IieMZjWCRJTfgBCE5J88qTsqaKagkZXcZakdAXKwOhQN+F2EStiM6UCZB5PrO\nS8dfrO8ML+ki8Zqck8L1qhiNb5zkXtKExy4u+gNr8khGcT6vqqoSxOoH3mPRgOfL\nJnppne8wlwIf7Vq3H8ka6zPSXEHma999gZcmy9t7AoIBAGbQhiLl79j3a0wXMvZp\nVf5IVYgXFDnAbG2hb7a06bhAAIgyexcjzsC4C2+DWdgOgwHkuoPg+062QV8zauGh\nsJKaa6cHlvIpSJeg3NjD/nfJN3CYzCd0yCIm2Z9Ka6xI5iYhm+pGPNhIG4Na8deS\ngVL46yv1pc/o73VxfoGg5UzgN3xlp97Cva0sHEGguHr4W8Qr59xZw3wGQ4SLW35M\nF6qXVNKUh12GSMCPbZK2RXBWVKqqJmca+WzJoJ6DlsT2lQdFhXCus9L007xlDXxF\nC/hCmw1dEl+VaNo2Ou26W/zdwTKYhNlxBwsg4SB8nPNxXIsmlBBY54froFhriNfn\nx/0CggEAUzz+VMtjoEWw2HSHLOXrO4EmwJniNgiiwfX3DfZE4tMNZgqZwLkq67ns\nT0n3b0XfAOOkLgMZrUoOxPHkxFeyLLf7pAEJe7QNB+Qilw8e2zVqtiJrRk6uDIGJ\nSv+yM52zkImZAe2jOdU3KeUZxSMmb5vIoiPBm+tb2WupAg3YdpKn1/jWTpVmV/+G\nUtTLVE6YpAyFp1gMxhutE9vfIS94ek+vt03AoEOlltt6hqZfv3xmY8vGuAjlnj12\nzHaq+fhCRPsbsZkzJ9nIVdXYnNIEGtMGNnxax7tYRej/UXqyazbxHiJ0iPF4PeDn\ndzxtGxpeTBi+KhKlca8SlCdCqYwG6Q==\n-----END PRIVATE KEY-----", + "key-id": "rsa-4096" + }, + "rsa-4096-public": { + "encrypt": true, + "decrypt": false, + "algorithm": "rsa", + "type": "public", + "bits": 4096, + "encoding": "pem", + "material": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAs7RoNYEPAIws89VV+kra\nrVv/4wbdmUAaAKWgWuxZi5na9GJSmnhCkqyLRm7wPbQY4LCoa5/IMUxkHLsYDPdu\nudY0Qm0GcoxOlvJKHYo4RjF7HyiS34D6dvyO4Gd3aq0mZHoxSGCxW/7hf03wEMzc\niVJXWHXhaI0lD6nrzIEgLrE4L+3V2LeAQjvZsTKd+bYMqeZOL2syiVVIAU8POwAG\nGVBroJoveFm/SUp6lCiN0M2kTeyQA2ax3QTtZSAa8nwrI7U52XOzVmdMicJsy2Pg\nuW98te3MuODdK24yNkHIkYameP/Umf/SJshUJQd5a/TUp3XE+HhOWAumx22tIDlC\nvZS11cuk2fp0WeHUnXaC19N5qWKfvHEKSugzty/z3lGP7ItFhrF2X1qJHeAAsL11\nkjo6Lc48KsE1vKvbnW4VLyB3wdNiVvmUNO29tPXwaR0Q5Gbr3jk3nUzdkEHouHWQ\n41lubOHCCBN3V13mh/MgtNhESHjfmmOnh54ErD9saA1d7CjTf8g2wqmjEqvGSW6N\nq7zhcWR2tp1olflS7oHzul4/I3hnkfL6Kb2xAWWaQKvg3mtsY2OPlzFEP0tR5UcH\nPfp5CeS1Xzg7hN6vRICW6m4l3u2HJFld2akDMm1vnSz8RCbPW7jp7YBxUkWJmypM\ntG7Yv2aGZXGbUtM8o1cZarECAwEAAQ==\n-----END PUBLIC KEY-----", + "key-id": "rsa-4096" + }, + "us-west-2-decryptable": { + "encrypt": true, + "decrypt": true, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" + }, + "us-west-2-encrypt-only": { + "encrypt": true, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:key/590fd781-ddde-4036-abec-3e1ab5a5d2ad" + }, + "us-west-2-mrk": { + "encrypt": true, + "decrypt": true, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "us-east-1-mrk": { + "encrypt": true, + "decrypt": true, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "aws:kms:us-west-2:658956600833:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "aws:kms:us-west-2:658956600833:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + ":aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": ":aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn-not:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn-not:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:kms:us-west-2:658956600833:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:kms:us-west-2:658956600833:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn::kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn::kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws-not:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws-not:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:us-west-2:658956600833:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:us-west-2:658956600833:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws::us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws::us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms-not:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms-not:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms:658956600833:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:658956600833:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms::658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms::658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms:us-west-2:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:key:mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms:us-west-2::key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2::key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms:us-west-2:658956600833-not:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833-not:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms:us-west-2:658956600833:mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms:us-west-2:658956600833:/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms:us-west-2:658956600833:key-not/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:key-not/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "arn:aws:kms:us-west-2:658956600833:key": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:key" + }, + "arn:aws:kms:us-west-2:658956600833:key/": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:key/" + }, + "arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7-not": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7-not" + }, + "arn:aws:kms:us-west-2:658956600833:alias/mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "arn:aws:kms:us-west-2:658956600833:alias/mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + }, + "mrk-80bd8ecdcd4342aebd84b7dc9da498a7": { + "encrypt": false, + "decrypt": false, + "type": "aws-kms", + "key-id": "mrk-80bd8ecdcd4342aebd84b7dc9da498a7" + } + } +} diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders new file mode 160000 index 00000000..9c0efc52 --- /dev/null +++ b/submodules/MaterialProviders @@ -0,0 +1 @@ +Subproject commit 9c0efc528a0fe5d58d8e0fe0985a29f40259bc00 From f2224626ed5bb6cdc90ee6d8f8eb61f07d3422ee Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:50:41 -0800 Subject: [PATCH 2/3] chore: update release process (#1888) --- cfn/ci_cd.yml | 2 +- codebuild/ci/release-ci.yml | 10 ++++++++-- codebuild/release/release-prod.yml | 7 +++++++ codebuild/release/release-staging.yml | 8 ++++++++ pom.xml | 2 +- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/cfn/ci_cd.yml b/cfn/ci_cd.yml index abef2108..bee48fc6 100644 --- a/cfn/ci_cd.yml +++ b/cfn/ci_cd.yml @@ -91,7 +91,7 @@ Resources: ## If this value is 0, greater than 25, or not provided then the full history is downloaded with each build project. GitCloneDepth: 0 GitSubmodulesConfig: - FetchSubmodules: false + FetchSubmodules: true InsecureSsl: false ReportBuildStatus: false Type: GITHUB diff --git a/codebuild/ci/release-ci.yml b/codebuild/ci/release-ci.yml index d6b89a23..0b4fd35b 100644 --- a/codebuild/ci/release-ci.yml +++ b/codebuild/ci/release-ci.yml @@ -58,7 +58,13 @@ phases: --package $PACKAGE \ --versions $VERSION_HASH \ --region $REGION; - + # Assume Role to access non-prod resources + - TMP_ROLE=$(aws sts assume-role --role-arn "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Java-Role-us-west-2" --role-session-name "CB-TestVectorResources") + - export TMP_ROLE + - export AWS_ACCESS_KEY_ID=$(echo "${TMP_ROLE}" | jq -r '.Credentials.AccessKeyId') + - export AWS_SECRET_ACCESS_KEY=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') + - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') + - aws sts get-caller-identity # See https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - echo "Setting version in POM to $VERSION_HASH" - mvn versions:set -DnewVersion="$VERSION_HASH" --no-transfer-progress @@ -66,7 +72,7 @@ phases: - | mvn deploy \ -PpublishingCodeArtifact \ - -Dmaven.test.skip=true \ + -Pfast-tests-only \ -DperformRelease \ -Dgpg.homedir="$HOME/mvn_gpg" \ -DautoReleaseAfterClose=true \ diff --git a/codebuild/release/release-prod.yml b/codebuild/release/release-prod.yml index 05c98ce9..0989e64f 100644 --- a/codebuild/release/release-prod.yml +++ b/codebuild/release/release-prod.yml @@ -43,6 +43,13 @@ phases: - cd $CODEBUILD_SRC_DIR build: commands: + # Assume Role to access non-prod resources + - TMP_ROLE=$(aws sts assume-role --role-arn "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Java-Role-us-west-2" --role-session-name "CB-TestVectorResources") + - export TMP_ROLE + - export AWS_ACCESS_KEY_ID=$(echo "${TMP_ROLE}" | jq -r '.Credentials.AccessKeyId') + - export AWS_SECRET_ACCESS_KEY=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') + - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') + - aws sts get-caller-identity - | mvn deploy \ -Ppublishing \ diff --git a/codebuild/release/release-staging.yml b/codebuild/release/release-staging.yml index c53cf1b6..f2246a2e 100644 --- a/codebuild/release/release-staging.yml +++ b/codebuild/release/release-staging.yml @@ -46,6 +46,14 @@ phases: - cd $CODEBUILD_SRC_DIR build: commands: + # Assume Role to access non-prod resources + - TMP_ROLE=$(aws sts assume-role --role-arn "arn:aws:iam::370957321024:role/GitHub-CI-Public-ESDK-Java-Role-us-west-2" --role-session-name "CB-TestVectorResources") + - export TMP_ROLE + - export AWS_ACCESS_KEY_ID=$(echo "${TMP_ROLE}" | jq -r '.Credentials.AccessKeyId') + - export AWS_SECRET_ACCESS_KEY=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SecretAccessKey') + - export AWS_SESSION_TOKEN=$(echo "${TMP_ROLE}" | jq -r '.Credentials.SessionToken') + - aws sts get-caller-identity + - VERSION_HASH="$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)-$CODEBUILD_RESOLVED_SOURCE_VERSION" # See https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html - echo "Setting version in POM to $VERSION_HASH" diff --git a/pom.xml b/pom.xml index 01d41440..f829299e 100644 --- a/pom.xml +++ b/pom.xml @@ -239,7 +239,7 @@ INSTRUCTION COVEREDRATIO - 0.88 + 0.87 BRANCH From aff5b56958ec1d4f2ee294b5b8b7e4e5fdd19957 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 6 Dec 2023 22:09:02 +0000 Subject: [PATCH 3/3] AWS Encryption SDK 3.0.0 Release -- 2023-12-06 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [3.0.0](https://github.com/aws/aws-encryption-sdk-java/compare/v2.4.1...v3.0.0) (2023-12-06) ### ⚠ BREAKING CHANGES * This feature update includes a breaking change that requires AWS SDK v2 Java as a hard dependency. ### Features * Integrate ESDK-Java with AWS Cryptographic Material Providers Library (MPL) for Keyring and CMM Support. ([#1864](https://github.com/aws/aws-encryption-sdk-java/issues/1864)) ([9297e1b](https://github.com/aws/aws-encryption-sdk-java/commit/9297e1bad3860301bcf52b73466ec4b762e4f817)) ### Maintenance * **CFN:** remove unused policy ([#1862](https://github.com/aws/aws-encryption-sdk-java/issues/1862)) ([43e078a](https://github.com/aws/aws-encryption-sdk-java/commit/43e078ae1a99c751dc59a354fbc2c7b70b4afd82)) * **CFN:** update CFN stack to add managed policies to ci and release role ([#1856](https://github.com/aws/aws-encryption-sdk-java/issues/1856)) ([64c970f](https://github.com/aws/aws-encryption-sdk-java/commit/64c970f11ac7e14998c8939f937feeb48eee6c66)) * **deps:** bump org.apache.commons:commons-lang3 from 3.12.0 to 3.13.0 ([#1717](https://github.com/aws/aws-encryption-sdk-java/issues/1717)) ([ec41ae2](https://github.com/aws/aws-encryption-sdk-java/commit/ec41ae2d492699bfbbc7d9ac477105cd2dccbabd)) * fix release script ([#1796](https://github.com/aws/aws-encryption-sdk-java/issues/1796)) ([3617210](https://github.com/aws/aws-encryption-sdk-java/commit/3617210c1e60949aacd75f3c436b3b3010d0db71)) * fix release-ci ([#1883](https://github.com/aws/aws-encryption-sdk-java/issues/1883)) ([92f29d0](https://github.com/aws/aws-encryption-sdk-java/commit/92f29d0c47e09807c46201bb2c100f2172c51294)) * **tests:** update ESDK tests to replace sun.security.* API ([#1852](https://github.com/aws/aws-encryption-sdk-java/issues/1852)) ([ca4c763](https://github.com/aws/aws-encryption-sdk-java/commit/ca4c763fc8b6bf11df2914d37188aa5db5248042)) * update release process ([#1888](https://github.com/aws/aws-encryption-sdk-java/issues/1888)) ([f222462](https://github.com/aws/aws-encryption-sdk-java/commit/f2224626ed5bb6cdc90ee6d8f8eb61f07d3422ee)) * update the javadoc release script ([#1857](https://github.com/aws/aws-encryption-sdk-java/issues/1857)) ([1870a08](https://github.com/aws/aws-encryption-sdk-java/commit/1870a082358d59e32c60d74116d6f43c0efa466b)) --- CHANGELOG.md | 23 +++++++++++++++++++++++ README.md | 2 +- pom.xml | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6db345c..75c83866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## [3.0.0](https://github.com/aws/aws-encryption-sdk-java/compare/v2.4.1...v3.0.0) (2023-12-06) + + +### ⚠ BREAKING CHANGES + +* This feature update includes a breaking change that requires AWS SDK v2 Java as a hard dependency. + +### Features + +* Integrate ESDK-Java with AWS Cryptographic Material Providers Library (MPL) for Keyring and CMM Support. ([#1864](https://github.com/aws/aws-encryption-sdk-java/issues/1864)) ([9297e1b](https://github.com/aws/aws-encryption-sdk-java/commit/9297e1bad3860301bcf52b73466ec4b762e4f817)) + + +### Maintenance + +* **CFN:** remove unused policy ([#1862](https://github.com/aws/aws-encryption-sdk-java/issues/1862)) ([43e078a](https://github.com/aws/aws-encryption-sdk-java/commit/43e078ae1a99c751dc59a354fbc2c7b70b4afd82)) +* **CFN:** update CFN stack to add managed policies to ci and release role ([#1856](https://github.com/aws/aws-encryption-sdk-java/issues/1856)) ([64c970f](https://github.com/aws/aws-encryption-sdk-java/commit/64c970f11ac7e14998c8939f937feeb48eee6c66)) +* **deps:** bump org.apache.commons:commons-lang3 from 3.12.0 to 3.13.0 ([#1717](https://github.com/aws/aws-encryption-sdk-java/issues/1717)) ([ec41ae2](https://github.com/aws/aws-encryption-sdk-java/commit/ec41ae2d492699bfbbc7d9ac477105cd2dccbabd)) +* fix release script ([#1796](https://github.com/aws/aws-encryption-sdk-java/issues/1796)) ([3617210](https://github.com/aws/aws-encryption-sdk-java/commit/3617210c1e60949aacd75f3c436b3b3010d0db71)) +* fix release-ci ([#1883](https://github.com/aws/aws-encryption-sdk-java/issues/1883)) ([92f29d0](https://github.com/aws/aws-encryption-sdk-java/commit/92f29d0c47e09807c46201bb2c100f2172c51294)) +* **tests:** update ESDK tests to replace sun.security.* API ([#1852](https://github.com/aws/aws-encryption-sdk-java/issues/1852)) ([ca4c763](https://github.com/aws/aws-encryption-sdk-java/commit/ca4c763fc8b6bf11df2914d37188aa5db5248042)) +* update release process ([#1888](https://github.com/aws/aws-encryption-sdk-java/issues/1888)) ([f222462](https://github.com/aws/aws-encryption-sdk-java/commit/f2224626ed5bb6cdc90ee6d8f8eb61f07d3422ee)) +* update the javadoc release script ([#1857](https://github.com/aws/aws-encryption-sdk-java/issues/1857)) ([1870a08](https://github.com/aws/aws-encryption-sdk-java/commit/1870a082358d59e32c60d74116d6f43c0efa466b)) + ## [2.4.1](https://github.com/aws/aws-encryption-sdk-java/compare/v2.4.0...v2.4.1) (2023-08-09) diff --git a/README.md b/README.md index a25a7f7f..9b2373dc 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ You can get the latest release from Maven: com.amazonaws aws-encryption-sdk-java - 2.4.1 + 3.0.0 ``` diff --git a/pom.xml b/pom.xml index f829299e..448edeac 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-encryption-sdk-java - 2.4.1 + 3.0.0 jar aws-encryption-sdk-java