From 42ff908c89af1eed190af4668b7d3bfc69016ed0 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Mon, 10 Aug 2020 19:40:37 -0700 Subject: [PATCH 01/15] Initial commit replacing AWS KMS keyring(s) with Spec 2020-07-01_aws-kms-keyring-redesign keyrings --- .../keyring/awskms/CustomClientSupplier.java | 10 +- .../keyring/awskms/CustomKmsClientConfig.java | 4 +- .../awskms/DiscoveryDecryptInRegionOnly.java | 4 +- .../DiscoveryDecryptWithPreferredRegions.java | 6 +- .../MultiRegionRecordPusherExample.java | 6 +- .../encryptionsdk/internal/VersionInfo.java | 8 +- .../encryptionsdk/keyrings/AwsKmsKeyring.java | 188 ------------- .../keyrings/AwsKmsKeyringBuilder.java | 122 --------- .../keyrings/AwsKmsSymmetricKeyring.java | 147 ++++++++++ ...AwsKmsSymmetricMultiCmkKeyringBuilder.java | 175 ++++++++++++ ...ricMultiRegionDiscoveryKeyringBuilder.java | 151 ++++++++++ ...AwsKmsSymmetricRegionDiscoveryKeyring.java | 115 ++++++++ .../keyrings/StandardKeyrings.java | 70 ++--- .../kms/AwsKmsClientSupplier.java | 59 ---- .../encryptionsdk/kms/AwsKmsCmkId.java | 19 ++ .../kms/AwsKmsDataKeyEncryptionDao.java | 64 +++-- .../AwsKmsDataKeyEncryptionDaoBuilder.java | 121 +++++++++ .../kms/DataKeyEncryptionDao.java | 13 - .../encryptionsdk/kms/KmsMasterKey.java | 124 ++++++--- .../kms/KmsMasterKeyProvider.java | 10 +- .../kms/StandardAwsKmsClientSuppliers.java | 257 ------------------ .../encryptionsdk/TestVectorRunner.java | 4 +- ...va => AwsKmsServiceClientBuilderTest.java} | 8 +- 23 files changed, 920 insertions(+), 765 deletions(-) delete mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyring.java delete mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java delete mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java delete mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliers.java rename src/test/java/com/amazonaws/encryptionsdk/kms/{StandardAwsKmsClientSuppliersTest.java => AwsKmsServiceClientBuilderTest.java} (96%) diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java index 7f42a795d..b10400479 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java @@ -12,7 +12,7 @@ import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; import com.amazonaws.services.kms.AWSKMS; import java.util.Arrays; @@ -50,13 +50,13 @@ public class CustomClientSupplier { static class MultiPartitionClientSupplier implements AwsKmsClientSupplier { - private final AwsKmsClientSupplier chinaSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + private final AwsKmsClientSupplier chinaSupplier = AwsKmsServiceClientBuilder.defaultBuilder() .credentialsProvider(new ProfileCredentialsProvider("china")).build(); - private final AwsKmsClientSupplier middleEastSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + private final AwsKmsClientSupplier middleEastSupplier = AwsKmsServiceClientBuilder.defaultBuilder() .credentialsProvider(new ProfileCredentialsProvider("middle-east")).build(); - private final AwsKmsClientSupplier hongKongSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + private final AwsKmsClientSupplier hongKongSupplier = AwsKmsServiceClientBuilder.defaultBuilder() .credentialsProvider(new ProfileCredentialsProvider("hong-kong")).build(); - private final AwsKmsClientSupplier defaultSupplier = StandardAwsKmsClientSuppliers.defaultBuilder().build(); + private final AwsKmsClientSupplier defaultSupplier = AwsKmsServiceClientBuilder.defaultBuilder().build(); /** * Returns a client for the requested region. diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java index ff6d71f93..cb011a062 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java @@ -13,7 +13,7 @@ import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; import java.util.Arrays; import java.util.HashMap; @@ -72,7 +72,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // Use your custom configuration values to configure your client supplier. // For this example we will just use the default credentials provider // but if you need to, you can set a custom credentials provider as well. - final AwsKmsClientSupplier clientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + final AwsKmsClientSupplier clientSupplier = AwsKmsServiceClientBuilder.defaultBuilder() .clientConfiguration(clientConfiguration) .build(); diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java index 828b73e68..26c400994 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java @@ -11,7 +11,7 @@ import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; import java.util.Arrays; import java.util.HashMap; @@ -85,7 +85,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // The keyring only attempts to decrypt data keys if it can get a client for that region, // so this keyring will now ignore any data keys that were encrypted under a CMK in another region. final Keyring decryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() - .awsKmsClientSupplier(StandardAwsKmsClientSuppliers.allowRegionsBuilder(singleton(decryptRegion)).build()) + .awsKmsClientSupplier(AwsKmsServiceClientBuilder.allowRegionsBuilder(singleton(decryptRegion)).build()) .build(); // Encrypt your plaintext data. diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java index b30e053aa..507b555c6 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java @@ -10,7 +10,7 @@ import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; import com.amazonaws.services.kms.AWSKMSClientBuilder; import java.util.Arrays; @@ -89,11 +89,11 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // // One that only works in the local region final Keyring localRegionDecryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() - .awsKmsClientSupplier(StandardAwsKmsClientSuppliers.allowRegionsBuilder(singleton(localRegion)).build()) + .awsKmsClientSupplier(AwsKmsServiceClientBuilder.allowRegionsBuilder(singleton(localRegion)).build()) .build(); // and one that will work in any other region but NOT the local region. final Keyring otherRegionsDecryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() - .awsKmsClientSupplier(StandardAwsKmsClientSuppliers.denyRegionsBuilder(singleton(localRegion)).build()) + .awsKmsClientSupplier(AwsKmsServiceClientBuilder.denyRegionsBuilder(singleton(localRegion)).build()) .build(); // Finally, combine those two keyrings into a multi-keyring. diff --git a/src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java index 747a8c851..fd3811cbc 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java @@ -12,7 +12,7 @@ import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; import com.amazonaws.regions.Region; import com.amazonaws.services.kinesis.AmazonKinesis; import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder; @@ -61,9 +61,9 @@ public MultiRegionRecordPusherExample(final Region[] regions, final String kmsAl .build()); keyrings.add(StandardKeyrings.awsKmsBuilder() - .awsKmsClientSupplier(StandardAwsKmsClientSuppliers + .awsKmsClientSupplier(AwsKmsServiceClientBuilder .allowRegionsBuilder(Collections.singleton(region.getName())) - .baseClientSupplier(StandardAwsKmsClientSuppliers.defaultBuilder() + .baseClientSupplier(AwsKmsServiceClientBuilder.defaultBuilder() .credentialsProvider(credentialsProvider).build()).build()) .generatorKeyId(AwsKmsCmkId.fromString(kmsAliasName)).build()); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/VersionInfo.java b/src/main/java/com/amazonaws/encryptionsdk/internal/VersionInfo.java index eb33ca99d..0ee00c1ec 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/VersionInfo.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/VersionInfo.java @@ -1,11 +1,11 @@ /* * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except * in compliance with the License. A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. @@ -20,7 +20,7 @@ public class VersionInfo { // incremented for design changes that break backward compatibility. public static final String VERSION_NUM = "1"; // incremented for major changes to the implementation - public static final String MAJOR_REVISION_NUM = "1"; + public static final String MAJOR_REVISION_NUM = "7"; // incremented for minor changes to the implementation public static final String MINOR_REVISION_NUM = "0"; // incremented for releases containing an immediate bug fix. diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyring.java deleted file mode 100644 index 76cd8a9b7..000000000 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyring.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.keyrings; - -import com.amazonaws.encryptionsdk.EncryptedDataKey; -import com.amazonaws.encryptionsdk.exception.AwsCryptoException; -import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; -import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; -import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.GenerateDataKeyResult; -import com.amazonaws.encryptionsdk.model.DecryptionMaterials; -import com.amazonaws.encryptionsdk.model.EncryptionMaterials; -import com.amazonaws.encryptionsdk.model.KeyBlob; -import org.apache.commons.lang3.Validate; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; -import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; -import static com.amazonaws.encryptionsdk.kms.AwsKmsCmkId.isKeyIdWellFormed; -import static java.util.Collections.emptyList; -import static java.util.Collections.unmodifiableList; -import static java.util.Objects.requireNonNull; - -/** - * A keyring which interacts with AWS Key Management Service (KMS) to create, - * encrypt, and decrypt data keys using AWS KMS defined Customer Master Keys (CMKs). - */ -class AwsKmsKeyring implements Keyring { - - private final DataKeyEncryptionDao dataKeyEncryptionDao; - private final List keyIds; - private final AwsKmsCmkId generatorKeyId; - private final boolean isDiscovery; - - AwsKmsKeyring(DataKeyEncryptionDao dataKeyEncryptionDao, List keyIds, AwsKmsCmkId generatorKeyId, boolean isDiscovery) { - requireNonNull(dataKeyEncryptionDao, "dataKeyEncryptionDao is required"); - this.dataKeyEncryptionDao = dataKeyEncryptionDao; - this.keyIds = keyIds == null ? emptyList() : unmodifiableList(new ArrayList<>(keyIds)); - this.generatorKeyId = generatorKeyId; - this.isDiscovery = isDiscovery; - - if(isDiscovery) { - Validate.isTrue(generatorKeyId == null && this.keyIds.isEmpty(), - "AWS KMS Discovery keyrings cannot specify any key IDs"); - } else { - Validate.isTrue(generatorKeyId != null || !this.keyIds.isEmpty(), - "GeneratorKeyId or KeyIds are required for non-discovery AWS KMS Keyrings."); - } - - if (this.keyIds.contains(generatorKeyId)) { - throw new IllegalArgumentException("KeyIds should not contain the generatorKeyId"); - } - } - - @Override - public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { - requireNonNull(encryptionMaterials, "encryptionMaterials are required"); - - // If this keyring is a discovery keyring, OnEncrypt MUST return the input encryption materials unmodified. - if (isDiscovery) { - return encryptionMaterials; - } - - EncryptionMaterials resultMaterials = encryptionMaterials; - - // If the input encryption materials do not contain a plaintext data key and this keyring does not - // have a generator defined, OnEncrypt MUST not modify the encryption materials and MUST fail. - if (!encryptionMaterials.hasCleartextDataKey() && generatorKeyId == null) { - throw new AwsCryptoException("Encryption materials must contain either a plaintext data key or a generator"); - } - - final List keyIdsToEncrypt = new ArrayList<>(keyIds); - - // If the input encryption materials do not contain a plaintext data key and a generator is defined onEncrypt - // MUST attempt to generate a new plaintext data key and encrypt that data key by calling KMS GenerateDataKey. - if (!encryptionMaterials.hasCleartextDataKey()) { - resultMaterials = generateDataKey(encryptionMaterials); - } else if (generatorKeyId != null) { - // If this keyring's generator is defined and was not used to generate a data key, OnEncrypt - // MUST also attempt to encrypt the plaintext data key using the CMK specified by the generator. - keyIdsToEncrypt.add(generatorKeyId); - } - - // Given a plaintext data key in the encryption materials, OnEncrypt MUST attempt - // to encrypt the plaintext data key using each CMK specified in it's key IDs list. - for (AwsKmsCmkId keyId : keyIdsToEncrypt) { - resultMaterials = encryptDataKey(keyId, resultMaterials); - } - - return resultMaterials; - } - - private EncryptionMaterials generateDataKey(final EncryptionMaterials encryptionMaterials) { - final GenerateDataKeyResult result = dataKeyEncryptionDao.generateDataKey(generatorKeyId, - encryptionMaterials.getAlgorithm(), encryptionMaterials.getEncryptionContext()); - - return encryptionMaterials - .withCleartextDataKey(result.getPlaintextDataKey(), - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, generatorKeyId.toString(), - KeyringTraceFlag.GENERATED_DATA_KEY)) - .withEncryptedDataKey(new KeyBlob(result.getEncryptedDataKey()), - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, generatorKeyId.toString(), - KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); - } - - private EncryptionMaterials encryptDataKey(final AwsKmsCmkId keyId, final EncryptionMaterials encryptionMaterials) { - final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao.encryptDataKey(keyId, - encryptionMaterials.getCleartextDataKey(), encryptionMaterials.getEncryptionContext()); - - return encryptionMaterials.withEncryptedDataKey(new KeyBlob(encryptedDataKey), - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, keyId.toString(), - KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); - } - - @Override - public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { - requireNonNull(decryptionMaterials, "decryptionMaterials are required"); - requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); - - if (decryptionMaterials.hasCleartextDataKey() || encryptedDataKeys.isEmpty()) { - return decryptionMaterials; - } - - final Set configuredKeyIds = new HashSet<>(keyIds); - - if (generatorKeyId != null) { - configuredKeyIds.add(generatorKeyId); - } - - for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { - if (okToDecrypt(encryptedDataKey, configuredKeyIds)) { - try { - final DecryptDataKeyResult result = dataKeyEncryptionDao.decryptDataKey(encryptedDataKey, - decryptionMaterials.getAlgorithm(), decryptionMaterials.getEncryptionContext()); - - return decryptionMaterials.withCleartextDataKey(result.getPlaintextDataKey(), - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, result.getKeyArn(), - KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); - } catch (CannotUnwrapDataKeyException e) { - continue; - } - } - } - - return decryptionMaterials; - } - - private boolean okToDecrypt(EncryptedDataKey encryptedDataKey, Set configuredKeyIds) { - // Only attempt to decrypt keys provided by KMS - if (!encryptedDataKey.getProviderId().equals(AWS_KMS_PROVIDER_ID)) { - return false; - } - - // If the key ID cannot be parsed, skip it - if(!isKeyIdWellFormed(new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING))) - { - return false; - } - - // If this keyring is a discovery keyring, OnDecrypt MUST attempt to - // decrypt every encrypted data key in the input encrypted data key list - if (isDiscovery) { - return true; - } - - // OnDecrypt MUST attempt to decrypt each input encrypted data key in the input - // encrypted data key list where the key provider info has a value equal to one - // of the ARNs in this keyring's key IDs or the generator - return configuredKeyIds.contains( - AwsKmsCmkId.fromString(new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING))); - } -} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java deleted file mode 100644 index 5a67334e8..000000000 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.keyrings; - -import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; -import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; - -import java.util.List; - -public class AwsKmsKeyringBuilder { - private AwsKmsClientSupplier awsKmsClientSupplier; - private List grantTokens; - private List keyIds; - private AwsKmsCmkId generatorKeyId; - private final boolean isDiscovery; - - private AwsKmsKeyringBuilder(boolean isDiscovery) { - // Use AwsKmsKeyringBuilder.standard() or StandardKeyrings.awsKmsBuilder() to instantiate - // a standard Aws Kms Keyring builder. If an Aws Kms Discovery Keyring builder is needed use - // AwsKmsKeyringBuilder.discovery() or StandardKeyrings.awsKmsDiscoveryBuilder(). - this.isDiscovery = isDiscovery; - } - - /** - * Constructs a new instance of {@code AwsKmsKeyringBuilder} - * - * @return The {@code AwsKmsKeyringBuilder} - */ - public static AwsKmsKeyringBuilder standard() { - return new AwsKmsKeyringBuilder(false); - } - - /** - * Constructs a new instance of {@code AwsKmsKeyringBuilder} that produces an AWS KMS Discovery keyring. - * AWS KMS Discovery keyrings do not specify any CMKs to decrypt with, and thus will attempt to decrypt - * using any encrypted data key in an encrypted message. AWS KMS Discovery keyrings do not perform encryption. - * - * @return The {@code AwsKmsKeyringBuilder} - */ - public static AwsKmsKeyringBuilder discovery() { - return new AwsKmsKeyringBuilder(true); - } - - /** - * A function that returns an AWS KMS client that can make GenerateDataKey, Encrypt, and Decrypt calls in - * a particular AWS region. If this is not supplied, the default AwsKmsClientSupplier will - * be used. AwsKmsClientSupplier.builder() can be used to construct this type. - * - * @param awsKmsClientSupplier The AWS KMS client supplier - * @return The AwsKmsKeyringBuilder, for method chaining - */ - public AwsKmsKeyringBuilder awsKmsClientSupplier(AwsKmsClientSupplier awsKmsClientSupplier) { - this.awsKmsClientSupplier = awsKmsClientSupplier; - return this; - } - - /** - * A list of string grant tokens to be included in all KMS calls. - * - * @param grantTokens The list of grant tokens - * @return The AwsKmsKeyringBuilder, for method chaining - */ - public AwsKmsKeyringBuilder grantTokens(List grantTokens) { - this.grantTokens = grantTokens; - return this; - } - - /** - * A list of {@link AwsKmsCmkId}s in ARN, CMK Alias, or ARN Alias format identifying AWS KMS CMKs - * used for encrypting and decrypting data keys. - * - * @param keyIds The list of AWS KMS CMKs - * @return The AwsKmsKeyringBuilder, for method chaining - */ - public AwsKmsKeyringBuilder keyIds(List keyIds) { - this.keyIds = keyIds; - return this; - } - - /** - * An {@link AwsKmsCmkId} in ARN, CMK Alias, or ARN Alias format that identifies a - * AWS KMS CMK responsible for generating a data key, as well as encrypting and - * decrypting data keys . - * - * @param generatorKeyId An {@link AwsKmsCmkId} in ARN, CMK Alias, or ARN Alias format that identifies a - * AWS KMS CMK responsible for generating a data key, as well as encrypting and - * decrypting data keys. - * @return The AwsKmsKeyringBuilder, for method chaining - */ - public AwsKmsKeyringBuilder generatorKeyId(AwsKmsCmkId generatorKeyId) { - this.generatorKeyId = generatorKeyId; - return this; - } - - /** - * Constructs the {@link Keyring} instance. - * - * @return The {@link Keyring} instance - */ - public Keyring build() { - if (awsKmsClientSupplier == null) { - awsKmsClientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder().build(); - } - - return new AwsKmsKeyring(DataKeyEncryptionDao.awsKms(awsKmsClientSupplier, grantTokens), - keyIds, generatorKeyId, isDiscovery); - } - -} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java new file mode 100644 index 000000000..93972f640 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java @@ -0,0 +1,147 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.KeyBlob; + +import java.util.List; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; +import static java.util.Objects.requireNonNull; + +/** + * A keyring which interacts with AWS Key Management Service (KMS) to create, + * encrypt, and decrypt data keys using an AWS KMS defined Customer Master Key (CMK). + */ +public class AwsKmsSymmetricKeyring implements Keyring { + + private final DataKeyEncryptionDao dataKeyEncryptionDao; + private final AwsKmsCmkId keyName; + + AwsKmsSymmetricKeyring(DataKeyEncryptionDao dataKeyEncryptionDao, AwsKmsCmkId keyName) { + requireNonNull(dataKeyEncryptionDao, "dataKeyEncryptionDao is required"); + requireNonNull(keyName, "keyName is required"); + + this.dataKeyEncryptionDao = dataKeyEncryptionDao; + this.keyName = keyName; + } + + @Override + public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { + requireNonNull(encryptionMaterials, "encryptionMaterials are required"); + + EncryptionMaterials resultMaterials = encryptionMaterials; + + // If the input encryption materials do not contain a plaintext data key, + // onEncrypt MUST attempt to generate a new plaintext data key + // and encrypt that data key by calling KMS GenerateDataKey. + if (!encryptionMaterials.hasCleartextDataKey()) { + resultMaterials = generateDataKey(encryptionMaterials); + } + + // Given a plaintext data key in the encryption materials, OnEncrypt MUST attempt + // to encrypt the plaintext data key using the provided key name + final AwsKmsCmkId keyNameToEncrypt = AwsKmsCmkId.fromString(keyName.toString()); + resultMaterials = encryptDataKey(keyNameToEncrypt, resultMaterials); + return resultMaterials; + } + + private EncryptionMaterials generateDataKey(final EncryptionMaterials encryptionMaterials) { + final DataKeyEncryptionDao.GenerateDataKeyResult result = dataKeyEncryptionDao.generateDataKey( + keyName, encryptionMaterials.getAlgorithm(), encryptionMaterials.getEncryptionContext()); + + return encryptionMaterials + .withCleartextDataKey( + result.getPlaintextDataKey(), + new KeyringTraceEntry( + AWS_KMS_PROVIDER_ID, + keyName.toString(), + KeyringTraceFlag.GENERATED_DATA_KEY)) + .withEncryptedDataKey( + new KeyBlob(result.getEncryptedDataKey()), + new KeyringTraceEntry( + AWS_KMS_PROVIDER_ID, + keyName.toString(), + KeyringTraceFlag.ENCRYPTED_DATA_KEY, + KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + } + + private EncryptionMaterials encryptDataKey(final AwsKmsCmkId keyId, final EncryptionMaterials encryptionMaterials) { + final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao.encryptDataKey( + keyId, encryptionMaterials.getCleartextDataKey(), encryptionMaterials.getEncryptionContext()); + + return encryptionMaterials.withEncryptedDataKey( + new KeyBlob(encryptedDataKey), + new KeyringTraceEntry( + AWS_KMS_PROVIDER_ID, + keyId.toString(), + KeyringTraceFlag.ENCRYPTED_DATA_KEY, + KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + } + + @Override + public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + requireNonNull(decryptionMaterials, "decryptionMaterials are required"); + requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); + + if (decryptionMaterials.hasCleartextDataKey() || encryptedDataKeys.isEmpty()) { + return decryptionMaterials; + } + + final AwsKmsCmkId configuredKeyName = AwsKmsCmkId.fromString(keyName.toString()); + for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { + if (okToDecrypt(encryptedDataKey, configuredKeyName)) { + try { + final DataKeyEncryptionDao.DecryptDataKeyResult result = dataKeyEncryptionDao.decryptDataKey( + encryptedDataKey, decryptionMaterials.getAlgorithm(), decryptionMaterials.getEncryptionContext()); + + return decryptionMaterials.withCleartextDataKey(result.getPlaintextDataKey(), + new KeyringTraceEntry( + AWS_KMS_PROVIDER_ID, + result.getKeyArn(), + KeyringTraceFlag.DECRYPTED_DATA_KEY, + KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); + } catch (CannotUnwrapDataKeyException e) { + continue; + } + } + } + + return decryptionMaterials; + } + + private boolean okToDecrypt(EncryptedDataKey encryptedDataKey, AwsKmsCmkId configuredKeyName) { + // Only attempt to decrypt keys provided by KMS + if (!encryptedDataKey.getProviderId().equals(AWS_KMS_PROVIDER_ID)) { + return false; + } + + // If the key name cannot be parsed, skip it + final String keyName = new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING); + if (!AwsKmsCmkId.isKeyIdWellFormed(keyName)) { + return false; + } + + // OnDecrypt MUST attempt to decrypt each input encrypted data key in the input encrypted data key list + // where the key provider info has a value equal to the keyring's key name + return configuredKeyName.equals(AwsKmsCmkId.fromString(keyName)); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java new file mode 100644 index 000000000..b1e214680 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java @@ -0,0 +1,175 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.arn.Arn; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.AwsKmsDataKeyEncryptionDaoBuilder; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class AwsKmsSymmetricMultiCmkKeyringBuilder { + + private static final String NULL_REGION = "null-region"; + + private List childKeyNames; + private AwsKmsCmkId generatorKeyName; + private List grantTokens; + private AWSCredentialsProvider credentialsProvider; + private ClientConfiguration clientConfiguration; + + private AwsKmsSymmetricMultiCmkKeyringBuilder() { + // Use AwsKmsSymmetricMultiCmkKeyringBuilder.standard() or StandardKeyrings.awsKmsSymmetricMultiCmkBuilder() + // to instantiate a standard AWS KMS symmetric multi-CMK keyring Builder. + // If an AWS KMS symmetric multi-region discovery keyring builder is needed use + // AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.standard() or + // StandardKeyrings.awsKmsSymmetricMultiRegionDiscoveryKeyringBuilder(). + } + + /** + * Constructs a new instance of {@code AwsKmsSymmetricMultiCmkKeyringBuilder} + * + * @return The {@code AwsKmsSymmetricMultiCmkKeyringBuilder} + */ + public static AwsKmsSymmetricMultiCmkKeyringBuilder standard() { + return new AwsKmsSymmetricMultiCmkKeyringBuilder(); + } + + /** + * An optional AWSCredentialsProvider for use with every AWS SDK KMS service client. + * + * @param credentialsProvider Custom AWSCredentialsProvider to use. + * @return The AwsKmsSymmetricMultiCmkKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiCmkKeyringBuilder credentialsProvider(AWSCredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * An optional ClientConfiguration for use with every AWS SDK KMS service client. + * + * @param clientConfiguration Custom ClientConfiguration to use. + * @return The AwsKmsSymmetricMultiCmkKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiCmkKeyringBuilder clientConfiguration(ClientConfiguration clientConfiguration) { + this.clientConfiguration = clientConfiguration; + return this; + } + + /** + * A list of string grant tokens to be included in all KMS calls. + * + * @param grantTokens The list of grant tokens. + * @return The AwsKmsSymmetricMultiCmkKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiCmkKeyringBuilder grantTokens(List grantTokens) { + this.grantTokens = grantTokens; + return this; + } + + /** + * A list of {@link AwsKmsCmkId}s in ARN, CMK Alias, or ARN Alias format identifying AWS KMS CMKs + * used for encrypting and decrypting data keys. + * + * @param childKeyNames The list of key names identifying AWS KMS CMKs. + * @return The AwsKmsSymmetricMultiCmkKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiCmkKeyringBuilder keyNames(List childKeyNames) { + this.childKeyNames = childKeyNames; + return this; + } + + /** + * An {@link AwsKmsCmkId} in ARN, CMK Alias, or ARN Alias format that identifies a + * AWS KMS CMK responsible for generating a data key, as well as encrypting and + * decrypting data keys. + * + * @param generatorKeyName An {@link AwsKmsCmkId} in ARN, CMK Alias, or ARN Alias format that identifies a + * AWS KMS CMK responsible for generating a data key, as well as encrypting and + * decrypting data keys. + * @return The AwsKmsSymmetricMultiCmkKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiCmkKeyringBuilder generator(AwsKmsCmkId generatorKeyName) { + this.generatorKeyName = generatorKeyName; + return this; + } + + /** + * Constructs the {@link Keyring} instance. + * + * @return The {@link Keyring} instance + */ + public Keyring build() { + // A mapping of AWS region to DataKeyEncryptionDao + final Map clientMapping = new ConcurrentHashMap<>(); + + // First construct the generator keyring + Keyring generatorKeyring = null; + if (this.generatorKeyName != null) { + // If we have an ARN, obtain the region from the ARN (to specify the region of the AWS SDK KMS service client) + final Optional generatorArn = AwsKmsCmkId.getArnFromKeyName(this.generatorKeyName.toString()); + final String generatorRegion = generatorArn.isPresent() ? generatorArn.get().getRegion() : null; + final DataKeyEncryptionDao generatorDao = constructDataKeyEncryptionDao(generatorRegion); + // Add the client to the mapping with the region key if available + // This prevents re-creating multiple clients for the same region during a single build call + clientMapping.put(generatorRegion == null ? NULL_REGION : generatorRegion, generatorDao); + // Construct the generator keyring + generatorKeyring = new AwsKmsSymmetricKeyring(generatorDao, this.generatorKeyName); + } + + // Next, construct the child keyrings + List childKeyrings = new ArrayList<>(); + if (this.childKeyNames != null) { + for (final AwsKmsCmkId keyName : this.childKeyNames) { + // If we have an ARN, obtain the region from the ARN (to specify the region of the AWS SDK KMS service client) + final Optional childArn = AwsKmsCmkId.getArnFromKeyName(keyName.toString()); + final String childRegion = childArn.isPresent() ? childArn.get().getRegion() : null; + final String childKey = childRegion == null ? NULL_REGION : childRegion; + + // Check if a client already exists for the given region + // and use the existing dao or construct a new one + if (clientMapping.containsKey(childKey)) { + final Keyring childKeyring = new AwsKmsSymmetricKeyring(clientMapping.get(childKey), keyName); + childKeyrings.add(childKeyring); + } else { + final DataKeyEncryptionDao childDao = constructDataKeyEncryptionDao(childRegion); + clientMapping.put(childKey, childDao); + final Keyring childKeyring = new AwsKmsSymmetricKeyring(childDao, keyName); + childKeyrings.add(childKeyring); + } + } + } + + // Finally, construct a multi-keyring + return new MultiKeyring(generatorKeyring, childKeyrings); + } + + private DataKeyEncryptionDao constructDataKeyEncryptionDao(String regionId) { + return AwsKmsDataKeyEncryptionDaoBuilder + .defaultBuilder() + .clientConfiguration(clientConfiguration) + .credentialsProvider(credentialsProvider) + .grantTokens(grantTokens) + .regionId(regionId) + .build(); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java new file mode 100644 index 000000000..83fbb9c80 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java @@ -0,0 +1,151 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.encryptionsdk.kms.AwsKmsDataKeyEncryptionDaoBuilder; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder { + + private static final String NULL_REGION = "null-region"; + + private List regionIds; + private String awsAccountId; + private List grantTokens; + private AWSCredentialsProvider credentialsProvider; + private ClientConfiguration clientConfiguration; + + private AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder() { + // Use AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.standard() + // or StandardKeyrings.awsKmsSymmetricMultiRegionDiscoveryKeyringBuilder() + // to instantiate a standard AWS KMS symmetric multi-region discovery keyring Builder. + // If an AWS KMS symmetric multi-CMK keyring builder is needed use + // AwsKmsSymmetricMultiCmkKeyringBuilder.standard() or + // StandardKeyrings.awsKmsSymmetricMultiCmkBuilder(). + } + + /** + * Constructs a new instance of {@code AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder} + * + * @return The {@code AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder} + */ + public static AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder standard() { + return new AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder(); + } + + /** + * An optional AWSCredentialsProvider for use with every AWS SDK KMS service client. + * + * @param credentialsProvider Custom AWSCredentialsProvider to use. + * @return The AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder credentialsProvider(AWSCredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * An optional ClientConfiguration for use with every AWS SDK KMS service client. + * + * @param clientConfiguration Custom ClientConfiguration to use. + * @return The AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder clientConfiguration(ClientConfiguration clientConfiguration) { + this.clientConfiguration = clientConfiguration; + return this; + } + + /** + * A list of string grant tokens to be included in all KMS calls. + * + * @param grantTokens The list of grant tokens. + * @return The AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder grantTokens(List grantTokens) { + this.grantTokens = grantTokens; + return this; + } + + /** + * A list of AWS regions Ids identifying the AWS regions to attempt decryption in. + * + * @param regionIds The list of regions. + * @return The AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder regions(List regionIds) { + this.regionIds = regionIds; + return this; + } + + /** + * An AWS Account Id to limit decryption to encrypted data keys for a specific AWS account. + * + * @param awsAccountId An AWS account id. + * @return The AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder, for method chaining + */ + public AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder awsAccountId(String awsAccountId) { + this.awsAccountId = awsAccountId; + return this; + } + + /** + * Constructs the {@link Keyring} instance. + * + * @return The {@link Keyring} instance + */ + public Keyring build() { + // A mapping of AWS region to DataKeyEncryptionDao + final Map clientMapping = new ConcurrentHashMap<>(); + + // Construct each AwsKmsSymmetricRegionDiscoveryKeyring + List discoveryKeyrings = new ArrayList<>(); + if (this.regionIds != null) { + for (final String region : this.regionIds) { + final String regionKey = region == null ? NULL_REGION : region; + + // Check if a client already exists for the given region + // and use the existing dao or construct a new one + if (clientMapping.containsKey(regionKey)) { + final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(clientMapping.get(regionKey), region, this.awsAccountId); + discoveryKeyrings.add(discoveryKeyring); + } else { + final DataKeyEncryptionDao discoveryDao = constructDataKeyEncryptionDao(region); + clientMapping.put(regionKey, discoveryDao); + final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(discoveryDao, region, this.awsAccountId); + discoveryKeyrings.add(discoveryKeyring); + } + } + } + + // Finally, construct a multi-keyring + return new MultiKeyring(null, discoveryKeyrings); + } + + private DataKeyEncryptionDao constructDataKeyEncryptionDao(String regionId) { + return AwsKmsDataKeyEncryptionDaoBuilder + .defaultBuilder() + .clientConfiguration(clientConfiguration) + .credentialsProvider(credentialsProvider) + .grantTokens(grantTokens) + .regionId(regionId) + .build(); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java new file mode 100644 index 000000000..b7244b233 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java @@ -0,0 +1,115 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.arn.Arn; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; + +import java.util.List; +import java.util.Optional; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; +import static java.util.Objects.requireNonNull; + +/** + * A keyring which interacts with AWS Key Management Service (KMS) + * to decrypt data keys using a specified AWS SDK KMS service client for a specific AWS region. + */ +public class AwsKmsSymmetricRegionDiscoveryKeyring implements Keyring { + + private final DataKeyEncryptionDao dataKeyEncryptionDao; + private final Optional awsAccountId; + private final String awsRegion; + + AwsKmsSymmetricRegionDiscoveryKeyring(DataKeyEncryptionDao dataKeyEncryptionDao, String awsRegion, String awsAccountId) { + requireNonNull(dataKeyEncryptionDao, "dataKeyEncryptionDao is required"); + requireNonNull(awsRegion, "AWS region is required"); + + this.dataKeyEncryptionDao = dataKeyEncryptionDao; + this.awsRegion = awsRegion; + this.awsAccountId = Optional.ofNullable(awsAccountId); + } + + @Override + public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { + throw new AwsCryptoException("The AWS KMS Region Discovery keyring cannot be used for encryption"); + } + + @Override + public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + requireNonNull(decryptionMaterials, "decryptionMaterials are required"); + requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); + + if (decryptionMaterials.hasCleartextDataKey() || encryptedDataKeys.isEmpty()) { + return decryptionMaterials; + } + + for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { + if (okToDecrypt(encryptedDataKey)) { + try { + final DataKeyEncryptionDao.DecryptDataKeyResult result = dataKeyEncryptionDao.decryptDataKey( + encryptedDataKey, decryptionMaterials.getAlgorithm(), decryptionMaterials.getEncryptionContext()); + + return decryptionMaterials.withCleartextDataKey( + result.getPlaintextDataKey(), + new KeyringTraceEntry( + AWS_KMS_PROVIDER_ID, + result.getKeyArn(), + KeyringTraceFlag.DECRYPTED_DATA_KEY, + KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); + } catch (CannotUnwrapDataKeyException e) { + continue; + } + } + } + + return decryptionMaterials; + } + + private boolean okToDecrypt(EncryptedDataKey encryptedDataKey) { + // Only attempt to decrypt keys provided by KMS + if (!encryptedDataKey.getProviderId().equals(AWS_KMS_PROVIDER_ID)) { + return false; + } + + // If the key ID cannot be parsed, skip it + String keyName = new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING); + if (!AwsKmsCmkId.isKeyIdWellFormed(keyName)) { + return false; + } + + // Determine the ARN + final Optional arn = AwsKmsCmkId.getArnFromKeyName(keyName); + if (!arn.isPresent()) { + return false; + } + + // If an AWS account ID is provided, + // this keyring must only decrypt encrypted data keys + // that were encrypted using an AWS KMS CMK in that AWS account + if (awsAccountId.isPresent() && !awsAccountId.get().equals(arn.get().getAccountId())) { + return false; + } + + // Finally, determine if the region matches the keyring's client's region + return arn.get().getRegion().equalsIgnoreCase(awsRegion); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java index 3772941b8..5d730c53a 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java @@ -14,7 +14,6 @@ package com.amazonaws.encryptionsdk.keyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; import java.util.Arrays; import java.util.List; @@ -47,54 +46,59 @@ public static RawAesKeyringBuilder rawAesBuilder() { public static RawRsaKeyringBuilder rawRsaBuilder() { return RawRsaKeyringBuilder.standard(); } - - /** - * Constructs a {@code Keyring} which interacts with AWS Key Management Service (KMS) to create, - * encrypt, and decrypt data keys using the supplied AWS KMS defined Customer Master Key (CMK). - * Use {@link #awsKmsBuilder()} for more advanced configuration using an {@link AwsKmsKeyringBuilder} + + /** + * Constructs a {@code MultiKeyring} which interacts with AWS Key Management Service (KMS) to create, + * encrypt, and decrypt data keys using the supplied AWS KMS defined Customer Master Keys (CMKs). * - * @param generatorKeyId An {@link AwsKmsCmkId} in ARN, CMK Alias, ARN Alias or Key Id format that identifies a - * AWS KMS CMK responsible for generating a data key, as well as encrypting and - * decrypting data keys . + * @param generatorKeyName An {@link AwsKmsCmkId} in ARN, CMK Alias, ARN Alias or Key Id format that identifies an + * AWS KMS CMK responsible for generating a data key, as well as encrypting and + * decrypting data keys. + * @param keyNames A list of {@link AwsKmsCmkId} in ARN, CMK Alias, ARN Alias or Key Id format that identifies + * AWS KMS CMKs responsible for encrypting and decrypting data keys. * @return The {@code Keyring} */ - public static Keyring awsKms(AwsKmsCmkId generatorKeyId) { - return AwsKmsKeyringBuilder.standard() - .generatorKeyId(generatorKeyId) - .build(); + public static Keyring awsKmsSymmetricMultiCmk(AwsKmsCmkId generatorKeyName, List keyNames) { + return AwsKmsSymmetricMultiCmkKeyringBuilder.standard() + .generator(generatorKeyName) + .keyNames(keyNames) + .build(); } /** - * Returns a {@link AwsKmsKeyringBuilder} for use in constructing a keyring which interacts with + * Returns a {@link AwsKmsSymmetricMultiCmkKeyringBuilder} for use in constructing a keyring which interacts with * AWS Key Management Service (KMS) to create, encrypt, and decrypt data keys using AWS KMS defined * Customer Master Keys (CMKs). * - * @return The {@link AwsKmsKeyringBuilder} + * @return The {@link AwsKmsSymmetricMultiCmkKeyringBuilder} + */ + public static AwsKmsSymmetricMultiCmkKeyringBuilder awsKmsSymmetricMultiCmkBuilder() { + return AwsKmsSymmetricMultiCmkKeyringBuilder.standard(); + } + + /** + * Constructs a {@code MultiKeyring} which interacts with AWS Key Management Service (KMS) to decrypt data keys + * using in the specified AWS regions. + * + * @param regionIds A list of strings representing AWS regions to attempt decryption in. + * @return The {@code Keyring} */ - public static AwsKmsKeyringBuilder awsKmsBuilder() { - return AwsKmsKeyringBuilder.standard(); + public static Keyring awsKmsSymmetricMultiRegionDiscovery(List regionIds) { + return AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.standard() + .regions(regionIds) + .build(); } /** - * Returns an {@link AwsKmsKeyringBuilder} for use in constructing an AWS KMS Discovery keyring. - * AWS KMS Discovery keyrings do not specify any CMKs to decrypt with, and thus will attempt to decrypt - * using any encrypted data key in an encrypted message. AWS KMS Discovery keyrings do not perform encryption. - *

- * To create an AWS KMS Regional Discovery Keyring, use {@link StandardAwsKmsClientSuppliers#allowRegionsBuilder} or - * {@link StandardAwsKmsClientSuppliers#denyRegionsBuilder} to specify which regions to include/exclude. - *

- * For example, to include only CMKs in the us-east-1 region: - *
-     * StandardKeyrings.awsKmsDiscovery()
-     *             .awsKmsClientSupplier(
-     *                     StandardAwsKmsClientSuppliers.allowRegionsBuilder(Collections.singleton("us-east-1")).build()
-     *             .build();
-     * 
+ * Returns an {@link AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder} + * for use in constructing an AWS KMS multi-region discovery keyring. + * 'Discovery' keyrings do not specify any CMKs to decrypt with, and thus will attempt to decrypt + * using any encrypted data key in the specified region(s). 'Discovery' keyrings do not perform encryption. * * @return The {@code AwsKmsKeyringBuilder} */ - public static AwsKmsKeyringBuilder awsKmsDiscoveryBuilder() { - return AwsKmsKeyringBuilder.discovery(); + public static AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder awsKmsSymmetricMultiRegionDiscoveryKeyringBuilder() { + return AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.standard(); } /** diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java deleted file mode 100644 index 92a0bfee1..000000000 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.kms; - -import com.amazonaws.arn.Arn; -import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; -import com.amazonaws.services.kms.AWSKMS; - -import javax.annotation.Nullable; - -import static java.util.Objects.requireNonNull; - -/** - * Represents a function that accepts an AWS region and returns an {@code AWSKMS} client for that region. The - * function should be able to handle when the region is null. - */ -@FunctionalInterface -public interface AwsKmsClientSupplier { - - /** - * Gets an {@code AWSKMS} client for the given regionId. - * - * @param regionId The AWS region (or null) - * @return The AWSKMS client - * @throws UnsupportedRegionException if a regionId is specified that this - * client supplier is configured to not allow. - */ - AWSKMS getClient(@Nullable String regionId) throws UnsupportedRegionException; - - /** - * Parses region from the given key id (if possible) and passes that region to the - * given clientSupplier to produce an {@code AWSKMS} client. - * - * @param keyId The Amazon Resource Name, Key Alias, Alias ARN or KeyId - * @param clientSupplier The client supplier - * @return AWSKMS The client - */ - static AWSKMS getClientByKeyId(AwsKmsCmkId keyId, AwsKmsClientSupplier clientSupplier) { - requireNonNull(keyId, "keyId is required"); - requireNonNull(clientSupplier, "clientSupplier is required"); - - if(keyId.isArn()) { - return clientSupplier.getClient(Arn.fromString(keyId.toString()).getRegion()); - } - - return clientSupplier.getClient(null); - } -} diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkId.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkId.java index 200308514..7008febfc 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkId.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkId.java @@ -19,6 +19,7 @@ import org.apache.commons.lang3.Validate; import java.util.Objects; +import java.util.Optional; /** * A representation of an AWS KMS Customer Master Key Identifier, which may be one either a @@ -109,6 +110,24 @@ public static boolean isKeyIdWellFormed(String keyId) { } } + /** + * Returns the ARN from a provided keyName or null if the keyName is not an Amazon Resource Name (ARN) + * + * @param keyName The key ID, key Amazon Resource Name (ARN), alias name, or alias ARN + * @return ARN (or null)) + */ + public static Optional getArnFromKeyName(String keyName) { + if (StringUtils.isBlank(keyName) || !keyName.startsWith(ARN_PREFIX)) { + return Optional.empty(); + } + + try { + return Optional.of(Arn.fromString(keyName)); + } catch (IllegalArgumentException e) { + return Optional.empty(); + } + } + /** * Returns true if this AwsKmsCmkId is in the Amazon Resource Name (ARN) format. * diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java index cefbf97f3..aa3507cc2 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java @@ -15,6 +15,8 @@ import com.amazonaws.AmazonServiceException; import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.ClientConfiguration; +import com.amazonaws.RequestClientOptions; import com.amazonaws.encryptionsdk.CryptoAlgorithm; import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; @@ -23,6 +25,7 @@ import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; import com.amazonaws.encryptionsdk.internal.VersionInfo; import com.amazonaws.encryptionsdk.model.KeyBlob; +import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.model.DecryptRequest; import com.amazonaws.services.kms.model.EncryptRequest; import com.amazonaws.services.kms.model.GenerateDataKeyRequest; @@ -37,7 +40,6 @@ import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; -import static com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier.getClientByKeyId; import static java.util.Objects.requireNonNull; import static org.apache.commons.lang3.Validate.isTrue; @@ -48,13 +50,20 @@ */ class AwsKmsDataKeyEncryptionDao implements DataKeyEncryptionDao, KmsMethods { - private final AwsKmsClientSupplier clientSupplier; + private final AWSKMS client; + private final boolean canAppendUserAgentString; private List grantTokens; - AwsKmsDataKeyEncryptionDao(AwsKmsClientSupplier clientSupplier, List grantTokens) { - requireNonNull(clientSupplier, "clientSupplier is required"); + AwsKmsDataKeyEncryptionDao(AWSKMS client, List grantTokens) { + // Assume the user agent string cannot be appended (default) + this(client, grantTokens, false); + } + + AwsKmsDataKeyEncryptionDao(AWSKMS client, List grantTokens, boolean canAppendUserAgentString) { + requireNonNull(client, "client is required"); - this.clientSupplier = clientSupplier; + this.canAppendUserAgentString = canAppendUserAgentString; + this.client = client; this.grantTokens = grantTokens == null ? new ArrayList<>() : new ArrayList<>(grantTokens); } @@ -67,13 +76,12 @@ public GenerateDataKeyResult generateDataKey(AwsKmsCmkId keyId, CryptoAlgorithm final com.amazonaws.services.kms.model.GenerateDataKeyResult kmsResult; try { - kmsResult = getClientByKeyId(keyId, clientSupplier) - .generateDataKey(updateUserAgent( - new GenerateDataKeyRequest() - .withKeyId(keyId.toString()) - .withNumberOfBytes(algorithmSuite.getDataKeyLength()) - .withEncryptionContext(encryptionContext) - .withGrantTokens(grantTokens))); + kmsResult = client.generateDataKey(updateUserAgent( + new GenerateDataKeyRequest() + .withKeyId(keyId.toString()) + .withNumberOfBytes(algorithmSuite.getDataKeyLength()) + .withEncryptionContext(encryptionContext) + .withGrantTokens(grantTokens))); } catch (final AmazonServiceException ex) { throw new AwsCryptoException(ex); } @@ -100,12 +108,12 @@ public EncryptedDataKey encryptDataKey(final AwsKmsCmkId keyId, SecretKey plaint final com.amazonaws.services.kms.model.EncryptResult kmsResult; try { - kmsResult = getClientByKeyId(keyId, clientSupplier) - .encrypt(updateUserAgent(new EncryptRequest() - .withKeyId(keyId.toString()) - .withPlaintext(ByteBuffer.wrap(plaintextDataKey.getEncoded())) - .withEncryptionContext(encryptionContext) - .withGrantTokens(grantTokens))); + kmsResult = client.encrypt(updateUserAgent( + new EncryptRequest() + .withKeyId(keyId.toString()) + .withPlaintext(ByteBuffer.wrap(plaintextDataKey.getEncoded())) + .withEncryptionContext(encryptionContext) + .withGrantTokens(grantTokens))); } catch (final AmazonServiceException ex) { throw new AwsCryptoException(ex); } @@ -126,11 +134,13 @@ public DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, Cr final com.amazonaws.services.kms.model.DecryptResult kmsResult; try { - kmsResult = getClientByKeyId(AwsKmsCmkId.fromString(providerInformation), clientSupplier) - .decrypt(updateUserAgent(new DecryptRequest() - .withCiphertextBlob(ByteBuffer.wrap(encryptedDataKey.getEncryptedDataKey())) - .withEncryptionContext(encryptionContext) - .withGrantTokens(grantTokens))); + kmsResult = client.decrypt(updateUserAgent( + new DecryptRequest() + .withCiphertextBlob(ByteBuffer.wrap(encryptedDataKey.getEncryptedDataKey())) + // provide the encrypted data key’s provider info as part of the AWS KMS Decrypt API call + .withKeyId(providerInformation) + .withEncryptionContext(encryptionContext) + .withGrantTokens(grantTokens))); } catch (final AmazonServiceException | UnsupportedRegionException ex) { throw new CannotUnwrapDataKeyException(ex); } @@ -150,8 +160,12 @@ public DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, Cr } private T updateUserAgent(T request) { - request.getRequestClientOptions().appendUserAgent(VersionInfo.USER_AGENT); - + // Only append the user agent string if the user agent string is the AWS SDK default + // and if we are allowed to append it + String marker = request.getRequestClientOptions().getClientMarker(RequestClientOptions.Marker.USER_AGENT); + if (this.canAppendUserAgentString && ClientConfiguration.DEFAULT_USER_AGENT.equals(marker)) { + request.getRequestClientOptions().appendUserAgent(VersionInfo.USER_AGENT); + } return request; } diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java new file mode 100644 index 000000000..e07136338 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.services.kms.AWSKMSClientBuilder; + +import java.util.List; + +/** + * Builder to construct an AwsKmsDataKeyEncryptionDao. + * CredentialProvider and ClientConfiguration are optional + * and may be configured if necessary. + */ +public class AwsKmsDataKeyEncryptionDaoBuilder { + + private AWSKMSClientBuilder awsKmsClientBuilder; + private AWSCredentialsProvider credentialsProvider; + private ClientConfiguration clientConfiguration; + private List grantTokens; + private String regionId; + + // The user agent string is used to note the AWS Encryption SDK's language and version in calls to AWS KMS + // Since the AWS KMS client is being constructed the AWS Encryption SDK, we can append this value + // unless a custom client configuration was provided + private boolean canAppendUserAgentString = true; + + /** + * A builder to construct the default AwsKmsDataKeyEncryptionDaoBuilder that will create clients + * for an AWS region. Credentials, client configuration, and grant tokens may be specified if necessary. + * + * @return The AwsKmsDataKeyEncryptionDaoBuilder + */ + public static AwsKmsDataKeyEncryptionDaoBuilder defaultBuilder() { + return new AwsKmsDataKeyEncryptionDaoBuilder(AWSKMSClientBuilder.standard()); + } + + AwsKmsDataKeyEncryptionDaoBuilder(AWSKMSClientBuilder awsKmsClientBuilder) { + this.awsKmsClientBuilder = awsKmsClientBuilder; + } + + public AwsKmsDataKeyEncryptionDao build() { + if (credentialsProvider != null) { + awsKmsClientBuilder = awsKmsClientBuilder.withCredentials(credentialsProvider); + } + + if (clientConfiguration != null) { + awsKmsClientBuilder = awsKmsClientBuilder.withClientConfiguration(clientConfiguration); + } + + if (regionId != null) { + awsKmsClientBuilder = awsKmsClientBuilder.withRegion(regionId); + } + + return new AwsKmsDataKeyEncryptionDao(awsKmsClientBuilder.build(), grantTokens, canAppendUserAgentString); + } + + /** + * Sets a list of string grant tokens to be included in all AWS KMS calls. + * + * @param grantTokens The list of grant tokens. + * @return The AwsKmsDataKeyEncryptionDaoBuilder, for method chaining + */ + public AwsKmsDataKeyEncryptionDaoBuilder grantTokens(List grantTokens) { + this.grantTokens = grantTokens; + return this; + } + + /** + * Sets a non-null AWSCredentialsProvider to be used by the client. + * + * @param credentialsProvider New AWSCredentialsProvider to use. + * @return The AwsKmsDataKeyEncryptionDaoBuilder, for method chaining + */ + public AwsKmsDataKeyEncryptionDaoBuilder credentialsProvider(AWSCredentialsProvider credentialsProvider) { + if (credentialsProvider != null) { + this.credentialsProvider = credentialsProvider; + } + return this; + } + + /** + * Sets a non-null ClientConfiguration to be used by the client. + * + * @param clientConfiguration Custom configuration to use. + * @return The AwsKmsDataKeyEncryptionDaoBuilder, for method chaining + */ + public AwsKmsDataKeyEncryptionDaoBuilder clientConfiguration(ClientConfiguration clientConfiguration) { + if (clientConfiguration != null) { + this.clientConfiguration = clientConfiguration; + // If a client configuration is provided, we must not modify the user agent string + this.canAppendUserAgentString = false; + } + return this; + } + + /** + * Sets a non-null AWS region string to be used by the client. + * + * @param regionId AWS region for the client. + * @return The AwsKmsDataKeyEncryptionDaoBuilder, for method chaining + */ + public AwsKmsDataKeyEncryptionDaoBuilder regionId(String regionId) { + if (regionId != null) { + this.regionId = regionId; + } + return this; + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java index a68227b2b..1267144d8 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java @@ -17,7 +17,6 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import javax.crypto.SecretKey; -import java.util.List; import java.util.Map; public interface DataKeyEncryptionDao { @@ -53,18 +52,6 @@ public interface DataKeyEncryptionDao { */ DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, CryptoAlgorithm algorithmSuite, Map encryptionContext); - /** - * Constructs an instance of DataKeyEncryptionDao that uses AWS Key Management Service (KMS) for - * generation, encryption, and decryption of data keys. - * - * @param clientSupplier A supplier of AWSKMS clients - * @param grantTokens A list of grant tokens to supply to KMS - * @return The DataKeyEncryptionDao - */ - static DataKeyEncryptionDao awsKms(AwsKmsClientSupplier clientSupplier, List grantTokens) { - return new AwsKmsDataKeyEncryptionDao(clientSupplier, grantTokens); - } - class GenerateDataKeyResult { private final SecretKey plaintextDataKey; private final EncryptedDataKey encryptedDataKey; diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java index 7cea5cd58..c1f0ab027 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java @@ -1,11 +1,11 @@ /* * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except * in compliance with the License. A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. @@ -14,12 +14,17 @@ package com.amazonaws.encryptionsdk.kms; import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Supplier; +import com.amazonaws.AmazonServiceException; +import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.encryptionsdk.AwsCrypto; @@ -30,10 +35,15 @@ import com.amazonaws.encryptionsdk.MasterKeyProvider; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; +import com.amazonaws.encryptionsdk.internal.VersionInfo; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.services.kms.AWSKMS; - -import static java.util.Collections.emptyList; +import com.amazonaws.services.kms.model.DecryptRequest; +import com.amazonaws.services.kms.model.DecryptResult; +import com.amazonaws.services.kms.model.EncryptRequest; +import com.amazonaws.services.kms.model.EncryptResult; +import com.amazonaws.services.kms.model.GenerateDataKeyRequest; +import com.amazonaws.services.kms.model.GenerateDataKeyResult; /** * Represents a single Customer Master Key (CMK) and is used to encrypt/decrypt data with @@ -43,9 +53,15 @@ */ @Deprecated public final class KmsMasterKey extends MasterKey implements KmsMethods { - private final AwsKmsDataKeyEncryptionDao dataKeyEncryptionDao_; + private final Supplier kms_; private final MasterKeyProvider sourceProvider_; private final String id_; + private final List grantTokens_ = new ArrayList<>(); + + private T updateUserAgent(T request) { + request.getRequestClientOptions().appendUserAgent(VersionInfo.USER_AGENT); + return request; + } /** * @@ -66,12 +82,12 @@ public static KmsMasterKey getInstance(final AWSCredentialsProvider creds, final } static KmsMasterKey getInstance(final Supplier kms, final String id, - final MasterKeyProvider provider) { - return new KmsMasterKey(new AwsKmsDataKeyEncryptionDao(s -> kms.get(), emptyList()), id, provider); + final MasterKeyProvider provider) { + return new KmsMasterKey(kms, id, provider); } - KmsMasterKey(final AwsKmsDataKeyEncryptionDao dataKeyEncryptionDao, final String id, final MasterKeyProvider provider) { - dataKeyEncryptionDao_ = dataKeyEncryptionDao; + private KmsMasterKey(final Supplier kms, final String id, final MasterKeyProvider provider) { + kms_ = kms; id_ = id; sourceProvider_ = provider; } @@ -88,59 +104,91 @@ public String getKeyId() { @Override public DataKey generateDataKey(final CryptoAlgorithm algorithm, - final Map encryptionContext) { - final DataKeyEncryptionDao.GenerateDataKeyResult gdkResult = dataKeyEncryptionDao_.generateDataKey( - AwsKmsCmkId.fromString(getKeyId()), algorithm, encryptionContext); - return new DataKey<>(gdkResult.getPlaintextDataKey(), - gdkResult.getEncryptedDataKey().getEncryptedDataKey(), - gdkResult.getEncryptedDataKey().getProviderInformation(), - this); + final Map encryptionContext) { + final GenerateDataKeyResult gdkResult = kms_.get().generateDataKey(updateUserAgent( + new GenerateDataKeyRequest() + .withKeyId(getKeyId()) + .withNumberOfBytes(algorithm.getDataKeyLength()) + .withEncryptionContext(encryptionContext) + .withGrantTokens(grantTokens_) + )); + final byte[] rawKey = new byte[algorithm.getDataKeyLength()]; + gdkResult.getPlaintext().get(rawKey); + if (gdkResult.getPlaintext().remaining() > 0) { + throw new IllegalStateException("Recieved an unexpected number of bytes from KMS"); + } + final byte[] encryptedKey = new byte[gdkResult.getCiphertextBlob().remaining()]; + gdkResult.getCiphertextBlob().get(encryptedKey); + + final SecretKeySpec key = new SecretKeySpec(rawKey, algorithm.getDataKeyAlgo()); + return new DataKey<>(key, encryptedKey, gdkResult.getKeyId().getBytes(StandardCharsets.UTF_8), this); } @Override public void setGrantTokens(final List grantTokens) { - dataKeyEncryptionDao_.setGrantTokens(grantTokens); + grantTokens_.clear(); + grantTokens_.addAll(grantTokens); } @Override public List getGrantTokens() { - return dataKeyEncryptionDao_.getGrantTokens(); + return grantTokens_; } @Override public void addGrantToken(final String grantToken) { - dataKeyEncryptionDao_.addGrantToken(grantToken); + grantTokens_.add(grantToken); } @Override public DataKey encryptDataKey(final CryptoAlgorithm algorithm, - final Map encryptionContext, - final DataKey dataKey) { + final Map encryptionContext, + final DataKey dataKey) { final SecretKey key = dataKey.getKey(); - final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao_.encryptDataKey( - AwsKmsCmkId.fromString(id_), key, encryptionContext); - - return new DataKey<>(dataKey.getKey(), - encryptedDataKey.getEncryptedDataKey(), - encryptedDataKey.getProviderInformation(), - this); + if (!key.getFormat().equals("RAW")) { + throw new IllegalArgumentException("Only RAW encoded keys are supported"); + } + try { + final EncryptResult encryptResult = kms_.get().encrypt(updateUserAgent( + new EncryptRequest() + .withKeyId(id_) + .withPlaintext(ByteBuffer.wrap(key.getEncoded())) + .withEncryptionContext(encryptionContext) + .withGrantTokens(grantTokens_))); + final byte[] edk = new byte[encryptResult.getCiphertextBlob().remaining()]; + encryptResult.getCiphertextBlob().get(edk); + return new DataKey<>(dataKey.getKey(), edk, encryptResult.getKeyId().getBytes(StandardCharsets.UTF_8), this); + } catch (final AmazonServiceException amazonServiceException) { + throw new AwsCryptoException(amazonServiceException); + } } @Override public DataKey decryptDataKey(final CryptoAlgorithm algorithm, - final Collection encryptedDataKeys, - final Map encryptionContext) + final Collection encryptedDataKeys, + final Map encryptionContext) throws UnsupportedProviderException, AwsCryptoException { final List exceptions = new ArrayList<>(); for (final EncryptedDataKey edk : encryptedDataKeys) { try { - final DataKeyEncryptionDao.DecryptDataKeyResult result = dataKeyEncryptionDao_.decryptDataKey(edk, algorithm, encryptionContext); - return new DataKey<>( - result.getPlaintextDataKey(), - edk.getEncryptedDataKey(), - edk.getProviderInformation(), this); - } catch (final AwsCryptoException ex) { - exceptions.add(ex); + final DecryptResult decryptResult = kms_.get().decrypt(updateUserAgent( + new DecryptRequest() + .withCiphertextBlob(ByteBuffer.wrap(edk.getEncryptedDataKey())) + .withEncryptionContext(encryptionContext) + .withGrantTokens(grantTokens_))); + if (decryptResult.getKeyId().equals(id_)) { + final byte[] rawKey = new byte[algorithm.getDataKeyLength()]; + decryptResult.getPlaintext().get(rawKey); + if (decryptResult.getPlaintext().remaining() > 0) { + throw new IllegalStateException("Received an unexpected number of bytes from KMS"); + } + return new DataKey<>( + new SecretKeySpec(rawKey, algorithm.getDataKeyAlgo()), + edk.getEncryptedDataKey(), + edk.getProviderInformation(), this); + } + } catch (final AmazonServiceException amazonServiceException) { + exceptions.add(amazonServiceException); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java index 36e48a635..f53ee101f 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java @@ -1,11 +1,11 @@ /* * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except * in compliance with the License. A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. @@ -487,8 +487,8 @@ public KmsMasterKeyProvider(final AWSCredentialsProvider creds, final Region reg } /** - * Returns an instance of this object with the supplied client and region; the client will be - * configured to use the provided region. All keys listed in {@code keyIds} will be used to + * Returns an instance of this object with the supplied client and region; the client will be + * configured to use the provided region. All keys listed in {@code keyIds} will be used to * protect data. * * @deprecated This constructor modifies the passed-in KMS client by setting its region. This functionality may be diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliers.java b/src/main/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliers.java deleted file mode 100644 index 24b71c45c..000000000 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliers.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.kms; - -import com.amazonaws.ClientConfiguration; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; -import com.amazonaws.services.kms.AWSKMS; -import com.amazonaws.services.kms.AWSKMSClientBuilder; -import com.amazonaws.services.kms.model.AWSKMSException; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Proxy; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import static java.util.Objects.requireNonNull; -import static org.apache.commons.lang3.Validate.notEmpty; - -/** - * Factory methods for instantiating the standard {@code AwsKmsClientSupplier}s provided by the AWS Encryption SDK. - */ -public class StandardAwsKmsClientSuppliers { - - /** - * A builder to construct the default AwsKmsClientSupplier that will create and cache clients - * for any region. Credentials and client configuration may be specified if necessary. - * - * @return The builder - */ - public static DefaultAwsKmsClientSupplierBuilder defaultBuilder() { - return new DefaultAwsKmsClientSupplierBuilder(AWSKMSClientBuilder.standard()); - } - - /** - * A builder to construct an AwsKmsClientSupplier that will - * only supply clients for a given set of AWS regions. - * - * @param allowedRegions the AWS regions that the client supplier is allowed to supply clients for - * @return The builder - */ - public static AllowRegionsAwsKmsClientSupplierBuilder allowRegionsBuilder(Set allowedRegions) { - return new AllowRegionsAwsKmsClientSupplierBuilder(allowedRegions); - } - - /** - * A builder to construct an AwsKmsClientSupplier that will - * supply clients for all AWS regions except the given set of regions. - * - * @param deniedRegions the AWS regions that the client supplier will not supply clients for - * @return The builder - */ - public static DenyRegionsAwsKmsClientSupplierBuilder denyRegionsBuilder(Set deniedRegions) { - return new DenyRegionsAwsKmsClientSupplierBuilder(deniedRegions); - } - - - /** - * Builder to construct an AwsKmsClientSupplier that will create and cache clients - * for any region. CredentialProvider and ClientConfiguration are optional and may - * be configured if necessary. - */ - public static class DefaultAwsKmsClientSupplierBuilder { - - private AWSCredentialsProvider credentialsProvider; - private ClientConfiguration clientConfiguration; - private final Map clientsCache = new ConcurrentHashMap<>(); - private static final Set AWSKMS_METHODS = new HashSet<>(); - private AWSKMSClientBuilder awsKmsClientBuilder; - private static final String NULL_REGION = "null-region"; - - static { - AWSKMS_METHODS.add("generateDataKey"); - AWSKMS_METHODS.add("encrypt"); - AWSKMS_METHODS.add("decrypt"); - } - - DefaultAwsKmsClientSupplierBuilder(AWSKMSClientBuilder awsKmsClientBuilder) { - this.awsKmsClientBuilder = awsKmsClientBuilder; - } - - public AwsKmsClientSupplier build() { - - return regionId -> { - - if(regionId == null) { - regionId = NULL_REGION; - } - - if (clientsCache.containsKey(regionId)) { - return clientsCache.get(regionId); - } - - if (credentialsProvider != null) { - awsKmsClientBuilder = awsKmsClientBuilder.withCredentials(credentialsProvider); - } - - if (clientConfiguration != null) { - awsKmsClientBuilder = awsKmsClientBuilder.withClientConfiguration(clientConfiguration); - } - - if (!regionId.equals(NULL_REGION)) { - awsKmsClientBuilder = awsKmsClientBuilder.withRegion(regionId); - } - - return newCachingProxy(awsKmsClientBuilder.build(), regionId); - }; - } - - /** - * Sets the AWSCredentialsProvider used by the client. - * - * @param credentialsProvider New AWSCredentialsProvider to use. - */ - public DefaultAwsKmsClientSupplierBuilder credentialsProvider(AWSCredentialsProvider credentialsProvider) { - this.credentialsProvider = credentialsProvider; - return this; - } - - /** - * Sets the ClientConfiguration to be used by the client. - * - * @param clientConfiguration Custom configuration to use. - */ - public DefaultAwsKmsClientSupplierBuilder clientConfiguration(ClientConfiguration clientConfiguration) { - this.clientConfiguration = clientConfiguration; - return this; - } - - /** - * Creates a proxy for the AWSKMS client that will populate the client into the client cache - * after an AWS KMS method successfully completes or an AWS KMS exception occurs. This is to prevent a - * a malicious user from causing a local resource DOS by sending ciphertext with a large number - * of spurious regions, thereby filling the cache with regions and exhausting resources. - * - * @param client The client to proxy - * @param regionId The region the client is associated with - * @return The proxy - */ - private AWSKMS newCachingProxy(AWSKMS client, String regionId) { - return (AWSKMS) Proxy.newProxyInstance( - AWSKMS.class.getClassLoader(), - new Class[]{AWSKMS.class}, - (proxy, method, methodArgs) -> { - try { - final Object result = method.invoke(client, methodArgs); - if (AWSKMS_METHODS.contains(method.getName())) { - clientsCache.put(regionId, client); - } - return result; - } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof AWSKMSException && - AWSKMS_METHODS.contains(method.getName())) { - clientsCache.put(regionId, client); - } - - throw e.getTargetException(); - } - }); - } - } - - /** - * An AwsKmsClientSupplier that will only supply clients for a given set of AWS regions. - */ - public static class AllowRegionsAwsKmsClientSupplierBuilder { - - private final Set allowedRegions; - private AwsKmsClientSupplier baseClientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder().build(); - - private AllowRegionsAwsKmsClientSupplierBuilder(Set allowedRegions) { - notEmpty(allowedRegions, "At least one region is required"); - requireNonNull(baseClientSupplier, "baseClientSupplier is required"); - - this.allowedRegions = allowedRegions; - } - - /** - * Constructs the AwsKmsClientSupplier. - * - * @return The AwsKmsClientSupplier - */ - public AwsKmsClientSupplier build() { - return regionId -> { - - if (!allowedRegions.contains(regionId)) { - throw new UnsupportedRegionException(String.format("Region %s is not in the set of allowed regions %s", - regionId, allowedRegions)); - } - - return baseClientSupplier.getClient(regionId); - }; - } - - /** - * Sets the client supplier that will supply the client if the region is allowed. - * - * @param baseClientSupplier the client supplier that will supply the client if the region is allowed. - */ - public AllowRegionsAwsKmsClientSupplierBuilder baseClientSupplier(AwsKmsClientSupplier baseClientSupplier) { - this.baseClientSupplier = baseClientSupplier; - return this; - } - } - - /** - * A client supplier that supplies clients for any region except the specified AWS regions. - */ - public static class DenyRegionsAwsKmsClientSupplierBuilder { - - private final Set deniedRegions; - private AwsKmsClientSupplier baseClientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder().build(); - - private DenyRegionsAwsKmsClientSupplierBuilder(Set deniedRegions) { - notEmpty(deniedRegions, "At least one region is required"); - requireNonNull(baseClientSupplier, "baseClientSupplier is required"); - - this.deniedRegions = deniedRegions; - } - - /** - * Sets the client supplier that will supply the client if the region is allowed. - * - * @param baseClientSupplier the client supplier that will supply the client if the region is allowed. - */ - public DenyRegionsAwsKmsClientSupplierBuilder baseClientSupplier(AwsKmsClientSupplier baseClientSupplier) { - this.baseClientSupplier = baseClientSupplier; - return this; - } - - public AwsKmsClientSupplier build() { - - return regionId -> { - - if (deniedRegions.contains(regionId)) { - throw new UnsupportedRegionException(String.format("Region %s is in the set of denied regions %s", - regionId, deniedRegions)); - } - - return baseClientSupplier.getClient(regionId); - }; - } - } -} diff --git a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java index 89f21479b..78b76387d 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java +++ b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java @@ -21,7 +21,7 @@ import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; -import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; import com.amazonaws.util.IOUtils; import com.fasterxml.jackson.core.type.TypeReference; @@ -65,7 +65,7 @@ class TestVectorRunner { // 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 AwsKmsClientSupplier awsKmsClientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + private static final AwsKmsClientSupplier awsKmsClientSupplier = AwsKmsServiceClientBuilder.defaultBuilder() .credentialsProvider(new DefaultAWSCredentialsProviderChain()) .build(); private static final KmsMasterKeyProvider kmsProv = KmsMasterKeyProvider diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliersTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsServiceClientBuilderTest.java similarity index 96% rename from src/test/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliersTest.java rename to src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsServiceClientBuilderTest.java index dec01d5a3..e18b4e65c 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliersTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsServiceClientBuilderTest.java @@ -16,7 +16,7 @@ import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; -import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers.DefaultAwsKmsClientSupplierBuilder; +import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder.DefaultAwsKmsClientSupplierBuilder; import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.AWSKMSClientBuilder; import com.amazonaws.services.kms.model.AWSKMSException; @@ -40,7 +40,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class StandardAwsKmsClientSuppliersTest { +class AwsKmsServiceClientBuilderTest { @Mock AWSKMSClientBuilder kmsClientBuilder; @Mock AWSKMS awskms; @@ -141,7 +141,7 @@ void testAllowedRegions() { assertNotNull(supplierWithDefaultValues.getClient(REGION_1)); - AwsKmsClientSupplier supplierWithAllowed = StandardAwsKmsClientSuppliers + AwsKmsClientSupplier supplierWithAllowed = AwsKmsServiceClientBuilder .allowRegionsBuilder(Collections.singleton(REGION_1)) .baseClientSupplier(new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder).build()).build(); @@ -162,7 +162,7 @@ void testDeniedRegions() { assertNotNull(supplierWithDefaultValues.getClient(REGION_1)); - AwsKmsClientSupplier supplierWithDenied = StandardAwsKmsClientSuppliers + AwsKmsClientSupplier supplierWithDenied = AwsKmsServiceClientBuilder .denyRegionsBuilder(Collections.singleton(REGION_1)) .baseClientSupplier(new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder).build()).build(); From 3b97840b5c1ce5c684613fd05fe2d8c2a7c68871 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Tue, 11 Aug 2020 23:40:33 -0700 Subject: [PATCH 02/15] Initial refactor of examples, tests. Some tests still failing. Still need to add new unit tests --- .../examples/FileStreamingDefaults.java | 2 +- .../examples/InMemoryStreamingDefaults.java | 2 +- .../crypto/examples/OneStepDefaults.java | 2 +- .../crypto/examples/OneStepUnsigned.java | 2 +- .../caching/SimpleCache.java | 2 +- .../custom/AlgorithmSuiteEnforcement.java | 2 +- .../RequiringEncryptionContextFields.java | 2 +- .../ActLikeAwsKmsMasterKeyProvider.java | 52 ++- ...r.java => CustomDataKeyEncryptionDao.java} | 89 ++--- .../keyring/awskms/CustomKmsClientConfig.java | 29 +- .../keyring/awskms/DiscoveryDecrypt.java | 66 ++-- .../awskms/DiscoveryDecryptInRegionOnly.java | 47 ++- .../DiscoveryDecryptWithPreferredRegions.java | 61 ++- .../keyring/awskms/MultipleRegions.java | 29 +- .../examples/keyring/awskms/SingleCmk.java | 15 +- .../keyring/multi/AwsKmsWithEscrow.java | 12 +- .../legacy/LambdaDecryptAndWriteExample.java | 18 +- .../MultiRegionRecordPusherExample.java | 15 +- .../legacy/SimpleDataKeyCachingExample.java | 2 +- ...AwsKmsSymmetricMultiCmkKeyringBuilder.java | 3 +- ...ricMultiRegionDiscoveryKeyringBuilder.java | 13 +- .../keyrings/StandardKeyrings.java | 78 +++- .../AwsKmsDataKeyEncryptionDaoBuilder.java | 3 +- .../encryptionsdk/kms/KmsMasterKey.java | 2 +- .../kms/KmsMasterKeyProvider.java | 2 +- .../encryptionsdk/TestVectorRunner.java | 13 +- .../keyrings/AwsKmsKeyringTest.java | 371 ------------------ .../MasterKeyProviderCompatibilityTest.java | 4 +- ...AwsKmsDataKeyEncryptionDaoBuilderTest.java | 77 ++++ .../kms/AwsKmsDataKeyEncryptionDaoTest.java | 16 +- .../kms/AwsKmsServiceClientBuilderTest.java | 175 --------- .../encryptionsdk/kms/KmsMasterKeyTest.java | 28 +- 32 files changed, 434 insertions(+), 800 deletions(-) rename src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/{CustomClientSupplier.java => CustomDataKeyEncryptionDao.java} (58%) delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilderTest.java delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsServiceClientBuilderTest.java diff --git a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java index e8a936d66..708b4a061 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java +++ b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java @@ -64,7 +64,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final File sourcePlaintextFi encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); // Create the encrypting input stream with the keyring and encryption context. // Because the file might be too large to load into memory, diff --git a/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java b/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java index a2bd7af1a..bb6685023 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java +++ b/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java @@ -54,7 +54,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); ByteArrayInputStream inputStream = new ByteArrayInputStream(sourcePlaintext); diff --git a/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java b/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java index 40adc9161..44a020fca 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java +++ b/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java @@ -46,7 +46,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( diff --git a/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java b/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java index 492307f80..b205f522a 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java +++ b/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java @@ -60,7 +60,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( diff --git a/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/caching/SimpleCache.java b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/caching/SimpleCache.java index aa362f485..043ebb00e 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/caching/SimpleCache.java +++ b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/caching/SimpleCache.java @@ -64,7 +64,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); // Create the caching cryptographic materials manager using your keyring. final CryptoMaterialsManager cmm = CachingCryptoMaterialsManager.newBuilder() diff --git a/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/AlgorithmSuiteEnforcement.java b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/AlgorithmSuiteEnforcement.java index e25d1faa5..0ae47f720 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/AlgorithmSuiteEnforcement.java +++ b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/AlgorithmSuiteEnforcement.java @@ -124,7 +124,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); // Create the algorithm suite restricting cryptographic materials manager using your keyring. final CryptoMaterialsManager cmm = new RequireApprovedAlgorithmSuitesCryptoMaterialsManager(keyring); diff --git a/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/RequiringEncryptionContextFields.java b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/RequiringEncryptionContextFields.java index b9233ad8a..df6817bf1 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/RequiringEncryptionContextFields.java +++ b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/RequiringEncryptionContextFields.java @@ -113,7 +113,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); // Create the classification requiring cryptographic materials manager using your keyring. final CryptoMaterialsManager cmm = new ClassificationRequiringCryptoMaterialsManager(keyring); diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActLikeAwsKmsMasterKeyProvider.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActLikeAwsKmsMasterKeyProvider.java index 3eefb26f5..b6c6adb6e 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActLikeAwsKmsMasterKeyProvider.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActLikeAwsKmsMasterKeyProvider.java @@ -11,6 +11,7 @@ import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.regions.Regions; import java.util.ArrayList; import java.util.Arrays; @@ -27,7 +28,7 @@ *

* The AWS Encryption SDK provided an AWS KMS master key provider for * interacting with AWS Key Management Service (AWS KMS). - * On encrypt, the AWS KMS master key provider behaves like the AWS KMS keyring + * On encrypt, the AWS KMS master key provider behaves like the AWS KMS symmetric multi-CMK keyring * and encrypts with all CMKs that you identify. * However, on decrypt, * the AWS KMS master key provider reviews each encrypted data key (EDK). @@ -38,11 +39,11 @@ * or succeeds in decrypting an EDK. * We have found that separating these two behaviors * makes the expected behavior clearer, - * so that is what we did with the AWS KMS keyring and the AWS KMS discovery keyring. + * so that is what we did with the AWS KMS symmetric keyring and the AWS KMS region discovery keyring. * However, as you migrate from master key providers to keyrings, * you might want a keyring that behaves like the AWS KMS master key provider. *

- * For more examples of how to use the AWS KMS keyring, + * For more examples of how to use the AWS KMS keyrings, * see the 'keyring/awskms' directory. */ public class ActLikeAwsKmsMasterKeyProvider { @@ -84,19 +85,39 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final List awsK // // This keyring reproduces the encryption behavior of the AWS KMS master key provider. // - // The AWS KMS keyring requires that you explicitly identify the CMK + // The AWS KMS symmetric multi-CMK keyring requires that you explicitly identify the CMK // that you want the keyring to use to generate the data key. - final Keyring cmkKeyring = StandardKeyrings.awsKmsBuilder() - .generatorKeyId(awsKmsCmk) - .keyIds(awsKmsAdditionalCmks) + final Keyring cmkKeyring = StandardKeyrings.awsKmsSymmetricMultiCmkBuilder() + .generator(awsKmsCmk) + .keyNames(awsKmsAdditionalCmks) .build(); - // Create an AWS KMS discovery keyring that will attempt to decrypt + // Create an AWS KMS symmetric multi-region discovery keyring that will attempt to decrypt // any data keys that were encrypted under an AWS KMS CMK. - final Keyring discoveryKeyring = StandardKeyrings.awsKmsDiscoveryBuilder().build(); - - // Combine the CMK and discovery keyrings - // to create a keyring that behaves like an AWS KMS master key provider. + // + // Please note that the multi-region discovery keyring requires the specific list of AWS regions + // it may communicate with. + // + // In production, if you need a keyring that attempts decryption in all AWS regions, + // you should call a service/API to get an updated list of AWS regions. + // This will prevent any AWS SDK-derived region-lists from potentially becoming stale over time. + // + // In most cases, you should simply call StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery + // with the specific regions you need. + // + // This will provide flexibility for adding more regions over time, + // without allowing unnecessary access to regions that are not currently required. + final List allRegionIds = new ArrayList<>(); + for (final Regions region: Regions.values()) { + allRegionIds.add(region.getName()); + } + final Keyring discoveryKeyring = StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery(allRegionIds); + + // Note that you cannot combine the AWS KMS symmetric multi-CMK and AWS KMS symmetric multi-region keyrings + // using a multi-keyring because the AWS KMS symmetric multi-region keyring is unable to perform + // an encryption operation. + // + // Therefore, you should use these keyrings separately (one for encrypt and one for decrypt). // // The CMK keyring reproduces the encryption behavior // and the discovery keyring reproduces the decryption behavior. @@ -105,13 +126,12 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final List awsK // it works on encrypt but fails to match any encrypted data keys on decrypt // because the serialized key name is the resulting CMK ARN rather than the alias name. // However, because the discovery keyring attempts to decrypt any AWS KMS-encrypted - // data keys that it finds, the message still decrypts successfully. - final Keyring keyring = StandardKeyrings.multi(cmkKeyring, discoveryKeyring); + // data keys that it finds, you are still able to successfully decrypt the message. // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( EncryptRequest.builder() - .keyring(keyring) + .keyring(cmkKeyring) .encryptionContext(encryptionContext) .plaintext(sourcePlaintext).build()); final byte[] ciphertext = encryptResult.getResult(); @@ -125,7 +145,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final List awsK // the header of the encrypted message includes the encryption context. final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( DecryptRequest.builder() - .keyring(keyring) + .keyring(discoveryKeyring) .ciphertext(ciphertext).build()); final byte[] decrypted = decryptResult.getResult(); diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java similarity index 58% rename from src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java rename to src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java index b10400479..c82f24011 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java @@ -10,19 +10,25 @@ import com.amazonaws.encryptionsdk.EncryptRequest; import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; -import com.amazonaws.services.kms.AWSKMS; +import com.amazonaws.encryptionsdk.kms.AwsKmsDataKeyEncryptionDaoBuilder; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import com.amazonaws.regions.Regions; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** - * By default, the AWS KMS keyring uses a client supplier that - * supplies a client with the same configuration for every region. - * If you need different behavior, you can write your own client supplier. + * The AWS KMS symmetric keyring is associated with a single DataKeyEncryptionDao and a single key name. + * Builders are provided to allow for quick generation of a multi-keyring of AWS KMS symmetric keyrings, + * where each AWS KMS symmetric keyring is initialized with a DataKeyEncryptionDao that encapsulates an AWS KMS service client. + * Builders are additionally provided to allow for customization of all required AWS KMS service clients. + * + * However, if you need different behavior, + * such as having each AWS KMS service client using a different AWS KMS client configuration, + * you can utilize the base AWS KMS symmetric keyring directly and provide it a custom DataKeyEncryptionDao. + * *

* You might use this * if you need different credentials in different AWS regions. @@ -30,56 +36,26 @@ * or if you are working with regions that have separate authentication silos * like "ap-east-1" and "me-south-1". *

- * This example shows how to create a client supplier - * that will supply KMS clients with valid credentials for the target region + * This example shows how to create a multi-keyring of AWS KMS symmetric keyrings + * that will supply AWS KMS clients with valid credentials for the target region * even when working with regions that need different credentials. *

* https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring *

- * For an example of how to use the AWS KMS keyring with CMKs in multiple regions, + * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, * see the {@link MultipleRegions} example. *

- * For another example of how to use the AWS KMS keyring with a custom client configuration, + * For another example of how to use the AWS KMS symmetric multi-CMK keyring with a custom client configuration, * see the {@link CustomKmsClientConfig} example. *

- * For examples of how to use the AWS KMS Discovery keyring on decrypt, + * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, * see the {@link DiscoveryDecrypt}, {@link DiscoveryDecryptInRegionOnly}, * and {@link DiscoveryDecryptWithPreferredRegions} examples. */ -public class CustomClientSupplier { - - static class MultiPartitionClientSupplier implements AwsKmsClientSupplier { - - private final AwsKmsClientSupplier chinaSupplier = AwsKmsServiceClientBuilder.defaultBuilder() - .credentialsProvider(new ProfileCredentialsProvider("china")).build(); - private final AwsKmsClientSupplier middleEastSupplier = AwsKmsServiceClientBuilder.defaultBuilder() - .credentialsProvider(new ProfileCredentialsProvider("middle-east")).build(); - private final AwsKmsClientSupplier hongKongSupplier = AwsKmsServiceClientBuilder.defaultBuilder() - .credentialsProvider(new ProfileCredentialsProvider("hong-kong")).build(); - private final AwsKmsClientSupplier defaultSupplier = AwsKmsServiceClientBuilder.defaultBuilder().build(); - - /** - * Returns a client for the requested region. - * - * @param regionId The AWS region - * @return The AWSKMS client - */ - @Override - public AWSKMS getClient(String regionId) { - if (regionId.startsWith("cn-")) { - return chinaSupplier.getClient(regionId); - } else if (regionId.startsWith("me-")) { - return middleEastSupplier.getClient(regionId); - } else if (regionId.equals("ap-east-1")) { - return hongKongSupplier.getClient(regionId); - } else { - return defaultSupplier.getClient(regionId); - } - } - } +public class CustomDataKeyEncryptionDao { /** - * Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with a custom client supplier. + * Demonstrate an encrypt/decrypt cycle using multiple AWS KMS symmetric keyrings, each with a unique dao. * * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys * @param sourcePlaintext Plaintext to encrypt @@ -98,11 +74,30 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("that can help you", "be confident that"); encryptionContext.put("the data you are handling", "is what you think it is"); + // Create a keyring per region + final DataKeyEncryptionDao chinaDao = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("china")) + .regionId(Regions.CN_NORTH_1.getName()) + .build(); + final Keyring chinaKeyring = StandardKeyrings.awsKmsSymmetric(chinaDao, awsKmsCmk); + + final DataKeyEncryptionDao middleEastDao = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("middle-east")) + .regionId(Regions.ME_SOUTH_1.getName()) + .build(); + final Keyring middleEasyKeyring = StandardKeyrings.awsKmsSymmetric(middleEastDao, awsKmsCmk); + + final DataKeyEncryptionDao hongKongDao = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("hong-kong")) + .regionId(Regions.AP_EAST_1.getName()) + .build(); + final Keyring hongKongKeyring = StandardKeyrings.awsKmsSymmetric(hongKongDao, awsKmsCmk); + + final DataKeyEncryptionDao defaultDao = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder().build(); + final Keyring defaultKeyring = StandardKeyrings.awsKmsSymmetric(defaultDao, awsKmsCmk); + // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.awsKmsBuilder() - .generatorKeyId(awsKmsCmk) - .awsKmsClientSupplier(new MultiPartitionClientSupplier()) - .build(); + final Keyring keyring = StandardKeyrings.multi(defaultKeyring, chinaKeyring, middleEasyKeyring, hongKongKeyring); // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java index cb011a062..193138cad 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java @@ -11,38 +11,36 @@ import com.amazonaws.encryptionsdk.internal.VersionInfo; import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** - * By default, the AWS KMS keyring uses the default configurations - * for all KMS clients and uses the default discoverable credentials. + * By default, the AWS KMS keyrings use the default configuration + * for all AWS KMS clients and use the default discoverable credentials. * If you need to change this configuration, - * you can configure the client supplier. + * you can configure the keyring builder. *

- * This example shows how to use custom-configured clients with the AWS KMS keyring. + * This example shows how to use custom-configured clients with the AWS KMS symmetric multi-CMK keyring. *

* https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring *

- * For an example of how to use the AWS KMS keyring with CMKs in multiple regions, + * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, * see the {@link MultipleRegions} example. *

- * For another example of how to use the AWS KMS keyring with a custom client configuration, + * For another example of how to use the AWS KMS symmetric multi-CMK keyring with a custom client configuration, * see the {@link CustomKmsClientConfig} example. *

- * For examples of how to use the AWS KMS Discovery keyring on decrypt, + * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, * see the {@link DiscoveryDecrypt}, {@link DiscoveryDecryptInRegionOnly}, * and {@link DiscoveryDecryptWithPreferredRegions} examples. */ public class CustomKmsClientConfig { /** - * Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with custom KMS client configuration. + * Demonstrate an encrypt/decrypt cycle using an AWS KMS symmetric multi-cmk keyring with custom KMS client configuration. * * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys * @param sourcePlaintext Plaintext to encrypt @@ -69,18 +67,15 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext .withConnectionTimeout(10000) // 10,000 milliseconds .withUserAgentSuffix(VersionInfo.USER_AGENT); - // Use your custom configuration values to configure your client supplier. + // Use your custom configuration values to configure your keyring. // For this example we will just use the default credentials provider // but if you need to, you can set a custom credentials provider as well. - final AwsKmsClientSupplier clientSupplier = AwsKmsServiceClientBuilder.defaultBuilder() - .clientConfiguration(clientConfiguration) - .build(); // Create the keyring that determines how your data keys are protected, // providing the client supplier that you created. - final Keyring keyring = StandardKeyrings.awsKmsBuilder() - .generatorKeyId(awsKmsCmk) - .awsKmsClientSupplier(clientSupplier) + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmkBuilder() + .generator(awsKmsCmk) + .clientConfiguration(clientConfiguration) .build(); // Encrypt your plaintext data. diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java index 2b124f9cb..606514950 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java @@ -10,44 +10,50 @@ import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.regions.Regions; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** - * When you give the AWS KMS keyring specific key IDs it will use those CMKs and nothing else. + * When you give the AWS KMS symmetric multi-CMK keyring specific key names it will use those CMKs and nothing else. * This is true both on encrypt and on decrypt. * However, sometimes you need more flexibility on decrypt, - * especially when you don't know which CMKs were used to encrypt a message. - * To address this need, you can use an AWS KMS discovery keyring. - * The AWS KMS discovery keyring does nothing on encrypt. - * On decrypt it reviews each encrypted data key (EDK). + * especially if you don't know which CMK was used to encrypt a message. + * To address this need, you can use an AWS KMS symmetric multi-region discovery keyring. + * The AWS KMS symmetric multi-region discovery keyring is a multi-keyring of AWS KMS symmetric region discovery keyrings. + * AWS KMS symmetric region discovery keyrings cannot encrypt. + * On decrypt each AWS KMS symmetric region discovery keyring reviews each encrypted data key (EDK). * If an EDK was encrypted under an AWS KMS CMK, - * the AWS KMS discovery keyring attempts to decrypt it. + * the AWS KMS symmetric region discovery keyring attempts to decrypt it if the EDK's region matches the region associated + * with the AWS KMS symmetric region discovery keyring. * Whether decryption succeeds depends on permissions on the CMK. - * This continues until the AWS KMS discovery keyring either runs out of EDKs + * This continues until the AWS KMS symmetric region discovery keyring either runs out of EDKs * or succeeds in decrypting an EDK. *

- * This example shows how to configure and use an AWS KMS discovery keyring. + * Each AWS KMS symmetric region discovery keyring is restricted to a single AWS region. + * Additionally, an AWS KMS symmetric multi-region discovery keyring restricts communication to the configured regions, + * in their configured order. + *

+ * This example shows how to configure and use an AWS KMS symmetric multi-region discovery keyring. + * The AWS KMS symmetric multi-region discovery keyring is an easy way to configure a multi-keyring of AWS KMS region discovery keyrings. *

* https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring *

- * For an example of how to use the AWS KMS keyring with CMKs in multiple regions, + * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, * see the {@link MultipleRegions} example. *

- * For examples of how to use the AWS KMS keyring with custom client configurations, - * see the {@link CustomClientSupplier} + * For examples of how to use the AWS KMS symmetric multi-CMK keyring with custom client configurations, + * see the {@link CustomDataKeyEncryptionDao} * and {@link CustomKmsClientConfig} examples. *

- * For examples of how to use the AWS KMS discovery keyring on decrypt, - * see the {@link DiscoveryDecryptInRegionOnly}, - * and {@link DiscoveryDecryptWithPreferredRegions} examples. + * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, + * and {@link DiscoveryDecryptInRegionOnly} examples. */ public class DiscoveryDecrypt { /** - * Demonstrate configuring an AWS KMS discovery keyring for decryption. + * Demonstrate configuring an AWS KMS symmetric multi-region discovery keyring for decryption. * * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys * @param sourcePlaintext Plaintext to encrypt @@ -67,10 +73,26 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring encryptKeyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring encryptKeyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); - // Create an AWS KMS discovery keyring to use on decrypt. - final Keyring decryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder().build(); + // Create an AWS KMS symmetric multi-region discovery keyring to use on decrypt. + // Please note that the multi-region discovery keyring requires the specific list of AWS regions + // it may communicate with. + // + // In production, if you need a keyring that attempts decryption in all AWS regions, + // you should call a service/API to get an updated list of AWS regions. + // This will prevent any AWS SDK-derived region-lists from potentially becoming stale over time. + // + // In most cases, you should simply call StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery + // with the specific regions you need. + // + // This will provide flexibility for adding more regions over time, + // without allowing unnecessary access to regions that are not currently required. + final List allRegionIds = new ArrayList<>(); + for (final Regions region: Regions.values()) { + allRegionIds.add(region.getName()); + } + final Keyring decryptKeyring = StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery(allRegionIds); // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( @@ -83,7 +105,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // Demonstrate that the ciphertext and plaintext are different. assert !Arrays.equals(ciphertext, sourcePlaintext); - // Decrypt your encrypted data using the AWS KMS discovery keyring. + // Decrypt your encrypted data using the AWS KMS symmetric multi-region discovery keyring. // // You do not need to specify the encryption context on decrypt because // the header of the encrypted message includes the encryption context. diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java index 26c400994..745de2e24 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java @@ -11,50 +11,51 @@ import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Map; -import static java.util.Collections.singleton; - /** - * When you give the AWS KMS keyring specific key IDs it will use those CMKs and nothing else. + * When you give the AWS KMS symmetric multi-CMK keyring specific key names it will use those CMKs and nothing else. * This is true both on encrypt and on decrypt. * However, sometimes you need more flexibility on decrypt, * especially if you don't know which CMK was used to encrypt a message. - * To address this need, you can use an AWS KMS discovery keyring. - * The AWS KMS discovery keyring does nothing on encrypt. - * On decrypt it reviews each encrypted data key (EDK). + * To address this need, you can use an AWS KMS symmetric multi-region discovery keyring. + * The AWS KMS symmetric multi-region discovery keyring is a multi-keyring of AWS KMS symmetric region discovery keyrings. + * AWS KMS symmetric region discovery keyrings cannot encrypt. + * On decrypt each AWS KMS symmetric region discovery keyring reviews each encrypted data key (EDK). * If an EDK was encrypted under an AWS KMS CMK, - * the AWS KMS discovery keyring attempts to decrypt it. + * the AWS KMS symmetric region discovery keyring attempts to decrypt it if the EDK's region matches the region associated + * with the AWS KMS symmetric region discovery keyring. * Whether decryption succeeds depends on permissions on the CMK. - * This continues until the AWS KMS discovery keyring either runs out of EDKs + * This continues until the AWS KMS symmetric region discovery keyring either runs out of EDKs * or succeeds in decrypting an EDK. *

- * However, sometimes you need to be a *bit* more restrictive than that. - * To address this need, you can use a client supplier that restricts the regions an AWS KMS keyring can talk to. + * Each AWS KMS symmetric region discovery keyring is restricted to a single AWS region. + * Additionally, an AWS KMS symmetric multi-region discovery keyring restricts communication to the configured regions, + * in their configured order. *

- * This example shows how to configure and use an AWS KMS regional discovery keyring that is restricted to one region. + * This example shows how to configure and use an AWS KMS symmetric multi-region discovery keyring that is restricted to one region. *

* https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring *

- * For an example of how to use the AWS KMS keyring with CMKs in multiple regions, + * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, * see the {@link MultipleRegions} example. *

- * For examples of how to use the AWS KMS keyring with custom client configurations, - * see the {@link CustomClientSupplier} + * For examples of how to use the AWS KMS symmetric multi-CMK keyring with custom client configurations, + * see the {@link CustomDataKeyEncryptionDao} * and {@link CustomKmsClientConfig} examples. *

- * For examples of how to use the AWS KMS discovery keyring on decrypt, + * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, * see the {@link DiscoveryDecrypt}, - * and {@link DiscoveryDecryptWithPreferredRegions} examples. + * and {@link DiscoveryDecryptInRegionOnly} examples. */ public class DiscoveryDecryptInRegionOnly { /** - * Demonstrate configuring an AWS KMS keyring to only work within a single region. + * Demonstrate configuring an AWS KMS symmetric multi-region keyring to only work within a single region. * * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys * @param sourcePlaintext Plaintext to encrypt @@ -74,19 +75,17 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring encryptKeyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring encryptKeyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); // Extract the region from the CMK ARN. final String decryptRegion = Arn.fromString(awsKmsCmk.toString()).getRegion(); - // Create the AWS KMS discovery keyring that we will use on decrypt. + // Create the AWS KMS symmetric multi-region discovery keyring that we will use on decrypt. // // The client supplier that we specify here will only supply clients for the specified region. // The keyring only attempts to decrypt data keys if it can get a client for that region, // so this keyring will now ignore any data keys that were encrypted under a CMK in another region. - final Keyring decryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() - .awsKmsClientSupplier(AwsKmsServiceClientBuilder.allowRegionsBuilder(singleton(decryptRegion)).build()) - .build(); + final Keyring decryptKeyring = StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery(Collections.singletonList(decryptRegion)); // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( @@ -99,7 +98,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // Demonstrate that the ciphertext and plaintext are different. assert !Arrays.equals(ciphertext, sourcePlaintext); - // Decrypt your encrypted data using the AWS KMS discovery keyring. + // Decrypt your encrypted data using the AWS KMS symmetric multi-region discovery keyring. // // You do not need to specify the encryption context on decrypt because // the header of the encrypted message includes the encryption context. diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java index 507b555c6..f01466fd4 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java @@ -10,50 +10,51 @@ import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; +import com.amazonaws.regions.Regions; import com.amazonaws.services.kms.AWSKMSClientBuilder; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import static java.util.Collections.singleton; - /** - * When you give the AWS KMS keyring specific key IDs it will use those CMKs and nothing else. + * When you give the AWS KMS symmetric multi-CMK keyring specific key names it will use those CMKs and nothing else. * This is true both on encrypt and on decrypt. * However, sometimes you need more flexibility on decrypt, - * especially if you might not know beforehand which CMK was used to encrypt a message. - * To address this need, you can use an AWS KMS discovery keyring. - * The AWS KMS discovery keyring does nothing on encrypt. - * On decrypt it reviews each encrypted data key (EDK). + * especially if you don't know which CMK was used to encrypt a message. + * To address this need, you can use an AWS KMS symmetric multi-region discovery keyring. + * The AWS KMS symmetric multi-region discovery keyring is a multi-keyring of AWS KMS symmetric region discovery keyrings. + * AWS KMS symmetric region discovery keyrings cannot encrypt. + * On decrypt each AWS KMS symmetric region discovery keyring reviews each encrypted data key (EDK). * If an EDK was encrypted under an AWS KMS CMK, - * the AWS KMS discovery keyring attempts to decrypt it. + * the AWS KMS symmetric region discovery keyring attempts to decrypt it if the EDK's region matches the region associated + * with the AWS KMS symmetric region discovery keyring. * Whether decryption succeeds depends on permissions on the CMK. - * This continues until the AWS KMS discovery keyring either runs out of EDKs + * This continues until the AWS KMS symmetric region discovery keyring either runs out of EDKs * or succeeds in decrypting an EDK. *

- * However, sometimes you need to be a *bit* more restrictive than that. - * To address this need, you can use a client supplier to restrict what regions an AWS KMS keyring can talk to. + * Each AWS KMS symmetric region discovery keyring is restricted to a single AWS region. + * Additionally, an AWS KMS symmetric multi-region discovery keyring restricts communication to the configured regions, + * in their configured order. *

* A more complex but more common use-case is that you would *prefer* to stay within a region, * but you would rather make calls to other regions than fail to decrypt the message. * In this case, you want a keyring that will try to decrypt data keys in this region first, * then try other regions. *

- * This example shows how to configure and use a multi-keyring with the AWS KMS keyring - * to prefer the current AWS region while also failing over to other AWS regions. + * This example shows how to configure and use an AWS KMS symmetric multi-region discovery keyring + * that prefers the current AWS region while also failing over to other AWS regions. *

* https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring *

- * For an example of how to use the AWS KMS keyring with CMKs in multiple regions, + * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, * see the {@link MultipleRegions} example. *

- * For examples of how to use the AWS KMS keyring with custom client configurations, - * see the {@link CustomClientSupplier} + * For examples of how to use the AWS KMS symmetric multi-CMK keyring with custom client configurations, + * see the {@link CustomDataKeyEncryptionDao} * and {@link CustomKmsClientConfig} examples. *

- * For examples of how to use the AWS KMS discovery keyring on decrypt, + * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, * see the {@link DiscoveryDecrypt}, * and {@link DiscoveryDecryptInRegionOnly} examples. */ @@ -80,29 +81,25 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring encryptKeyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring encryptKeyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); // To create our decrypt keyring, we need to know our current default AWS region. + // Please note that this *may* return a null region. + // As a result, we recommend specifying the region you are operating in directly. final String localRegion = AWSKMSClientBuilder.standard().getRegion(); - // Now, use that region name to create two AWS KMS discovery keyrings: - // - // One that only works in the local region - final Keyring localRegionDecryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() - .awsKmsClientSupplier(AwsKmsServiceClientBuilder.allowRegionsBuilder(singleton(localRegion)).build()) - .build(); - // and one that will work in any other region but NOT the local region. - final Keyring otherRegionsDecryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() - .awsKmsClientSupplier(AwsKmsServiceClientBuilder.denyRegionsBuilder(singleton(localRegion)).build()) - .build(); - - // Finally, combine those two keyrings into a multi-keyring. + // Now, use that region name to create an AWS KMS symmetric multi-region discovery keyring. + // The AWS KMS symmetric multi-region discovery keyring represents a multi-keying + // of child AWS KMS symmetric region discovery keyrings. // // The multi-keyring steps through its member keyrings in the order that you provide them, // attempting to decrypt every encrypted data key with each keyring before moving on to the next keyring. // Because of this, otherRegionsDecryptKeyring will not be called // unless localRegionDecryptKeyring fails to decrypt every encrypted data key. - final Keyring decryptKeyring = StandardKeyrings.multi(localRegionDecryptKeyring, otherRegionsDecryptKeyring); + // + // In this example, we first try our localRegion and then fall back to 'us-west-2' + final Keyring decryptKeyring = StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery( + Arrays.asList(localRegion, Regions.US_WEST_2.getName())); // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java index 832e4ad99..d181d58df 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java @@ -18,18 +18,19 @@ import java.util.Map; /** - * This example shows how to configure and use an AWS KMS keyring with CMKs in multiple regions. + * This example shows how to configure and use an AWS KMS symmetric multi-cmk keyring with CMKs in multiple regions. *

* https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring *

- * For an example of how to use the AWS KMS keyring with a single CMK, + * For an example of how to use the AWS KMS symmetric multi-cmk keyring with a single CMK, * see the {@link SingleCmk} example. *

- * For examples of how to use the AWS KMS keyring with custom client configurations, - * see the {@link CustomClientSupplier} + * For examples of how to use the AWS KMS symmetric keyring + * and the AWS KMS symmetric multi-CMK keyring with custom client configurations, + * see the {@link CustomDataKeyEncryptionDao} * and {@link CustomKmsClientConfig} examples. *

- * For examples of how to use the AWS KMS Discovery keyring on decrypt, + * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, * see the {@link DiscoveryDecrypt}, * {@link DiscoveryDecryptInRegionOnly}, * and {@link DiscoveryDecryptWithPreferredRegions} examples. @@ -37,7 +38,7 @@ public class MultipleRegions { /** - * Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with CMKs in multiple regions. + * Demonstrate an encrypt/decrypt cycle using an AWS KMS symmetric multi-cmk keyring with CMKs in multiple regions. * * @param awsKmsGeneratorCmk The ARN of an AWS KMS CMK that protects data keys * @param awsKmsAdditionalCmks Additional ARNs of secondary AWS KMS CMKs @@ -58,22 +59,22 @@ public static void run(final AwsKmsCmkId awsKmsGeneratorCmk, final List * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring *

- * For an example of how to use the AWS KMS keyring with CMKs in multiple regions, + * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, * see the {@link MultipleRegions} example. *

- * For examples of how to use the AWS KMS keyring with custom client configurations, - * see the {@link CustomClientSupplier} + * For examples of how to use the AWS KMS symmetric keyring + * and the AWS KMS symmetric multi-CMK keyring with custom client configurations, + * see the {@link CustomDataKeyEncryptionDao} * and {@link CustomKmsClientConfig} examples. *

- * For examples of how to use the AWS KMS Discovery keyring on decrypt, + * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, * see the {@link DiscoveryDecrypt}, * {@link DiscoveryDecryptInRegionOnly}, * and {@link DiscoveryDecryptWithPreferredRegions} examples. @@ -35,7 +36,7 @@ public class SingleCmk { /** - * Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with a single CMK. + * Demonstrate an encrypt/decrypt cycle using an AWS KMS symmetric multi-cmk keyring with a single CMK. * * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys * @param sourcePlaintext Plaintext to encrypt @@ -55,7 +56,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("the data you are handling", "is what you think it is"); // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java index d2b714dd6..91aeeb84f 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java @@ -24,11 +24,11 @@ * the ability to enjoy the benefits of AWS KMS during normal operation * but retain the ability to decrypt encrypted messages without access to AWS KMS. * This example shows how you can use the multi-keyring to achieve this - * by combining an AWS KMS keyring with a raw RSA keyring. + * by combining an AWS KMS symmetric multi-CMK keyring with a raw RSA keyring. *

* https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-multi-keyring *

- * For more examples of how to use the AWS KMS keyring, see the keyring/awskms examples. + * For more examples of how to use the AWS KMS keyrings, see the keyring/awskms examples. *

* For more examples of how to use the raw RSA keyring, see the keyring/rawrsa examples. *

@@ -97,10 +97,10 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext .paddingScheme(RsaPaddingScheme.OAEP_SHA256_MGF1) .build(); - // Create the AWS KMS keyring that you will use from decryption during normal operations. - final Keyring kmsKeyring = StandardKeyrings.awsKms(awsKmsCmk); + // Create the AWS KMS symmetric multi-CMK keyring that you will use from decryption during normal operations. + final Keyring kmsKeyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); - // Combine the AWS KMS keyring and the escrow encrypt keyring using the multi-keyring. + // Combine the AWS KMS symmetric multi-CMK keyring and the escrow encrypt keyring using the multi-keyring. final Keyring encryptKeyring = StandardKeyrings.multi(kmsKeyring, escrowEncryptKeyring); // Encrypt your plaintext data using the multi-keyring. @@ -118,7 +118,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // Demonstrate that the ciphertext and plaintext are different. assert !Arrays.equals(ciphertext, sourcePlaintext); - // Decrypt your encrypted data separately using the AWS KMS keyring and the escrow decrypt keyring. + // Decrypt your encrypted data separately using the AWS KMS symmetric multi-CMK keyring and the escrow decrypt keyring. // // You do not need to specify the encryption context on decrypt because // the header of the encrypted message includes the encryption context. diff --git a/src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java index badadb3e0..004663a05 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java @@ -30,30 +30,30 @@ public class LambdaDecryptAndWriteExample implements RequestHandler { private static final long MAX_ENTRY_AGE_MILLISECONDS = 600000; private static final int MAX_CACHE_ENTRIES = 100; - + // For best caching performance in Lambda, we want our cache to be a static final field // configured by environment variables. // However, to make this example easier for people to experiment with, we also provide a non-static // version with simpler configuration. private static final CachingCryptoMaterialsManager CACHING_CRYPTO_MATERIALS_MANAGER; private static final String TABLE_NAME = System.getProperty("TABLE_NAME"); - + static { final String cmkArn = System.getProperty("CMK_ARN"); CACHING_CRYPTO_MATERIALS_MANAGER = CachingCryptoMaterialsManager.newBuilder() - .withKeyring(StandardKeyrings.awsKms(AwsKmsCmkId.fromString(cmkArn))) + .withKeyring(StandardKeyrings.awsKmsSymmetricMultiCmk(AwsKmsCmkId.fromString(cmkArn))) .withCache(new LocalCryptoMaterialsCache(MAX_CACHE_ENTRIES)) .withMaxAge(MAX_ENTRY_AGE_MILLISECONDS, TimeUnit.MILLISECONDS) .build(); } - + private final CachingCryptoMaterialsManager cachingMaterialsManager_; private final AwsCrypto crypto_; private final Table table_; /** * No-argument constructor for use with Lambda. - * + * * This is almost equivalent to calling {@link #LambdaDecryptAndWriteExample(String, String)} with * {@code cmkArn = System.getProperty("CMK_ARN")} * and @@ -61,7 +61,7 @@ public class LambdaDecryptAndWriteExample implements RequestHandler encryptionContext = Collections.singletonMap("purpose", "test"); // Create a keyring. - final Keyring keyring = StandardKeyrings.awsKms(kmsCmkArn); + final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(kmsCmkArn); // Create a cache. final CryptoMaterialsCache cache = new LocalCryptoMaterialsCache(CAPACITY); diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java index b1e214680..8c70028e5 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java @@ -19,6 +19,7 @@ import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.AwsKmsDataKeyEncryptionDaoBuilder; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; @@ -143,7 +144,7 @@ public Keyring build() { // If we have an ARN, obtain the region from the ARN (to specify the region of the AWS SDK KMS service client) final Optional childArn = AwsKmsCmkId.getArnFromKeyName(keyName.toString()); final String childRegion = childArn.isPresent() ? childArn.get().getRegion() : null; - final String childKey = childRegion == null ? NULL_REGION : childRegion; + final String childKey = StringUtils.isBlank(childRegion) ? NULL_REGION : childRegion; // Check if a client already exists for the given region // and use the existing dao or construct a new one diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java index 83fbb9c80..4882717f2 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java @@ -17,6 +17,7 @@ import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.encryptionsdk.kms.AwsKmsDataKeyEncryptionDaoBuilder; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; @@ -25,8 +26,6 @@ public class AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder { - private static final String NULL_REGION = "null-region"; - private List regionIds; private String awsAccountId; private List grantTokens; @@ -119,16 +118,18 @@ public Keyring build() { List discoveryKeyrings = new ArrayList<>(); if (this.regionIds != null) { for (final String region : this.regionIds) { - final String regionKey = region == null ? NULL_REGION : region; + if (StringUtils.isBlank(region)) { + continue; + } // Check if a client already exists for the given region // and use the existing dao or construct a new one - if (clientMapping.containsKey(regionKey)) { - final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(clientMapping.get(regionKey), region, this.awsAccountId); + if (clientMapping.containsKey(region)) { + final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(clientMapping.get(region), region, this.awsAccountId); discoveryKeyrings.add(discoveryKeyring); } else { final DataKeyEncryptionDao discoveryDao = constructDataKeyEncryptionDao(region); - clientMapping.put(regionKey, discoveryDao); + clientMapping.put(region, discoveryDao); final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(discoveryDao, region, this.awsAccountId); discoveryKeyrings.add(discoveryKeyring); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java index 5d730c53a..e1eb1e940 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java @@ -14,6 +14,7 @@ package com.amazonaws.encryptionsdk.keyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; import java.util.Arrays; import java.util.List; @@ -48,7 +49,39 @@ public static RawRsaKeyringBuilder rawRsaBuilder() { } /** - * Constructs a {@code MultiKeyring} which interacts with AWS Key Management Service (KMS) to create, + * Constructs an {@code AwsKmsSymmetricKeyring} which interacts with AWS Key Management Service (KMS) to create, + * encrypt, and decrypt data keys using the supplied AWS KMS defined Customer Master Key (CMK) + * and DataKeyEncryptionDao. + * + * @param dataKeyEncryptionDao A {@link DataKeyEncryptionDao} used to make calls to AWS KMS. + * @param keyName An {@link AwsKmsCmkId} in ARN, CMK Alias, ARN Alias or Key Id format that identifies an + * AWS KMS CMK responsible for generating a data key, as well as encrypting and + * decrypting data keys. + * @return The {@code Keyring} + */ + public static Keyring awsKmsSymmetric(DataKeyEncryptionDao dataKeyEncryptionDao, AwsKmsCmkId keyName) { + return new AwsKmsSymmetricKeyring(dataKeyEncryptionDao, keyName); + } + + /** + * Constructs a {@code MultiKeyring} of {@code AwsKmsSymmetricKeyring}(s), + * which interacts with AWS Key Management Service (KMS) to create, + * encrypt, and decrypt data keys using the supplied AWS KMS defined Customer Master Keys (CMKs). + * + * @param generatorKeyName An {@link AwsKmsCmkId} in ARN, CMK Alias, ARN Alias or Key Id format that identifies an + * AWS KMS CMK responsible for generating a data key, as well as encrypting and + * decrypting data keys. + * @return The {@code Keyring} + */ + public static Keyring awsKmsSymmetricMultiCmk(AwsKmsCmkId generatorKeyName) { + return AwsKmsSymmetricMultiCmkKeyringBuilder.standard() + .generator(generatorKeyName) + .build(); + } + + /** + * Constructs a {@code MultiKeyring} of {@code AwsKmsSymmetricKeyring}(s), + * which interacts with AWS Key Management Service (KMS) to create, * encrypt, and decrypt data keys using the supplied AWS KMS defined Customer Master Keys (CMKs). * * @param generatorKeyName An {@link AwsKmsCmkId} in ARN, CMK Alias, ARN Alias or Key Id format that identifies an @@ -66,21 +99,50 @@ public static Keyring awsKmsSymmetricMultiCmk(AwsKmsCmkId generatorKeyName, List } /** - * Returns a {@link AwsKmsSymmetricMultiCmkKeyringBuilder} for use in constructing a keyring which interacts with + * Construct an {@code AwsKmsSymmetricMultiCmkKeyringBuilder} for use in constructing a keyring which interacts with * AWS Key Management Service (KMS) to create, encrypt, and decrypt data keys using AWS KMS defined * Customer Master Keys (CMKs). * - * @return The {@link AwsKmsSymmetricMultiCmkKeyringBuilder} + * @return The {@code AwsKmsSymmetricMultiCmkKeyringBuilder} */ public static AwsKmsSymmetricMultiCmkKeyringBuilder awsKmsSymmetricMultiCmkBuilder() { return AwsKmsSymmetricMultiCmkKeyringBuilder.standard(); } /** - * Constructs a {@code MultiKeyring} which interacts with AWS Key Management Service (KMS) to decrypt data keys - * using in the specified AWS regions. + * Constructs an {@code AwsKmsSymmetricRegionDiscoveryKeyring} which interacts with AWS Key Management Service (KMS) + * in the specified AWS region using the provided DataKeyEncryptionDao. + * + * @param dataKeyEncryptionDao A {@link DataKeyEncryptionDao} used to make calls to AWS KMS. + * @param regionId A string representing the AWS region to attempt decryption in. + * @return The {@code Keyring} + */ + public static Keyring awsKmsSymmetricRegionDiscovery(DataKeyEncryptionDao dataKeyEncryptionDao, String regionId) { + return new AwsKmsSymmetricRegionDiscoveryKeyring(dataKeyEncryptionDao, regionId, null); + } + + /** + * Constructs an {@code AwsKmsSymmetricRegionDiscoveryKeyring} which interacts with AWS Key Management Service (KMS) + * in the specified AWS region using the provided DataKeyEncryptionDao. + * If an {@code awsAccountId} is provided, + * the {@code AwsKmsSymmetricRegionDiscoveryKeyring} will only attempt to decrypt encrypted data keys + * associated with that AWS account. + * + * @param dataKeyEncryptionDao A {@link DataKeyEncryptionDao} used to make calls to AWS KMS. + * @param regionId A string representing the AWS region to attempt decryption in. + * @param awsAccountId An optional string representing an AWS account Id. + * @return The {@code Keyring} + */ + public static Keyring awsKmsSymmetricRegionDiscovery(DataKeyEncryptionDao dataKeyEncryptionDao, String regionId, String awsAccountId) { + return new AwsKmsSymmetricRegionDiscoveryKeyring(dataKeyEncryptionDao, regionId, awsAccountId); + } + + /** + * Constructs a {@code MultiKeyring} of {@code AwsKmsSymmetricRegionDiscoveryKeyring}(s) + * which interacts with AWS Key Management Service (KMS) to decrypt data keys + * in the specified AWS regions. * - * @param regionIds A list of strings representing AWS regions to attempt decryption in. + * @param regionIds A list of strings representing AWS regions to attempt decryption in. * @return The {@code Keyring} */ public static Keyring awsKmsSymmetricMultiRegionDiscovery(List regionIds) { @@ -90,8 +152,8 @@ public static Keyring awsKmsSymmetricMultiRegionDiscovery(List regionIds } /** - * Returns an {@link AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder} - * for use in constructing an AWS KMS multi-region discovery keyring. + * Constructs an {@code AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder} + * for use in constructing an AWS KMS symmetric multi-region discovery keyring. * 'Discovery' keyrings do not specify any CMKs to decrypt with, and thus will attempt to decrypt * using any encrypted data key in the specified region(s). 'Discovery' keyrings do not perform encryption. * diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java index e07136338..8f4731d58 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java @@ -16,6 +16,7 @@ import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.services.kms.AWSKMSClientBuilder; +import org.apache.commons.lang3.StringUtils; import java.util.List; @@ -113,7 +114,7 @@ public AwsKmsDataKeyEncryptionDaoBuilder clientConfiguration(ClientConfiguration * @return The AwsKmsDataKeyEncryptionDaoBuilder, for method chaining */ public AwsKmsDataKeyEncryptionDaoBuilder regionId(String regionId) { - if (regionId != null) { + if (!StringUtils.isBlank(regionId)) { this.regionId = regionId; } return this; diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java index c1f0ab027..6c3af788b 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java @@ -49,7 +49,7 @@ * Represents a single Customer Master Key (CMK) and is used to encrypt/decrypt data with * {@link AwsCrypto}. * - * @deprecated Replaced by {@code KmsKeyring}. See {@link StandardKeyrings}. + * @deprecated Replaced by {@code AwsKmsSymmetricKeyring} and {@code AwsKmsSymmetricRegionDiscoveryKeyring}. See {@link StandardKeyrings}. */ @Deprecated public final class KmsMasterKey extends MasterKey implements KmsMethods { diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java index f53ee101f..7b49b77c6 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java @@ -56,7 +56,7 @@ * Provides {@link MasterKey}s backed by the AWS Key Management Service. This object is regional and * if you want to use keys from multiple regions, you'll need multiple copies of this object. * - * @deprecated Replaced by {@code KmsKeyring}. See {@link StandardKeyrings}. + * @deprecated Replaced by {@code AwsKmsSymmetricKeyring} and {@code AwsKmsSymmetricRegionDiscoveryKeyring}. See {@link StandardKeyrings}. */ @Deprecated public class KmsMasterKeyProvider extends MasterKeyProvider implements KmsMethods { diff --git a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java index 78b76387d..1f13d7820 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java +++ b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java @@ -18,10 +18,8 @@ import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; -import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder; import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; import com.amazonaws.util.IOUtils; import com.fasterxml.jackson.core.type.TypeReference; @@ -65,12 +63,9 @@ class TestVectorRunner { // 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 AwsKmsClientSupplier awsKmsClientSupplier = AwsKmsServiceClientBuilder.defaultBuilder() - .credentialsProvider(new DefaultAWSCredentialsProviderChain()) - .build(); private static final KmsMasterKeyProvider kmsProv = KmsMasterKeyProvider .builder() - .withCustomClientFactory(awsKmsClientSupplier::getClient) + .withCredentials(new DefaultAWSCredentialsProviderChain()) .build(); @ParameterizedTest(name = "Compatibility Test: {0}") @@ -158,9 +153,9 @@ private static TestCase parseTest(String testName, Map data, Map final KeyEntry key = keys.get(keyName); if ("aws-kms".equals(type)) { - keyrings.add(StandardKeyrings.awsKmsBuilder() - .awsKmsClientSupplier(awsKmsClientSupplier) - .generatorKeyId(AwsKmsCmkId.fromString(key.keyId)) + keyrings.add(StandardKeyrings.awsKmsSymmetricMultiCmkBuilder() + .credentialsProvider(new DefaultAWSCredentialsProviderChain()) + .generator(AwsKmsCmkId.fromString(key.keyId)) .build()); mks.add(kmsProv.getMasterKey(key.keyId)); } else if ("raw".equals(type)) { diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringTest.java deleted file mode 100644 index c295d3f9c..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringTest.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.keyrings; - -import com.amazonaws.encryptionsdk.CryptoAlgorithm; -import com.amazonaws.encryptionsdk.EncryptedDataKey; -import com.amazonaws.encryptionsdk.exception.AwsCryptoException; -import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; -import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; -import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; -import com.amazonaws.encryptionsdk.model.DecryptionMaterials; -import com.amazonaws.encryptionsdk.model.EncryptionMaterials; -import com.amazonaws.encryptionsdk.model.KeyBlob; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; -import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; -import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; -import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; -import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY; -import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class AwsKmsKeyringTest { - - private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; - private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); - private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); - private static final String GENERATOR_KEY_ID = "arn:aws:kms:us-east-1:999999999999:key/generator-89ab-cdef-fedc-ba9876543210"; - private static final String KEY_ID_1 = "arn:aws:kms:us-east-1:999999999999:key/key1-23bv-sdfs-werw-234323nfdsf"; - private static final String KEY_ID_2 = "arn:aws:kms:us-east-1:999999999999:key/key2-02ds-wvjs-aswe-a4923489273"; - private static final AwsKmsCmkId GENERATOR_KEY = AwsKmsCmkId.fromString(GENERATOR_KEY_ID); - private static final AwsKmsCmkId KEY_1 = AwsKmsCmkId.fromString(KEY_ID_1); - private static final AwsKmsCmkId KEY_2 = AwsKmsCmkId.fromString(KEY_ID_2); - private static final EncryptedDataKey ENCRYPTED_GENERATOR_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, - GENERATOR_KEY_ID.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); - private static final EncryptedDataKey ENCRYPTED_KEY_1 = new KeyBlob(AWS_KMS_PROVIDER_ID, - KEY_ID_1.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); - private static final EncryptedDataKey ENCRYPTED_KEY_2 = new KeyBlob(AWS_KMS_PROVIDER_ID, - KEY_ID_2.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); - private static final KeyringTraceEntry ENCRYPTED_GENERATOR_KEY_TRACE = - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, GENERATOR_KEY_ID, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); - private static final KeyringTraceEntry ENCRYPTED_KEY_1_TRACE = - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); - private static final KeyringTraceEntry ENCRYPTED_KEY_2_TRACE = - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_2, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); - private static final KeyringTraceEntry GENERATED_DATA_KEY_TRACE = - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, GENERATOR_KEY_ID, GENERATED_DATA_KEY); - @Mock(lenient = true) private DataKeyEncryptionDao dataKeyEncryptionDao; - private Keyring keyring; - - @BeforeEach - void setup() { - when(dataKeyEncryptionDao.encryptDataKey(GENERATOR_KEY, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_GENERATOR_KEY); - when(dataKeyEncryptionDao.encryptDataKey(KEY_1, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_KEY_1); - when(dataKeyEncryptionDao.encryptDataKey(KEY_2, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_KEY_2); - - when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_GENERATOR_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) - .thenReturn(new DecryptDataKeyResult(GENERATOR_KEY_ID, PLAINTEXT_DATA_KEY)); - when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) - .thenReturn(new DecryptDataKeyResult(KEY_ID_1, PLAINTEXT_DATA_KEY)); - when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_2, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) - .thenReturn(new DecryptDataKeyResult(KEY_ID_2, PLAINTEXT_DATA_KEY)); - - List keyIds = new ArrayList<>(); - keyIds.add(AwsKmsCmkId.fromString(KEY_ID_1)); - keyIds.add(AwsKmsCmkId.fromString(KEY_ID_2)); - keyring = new AwsKmsKeyring(dataKeyEncryptionDao, keyIds, AwsKmsCmkId.fromString(GENERATOR_KEY_ID), false); - } - - @Test - void testMalformedArns() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - List encryptedDataKeys = new ArrayList<>(); - encryptedDataKeys.add(new KeyBlob(AWS_KMS_PROVIDER_ID, "arn:badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); - encryptedDataKeys.add(ENCRYPTED_KEY_1); - - decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - // Malformed Arn for a non KMS provider shouldn't fail - encryptedDataKeys.clear(); - encryptedDataKeys.add(new KeyBlob("OtherProviderId", "arn:badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); - assertFalse(keyring.onDecrypt(decryptionMaterials, encryptedDataKeys).hasCleartextDataKey()); - } - - @Test - void testGeneratorKeyInKeyIds() { - assertThrows(IllegalArgumentException.class, () -> new AwsKmsKeyring(dataKeyEncryptionDao, - Collections.singletonList(AwsKmsCmkId.fromString(GENERATOR_KEY_ID)), - AwsKmsCmkId.fromString(GENERATOR_KEY_ID), false)); - } - - @Test - void testNotDiscoveryNoKeysIds() { - assertThrows(IllegalArgumentException.class, () -> new AwsKmsKeyring(dataKeyEncryptionDao, - null,null, false)); - } - - @Test - void testDiscoveryWithKeyId() { - assertThrows(IllegalArgumentException.class, () -> new AwsKmsKeyring(dataKeyEncryptionDao, - null, - AwsKmsCmkId.fromString(GENERATOR_KEY_ID), true)); - assertThrows(IllegalArgumentException.class, () -> new AwsKmsKeyring(dataKeyEncryptionDao, - Collections.singletonList(AwsKmsCmkId.fromString(GENERATOR_KEY_ID)), - null, true)); - } - - @Test - void testEncryptDecryptExistingDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setCleartextDataKey(PLAINTEXT_DATA_KEY) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - encryptionMaterials = keyring.onEncrypt(encryptionMaterials); - - assertEquals(3, encryptionMaterials.getEncryptedDataKeys().size()); - assertEncryptedDataKeyEquals(ENCRYPTED_KEY_1, encryptionMaterials.getEncryptedDataKeys().get(0)); - assertEncryptedDataKeyEquals(ENCRYPTED_KEY_2, encryptionMaterials.getEncryptedDataKeys().get(1)); - assertEncryptedDataKeyEquals(ENCRYPTED_GENERATOR_KEY, encryptionMaterials.getEncryptedDataKeys().get(2)); - - assertEquals(3, encryptionMaterials.getKeyringTrace().getEntries().size()); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_GENERATOR_KEY_TRACE)); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_1_TRACE)); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_2_TRACE)); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - List encryptedDataKeys = new ArrayList<>(); - encryptedDataKeys.add(ENCRYPTED_GENERATOR_KEY); - encryptedDataKeys.add(ENCRYPTED_KEY_1); - encryptedDataKeys.add(ENCRYPTED_KEY_2); - decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); - } - - @Test - void testEncryptNullDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - when(dataKeyEncryptionDao.generateDataKey(GENERATOR_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) - .thenReturn(new DataKeyEncryptionDao.GenerateDataKeyResult(PLAINTEXT_DATA_KEY, ENCRYPTED_GENERATOR_KEY)); - encryptionMaterials = keyring.onEncrypt(encryptionMaterials); - - assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getCleartextDataKey()); - - assertEquals(4, encryptionMaterials.getKeyringTrace().getEntries().size()); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(GENERATED_DATA_KEY_TRACE)); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_GENERATOR_KEY_TRACE)); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_1_TRACE)); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_2_TRACE)); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - List encryptedDataKeys = new ArrayList<>(); - encryptedDataKeys.add(ENCRYPTED_GENERATOR_KEY); - encryptedDataKeys.add(ENCRYPTED_KEY_1); - encryptedDataKeys.add(ENCRYPTED_KEY_2); - decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); - } - - @Test - void testEncryptNullGenerator() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setCleartextDataKey(PLAINTEXT_DATA_KEY) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - Keyring keyring = new AwsKmsKeyring(dataKeyEncryptionDao, - Collections.singletonList(AwsKmsCmkId.fromString(KEY_ID_1)), null, false); - - encryptionMaterials = keyring.onEncrypt(encryptionMaterials); - - assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); - assertEncryptedDataKeyEquals(ENCRYPTED_KEY_1, encryptionMaterials.getEncryptedDataKeys().get(0)); - - assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getCleartextDataKey()); - - assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().size()); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_1_TRACE)); - } - - @Test - void testDiscoveryEncrypt() { - keyring = new AwsKmsKeyring(dataKeyEncryptionDao, null, null, true); - - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - encryptionMaterials = keyring.onEncrypt(encryptionMaterials); - - assertFalse(encryptionMaterials.hasCleartextDataKey()); - assertEquals(0, encryptionMaterials.getKeyringTrace().getEntries().size()); - } - - @Test - void testEncryptNoGeneratorOrCleartextDataKey() { - keyring = new AwsKmsKeyring(dataKeyEncryptionDao, - Collections.singletonList(AwsKmsCmkId.fromString(KEY_ID_1)), null, false); - - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder().setAlgorithm(ALGORITHM_SUITE).build(); - assertThrows(AwsCryptoException.class, () -> keyring.onEncrypt(encryptionMaterials)); - } - - @Test - void testDecryptFirstKeyFails() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new CannotUnwrapDataKeyException()); - - List encryptedDataKeys = new ArrayList<>(); - encryptedDataKeys.add(ENCRYPTED_KEY_1); - encryptedDataKeys.add(ENCRYPTED_KEY_2); - decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); - } - - @Test - void testDecryptMismatchedDataKeyException() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new MismatchedDataKeyException()); - - assertThrows(MismatchedDataKeyException.class, () -> keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_KEY_1))); - } - - @Test - void testDecryptFirstKeyWrongProvider() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - EncryptedDataKey wrongProviderKey = new KeyBlob("OtherProvider", KEY_ID_1.getBytes(PROVIDER_ENCODING), new byte[]{}); - - List encryptedDataKeys = new ArrayList<>(); - encryptedDataKeys.add(wrongProviderKey); - encryptedDataKeys.add(ENCRYPTED_KEY_2); - decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); - } - - @Test - void testDiscoveryDecrypt() { - keyring = new AwsKmsKeyring(dataKeyEncryptionDao, null, null, true); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - List encryptedDataKeys = new ArrayList<>(); - encryptedDataKeys.add(ENCRYPTED_KEY_1); - encryptedDataKeys.add(ENCRYPTED_KEY_2); - decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_1, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); - } - - @Test - void testDecryptAlreadyDecryptedDataKey() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setCleartextDataKey(PLAINTEXT_DATA_KEY) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_GENERATOR_KEY)); - - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); - } - - @Test - void testDecryptNoDataKey() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() - .setAlgorithm(ALGORITHM_SUITE) - .setEncryptionContext(ENCRYPTION_CONTEXT) - .build(); - - decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); - - assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); - } - - private void assertEncryptedDataKeyEquals(EncryptedDataKey expected, EncryptedDataKey actual) { - assertEquals(expected.getProviderId(), actual.getProviderId()); - assertArrayEquals(expected.getProviderInformation(), actual.getProviderInformation()); - assertArrayEquals(expected.getEncryptedDataKey(), actual.getEncryptedDataKey()); - } -} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java index ffe759989..f6c84a953 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java @@ -54,7 +54,7 @@ class MasterKeyProviderCompatibilityTest { void testAwsKmsKeyringCompatibility() { MasterKeyProvider mkp = KmsMasterKeyProvider.builder() .withKeysForEncryption(KMSTestFixtures.TEST_KEY_IDS[0]).build(); - Keyring keyring = StandardKeyrings.awsKms(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); + Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmk(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); testCompatibility(keyring, mkp); } @@ -103,7 +103,7 @@ void testMultiKeyringCompatibility() { MasterKeyProvider mkp = MultipleProviderFactory.buildMultiProvider(mkp1, mkp2); - Keyring keyring1 = StandardKeyrings.awsKms(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); + Keyring keyring1 = StandardKeyrings.awsKmsSymmetricMultiCmk(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); Keyring keyring2 = StandardKeyrings.rawAesBuilder() .keyNamespace(KEY_NAMESPACE) .keyName(KEY_NAME) diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilderTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilderTest.java new file mode 100644 index 000000000..7d75f3b78 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilderTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.services.kms.AWSKMS; +import com.amazonaws.services.kms.AWSKMSClientBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AwsKmsDataKeyEncryptionDaoBuilderTest { + + @Mock AWSKMSClientBuilder kmsClientBuilder; + @Mock AWSKMS awskms; + @Mock AWSCredentialsProvider credentialsProvider; + @Mock ClientConfiguration clientConfiguration; + private static final String REGION = "us-east-1"; + + @Test + void testCredentialsClientAndRegionConfiguration() { + when(kmsClientBuilder.withClientConfiguration(clientConfiguration)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.withCredentials(credentialsProvider)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.withRegion(REGION)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + DataKeyEncryptionDao builder = new AwsKmsDataKeyEncryptionDaoBuilder(kmsClientBuilder) + .credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .regionId(REGION) + .build(); + + verify(kmsClientBuilder).withCredentials(credentialsProvider); + verify(kmsClientBuilder).withClientConfiguration(clientConfiguration); + verify(kmsClientBuilder).withRegion(REGION); + verify(kmsClientBuilder).build(); + } + + @Test + void testDefaultConfiguration() { + when(kmsClientBuilder.build()).thenReturn(awskms); + + DataKeyEncryptionDao builder = new AwsKmsDataKeyEncryptionDaoBuilder(kmsClientBuilder).build(); + + verify(kmsClientBuilder).build(); + } + + @Test + void testNullConfiguration() { + when(kmsClientBuilder.build()).thenReturn(awskms); + + DataKeyEncryptionDao builder = new AwsKmsDataKeyEncryptionDaoBuilder(kmsClientBuilder) + .credentialsProvider(null) + .clientConfiguration(null) + .regionId(null) + .build(); + + verify(kmsClientBuilder).build(); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java index ff4099e45..eaecc8c9d 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java @@ -66,7 +66,7 @@ class AwsKmsDataKeyEncryptionDaoTest { @Test void testEncryptAndDecrypt() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); EncryptedDataKey encryptedDataKeyResult = dao.encryptDataKey( @@ -105,7 +105,7 @@ void testEncryptAndDecrypt() { @Test void testGenerateAndDecrypt() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); DataKeyEncryptionDao.GenerateDataKeyResult generateDataKeyResult = dao.generateDataKey( @@ -145,7 +145,7 @@ void testGenerateAndDecrypt() { @Test void testEncryptWithRawKeyId() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); String rawKeyId = keyId.split("/")[1]; @@ -174,7 +174,7 @@ void testEncryptWrongKeyFormat() { when(key.getFormat()).thenReturn("BadFormat"); AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); @@ -185,7 +185,7 @@ void testEncryptWrongKeyFormat() { @Test void testKmsFailure() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); doThrow(new KMSInvalidStateException("fail")).when(client).generateDataKey(isA(GenerateDataKeyRequest.class)); @@ -202,7 +202,7 @@ void testKmsFailure() { @Test void testUnsupportedRegionException() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); doThrow(new UnsupportedRegionException("fail")).when(client).generateDataKey(isA(GenerateDataKeyRequest.class)); @@ -219,7 +219,7 @@ void testUnsupportedRegionException() { @Test void testDecryptBadKmsKeyId() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); DecryptResult badResult = new DecryptResult(); badResult.setKeyId("badKeyId"); @@ -232,7 +232,7 @@ void testDecryptBadKmsKeyId() { @Test void testDecryptBadKmsKeyLength() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); DecryptResult badResult = new DecryptResult(); badResult.setKeyId(new String(ENCRYPTED_DATA_KEY.getProviderInformation(), EncryptedDataKey.PROVIDER_ENCODING)); diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsServiceClientBuilderTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsServiceClientBuilderTest.java deleted file mode 100644 index e18b4e65c..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsServiceClientBuilderTest.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.kms; - -import com.amazonaws.ClientConfiguration; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; -import com.amazonaws.encryptionsdk.kms.AwsKmsServiceClientBuilder.DefaultAwsKmsClientSupplierBuilder; -import com.amazonaws.services.kms.AWSKMS; -import com.amazonaws.services.kms.AWSKMSClientBuilder; -import com.amazonaws.services.kms.model.AWSKMSException; -import com.amazonaws.services.kms.model.DecryptRequest; -import com.amazonaws.services.kms.model.EncryptRequest; -import com.amazonaws.services.kms.model.EncryptResult; -import com.amazonaws.services.kms.model.GenerateDataKeyRequest; -import com.amazonaws.services.kms.model.KMSInvalidStateException; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class AwsKmsServiceClientBuilderTest { - - @Mock AWSKMSClientBuilder kmsClientBuilder; - @Mock AWSKMS awskms; - @Mock EncryptRequest encryptRequest; - @Mock DecryptRequest decryptRequest; - @Mock GenerateDataKeyRequest generateDataKeyRequest; - @Mock AWSCredentialsProvider credentialsProvider; - @Mock ClientConfiguration clientConfiguration; - private static final String REGION_1 = "us-east-1"; - private static final String REGION_2 = "us-west-2"; - private static final String REGION_3 = "eu-west-1"; - - @Test - void testCredentialsAndClientConfiguration() { - when(kmsClientBuilder.withClientConfiguration(clientConfiguration)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.withCredentials(credentialsProvider)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - AwsKmsClientSupplier supplier = new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder) - .credentialsProvider(credentialsProvider) - .clientConfiguration(clientConfiguration) - .build(); - - supplier.getClient(null); - - verify(kmsClientBuilder).withClientConfiguration(clientConfiguration); - verify(kmsClientBuilder).withCredentials(credentialsProvider); - verify(kmsClientBuilder).build(); - } - - @Test - void testClientCaching() { - AwsKmsClientSupplier supplier = new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder) - .build(); - - when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.withRegion(REGION_2)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.withRegion(REGION_3)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - AWSKMS client = supplier.getClient(REGION_1); - AWSKMS client2 = supplier.getClient(REGION_2); - AWSKMS client3 = supplier.getClient(REGION_3); - verify(kmsClientBuilder, times(3)).build(); - - // No KMS methods have been called yet, so clients remain uncached - supplier.getClient(REGION_1); - supplier.getClient(REGION_2); - supplier.getClient(REGION_3); - verify(kmsClientBuilder, times(6)).build(); - - when(awskms.encrypt(encryptRequest)).thenReturn(new EncryptResult()); - when(awskms.decrypt(decryptRequest)).thenThrow(new KMSInvalidStateException("test")); - when(awskms.generateDataKey(generateDataKeyRequest)).thenThrow(new IllegalArgumentException("test")); - - // Successful KMS call, client is cached - client.encrypt(encryptRequest); - supplier.getClient(REGION_1); - verify(kmsClientBuilder, times(6)).build(); - - // KMS call resulted in KMS exception, client is cached - assertThrows(AWSKMSException.class, () -> client2.decrypt(decryptRequest)); - supplier.getClient(REGION_2); - verify(kmsClientBuilder, times(6)).build(); - - // KMS call resulted in non-KMS exception, client is not cached - assertThrows(IllegalArgumentException.class, () -> client3.generateDataKey(generateDataKeyRequest)); - supplier.getClient(REGION_3); - verify(kmsClientBuilder, times(7)).build(); - - // Non-KMS method, client is not cached - client3.toString(); - supplier.getClient(REGION_3); - verify(kmsClientBuilder, times(8)).build(); - } - - @Test - void testGetClientByKeyId() { - - final String arn = "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210"; - final String aliasArn = "arn:aws:kms:us-east-1:999999999999:alias/MyCryptoKey"; - final String alias = "alias/MyCryptoKey"; - final String keyId = "01234567-89ab-cdef-fedc-ba9876543210"; - - assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(arn), s -> awskms)); - assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(aliasArn), s -> awskms)); - assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(alias), s -> awskms)); - assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(keyId), s -> awskms)); - } - - @Test - void testAllowedRegions() { - AwsKmsClientSupplier supplierWithDefaultValues = new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder) - .build(); - - when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - assertNotNull(supplierWithDefaultValues.getClient(REGION_1)); - - AwsKmsClientSupplier supplierWithAllowed = AwsKmsServiceClientBuilder - .allowRegionsBuilder(Collections.singleton(REGION_1)) - .baseClientSupplier(new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder).build()).build(); - - when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - assertNotNull(supplierWithAllowed.getClient(REGION_1)); - assertThrows(UnsupportedRegionException.class, () -> supplierWithAllowed.getClient(REGION_2)); - } - - @Test - void testDeniedRegions() { - AwsKmsClientSupplier supplierWithDefaultValues = new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder) - .build(); - - when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - assertNotNull(supplierWithDefaultValues.getClient(REGION_1)); - - AwsKmsClientSupplier supplierWithDenied = AwsKmsServiceClientBuilder - .denyRegionsBuilder(Collections.singleton(REGION_1)) - .baseClientSupplier(new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder).build()).build(); - - when(kmsClientBuilder.withRegion(REGION_2)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - assertThrows(UnsupportedRegionException.class, () -> supplierWithDenied.getClient(REGION_1)); - assertNotNull(supplierWithDenied.getClient(REGION_2)); - } -} diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java index d1ea1a0ec..1235ae137 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java @@ -18,6 +18,9 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; import com.amazonaws.encryptionsdk.model.KeyBlob; +import com.amazonaws.services.kms.AWSKMS; +import com.amazonaws.services.kms.model.DecryptRequest; +import com.amazonaws.services.kms.model.DecryptResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -25,15 +28,18 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -42,7 +48,8 @@ class KmsMasterKeyTest { private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384; private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("test", "value"); private static final String CMK_ARN = "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210"; - @Mock AwsKmsDataKeyEncryptionDao dataKeyEncryptionDao; + @Mock Supplier supplier; + @Mock AWSKMS awsKmsClient; /** * Test that when decryption of an encrypted data key throws a MismatchedDataKeyException, this @@ -52,14 +59,20 @@ class KmsMasterKeyTest { void testMismatchedDataKeyException() { EncryptedDataKey encryptedDataKey1 = new KeyBlob(AWS_KMS_PROVIDER_ID, "KeyId1".getBytes(PROVIDER_ENCODING), generate(64)); EncryptedDataKey encryptedDataKey2 = new KeyBlob(AWS_KMS_PROVIDER_ID, "KeyId2".getBytes(PROVIDER_ENCODING), generate(64)); - SecretKey secretKey = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + byte[] generated = generate(ALGORITHM_SUITE.getDataKeyLength()); + SecretKey secretKey = new SecretKeySpec(generated, ALGORITHM_SUITE.getDataKeyAlgo()); + when(supplier.get()).thenReturn(awsKmsClient); - when(dataKeyEncryptionDao.decryptDataKey(encryptedDataKey1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) - .thenThrow(new MismatchedDataKeyException()); - when(dataKeyEncryptionDao.decryptDataKey(encryptedDataKey2, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) - .thenReturn(new DataKeyEncryptionDao.DecryptDataKeyResult("KeyId2", secretKey)); + when(awsKmsClient.decrypt(any(DecryptRequest.class))) + // Fail the first call + .thenThrow(new MismatchedDataKeyException()) + // Return the decrypted result for the second EncryptedDataKey on the second decrypt call + .thenReturn(new DecryptResult() + .withKeyId("KeyId2") + .withEncryptionAlgorithm(secretKey.getAlgorithm()) + .withPlaintext(ByteBuffer.wrap(generated))); - KmsMasterKey kmsMasterKey = new KmsMasterKey(dataKeyEncryptionDao, CMK_ARN, null); + KmsMasterKey kmsMasterKey = KmsMasterKey.getInstance(supplier, CMK_ARN, null); List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(encryptedDataKey1); @@ -69,5 +82,4 @@ void testMismatchedDataKeyException() { assertEquals(secretKey, result.getKey()); } - } From 3921a45a059b885ee15ee2059dba2bf7c2810d4a Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Wed, 12 Aug 2020 00:39:48 -0700 Subject: [PATCH 03/15] Fix AwsKmsDataKeyEncryptionDaoTest --- .../kms/AwsKmsDataKeyEncryptionDao.java | 5 +- .../kms/AwsKmsDataKeyEncryptionDaoTest.java | 50 ++++++++++++++----- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java index aa3507cc2..7a9cbeea4 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java @@ -29,6 +29,7 @@ import com.amazonaws.services.kms.model.DecryptRequest; import com.amazonaws.services.kms.model.EncryptRequest; import com.amazonaws.services.kms.model.GenerateDataKeyRequest; +import org.apache.commons.lang3.StringUtils; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -162,8 +163,8 @@ public DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, Cr private T updateUserAgent(T request) { // Only append the user agent string if the user agent string is the AWS SDK default // and if we are allowed to append it - String marker = request.getRequestClientOptions().getClientMarker(RequestClientOptions.Marker.USER_AGENT); - if (this.canAppendUserAgentString && ClientConfiguration.DEFAULT_USER_AGENT.equals(marker)) { + final String marker = request.getRequestClientOptions().getClientMarker(RequestClientOptions.Marker.USER_AGENT); + if (this.canAppendUserAgentString && marker == null) { request.getRequestClientOptions().appendUserAgent(VersionInfo.USER_AGENT); } return request; diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java index eaecc8c9d..fa0ec3c61 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java @@ -43,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.isA; @@ -56,6 +57,7 @@ class AwsKmsDataKeyEncryptionDaoTest { + private static final boolean CAN_APPEND_USER_AGENT_STRING = true; private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; private static final SecretKey DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); private static final List GRANT_TOKENS = Collections.singletonList("testGrantToken"); @@ -65,12 +67,21 @@ class AwsKmsDataKeyEncryptionDaoTest { @Test void testEncryptAndDecrypt() { + encryptAndDecrypt(CAN_APPEND_USER_AGENT_STRING); + } + + @Test + void testEncryptAndDecryptCannotAppendUserAgentString() { + encryptAndDecrypt(!CAN_APPEND_USER_AGENT_STRING); + } + + void encryptAndDecrypt(boolean canAppendUserAgentString) { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS, canAppendUserAgentString); String keyId = client.createKey().getKeyMetadata().getArn(); EncryptedDataKey encryptedDataKeyResult = dao.encryptDataKey( - AwsKmsCmkId.fromString(keyId), DATA_KEY, ENCRYPTION_CONTEXT); + AwsKmsCmkId.fromString(keyId), DATA_KEY, ENCRYPTION_CONTEXT); ArgumentCaptor er = ArgumentCaptor.forClass(EncryptRequest.class); verify(client, times(1)).encrypt(er.capture()); @@ -81,7 +92,7 @@ void testEncryptAndDecrypt() { assertEquals(GRANT_TOKENS, actualRequest.getGrantTokens()); assertEquals(ENCRYPTION_CONTEXT, actualRequest.getEncryptionContext()); assertArrayEquals(DATA_KEY.getEncoded(), actualRequest.getPlaintext().array()); - assertUserAgent(actualRequest); + assertUserAgent(actualRequest, canAppendUserAgentString); assertEquals(AWS_KMS_PROVIDER_ID, encryptedDataKeyResult.getProviderId()); assertArrayEquals(keyId.getBytes(EncryptedDataKey.PROVIDER_ENCODING), encryptedDataKeyResult.getProviderInformation()); @@ -96,7 +107,7 @@ void testEncryptAndDecrypt() { assertEquals(GRANT_TOKENS, actualDecryptRequest.getGrantTokens()); assertEquals(ENCRYPTION_CONTEXT, actualDecryptRequest.getEncryptionContext()); assertArrayEquals(encryptedDataKeyResult.getEncryptedDataKey(), actualDecryptRequest.getCiphertextBlob().array()); - assertUserAgent(actualDecryptRequest); + assertUserAgent(actualDecryptRequest, canAppendUserAgentString); assertEquals(DATA_KEY, decryptDataKeyResult.getPlaintextDataKey()); assertEquals(keyId, decryptDataKeyResult.getKeyArn()); @@ -104,12 +115,21 @@ void testEncryptAndDecrypt() { @Test void testGenerateAndDecrypt() { + generateAndDecrypt(CAN_APPEND_USER_AGENT_STRING); + } + + @Test + void testGenerateAndDecryptCannotAppendUserAgentString() { + generateAndDecrypt(!CAN_APPEND_USER_AGENT_STRING); + } + + void generateAndDecrypt(boolean canAppendUserAgentString) { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS, canAppendUserAgentString); String keyId = client.createKey().getKeyMetadata().getArn(); DataKeyEncryptionDao.GenerateDataKeyResult generateDataKeyResult = dao.generateDataKey( - AwsKmsCmkId.fromString(keyId), ALGORITHM_SUITE, ENCRYPTION_CONTEXT); + AwsKmsCmkId.fromString(keyId), ALGORITHM_SUITE, ENCRYPTION_CONTEXT); ArgumentCaptor gr = ArgumentCaptor.forClass(GenerateDataKeyRequest.class); verify(client, times(1)).generateDataKey(gr.capture()); @@ -120,7 +140,7 @@ void testGenerateAndDecrypt() { assertEquals(GRANT_TOKENS, actualRequest.getGrantTokens()); assertEquals(ENCRYPTION_CONTEXT, actualRequest.getEncryptionContext()); assertEquals(ALGORITHM_SUITE.getDataKeyLength(), actualRequest.getNumberOfBytes()); - assertUserAgent(actualRequest); + assertUserAgent(actualRequest, canAppendUserAgentString); assertNotNull(generateDataKeyResult.getPlaintextDataKey()); assertEquals(ALGORITHM_SUITE.getDataKeyLength(), generateDataKeyResult.getPlaintextDataKey().getEncoded().length); @@ -136,7 +156,7 @@ void testGenerateAndDecrypt() { assertEquals(GRANT_TOKENS, actualDecryptRequest.getGrantTokens()); assertEquals(ENCRYPTION_CONTEXT, actualDecryptRequest.getEncryptionContext()); assertArrayEquals(generateDataKeyResult.getEncryptedDataKey().getEncryptedDataKey(), actualDecryptRequest.getCiphertextBlob().array()); - assertUserAgent(actualDecryptRequest); + assertUserAgent(actualDecryptRequest, canAppendUserAgentString); assertEquals(generateDataKeyResult.getPlaintextDataKey(), decryptDataKeyResult.getPlaintextDataKey()); assertEquals(keyId, decryptDataKeyResult.getKeyArn()); @@ -145,7 +165,7 @@ void testGenerateAndDecrypt() { @Test void testEncryptWithRawKeyId() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(client, GRANT_TOKENS, CAN_APPEND_USER_AGENT_STRING); String keyId = client.createKey().getKeyMetadata().getArn(); String rawKeyId = keyId.split("/")[1]; @@ -161,7 +181,7 @@ void testEncryptWithRawKeyId() { assertEquals(GRANT_TOKENS, actualRequest.getGrantTokens()); assertEquals(ENCRYPTION_CONTEXT, actualRequest.getEncryptionContext()); assertArrayEquals(DATA_KEY.getEncoded(), actualRequest.getPlaintext().array()); - assertUserAgent(actualRequest); + assertUserAgent(actualRequest, CAN_APPEND_USER_AGENT_STRING); assertEquals(AWS_KMS_PROVIDER_ID, encryptedDataKeyResult.getProviderId()); assertArrayEquals(keyId.getBytes(EncryptedDataKey.PROVIDER_ENCODING), encryptedDataKeyResult.getProviderInformation()); @@ -243,9 +263,13 @@ void testDecryptBadKmsKeyLength() { assertThrows(IllegalStateException.class, () -> dao.decryptDataKey(ENCRYPTED_DATA_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); } - private void assertUserAgent(AmazonWebServiceRequest request) { - assertTrue(request.getRequestClientOptions().getClientMarker(RequestClientOptions.Marker.USER_AGENT) + private void assertUserAgent(AmazonWebServiceRequest request, boolean couldAppend) { + if (couldAppend) { + assertTrue(request.getRequestClientOptions().getClientMarker(RequestClientOptions.Marker.USER_AGENT) .contains(VersionInfo.USER_AGENT)); + } else { + // In this case, we never appended anything to the user agent string, so it remains null + assertNull(request.getRequestClientOptions().getClientMarker(RequestClientOptions.Marker.USER_AGENT)); + } } - } From 3f76b5df1baadd67d6f6e8f2bd14d68c718271d9 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Wed, 12 Aug 2020 15:30:14 -0700 Subject: [PATCH 04/15] Example updates, fixed broken links --- src/examples/README.md | 6 +- ... ActSimilarToAwsKmsMasterKeyProvider.java} | 31 +++-- .../awskms/CustomDataKeyEncryptionDao.java | 2 +- .../keyring/awskms/CustomKmsClientConfig.java | 6 +- .../keyring/awskms/DiscoveryDecrypt.java | 130 ------------------ .../awskms/DiscoveryDecryptInRegionOnly.java | 8 +- .../DiscoveryDecryptWithPreferredRegions.java | 5 +- .../keyring/awskms/MultipleRegions.java | 3 +- .../examples/keyring/awskms/SingleCmk.java | 3 +- .../keyrings/AwsKmsSymmetricKeyring.java | 34 +++-- ...AwsKmsSymmetricMultiCmkKeyringBuilder.java | 4 + ...AwsKmsSymmetricRegionDiscoveryKeyring.java | 8 +- 12 files changed, 61 insertions(+), 179 deletions(-) rename src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/{ActLikeAwsKmsMasterKeyProvider.java => ActSimilarToAwsKmsMasterKeyProvider.java} (87%) delete mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java diff --git a/src/examples/README.md b/src/examples/README.md index 3ba2e035f..fcb3db378 100644 --- a/src/examples/README.md +++ b/src/examples/README.md @@ -33,18 +33,18 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java) * [with master key providers](./java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/MultipleRegions.java) * How to decrypt when you don't know the CMK - * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java) + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java) * [with master key providers](./java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/DiscoveryDecrypt.java) * How to decrypt within a region * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java) * How to decrypt with a preferred region but failover to others * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java) * How to reproduce the behavior of an AWS KMS master key provider - * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/ActLikeAwsKmsMasterKeyProvider.java) + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/ActSimilarToAwsKmsMasterKeyProvider.java) * How to use AWS KMS clients with custom configuration * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java) * How to use different AWS KMS client for different regions - * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java) + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java) * Using raw wrapping keys * How to use a raw AES wrapping key * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java) diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActLikeAwsKmsMasterKeyProvider.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActSimilarToAwsKmsMasterKeyProvider.java similarity index 87% rename from src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActLikeAwsKmsMasterKeyProvider.java rename to src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActSimilarToAwsKmsMasterKeyProvider.java index b6c6adb6e..4dfd3d9ae 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActLikeAwsKmsMasterKeyProvider.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActSimilarToAwsKmsMasterKeyProvider.java @@ -3,6 +3,7 @@ package com.amazonaws.crypto.examples.keyring.awskms; +import com.amazonaws.arn.Arn; import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.AwsCryptoResult; import com.amazonaws.encryptionsdk.DecryptRequest; @@ -11,7 +12,6 @@ import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; -import com.amazonaws.regions.Regions; import java.util.ArrayList; import java.util.Arrays; @@ -24,12 +24,14 @@ /** * You might have used master key providers to protect your data keys * in an earlier version of the AWS Encryption SDK. - * This example shows how to configure a keyring that behaves like an AWS KMS master key provider. + * This example shows how to configure a keyring that behaves similarly to an AWS KMS master key provider. *

* The AWS Encryption SDK provided an AWS KMS master key provider for * interacting with AWS Key Management Service (AWS KMS). + * * On encrypt, the AWS KMS master key provider behaves like the AWS KMS symmetric multi-CMK keyring * and encrypts with all CMKs that you identify. + * * However, on decrypt, * the AWS KMS master key provider reviews each encrypted data key (EDK). * If the EDK was encrypted under an AWS KMS CMK, @@ -37,16 +39,27 @@ * Whether decryption succeeds depends on permissions on the CMK. * This continues until the AWS KMS master key provider either runs out of EDKs * or succeeds in decrypting an EDK. + * In order to maintain a similar behavior, + * we use an AWS KMS symmetric multi-region keyring + * that has a list of regions it will attempt decryption in. + * * We have found that separating these two behaviors * makes the expected behavior clearer, * so that is what we did with the AWS KMS symmetric keyring and the AWS KMS region discovery keyring. * However, as you migrate from master key providers to keyrings, - * you might want a keyring that behaves like the AWS KMS master key provider. + * you might want a keyring that behaves similarly the AWS KMS master key provider. + * + * Since the AWS KMS symmetric multi-region keyring cannot perform encryption, + * it cannot be combined with an AWS KMS symmetric multi-CMK in a multi-keyring, + * if the encrypt operation is ever called. + * Therefore, we have two separate keyrings. + * One for encrypting with a specific list of CMKs + * and one for decrypting with a specific list of regions. *

* For more examples of how to use the AWS KMS keyrings, * see the 'keyring/awskms' directory. */ -public class ActLikeAwsKmsMasterKeyProvider { +public class ActSimilarToAwsKmsMasterKeyProvider { /** * Demonstrate how to create a keyring that behaves like an AWS KMS master key provider. @@ -93,7 +106,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final List awsK .build(); // Create an AWS KMS symmetric multi-region discovery keyring that will attempt to decrypt - // any data keys that were encrypted under an AWS KMS CMK. + // any data keys that were encrypted under an AWS KMS CMK in a specific list of AWS regions. // // Please note that the multi-region discovery keyring requires the specific list of AWS regions // it may communicate with. @@ -103,13 +116,15 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final List awsK // This will prevent any AWS SDK-derived region-lists from potentially becoming stale over time. // // In most cases, you should simply call StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery - // with the specific regions you need. + // with the specific regions you need, + // and not attempt decryption in any AWS region. // // This will provide flexibility for adding more regions over time, // without allowing unnecessary access to regions that are not currently required. final List allRegionIds = new ArrayList<>(); - for (final Regions region: Regions.values()) { - allRegionIds.add(region.getName()); + allRegionIds.add(Arn.fromString(awsKmsCmk.toString()).getRegion()); + for (final AwsKmsCmkId additionalKeyName : awsKmsAdditionalCmks) { + allRegionIds.add(Arn.fromString(additionalKeyName.toString()).getRegion()); } final Keyring discoveryKeyring = StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery(allRegionIds); diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java index c82f24011..145a8f96d 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java @@ -49,7 +49,7 @@ * see the {@link CustomKmsClientConfig} example. *

* For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, - * see the {@link DiscoveryDecrypt}, {@link DiscoveryDecryptInRegionOnly}, + * see the {@link DiscoveryDecryptInRegionOnly} * and {@link DiscoveryDecryptWithPreferredRegions} examples. */ public class CustomDataKeyEncryptionDao { diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java index 193138cad..909b02738 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java @@ -31,10 +31,10 @@ * see the {@link MultipleRegions} example. *

* For another example of how to use the AWS KMS symmetric multi-CMK keyring with a custom client configuration, - * see the {@link CustomKmsClientConfig} example. + * see the {@link CustomDataKeyEncryptionDao} example. *

* For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, - * see the {@link DiscoveryDecrypt}, {@link DiscoveryDecryptInRegionOnly}, + * see the {@link DiscoveryDecryptInRegionOnly} * and {@link DiscoveryDecryptWithPreferredRegions} examples. */ public class CustomKmsClientConfig { @@ -72,7 +72,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // but if you need to, you can set a custom credentials provider as well. // Create the keyring that determines how your data keys are protected, - // providing the client supplier that you created. + // providing the client configuration that you created. final Keyring keyring = StandardKeyrings.awsKmsSymmetricMultiCmkBuilder() .generator(awsKmsCmk) .clientConfiguration(clientConfiguration) diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java deleted file mode 100644 index 606514950..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.amazonaws.crypto.examples.keyring.awskms; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.regions.Regions; - -import java.util.*; - -/** - * When you give the AWS KMS symmetric multi-CMK keyring specific key names it will use those CMKs and nothing else. - * This is true both on encrypt and on decrypt. - * However, sometimes you need more flexibility on decrypt, - * especially if you don't know which CMK was used to encrypt a message. - * To address this need, you can use an AWS KMS symmetric multi-region discovery keyring. - * The AWS KMS symmetric multi-region discovery keyring is a multi-keyring of AWS KMS symmetric region discovery keyrings. - * AWS KMS symmetric region discovery keyrings cannot encrypt. - * On decrypt each AWS KMS symmetric region discovery keyring reviews each encrypted data key (EDK). - * If an EDK was encrypted under an AWS KMS CMK, - * the AWS KMS symmetric region discovery keyring attempts to decrypt it if the EDK's region matches the region associated - * with the AWS KMS symmetric region discovery keyring. - * Whether decryption succeeds depends on permissions on the CMK. - * This continues until the AWS KMS symmetric region discovery keyring either runs out of EDKs - * or succeeds in decrypting an EDK. - *

- * Each AWS KMS symmetric region discovery keyring is restricted to a single AWS region. - * Additionally, an AWS KMS symmetric multi-region discovery keyring restricts communication to the configured regions, - * in their configured order. - *

- * This example shows how to configure and use an AWS KMS symmetric multi-region discovery keyring. - * The AWS KMS symmetric multi-region discovery keyring is an easy way to configure a multi-keyring of AWS KMS region discovery keyrings. - *

- * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring - *

- * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, - * see the {@link MultipleRegions} example. - *

- * For examples of how to use the AWS KMS symmetric multi-CMK keyring with custom client configurations, - * see the {@link CustomDataKeyEncryptionDao} - * and {@link CustomKmsClientConfig} examples. - *

- * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, - * see the {@link DiscoveryDecrypt}, - * and {@link DiscoveryDecryptInRegionOnly} examples. - */ -public class DiscoveryDecrypt { - - /** - * Demonstrate configuring an AWS KMS symmetric multi-region discovery keyring for decryption. - * - * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys - * @param sourcePlaintext Plaintext to encrypt - */ - public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { - // Instantiate the AWS Encryption SDK. - final AwsCrypto awsEncryptionSdk = new AwsCrypto(); - - // Prepare your encryption context. - // Remember that your encryption context is NOT SECRET. - // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context - final Map encryptionContext = new HashMap<>(); - encryptionContext.put("encryption", "context"); - encryptionContext.put("is not", "secret"); - encryptionContext.put("but adds", "useful metadata"); - encryptionContext.put("that can help you", "be confident that"); - encryptionContext.put("the data you are handling", "is what you think it is"); - - // Create the keyring that determines how your data keys are protected. - final Keyring encryptKeyring = StandardKeyrings.awsKmsSymmetricMultiCmk(awsKmsCmk); - - // Create an AWS KMS symmetric multi-region discovery keyring to use on decrypt. - // Please note that the multi-region discovery keyring requires the specific list of AWS regions - // it may communicate with. - // - // In production, if you need a keyring that attempts decryption in all AWS regions, - // you should call a service/API to get an updated list of AWS regions. - // This will prevent any AWS SDK-derived region-lists from potentially becoming stale over time. - // - // In most cases, you should simply call StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery - // with the specific regions you need. - // - // This will provide flexibility for adding more regions over time, - // without allowing unnecessary access to regions that are not currently required. - final List allRegionIds = new ArrayList<>(); - for (final Regions region: Regions.values()) { - allRegionIds.add(region.getName()); - } - final Keyring decryptKeyring = StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery(allRegionIds); - - // Encrypt your plaintext data. - final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( - EncryptRequest.builder() - .keyring(encryptKeyring) - .encryptionContext(encryptionContext) - .plaintext(sourcePlaintext).build()); - final byte[] ciphertext = encryptResult.getResult(); - - // Demonstrate that the ciphertext and plaintext are different. - assert !Arrays.equals(ciphertext, sourcePlaintext); - - // Decrypt your encrypted data using the AWS KMS symmetric multi-region discovery keyring. - // - // You do not need to specify the encryption context on decrypt because - // the header of the encrypted message includes the encryption context. - final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( - DecryptRequest.builder() - .keyring(decryptKeyring) - .ciphertext(ciphertext).build()); - final byte[] decrypted = decryptResult.getResult(); - - // Demonstrate that the decrypted plaintext is identical to the original plaintext. - assert Arrays.equals(decrypted, sourcePlaintext); - - // Verify that the encryption context used in the decrypt operation includes - // the encryption context that you specified when encrypting. - // The AWS Encryption SDK can add pairs, so don't require an exact match. - // - // In production, always use a meaningful encryption context. - encryptionContext.forEach((k, v) -> { - assert v.equals(decryptResult.getEncryptionContext().get(k)); - }); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java index 745de2e24..c1d33acae 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java @@ -48,9 +48,8 @@ * see the {@link CustomDataKeyEncryptionDao} * and {@link CustomKmsClientConfig} examples. *

- * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, - * see the {@link DiscoveryDecrypt}, - * and {@link DiscoveryDecryptInRegionOnly} examples. + * For more examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, + * see the {@link DiscoveryDecryptWithPreferredRegions} examples. */ public class DiscoveryDecryptInRegionOnly { @@ -82,8 +81,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // Create the AWS KMS symmetric multi-region discovery keyring that we will use on decrypt. // - // The client supplier that we specify here will only supply clients for the specified region. - // The keyring only attempts to decrypt data keys if it can get a client for that region, + // The keyring only attempts to decrypt data keys if they are associated with a configured region, // so this keyring will now ignore any data keys that were encrypted under a CMK in another region. final Keyring decryptKeyring = StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery(Collections.singletonList(decryptRegion)); diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java index f01466fd4..cb28d8425 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java @@ -54,9 +54,8 @@ * see the {@link CustomDataKeyEncryptionDao} * and {@link CustomKmsClientConfig} examples. *

- * For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, - * see the {@link DiscoveryDecrypt}, - * and {@link DiscoveryDecryptInRegionOnly} examples. + * For more examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, + * see the {@link DiscoveryDecryptInRegionOnly} examples. */ public class DiscoveryDecryptWithPreferredRegions { diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java index d181d58df..829aa6537 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java @@ -31,8 +31,7 @@ * and {@link CustomKmsClientConfig} examples. *

* For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, - * see the {@link DiscoveryDecrypt}, - * {@link DiscoveryDecryptInRegionOnly}, + * see the {@link DiscoveryDecryptInRegionOnly} * and {@link DiscoveryDecryptWithPreferredRegions} examples. */ public class MultipleRegions { diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java index eff21fdac..c4731cf86 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java @@ -29,8 +29,7 @@ * and {@link CustomKmsClientConfig} examples. *

* For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, - * see the {@link DiscoveryDecrypt}, - * {@link DiscoveryDecryptInRegionOnly}, + * see the {@link DiscoveryDecryptInRegionOnly} * and {@link DiscoveryDecryptWithPreferredRegions} examples. */ public class SingleCmk { diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java index 93972f640..efda37455 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java @@ -59,40 +59,39 @@ public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { // Given a plaintext data key in the encryption materials, OnEncrypt MUST attempt // to encrypt the plaintext data key using the provided key name - final AwsKmsCmkId keyNameToEncrypt = AwsKmsCmkId.fromString(keyName.toString()); - resultMaterials = encryptDataKey(keyNameToEncrypt, resultMaterials); + resultMaterials = encryptDataKey(resultMaterials); return resultMaterials; } private EncryptionMaterials generateDataKey(final EncryptionMaterials encryptionMaterials) { - final DataKeyEncryptionDao.GenerateDataKeyResult result = dataKeyEncryptionDao.generateDataKey( - keyName, encryptionMaterials.getAlgorithm(), encryptionMaterials.getEncryptionContext()); + final DataKeyEncryptionDao.GenerateDataKeyResult result = this.dataKeyEncryptionDao.generateDataKey( + this.keyName, encryptionMaterials.getAlgorithm(), encryptionMaterials.getEncryptionContext()); return encryptionMaterials .withCleartextDataKey( result.getPlaintextDataKey(), new KeyringTraceEntry( AWS_KMS_PROVIDER_ID, - keyName.toString(), + this.keyName.toString(), KeyringTraceFlag.GENERATED_DATA_KEY)) .withEncryptedDataKey( new KeyBlob(result.getEncryptedDataKey()), new KeyringTraceEntry( AWS_KMS_PROVIDER_ID, - keyName.toString(), + this.keyName.toString(), KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); } - private EncryptionMaterials encryptDataKey(final AwsKmsCmkId keyId, final EncryptionMaterials encryptionMaterials) { - final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao.encryptDataKey( - keyId, encryptionMaterials.getCleartextDataKey(), encryptionMaterials.getEncryptionContext()); + private EncryptionMaterials encryptDataKey(final EncryptionMaterials encryptionMaterials) { + final EncryptedDataKey encryptedDataKey = this.dataKeyEncryptionDao.encryptDataKey( + this.keyName, encryptionMaterials.getCleartextDataKey(), encryptionMaterials.getEncryptionContext()); return encryptionMaterials.withEncryptedDataKey( new KeyBlob(encryptedDataKey), new KeyringTraceEntry( AWS_KMS_PROVIDER_ID, - keyId.toString(), + this.keyName.toString(), KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); } @@ -106,11 +105,10 @@ public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, Li return decryptionMaterials; } - final AwsKmsCmkId configuredKeyName = AwsKmsCmkId.fromString(keyName.toString()); for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { - if (okToDecrypt(encryptedDataKey, configuredKeyName)) { + if (okToDecrypt(encryptedDataKey)) { try { - final DataKeyEncryptionDao.DecryptDataKeyResult result = dataKeyEncryptionDao.decryptDataKey( + final DataKeyEncryptionDao.DecryptDataKeyResult result = this.dataKeyEncryptionDao.decryptDataKey( encryptedDataKey, decryptionMaterials.getAlgorithm(), decryptionMaterials.getEncryptionContext()); return decryptionMaterials.withCleartextDataKey(result.getPlaintextDataKey(), @@ -128,20 +126,20 @@ public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, Li return decryptionMaterials; } - private boolean okToDecrypt(EncryptedDataKey encryptedDataKey, AwsKmsCmkId configuredKeyName) { + private boolean okToDecrypt(EncryptedDataKey encryptedDataKey) { // Only attempt to decrypt keys provided by KMS - if (!encryptedDataKey.getProviderId().equals(AWS_KMS_PROVIDER_ID)) { + if (encryptedDataKey == null || !encryptedDataKey.getProviderId().equals(AWS_KMS_PROVIDER_ID)) { return false; } // If the key name cannot be parsed, skip it - final String keyName = new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING); - if (!AwsKmsCmkId.isKeyIdWellFormed(keyName)) { + final String edkKeyName = new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING); + if (!AwsKmsCmkId.isKeyIdWellFormed(edkKeyName)) { return false; } // OnDecrypt MUST attempt to decrypt each input encrypted data key in the input encrypted data key list // where the key provider info has a value equal to the keyring's key name - return configuredKeyName.equals(AwsKmsCmkId.fromString(keyName)); + return this.keyName.equals(AwsKmsCmkId.fromString(edkKeyName)); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java index 8c70028e5..1af233619 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java @@ -141,6 +141,10 @@ public Keyring build() { List childKeyrings = new ArrayList<>(); if (this.childKeyNames != null) { for (final AwsKmsCmkId keyName : this.childKeyNames) { + if (keyName == null) { + continue; + } + // If we have an ARN, obtain the region from the ARN (to specify the region of the AWS SDK KMS service client) final Optional childArn = AwsKmsCmkId.getArnFromKeyName(keyName.toString()); final String childRegion = childArn.isPresent() ? childArn.get().getRegion() : null; diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java index b7244b233..707864d2f 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java @@ -65,7 +65,7 @@ public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, Li for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { if (okToDecrypt(encryptedDataKey)) { try { - final DataKeyEncryptionDao.DecryptDataKeyResult result = dataKeyEncryptionDao.decryptDataKey( + final DataKeyEncryptionDao.DecryptDataKeyResult result = this.dataKeyEncryptionDao.decryptDataKey( encryptedDataKey, decryptionMaterials.getAlgorithm(), decryptionMaterials.getEncryptionContext()); return decryptionMaterials.withCleartextDataKey( @@ -86,7 +86,7 @@ public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, Li private boolean okToDecrypt(EncryptedDataKey encryptedDataKey) { // Only attempt to decrypt keys provided by KMS - if (!encryptedDataKey.getProviderId().equals(AWS_KMS_PROVIDER_ID)) { + if (encryptedDataKey == null || !encryptedDataKey.getProviderId().equals(AWS_KMS_PROVIDER_ID)) { return false; } @@ -105,11 +105,11 @@ private boolean okToDecrypt(EncryptedDataKey encryptedDataKey) { // If an AWS account ID is provided, // this keyring must only decrypt encrypted data keys // that were encrypted using an AWS KMS CMK in that AWS account - if (awsAccountId.isPresent() && !awsAccountId.get().equals(arn.get().getAccountId())) { + if (this.awsAccountId.isPresent() && !this.awsAccountId.get().equals(arn.get().getAccountId())) { return false; } // Finally, determine if the region matches the keyring's client's region - return arn.get().getRegion().equalsIgnoreCase(awsRegion); + return this.awsRegion.equals(arn.get().getRegion()); } } From 0ee4799924d78dfbe45950be6974fe322bb768d9 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Wed, 12 Aug 2020 15:59:03 -0700 Subject: [PATCH 05/15] Clean up builders --- ...AwsKmsSymmetricMultiCmkKeyringBuilder.java | 40 ++++++++++--------- ...ricMultiRegionDiscoveryKeyringBuilder.java | 23 +++++------ .../AwsKmsDataKeyEncryptionDaoBuilder.java | 25 +++++------- 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java index 1af233619..ec4964358 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java @@ -22,10 +22,10 @@ import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; public class AwsKmsSymmetricMultiCmkKeyringBuilder { @@ -121,23 +121,27 @@ public AwsKmsSymmetricMultiCmkKeyringBuilder generator(AwsKmsCmkId generatorKeyN */ public Keyring build() { // A mapping of AWS region to DataKeyEncryptionDao - final Map clientMapping = new ConcurrentHashMap<>(); + final Map clientMapping = new HashMap<>(); // First construct the generator keyring Keyring generatorKeyring = null; if (this.generatorKeyName != null) { - // If we have an ARN, obtain the region from the ARN (to specify the region of the AWS SDK KMS service client) + // If we have an ARN, obtain the region from the ARN + // to specify the region of the AWS SDK KMS service client final Optional generatorArn = AwsKmsCmkId.getArnFromKeyName(this.generatorKeyName.toString()); final String generatorRegion = generatorArn.isPresent() ? generatorArn.get().getRegion() : null; final DataKeyEncryptionDao generatorDao = constructDataKeyEncryptionDao(generatorRegion); - // Add the client to the mapping with the region key if available + + // Add the client to the mapping with the region key // This prevents re-creating multiple clients for the same region during a single build call - clientMapping.put(generatorRegion == null ? NULL_REGION : generatorRegion, generatorDao); + final String generatorRegionKey = StringUtils.isBlank(generatorRegion) ? NULL_REGION : generatorRegion; + clientMapping.put(generatorRegionKey, generatorDao); + // Construct the generator keyring generatorKeyring = new AwsKmsSymmetricKeyring(generatorDao, this.generatorKeyName); } - // Next, construct the child keyrings + // Next, construct the child (non-generator) keyrings List childKeyrings = new ArrayList<>(); if (this.childKeyNames != null) { for (final AwsKmsCmkId keyName : this.childKeyNames) { @@ -150,30 +154,28 @@ public Keyring build() { final String childRegion = childArn.isPresent() ? childArn.get().getRegion() : null; final String childKey = StringUtils.isBlank(childRegion) ? NULL_REGION : childRegion; - // Check if a client already exists for the given region - // and use the existing dao or construct a new one - if (clientMapping.containsKey(childKey)) { - final Keyring childKeyring = new AwsKmsSymmetricKeyring(clientMapping.get(childKey), keyName); - childKeyrings.add(childKeyring); - } else { - final DataKeyEncryptionDao childDao = constructDataKeyEncryptionDao(childRegion); + // Check if a dao already exists for the given region + // and use the existing dao or construct a new one and save it + final boolean daoExists = clientMapping.containsKey(childKey); + final DataKeyEncryptionDao childDao = daoExists ? clientMapping.get(childKey) : constructDataKeyEncryptionDao(childRegion); + if (!daoExists) { clientMapping.put(childKey, childDao); - final Keyring childKeyring = new AwsKmsSymmetricKeyring(childDao, keyName); - childKeyrings.add(childKeyring); } + final Keyring childKeyring = new AwsKmsSymmetricKeyring(childDao, keyName); + childKeyrings.add(childKeyring); } } // Finally, construct a multi-keyring - return new MultiKeyring(generatorKeyring, childKeyrings); + return StandardKeyrings.multi(generatorKeyring, childKeyrings); } private DataKeyEncryptionDao constructDataKeyEncryptionDao(String regionId) { return AwsKmsDataKeyEncryptionDaoBuilder .defaultBuilder() - .clientConfiguration(clientConfiguration) - .credentialsProvider(credentialsProvider) - .grantTokens(grantTokens) + .clientConfiguration(this.clientConfiguration) + .credentialsProvider(this.credentialsProvider) + .grantTokens(this.grantTokens) .regionId(regionId) .build(); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java index 4882717f2..1f0b30d09 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java @@ -20,9 +20,9 @@ import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; public class AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder { @@ -112,7 +112,7 @@ public AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder awsAccountId(String aws */ public Keyring build() { // A mapping of AWS region to DataKeyEncryptionDao - final Map clientMapping = new ConcurrentHashMap<>(); + final Map clientMapping = new HashMap<>(); // Construct each AwsKmsSymmetricRegionDiscoveryKeyring List discoveryKeyrings = new ArrayList<>(); @@ -122,22 +122,21 @@ public Keyring build() { continue; } - // Check if a client already exists for the given region - // and use the existing dao or construct a new one - if (clientMapping.containsKey(region)) { - final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(clientMapping.get(region), region, this.awsAccountId); - discoveryKeyrings.add(discoveryKeyring); - } else { - final DataKeyEncryptionDao discoveryDao = constructDataKeyEncryptionDao(region); + // Check if a dao already exists for the given region + // and use the existing dao or construct a new one and save it + final boolean discoveryDaoExists = clientMapping.containsKey(region); + final DataKeyEncryptionDao discoveryDao = discoveryDaoExists ? clientMapping.get(region) : constructDataKeyEncryptionDao(region); + if (!discoveryDaoExists) { clientMapping.put(region, discoveryDao); - final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(discoveryDao, region, this.awsAccountId); - discoveryKeyrings.add(discoveryKeyring); } + + final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(discoveryDao, region, this.awsAccountId); + discoveryKeyrings.add(discoveryKeyring); } } // Finally, construct a multi-keyring - return new MultiKeyring(null, discoveryKeyrings); + return StandardKeyrings.multi(null, discoveryKeyrings); } private DataKeyEncryptionDao constructDataKeyEncryptionDao(String regionId) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java index 8f4731d58..4ea62761f 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilder.java @@ -58,10 +58,11 @@ public AwsKmsDataKeyEncryptionDao build() { } if (clientConfiguration != null) { + canAppendUserAgentString = false; awsKmsClientBuilder = awsKmsClientBuilder.withClientConfiguration(clientConfiguration); } - if (regionId != null) { + if (StringUtils.isNotBlank(regionId)) { awsKmsClientBuilder = awsKmsClientBuilder.withRegion(regionId); } @@ -80,43 +81,35 @@ public AwsKmsDataKeyEncryptionDaoBuilder grantTokens(List grantTokens) { } /** - * Sets a non-null AWSCredentialsProvider to be used by the client. + * Sets an AWSCredentialsProvider to be used by the client. * - * @param credentialsProvider New AWSCredentialsProvider to use. + * @param credentialsProvider Custom AWSCredentialsProvider to use. * @return The AwsKmsDataKeyEncryptionDaoBuilder, for method chaining */ public AwsKmsDataKeyEncryptionDaoBuilder credentialsProvider(AWSCredentialsProvider credentialsProvider) { - if (credentialsProvider != null) { - this.credentialsProvider = credentialsProvider; - } + this.credentialsProvider = credentialsProvider; return this; } /** - * Sets a non-null ClientConfiguration to be used by the client. + * Sets a ClientConfiguration to be used by the client. * * @param clientConfiguration Custom configuration to use. * @return The AwsKmsDataKeyEncryptionDaoBuilder, for method chaining */ public AwsKmsDataKeyEncryptionDaoBuilder clientConfiguration(ClientConfiguration clientConfiguration) { - if (clientConfiguration != null) { - this.clientConfiguration = clientConfiguration; - // If a client configuration is provided, we must not modify the user agent string - this.canAppendUserAgentString = false; - } + this.clientConfiguration = clientConfiguration; return this; } /** - * Sets a non-null AWS region string to be used by the client. + * Sets an AWS region string to be used by the client. * * @param regionId AWS region for the client. * @return The AwsKmsDataKeyEncryptionDaoBuilder, for method chaining */ public AwsKmsDataKeyEncryptionDaoBuilder regionId(String regionId) { - if (!StringUtils.isBlank(regionId)) { - this.regionId = regionId; - } + this.regionId = regionId; return this; } } From f59f07dfa879d3cf2f0ee14a284c26c32124d27f Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Wed, 12 Aug 2020 17:28:50 -0700 Subject: [PATCH 06/15] Fix bug where generator double encrypts, fix examples --- .../awskms/CustomDataKeyEncryptionDao.java | 88 ++++++++++++------- .../keyring/awskms/CustomKmsClientConfig.java | 2 +- .../awskms/DiscoveryDecryptInRegionOnly.java | 3 +- .../DiscoveryDecryptWithPreferredRegions.java | 3 +- .../keyrings/AwsKmsSymmetricKeyring.java | 2 +- .../kms/AwsKmsDataKeyEncryptionDao.java | 2 - 6 files changed, 62 insertions(+), 38 deletions(-) diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java index 145a8f96d..ef5695693 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java @@ -3,11 +3,9 @@ package com.amazonaws.crypto.examples.keyring.awskms; +import com.amazonaws.arn.Arn; import com.amazonaws.auth.profile.ProfileCredentialsProvider; -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.*; import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; @@ -15,9 +13,8 @@ import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; import com.amazonaws.regions.Regions; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import javax.crypto.SecretKey; +import java.util.*; /** * The AWS KMS symmetric keyring is associated with a single DataKeyEncryptionDao and a single key name. @@ -36,8 +33,8 @@ * or if you are working with regions that have separate authentication silos * like "ap-east-1" and "me-south-1". *

- * This example shows how to create a multi-keyring of AWS KMS symmetric keyrings - * that will supply AWS KMS clients with valid credentials for the target region + * This example shows how to create an AWS KMS symmetric keyring + * that is configured with AWS KMS clients that have valid credentials for the target region, * even when working with regions that need different credentials. *

* https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring @@ -54,6 +51,52 @@ */ public class CustomDataKeyEncryptionDao { + static class CustomMultiPartitionDao implements DataKeyEncryptionDao { + + private static final AwsKmsDataKeyEncryptionDaoBuilder CHINA_BUILDER = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("china")) + .regionId(Regions.CN_NORTH_1.getName()); + private static final AwsKmsDataKeyEncryptionDaoBuilder MIDDLE_EAST_BUILDER = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("middle-east")) + .regionId(Regions.ME_SOUTH_1.getName()); + private static final AwsKmsDataKeyEncryptionDaoBuilder HONG_KONG_BUILDER = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("hong-kong")) + .regionId(Regions.AP_EAST_1.getName()); + + private final DataKeyEncryptionDao usableDao; + + private CustomMultiPartitionDao(DataKeyEncryptionDao usableDao){ + this.usableDao = usableDao; + } + + static CustomMultiPartitionDao daoGivenRegionId(String regionId) { + if (regionId.startsWith("cn-")) { + return new CustomMultiPartitionDao(CHINA_BUILDER.build()); + } else if (regionId.startsWith("me-")) { + return new CustomMultiPartitionDao(MIDDLE_EAST_BUILDER.build()); + } else if (regionId.equals("ap-east-1")) { + return new CustomMultiPartitionDao(HONG_KONG_BUILDER.build()); + } else { + return new CustomMultiPartitionDao(AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder().build()); + } + } + + @Override + public GenerateDataKeyResult generateDataKey(AwsKmsCmkId keyId, CryptoAlgorithm algorithmSuite, Map encryptionContext) { + return this.usableDao.generateDataKey(keyId, algorithmSuite, encryptionContext); + } + + @Override + public EncryptedDataKey encryptDataKey(final AwsKmsCmkId keyId, SecretKey plaintextDataKey, Map encryptionContext) { + return this.usableDao.encryptDataKey(keyId, plaintextDataKey, encryptionContext); + } + + @Override + public DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, CryptoAlgorithm algorithmSuite, Map encryptionContext) { + return this.usableDao.decryptDataKey(encryptedDataKey, algorithmSuite, encryptionContext); + } + } + /** * Demonstrate an encrypt/decrypt cycle using multiple AWS KMS symmetric keyrings, each with a unique dao. * @@ -74,30 +117,11 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext encryptionContext.put("that can help you", "be confident that"); encryptionContext.put("the data you are handling", "is what you think it is"); - // Create a keyring per region - final DataKeyEncryptionDao chinaDao = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder() - .credentialsProvider(new ProfileCredentialsProvider("china")) - .regionId(Regions.CN_NORTH_1.getName()) - .build(); - final Keyring chinaKeyring = StandardKeyrings.awsKmsSymmetric(chinaDao, awsKmsCmk); - - final DataKeyEncryptionDao middleEastDao = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder() - .credentialsProvider(new ProfileCredentialsProvider("middle-east")) - .regionId(Regions.ME_SOUTH_1.getName()) - .build(); - final Keyring middleEasyKeyring = StandardKeyrings.awsKmsSymmetric(middleEastDao, awsKmsCmk); - - final DataKeyEncryptionDao hongKongDao = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder() - .credentialsProvider(new ProfileCredentialsProvider("hong-kong")) - .regionId(Regions.AP_EAST_1.getName()) - .build(); - final Keyring hongKongKeyring = StandardKeyrings.awsKmsSymmetric(hongKongDao, awsKmsCmk); - - final DataKeyEncryptionDao defaultDao = AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder().build(); - final Keyring defaultKeyring = StandardKeyrings.awsKmsSymmetric(defaultDao, awsKmsCmk); - // Create the keyring that determines how your data keys are protected. - final Keyring keyring = StandardKeyrings.multi(defaultKeyring, chinaKeyring, middleEasyKeyring, hongKongKeyring); + final String region = Arn.fromString(awsKmsCmk.toString()).getRegion(); + final Keyring keyring = StandardKeyrings.awsKmsSymmetric( + CustomMultiPartitionDao.daoGivenRegionId(region), + awsKmsCmk); // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java index 909b02738..d2b6a338c 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java @@ -30,7 +30,7 @@ * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, * see the {@link MultipleRegions} example. *

- * For another example of how to use the AWS KMS symmetric multi-CMK keyring with a custom client configuration, + * For another example of how to use the AWS KMS symmetric keyring with a custom client configuration, * see the {@link CustomDataKeyEncryptionDao} example. *

* For examples of how to use the AWS KMS symmetric multi-region discovery keyring on decrypt, diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java index c1d33acae..d833491e3 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java @@ -44,7 +44,8 @@ * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, * see the {@link MultipleRegions} example. *

- * For examples of how to use the AWS KMS symmetric multi-CMK keyring with custom client configurations, + * For examples of how to use the AWS KMS symmetric keyring + * and the AWS KMS symmetric multi-CMK keyring with custom client configurations, * see the {@link CustomDataKeyEncryptionDao} * and {@link CustomKmsClientConfig} examples. *

diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java index cb28d8425..3d3e569d3 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java @@ -50,7 +50,8 @@ * For an example of how to use the AWS KMS symmetric multi-CMK keyring with CMKs in multiple regions, * see the {@link MultipleRegions} example. *

- * For examples of how to use the AWS KMS symmetric multi-CMK keyring with custom client configurations, + * For examples of how to use the AWS KMS symmetric keyring + * and the AWS KMS symmetric multi-CMK keyring with custom client configurations, * see the {@link CustomDataKeyEncryptionDao} * and {@link CustomKmsClientConfig} examples. *

diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java index efda37455..f7cb297b4 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java @@ -54,7 +54,7 @@ public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { // onEncrypt MUST attempt to generate a new plaintext data key // and encrypt that data key by calling KMS GenerateDataKey. if (!encryptionMaterials.hasCleartextDataKey()) { - resultMaterials = generateDataKey(encryptionMaterials); + return generateDataKey(encryptionMaterials); } // Given a plaintext data key in the encryption materials, OnEncrypt MUST attempt diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java index 7a9cbeea4..620fd78ab 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java @@ -15,7 +15,6 @@ import com.amazonaws.AmazonServiceException; import com.amazonaws.AmazonWebServiceRequest; -import com.amazonaws.ClientConfiguration; import com.amazonaws.RequestClientOptions; import com.amazonaws.encryptionsdk.CryptoAlgorithm; import com.amazonaws.encryptionsdk.EncryptedDataKey; @@ -29,7 +28,6 @@ import com.amazonaws.services.kms.model.DecryptRequest; import com.amazonaws.services.kms.model.EncryptRequest; import com.amazonaws.services.kms.model.GenerateDataKeyRequest; -import org.apache.commons.lang3.StringUtils; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; From ee83536e0c79020959417e56aab362a6692299d9 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Wed, 12 Aug 2020 19:46:10 -0700 Subject: [PATCH 07/15] Revert KmsMasterKey back to keyring branch, update constructor --- .../encryptionsdk/kms/KmsMasterKey.java | 109 +++++------------- .../encryptionsdk/kms/KmsMasterKeyTest.java | 27 ++--- 2 files changed, 38 insertions(+), 98 deletions(-) diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java index 6c3af788b..c6629cc0d 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java @@ -14,17 +14,12 @@ package com.amazonaws.encryptionsdk.kms; import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Supplier; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.encryptionsdk.AwsCrypto; @@ -35,15 +30,10 @@ import com.amazonaws.encryptionsdk.MasterKeyProvider; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; -import com.amazonaws.encryptionsdk.internal.VersionInfo; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.services.kms.AWSKMS; -import com.amazonaws.services.kms.model.DecryptRequest; -import com.amazonaws.services.kms.model.DecryptResult; -import com.amazonaws.services.kms.model.EncryptRequest; -import com.amazonaws.services.kms.model.EncryptResult; -import com.amazonaws.services.kms.model.GenerateDataKeyRequest; -import com.amazonaws.services.kms.model.GenerateDataKeyResult; + +import static java.util.Collections.emptyList; /** * Represents a single Customer Master Key (CMK) and is used to encrypt/decrypt data with @@ -53,15 +43,9 @@ */ @Deprecated public final class KmsMasterKey extends MasterKey implements KmsMethods { - private final Supplier kms_; + private final AwsKmsDataKeyEncryptionDao dataKeyEncryptionDao_; private final MasterKeyProvider sourceProvider_; private final String id_; - private final List grantTokens_ = new ArrayList<>(); - - private T updateUserAgent(T request) { - request.getRequestClientOptions().appendUserAgent(VersionInfo.USER_AGENT); - return request; - } /** * @@ -83,11 +67,12 @@ public static KmsMasterKey getInstance(final AWSCredentialsProvider creds, final static KmsMasterKey getInstance(final Supplier kms, final String id, final MasterKeyProvider provider) { - return new KmsMasterKey(kms, id, provider); + // Allow the user agent string to be appended (in order to match existing behavior) + return new KmsMasterKey(new AwsKmsDataKeyEncryptionDao(kms.get(), emptyList(), true), id, provider); } - private KmsMasterKey(final Supplier kms, final String id, final MasterKeyProvider provider) { - kms_ = kms; + KmsMasterKey(final AwsKmsDataKeyEncryptionDao dataKeyEncryptionDao, final String id, final MasterKeyProvider provider) { + dataKeyEncryptionDao_ = dataKeyEncryptionDao; id_ = id; sourceProvider_ = provider; } @@ -105,39 +90,27 @@ public String getKeyId() { @Override public DataKey generateDataKey(final CryptoAlgorithm algorithm, final Map encryptionContext) { - final GenerateDataKeyResult gdkResult = kms_.get().generateDataKey(updateUserAgent( - new GenerateDataKeyRequest() - .withKeyId(getKeyId()) - .withNumberOfBytes(algorithm.getDataKeyLength()) - .withEncryptionContext(encryptionContext) - .withGrantTokens(grantTokens_) - )); - final byte[] rawKey = new byte[algorithm.getDataKeyLength()]; - gdkResult.getPlaintext().get(rawKey); - if (gdkResult.getPlaintext().remaining() > 0) { - throw new IllegalStateException("Recieved an unexpected number of bytes from KMS"); - } - final byte[] encryptedKey = new byte[gdkResult.getCiphertextBlob().remaining()]; - gdkResult.getCiphertextBlob().get(encryptedKey); - - final SecretKeySpec key = new SecretKeySpec(rawKey, algorithm.getDataKeyAlgo()); - return new DataKey<>(key, encryptedKey, gdkResult.getKeyId().getBytes(StandardCharsets.UTF_8), this); + final DataKeyEncryptionDao.GenerateDataKeyResult gdkResult = dataKeyEncryptionDao_.generateDataKey( + AwsKmsCmkId.fromString(getKeyId()), algorithm, encryptionContext); + return new DataKey<>(gdkResult.getPlaintextDataKey(), + gdkResult.getEncryptedDataKey().getEncryptedDataKey(), + gdkResult.getEncryptedDataKey().getProviderInformation(), + this); } @Override public void setGrantTokens(final List grantTokens) { - grantTokens_.clear(); - grantTokens_.addAll(grantTokens); + dataKeyEncryptionDao_.setGrantTokens(grantTokens); } @Override public List getGrantTokens() { - return grantTokens_; + return dataKeyEncryptionDao_.getGrantTokens(); } @Override public void addGrantToken(final String grantToken) { - grantTokens_.add(grantToken); + dataKeyEncryptionDao_.addGrantToken(grantToken); } @Override @@ -145,50 +118,30 @@ public DataKey encryptDataKey(final CryptoAlgorithm algorithm, final Map encryptionContext, final DataKey dataKey) { final SecretKey key = dataKey.getKey(); - if (!key.getFormat().equals("RAW")) { - throw new IllegalArgumentException("Only RAW encoded keys are supported"); - } - try { - final EncryptResult encryptResult = kms_.get().encrypt(updateUserAgent( - new EncryptRequest() - .withKeyId(id_) - .withPlaintext(ByteBuffer.wrap(key.getEncoded())) - .withEncryptionContext(encryptionContext) - .withGrantTokens(grantTokens_))); - final byte[] edk = new byte[encryptResult.getCiphertextBlob().remaining()]; - encryptResult.getCiphertextBlob().get(edk); - return new DataKey<>(dataKey.getKey(), edk, encryptResult.getKeyId().getBytes(StandardCharsets.UTF_8), this); - } catch (final AmazonServiceException amazonServiceException) { - throw new AwsCryptoException(amazonServiceException); - } + final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao_.encryptDataKey( + AwsKmsCmkId.fromString(id_), key, encryptionContext); + + return new DataKey<>(dataKey.getKey(), + encryptedDataKey.getEncryptedDataKey(), + encryptedDataKey.getProviderInformation(), + this); } @Override public DataKey decryptDataKey(final CryptoAlgorithm algorithm, final Collection encryptedDataKeys, final Map encryptionContext) - throws UnsupportedProviderException, AwsCryptoException { + throws UnsupportedProviderException, AwsCryptoException { final List exceptions = new ArrayList<>(); for (final EncryptedDataKey edk : encryptedDataKeys) { try { - final DecryptResult decryptResult = kms_.get().decrypt(updateUserAgent( - new DecryptRequest() - .withCiphertextBlob(ByteBuffer.wrap(edk.getEncryptedDataKey())) - .withEncryptionContext(encryptionContext) - .withGrantTokens(grantTokens_))); - if (decryptResult.getKeyId().equals(id_)) { - final byte[] rawKey = new byte[algorithm.getDataKeyLength()]; - decryptResult.getPlaintext().get(rawKey); - if (decryptResult.getPlaintext().remaining() > 0) { - throw new IllegalStateException("Received an unexpected number of bytes from KMS"); - } - return new DataKey<>( - new SecretKeySpec(rawKey, algorithm.getDataKeyAlgo()), - edk.getEncryptedDataKey(), - edk.getProviderInformation(), this); - } - } catch (final AmazonServiceException amazonServiceException) { - exceptions.add(amazonServiceException); + final DataKeyEncryptionDao.DecryptDataKeyResult result = dataKeyEncryptionDao_.decryptDataKey(edk, algorithm, encryptionContext); + return new DataKey<>( + result.getPlaintextDataKey(), + edk.getEncryptedDataKey(), + edk.getProviderInformation(), this); + } catch (final AwsCryptoException ex) { + exceptions.add(ex); } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java index 1235ae137..62d5ed7e3 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java @@ -18,9 +18,6 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; import com.amazonaws.encryptionsdk.model.KeyBlob; -import com.amazonaws.services.kms.AWSKMS; -import com.amazonaws.services.kms.model.DecryptRequest; -import com.amazonaws.services.kms.model.DecryptResult; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -28,18 +25,15 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.function.Supplier; import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -48,8 +42,7 @@ class KmsMasterKeyTest { private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384; private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("test", "value"); private static final String CMK_ARN = "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210"; - @Mock Supplier supplier; - @Mock AWSKMS awsKmsClient; + @Mock AwsKmsDataKeyEncryptionDao dataKeyEncryptionDao; /** * Test that when decryption of an encrypted data key throws a MismatchedDataKeyException, this @@ -59,20 +52,14 @@ class KmsMasterKeyTest { void testMismatchedDataKeyException() { EncryptedDataKey encryptedDataKey1 = new KeyBlob(AWS_KMS_PROVIDER_ID, "KeyId1".getBytes(PROVIDER_ENCODING), generate(64)); EncryptedDataKey encryptedDataKey2 = new KeyBlob(AWS_KMS_PROVIDER_ID, "KeyId2".getBytes(PROVIDER_ENCODING), generate(64)); - byte[] generated = generate(ALGORITHM_SUITE.getDataKeyLength()); - SecretKey secretKey = new SecretKeySpec(generated, ALGORITHM_SUITE.getDataKeyAlgo()); - when(supplier.get()).thenReturn(awsKmsClient); + SecretKey secretKey = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); - when(awsKmsClient.decrypt(any(DecryptRequest.class))) - // Fail the first call - .thenThrow(new MismatchedDataKeyException()) - // Return the decrypted result for the second EncryptedDataKey on the second decrypt call - .thenReturn(new DecryptResult() - .withKeyId("KeyId2") - .withEncryptionAlgorithm(secretKey.getAlgorithm()) - .withPlaintext(ByteBuffer.wrap(generated))); + when(dataKeyEncryptionDao.decryptDataKey(encryptedDataKey1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenThrow(new MismatchedDataKeyException()); + when(dataKeyEncryptionDao.decryptDataKey(encryptedDataKey2, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DataKeyEncryptionDao.DecryptDataKeyResult("KeyId2", secretKey)); - KmsMasterKey kmsMasterKey = KmsMasterKey.getInstance(supplier, CMK_ARN, null); + KmsMasterKey kmsMasterKey = new KmsMasterKey(dataKeyEncryptionDao, CMK_ARN, null); List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(encryptedDataKey1); From 5e28cf6e8b308941f884c122e3f649ef03768247 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Wed, 12 Aug 2020 20:13:33 -0700 Subject: [PATCH 08/15] Add passthrough region to customDataKeyEncryptionDao --- .../examples/keyring/awskms/CustomDataKeyEncryptionDao.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java index ef5695693..f46956b77 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java @@ -77,7 +77,8 @@ static CustomMultiPartitionDao daoGivenRegionId(String regionId) { } else if (regionId.equals("ap-east-1")) { return new CustomMultiPartitionDao(HONG_KONG_BUILDER.build()); } else { - return new CustomMultiPartitionDao(AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder().build()); + return new CustomMultiPartitionDao( + AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder().regionId(regionId).build()); } } From 6fafd3331ce5cf03302577010838baf57a965e57 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Thu, 13 Aug 2020 11:14:16 -0700 Subject: [PATCH 09/15] Add AwsKmsSymmetricKeyringTests --- .../awskms/CustomDataKeyEncryptionDao.java | 18 +- .../kms/AwsKmsDataKeyEncryptionDao.java | 2 +- .../encryptionsdk/kms/KmsMasterKey.java | 2 - .../keyrings/AwsKmsSymmetricKeyringTest.java | 279 ++++++++++++++++++ 4 files changed, 289 insertions(+), 12 deletions(-) create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java index f46956b77..af0686d1f 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomDataKeyEncryptionDao.java @@ -21,7 +21,7 @@ * Builders are provided to allow for quick generation of a multi-keyring of AWS KMS symmetric keyrings, * where each AWS KMS symmetric keyring is initialized with a DataKeyEncryptionDao that encapsulates an AWS KMS service client. * Builders are additionally provided to allow for customization of all required AWS KMS service clients. - * + *

* However, if you need different behavior, * such as having each AWS KMS service client using a different AWS KMS client configuration, * you can utilize the base AWS KMS symmetric keyring directly and provide it a custom DataKeyEncryptionDao. @@ -65,7 +65,7 @@ static class CustomMultiPartitionDao implements DataKeyEncryptionDao { private final DataKeyEncryptionDao usableDao; - private CustomMultiPartitionDao(DataKeyEncryptionDao usableDao){ + private CustomMultiPartitionDao(DataKeyEncryptionDao usableDao) { this.usableDao = usableDao; } @@ -126,10 +126,10 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // Encrypt your plaintext data. final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( - EncryptRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .plaintext(sourcePlaintext).build()); + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); final byte[] ciphertext = encryptResult.getResult(); // Demonstrate that the ciphertext and plaintext are different. @@ -140,9 +140,9 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // You do not need to specify the encryption context on decrypt because // the header of the encrypted message includes the encryption context. final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( - DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext).build()); + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); final byte[] decrypted = decryptResult.getResult(); // Demonstrate that the decrypted plaintext is identical to the original plaintext. diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java index 620fd78ab..65ce39b22 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java @@ -94,7 +94,7 @@ public GenerateDataKeyResult generateDataKey(AwsKmsCmkId keyId, CryptoAlgorithm kmsResult.getCiphertextBlob().get(encryptedKey); return new GenerateDataKeyResult(new SecretKeySpec(rawKey, algorithmSuite.getDataKeyAlgo()), - new KeyBlob(AWS_KMS_PROVIDER_ID, kmsResult.getKeyId().getBytes(PROVIDER_ENCODING), encryptedKey)); + new KeyBlob(AWS_KMS_PROVIDER_ID, kmsResult.getKeyId().getBytes(PROVIDER_ENCODING), encryptedKey)); } @Override diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java index c6629cc0d..e1d3d88fb 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java @@ -48,7 +48,6 @@ public final class KmsMasterKey extends MasterKey implements KmsMe private final String id_; /** - * * @deprecated Use a {@link KmsMasterKeyProvider} to obtain {@link KmsMasterKey}s. */ @Deprecated @@ -57,7 +56,6 @@ public static KmsMasterKey getInstance(final AWSCredentials creds, final String } /** - * * @deprecated Use a {@link KmsMasterKeyProvider} to obtain {@link KmsMasterKey}s. */ @Deprecated diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java new file mode 100644 index 000000000..aa51b9d29 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java @@ -0,0 +1,279 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AwsKmsSymmetricKeyringTest { + + private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; + private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); + private static final String KEY_ARN = "arn:aws:kms:us-east-1:999999999999:key/key1-23bv-sdfs-werw-234323nfdsf"; + private static final String FAILING_KEY_ARN = "arn:aws:kms:us-east-1:999999999999:key/key2-02ds-wvjs-aswe-a4923489273"; + private static final AwsKmsCmkId KEY_NAME = AwsKmsCmkId.fromString(KEY_ARN); + private static final EncryptedDataKey ENCRYPTED_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, + KEY_ARN.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + private static final EncryptedDataKey FAILING_ENCRYPTED_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, + FAILING_KEY_ARN.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + private static final KeyringTraceEntry ENCRYPTED_KEY_TRACE = + new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); + private static final KeyringTraceEntry GENERATED_DATA_KEY_TRACE = + new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, GENERATED_DATA_KEY); + @Mock(lenient = true) + private DataKeyEncryptionDao dataKeyEncryptionDao; + private Keyring keyring; + + @BeforeEach + void setup() { + when(dataKeyEncryptionDao.encryptDataKey(KEY_NAME, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_KEY); + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DecryptDataKeyResult(KEY_ARN, PLAINTEXT_DATA_KEY)); + + keyring = new AwsKmsSymmetricKeyring(dataKeyEncryptionDao, KEY_NAME); + } + + @Test + void testMalformedArns() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(new KeyBlob(AWS_KMS_PROVIDER_ID, "arn:badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); + encryptedDataKeys.add(ENCRYPTED_KEY); + + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + // Malformed Arn for a non KMS provider shouldn't fail + encryptedDataKeys.clear(); + encryptedDataKeys.add(new KeyBlob("OtherProviderId", "arn:badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); + assertFalse(keyring.onDecrypt(decryptionMaterials, encryptedDataKeys).hasCleartextDataKey()); + } + + @Test + void testNullDao() { + assertThrows( + NullPointerException.class, + () -> new AwsKmsSymmetricKeyring(null, KEY_NAME), + "dataKeyEncryptionDao is required"); + } + + @Test + void testNullKeyName() { + assertThrows( + NullPointerException.class, + () -> new AwsKmsSymmetricKeyring(dataKeyEncryptionDao, null), + "keyName is required"); + } + + @Test + void testEncryptDecryptExistingDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); + + assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); + assertEncryptedDataKeyEquals(ENCRYPTED_KEY, encryptionMaterials.getEncryptedDataKeys().get(0)); + + assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_TRACE)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_KEY); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testEncryptNullDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + when(dataKeyEncryptionDao.generateDataKey(KEY_NAME, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DataKeyEncryptionDao.GenerateDataKeyResult(PLAINTEXT_DATA_KEY, ENCRYPTED_KEY)); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); + + assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getCleartextDataKey()); + + assertEquals(2, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(GENERATED_DATA_KEY_TRACE)); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_TRACE)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_KEY); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptFirstKeyIncorrectKeyName() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(FAILING_ENCRYPTED_KEY); + encryptedDataKeys.add(ENCRYPTED_KEY); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptMismatchedDataKeyException() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new MismatchedDataKeyException()); + + assertThrows(MismatchedDataKeyException.class, () -> keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_KEY))); + } + + @Test + void testDecryptFirstKeyWrongProvider() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + EncryptedDataKey wrongProviderKey = new KeyBlob("OtherProvider", FAILING_KEY_ARN.getBytes(PROVIDER_ENCODING), new byte[]{}); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(wrongProviderKey); + encryptedDataKeys.add(ENCRYPTED_KEY); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptAlreadyDecryptedDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_KEY)); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } + + @Test + void testDecryptCannotUnwrapDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new CannotUnwrapDataKeyException()); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_KEY); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } + + @Test + void testDecryptNoDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); + + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } + + private void assertEncryptedDataKeyEquals(EncryptedDataKey expected, EncryptedDataKey actual) { + assertEquals(expected.getProviderId(), actual.getProviderId()); + assertArrayEquals(expected.getProviderInformation(), actual.getProviderInformation()); + assertArrayEquals(expected.getEncryptedDataKey(), actual.getEncryptedDataKey()); + } +} From 5efeed3db263cce09c7b2b5267beaf69319209d0 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Thu, 13 Aug 2020 13:29:45 -0700 Subject: [PATCH 10/15] Add AwsKmsSymmetricRegionDiscoveryKeyringTests --- ...msSymmetricRegionDiscoveryKeyringTest.java | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java new file mode 100644 index 000000000..db567481e --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java @@ -0,0 +1,308 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AwsKmsSymmetricRegionDiscoveryKeyringTest { + + private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; + private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); + private static final String KEY_NAME_1 = "arn:aws:kms:us-east-1:999999999999:key/key1-23bv-sdfs-werw-234323nfdsf"; + private static final String KEY_NAME_2 = "arn:aws:kms:us-east-1:999999999999:key/key2-02ds-wvjs-aswe-a4923489273"; + private static final String DIFFERENT_REGION_KEY_NAME = "arn:aws:kms:us-west-2:999999999999:key/key2-02ds-wvjs-aswe-a4923489273"; + private static final String DIFFERENT_AWS_ACCOUNT_ID_KEY_NAME = "arn:aws:kms:us-east-1:000000000000:key/key2-02ds-wvjs-aswe-a4923489273"; + private static final String AWS_ACCOUNT_ID = "999999999999"; + private static final String AWS_REGION = "us-east-1"; + private static final EncryptedDataKey ENCRYPTED_KEY_1 = new KeyBlob(AWS_KMS_PROVIDER_ID, + KEY_NAME_1.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + private static final EncryptedDataKey ENCRYPTED_KEY_2 = new KeyBlob(AWS_KMS_PROVIDER_ID, + KEY_NAME_2.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + private static final EncryptedDataKey ENCRYPTED_DIFFERENT_REGION_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, + DIFFERENT_REGION_KEY_NAME.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + private static final EncryptedDataKey ENCRYPTED_DIFFERENT_AWS_ACCOUNT_ID_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, + DIFFERENT_AWS_ACCOUNT_ID_KEY_NAME.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + @Mock(lenient = true) private DataKeyEncryptionDao dataKeyEncryptionDao; + private Keyring keyring; + + @BeforeEach + void setup() { + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DecryptDataKeyResult(KEY_NAME_1, PLAINTEXT_DATA_KEY)); + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_2, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DecryptDataKeyResult(KEY_NAME_2, PLAINTEXT_DATA_KEY)); + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_DIFFERENT_AWS_ACCOUNT_ID_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DecryptDataKeyResult(DIFFERENT_AWS_ACCOUNT_ID_KEY_NAME, PLAINTEXT_DATA_KEY)); + keyring = new AwsKmsSymmetricRegionDiscoveryKeyring(dataKeyEncryptionDao, AWS_REGION, AWS_ACCOUNT_ID); + } + + @Test + void testMalformedArns() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(new KeyBlob(AWS_KMS_PROVIDER_ID, "arn:badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); + encryptedDataKeys.add(ENCRYPTED_KEY_1); + + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + // Malformed Arn for a non KMS provider shouldn't fail + encryptedDataKeys.clear(); + encryptedDataKeys.add(new KeyBlob("OtherProviderId", "arn:badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); + assertFalse(keyring.onDecrypt(decryptionMaterials, encryptedDataKeys).hasCleartextDataKey()); + } + + @Test + void testNullDao() { + assertThrows( + NullPointerException.class, + () -> new AwsKmsSymmetricRegionDiscoveryKeyring(null, AWS_REGION, null), + "dataKeyEncryptionDao is required"); + } + + @Test + void testNullKeyName() { + assertThrows( + NullPointerException.class, + () -> new AwsKmsSymmetricRegionDiscoveryKeyring(dataKeyEncryptionDao, null, null), + "AWS region is required"); + } + + @Test + void testEncrypt() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + assertThrows( + AwsCryptoException.class, + () -> keyring.onEncrypt(encryptionMaterials), + "The AWS KMS Region Discovery keyring cannot be used for encryption"); + } + + @Test + void testDecrypt() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_KEY_1); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_1, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptFirstKeyFails() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new CannotUnwrapDataKeyException()); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_KEY_1); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptFirstKeyIncorrectAccountId() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_DIFFERENT_AWS_ACCOUNT_ID_KEY); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptNoAwsAccountIdRequirement() { + final Keyring noAwsAccountIdCheckDiscoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(dataKeyEncryptionDao, AWS_REGION, null); + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_DIFFERENT_AWS_ACCOUNT_ID_KEY); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + decryptionMaterials = noAwsAccountIdCheckDiscoveryKeyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, DIFFERENT_AWS_ACCOUNT_ID_KEY_NAME, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptFirstKeyIncorrectRegion() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_DIFFERENT_REGION_KEY); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptMismatchedDataKeyException() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new MismatchedDataKeyException()); + + assertThrows(MismatchedDataKeyException.class, () -> keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_KEY_1))); + } + + @Test + void testDecryptFirstKeyWrongProvider() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + EncryptedDataKey wrongProviderKey = new KeyBlob("OtherProvider", KEY_NAME_1.getBytes(PROVIDER_ENCODING), new byte[]{}); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(wrongProviderKey); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptFirstKeyNotARN() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + EncryptedDataKey notArnKey = new KeyBlob( + AWS_KMS_PROVIDER_ID, + "notanARN".getBytes(PROVIDER_ENCODING), + generate(ALGORITHM_SUITE.getDataKeyLength())); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(notArnKey); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptAlreadyDecryptedDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_KEY_1)); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } + + @Test + void testDecryptNoDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); + + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } +} From 612a92b811163086a9106d2f9bc018dc9b0291e7 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Thu, 13 Aug 2020 15:42:27 -0700 Subject: [PATCH 11/15] Add AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTests --- ...AwsKmsSymmetricMultiCmkKeyringBuilder.java | 18 ++- ...ricMultiRegionDiscoveryKeyringBuilder.java | 16 +- .../kms/AwsKmsDataKeyEncryptionDao.java | 2 +- ...ultiRegionDiscoveryKeyringBuilderTest.java | 143 ++++++++++++++++++ ...msSymmetricRegionDiscoveryKeyringTest.java | 3 +- ...AwsKmsDataKeyEncryptionDaoBuilderTest.java | 6 + .../kms/AwsKmsDataKeyEncryptionDaoTest.java | 14 +- 7 files changed, 178 insertions(+), 24 deletions(-) create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java index ec4964358..ff0951456 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java @@ -37,12 +37,15 @@ public class AwsKmsSymmetricMultiCmkKeyringBuilder { private AWSCredentialsProvider credentialsProvider; private ClientConfiguration clientConfiguration; - private AwsKmsSymmetricMultiCmkKeyringBuilder() { + private AwsKmsDataKeyEncryptionDaoBuilder daoBuilder; + + AwsKmsSymmetricMultiCmkKeyringBuilder(AwsKmsDataKeyEncryptionDaoBuilder daoBuilder) { // Use AwsKmsSymmetricMultiCmkKeyringBuilder.standard() or StandardKeyrings.awsKmsSymmetricMultiCmkBuilder() // to instantiate a standard AWS KMS symmetric multi-CMK keyring Builder. // If an AWS KMS symmetric multi-region discovery keyring builder is needed use // AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.standard() or // StandardKeyrings.awsKmsSymmetricMultiRegionDiscoveryKeyringBuilder(). + this.daoBuilder = daoBuilder; } /** @@ -51,7 +54,7 @@ private AwsKmsSymmetricMultiCmkKeyringBuilder() { * @return The {@code AwsKmsSymmetricMultiCmkKeyringBuilder} */ public static AwsKmsSymmetricMultiCmkKeyringBuilder standard() { - return new AwsKmsSymmetricMultiCmkKeyringBuilder(); + return new AwsKmsSymmetricMultiCmkKeyringBuilder(AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder()); } /** @@ -115,11 +118,11 @@ public AwsKmsSymmetricMultiCmkKeyringBuilder generator(AwsKmsCmkId generatorKeyN } /** - * Constructs the {@link Keyring} instance. + * Constructs the {@code MultiKeyring} of {@code AwsKmsSymmetricKeyring}s. * - * @return The {@link Keyring} instance + * @return The {@link MultiKeyring} instance */ - public Keyring build() { + public MultiKeyring build() { // A mapping of AWS region to DataKeyEncryptionDao final Map clientMapping = new HashMap<>(); @@ -167,12 +170,11 @@ public Keyring build() { } // Finally, construct a multi-keyring - return StandardKeyrings.multi(generatorKeyring, childKeyrings); + return new MultiKeyring(generatorKeyring, childKeyrings); } private DataKeyEncryptionDao constructDataKeyEncryptionDao(String regionId) { - return AwsKmsDataKeyEncryptionDaoBuilder - .defaultBuilder() + return this.daoBuilder .clientConfiguration(this.clientConfiguration) .credentialsProvider(this.credentialsProvider) .grantTokens(this.grantTokens) diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java index 1f0b30d09..000f251c4 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java @@ -32,13 +32,16 @@ public class AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder { private AWSCredentialsProvider credentialsProvider; private ClientConfiguration clientConfiguration; - private AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder() { + private AwsKmsDataKeyEncryptionDaoBuilder daoBuilder; + + AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder(AwsKmsDataKeyEncryptionDaoBuilder daoBuilder) { // Use AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.standard() // or StandardKeyrings.awsKmsSymmetricMultiRegionDiscoveryKeyringBuilder() // to instantiate a standard AWS KMS symmetric multi-region discovery keyring Builder. // If an AWS KMS symmetric multi-CMK keyring builder is needed use // AwsKmsSymmetricMultiCmkKeyringBuilder.standard() or // StandardKeyrings.awsKmsSymmetricMultiCmkBuilder(). + this.daoBuilder = daoBuilder; } /** @@ -47,7 +50,7 @@ private AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder() { * @return The {@code AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder} */ public static AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder standard() { - return new AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder(); + return new AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder(AwsKmsDataKeyEncryptionDaoBuilder.defaultBuilder()); } /** @@ -106,11 +109,11 @@ public AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder awsAccountId(String aws } /** - * Constructs the {@link Keyring} instance. + * Constructs the {@code MultiKeyring} of {@code AwsKmsSymmetricRegionDiscoveryKeyring}s. * * @return The {@link Keyring} instance */ - public Keyring build() { + public MultiKeyring build() { // A mapping of AWS region to DataKeyEncryptionDao final Map clientMapping = new HashMap<>(); @@ -136,12 +139,11 @@ public Keyring build() { } // Finally, construct a multi-keyring - return StandardKeyrings.multi(null, discoveryKeyrings); + return new MultiKeyring(null, discoveryKeyrings); } private DataKeyEncryptionDao constructDataKeyEncryptionDao(String regionId) { - return AwsKmsDataKeyEncryptionDaoBuilder - .defaultBuilder() + return this.daoBuilder .clientConfiguration(clientConfiguration) .credentialsProvider(credentialsProvider) .grantTokens(grantTokens) diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java index 65ce39b22..4534f811e 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java @@ -47,7 +47,7 @@ * generation, encryption, and decryption of data keys. The KmsMethods interface is implemented * to allow usage in KmsMasterKey. */ -class AwsKmsDataKeyEncryptionDao implements DataKeyEncryptionDao, KmsMethods { +public class AwsKmsDataKeyEncryptionDao implements DataKeyEncryptionDao, KmsMethods { private final AWSKMS client; private final boolean canAppendUserAgentString; diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java new file mode 100644 index 000000000..479c18549 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java @@ -0,0 +1,143 @@ +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.encryptionsdk.kms.AwsKmsDataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.kms.AwsKmsDataKeyEncryptionDaoBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.List; + +import static com.amazonaws.encryptionsdk.TestUtils.assertThrows; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest { + + @Mock + AWSCredentialsProvider credentialsProvider; + @Mock + ClientConfiguration clientConfiguration; + @Mock(lenient = true) + AwsKmsDataKeyEncryptionDaoBuilder daoBuilder; + @Mock + AwsKmsDataKeyEncryptionDao doa; + + private static final List GRANT_TOKENS = Arrays.asList("some", "grant", "tokens"); + private static final List REGIONS = Arrays.asList("us-west-2", "us-east-1"); + private static final String AWS_ACCOUNT_ID = "999999999999"; + + private AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder builder; + + @BeforeEach + void setup() { + when(daoBuilder.clientConfiguration(clientConfiguration)).thenReturn(daoBuilder); + when(daoBuilder.credentialsProvider(credentialsProvider)).thenReturn(daoBuilder); + when(daoBuilder.grantTokens(GRANT_TOKENS)).thenReturn(daoBuilder); + for (String region : REGIONS) { + when(daoBuilder.regionId(region)).thenReturn(daoBuilder); + } + when(daoBuilder.build()).thenReturn(doa); + builder = new AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder(daoBuilder); + } + + @Test + void testBuildFullyConfigured() { + MultiKeyring keyring = builder.credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .grantTokens(GRANT_TOKENS) + .regions(REGIONS) + .awsAccountId(AWS_ACCOUNT_ID) + .build(); + + assertNull(keyring.generatorKeyring); + assertNotNull(keyring.childrenKeyrings); + assertEquals(2, keyring.childrenKeyrings.size()); + + verify(daoBuilder, times(REGIONS.size())).clientConfiguration(clientConfiguration); + verify(daoBuilder, times(REGIONS.size())).credentialsProvider(credentialsProvider); + verify(daoBuilder, times(REGIONS.size())).grantTokens(GRANT_TOKENS); + for (String region : REGIONS) { + verify(daoBuilder).regionId(region); + } + verify(daoBuilder, times(REGIONS.size())).build(); + } + + @Test + void testBuildMultipleSameRegion() { + MultiKeyring keyring = builder.credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .grantTokens(GRANT_TOKENS) + .regions(Arrays.asList("us-west-2", "us-east-1", "us-west-2")) + .build(); + + assertNull(keyring.generatorKeyring); + assertNotNull(keyring.childrenKeyrings); + // Regions are not de-duplicated + assertEquals(3, keyring.childrenKeyrings.size()); + + verify(daoBuilder, times(2)).clientConfiguration(clientConfiguration); + verify(daoBuilder, times(2)).credentialsProvider(credentialsProvider); + verify(daoBuilder, times(2)).grantTokens(GRANT_TOKENS); + verify(daoBuilder).regionId("us-west-2"); + verify(daoBuilder).regionId("us-east-1"); + verify(daoBuilder, times(REGIONS.size())).build(); + } + + @Test + void testBuildEmptyAndNullRegions() { + MultiKeyring keyring = builder.credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .grantTokens(GRANT_TOKENS) + .regions(Arrays.asList("us-west-2", null, "", " ", "us-east-1")) + .build(); + + assertNull(keyring.generatorKeyring); + assertNotNull(keyring.childrenKeyrings); + assertEquals(2, keyring.childrenKeyrings.size()); + + verify(daoBuilder, times(2)).clientConfiguration(clientConfiguration); + verify(daoBuilder, times(2)).credentialsProvider(credentialsProvider); + verify(daoBuilder, times(2)).grantTokens(GRANT_TOKENS); + verify(daoBuilder).regionId("us-west-2"); + verify(daoBuilder).regionId("us-east-1"); + verify(daoBuilder, times(REGIONS.size())).build(); + } + + @Test + void testBuildNoRegions() { + assertThrows(IllegalArgumentException.class, + () -> builder.credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .grantTokens(GRANT_TOKENS) + .build()); + } + + @Test + void testBuildNoCustomization() { + when(daoBuilder.clientConfiguration(null)).thenReturn(daoBuilder); + when(daoBuilder.credentialsProvider(null)).thenReturn(daoBuilder); + when(daoBuilder.grantTokens(null)).thenReturn(daoBuilder); + + MultiKeyring keyring = builder + .regions(REGIONS) + .build(); + + assertNull(keyring.generatorKeyring); + assertNotNull(keyring.childrenKeyrings); + assertEquals(2, keyring.childrenKeyrings.size()); + + verify(daoBuilder, times(REGIONS.size())).clientConfiguration(null); + verify(daoBuilder, times(REGIONS.size())).credentialsProvider(null); + verify(daoBuilder, times(REGIONS.size())).grantTokens(null); + verify(daoBuilder).regionId("us-west-2"); + verify(daoBuilder).regionId("us-east-1"); + verify(daoBuilder, times(REGIONS.size())).build(); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java index db567481e..7abbb48e5 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java @@ -62,7 +62,8 @@ class AwsKmsSymmetricRegionDiscoveryKeyringTest { DIFFERENT_REGION_KEY_NAME.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); private static final EncryptedDataKey ENCRYPTED_DIFFERENT_AWS_ACCOUNT_ID_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, DIFFERENT_AWS_ACCOUNT_ID_KEY_NAME.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); - @Mock(lenient = true) private DataKeyEncryptionDao dataKeyEncryptionDao; + @Mock(lenient = true) + private DataKeyEncryptionDao dataKeyEncryptionDao; private Keyring keyring; @BeforeEach diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilderTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilderTest.java index 7d75f3b78..a9b37c1c7 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilderTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoBuilderTest.java @@ -22,6 +22,9 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.util.Arrays; +import java.util.List; + import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,7 +35,9 @@ class AwsKmsDataKeyEncryptionDaoBuilderTest { @Mock AWSKMS awskms; @Mock AWSCredentialsProvider credentialsProvider; @Mock ClientConfiguration clientConfiguration; + private static final String REGION = "us-east-1"; + private static final List GRANT_TOKENS = Arrays.asList("some", "grant", "tokens"); @Test void testCredentialsClientAndRegionConfiguration() { @@ -45,6 +50,7 @@ void testCredentialsClientAndRegionConfiguration() { .credentialsProvider(credentialsProvider) .clientConfiguration(clientConfiguration) .regionId(REGION) + .grantTokens(GRANT_TOKENS) .build(); verify(kmsClientBuilder).withCredentials(credentialsProvider); diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java index fa0ec3c61..09c12341f 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java @@ -63,7 +63,7 @@ class AwsKmsDataKeyEncryptionDaoTest { private static final List GRANT_TOKENS = Collections.singletonList("testGrantToken"); private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); private static final EncryptedDataKey ENCRYPTED_DATA_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, - "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210".getBytes(EncryptedDataKey.PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210".getBytes(EncryptedDataKey.PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); @Test void testEncryptAndDecrypt() { @@ -170,7 +170,7 @@ void testEncryptWithRawKeyId() { String keyId = client.createKey().getKeyMetadata().getArn(); String rawKeyId = keyId.split("/")[1]; EncryptedDataKey encryptedDataKeyResult = dao.encryptDataKey( - AwsKmsCmkId.fromString(rawKeyId), DATA_KEY, ENCRYPTION_CONTEXT); + AwsKmsCmkId.fromString(rawKeyId), DATA_KEY, ENCRYPTION_CONTEXT); ArgumentCaptor er = ArgumentCaptor.forClass(EncryptRequest.class); verify(client, times(1)).encrypt(er.capture()); @@ -199,7 +199,7 @@ void testEncryptWrongKeyFormat() { String keyId = client.createKey().getKeyMetadata().getArn(); assertThrows(IllegalArgumentException.class, () -> dao.encryptDataKey( - AwsKmsCmkId.fromString(keyId), key, ENCRYPTION_CONTEXT)); + AwsKmsCmkId.fromString(keyId), key, ENCRYPTION_CONTEXT)); } @Test @@ -213,9 +213,9 @@ void testKmsFailure() { doThrow(new KMSInvalidStateException("fail")).when(client).decrypt(isA(DecryptRequest.class)); assertThrows(AwsCryptoException.class, () -> dao.generateDataKey( - AwsKmsCmkId.fromString(keyId), ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + AwsKmsCmkId.fromString(keyId), ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); assertThrows(AwsCryptoException.class, () -> dao.encryptDataKey( - AwsKmsCmkId.fromString(keyId), DATA_KEY, ENCRYPTION_CONTEXT)); + AwsKmsCmkId.fromString(keyId), DATA_KEY, ENCRYPTION_CONTEXT)); assertThrows(AwsCryptoException.class, () -> dao.decryptDataKey(ENCRYPTED_DATA_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); } @@ -230,9 +230,9 @@ void testUnsupportedRegionException() { doThrow(new UnsupportedRegionException("fail")).when(client).decrypt(isA(DecryptRequest.class)); assertThrows(AwsCryptoException.class, () -> dao.generateDataKey( - AwsKmsCmkId.fromString(keyId), ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + AwsKmsCmkId.fromString(keyId), ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); assertThrows(AwsCryptoException.class, () -> dao.encryptDataKey( - AwsKmsCmkId.fromString(keyId), DATA_KEY, ENCRYPTION_CONTEXT)); + AwsKmsCmkId.fromString(keyId), DATA_KEY, ENCRYPTION_CONTEXT)); assertThrows(AwsCryptoException.class, () -> dao.decryptDataKey(ENCRYPTED_DATA_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); } From 664495bea51025213377e9c7683dbb43b5283cb0 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Thu, 13 Aug 2020 16:07:04 -0700 Subject: [PATCH 12/15] Add AwsKmsSymmetricMultiCmkKeyringBuilderTests --- ...msSymmetricMultiCmkKeyringBuilderTest.java | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java new file mode 100644 index 000000000..ba31521af --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java @@ -0,0 +1,179 @@ +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.AwsKmsDataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.kms.AwsKmsDataKeyEncryptionDaoBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.List; + +import static com.amazonaws.encryptionsdk.TestUtils.assertThrows; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class AwsKmsSymmetricMultiCmkKeyringBuilderTest { + + @Mock + AWSCredentialsProvider credentialsProvider; + @Mock + ClientConfiguration clientConfiguration; + @Mock(lenient = true) + AwsKmsDataKeyEncryptionDaoBuilder daoBuilder; + @Mock + AwsKmsDataKeyEncryptionDao doa; + + private static final List GRANT_TOKENS = Arrays.asList("some", "grant", "tokens"); + private static final String GENERATOR = "arn:aws:kms:us-east-1:999999999999:key/generator-89ab-cdef-fedc-ba9876543210"; + private static final String US_EAST_1_ARN = "arn:aws:kms:us-east-1:999999999999:key/key1-23bv-sdfs-werw-234323nfdsf"; + private static final String US_WEST_2_ARN_1 = "arn:aws:kms:us-west-2:999999999999:key/key2-02ds-wvjs-aswe-a4923489273"; + private static final String US_WEST_2_ARN_2 = "arn:aws:kms:us-west-2:999999999999:key/key2-02ds-wvjs-aswe-a4923489273"; + private static final String NON_ARN = "key1-23bv-sdfs-werw-234323nfdsf"; + + private static final AwsKmsCmkId GENERATOR_KEY_NAME = AwsKmsCmkId.fromString(GENERATOR); + private static final AwsKmsCmkId KEY_NAME_US_EAST_1 = AwsKmsCmkId.fromString(US_EAST_1_ARN); + private static final AwsKmsCmkId KEY_NAME_US_WEST_2_ARN_1 = AwsKmsCmkId.fromString(US_WEST_2_ARN_1); + private static final AwsKmsCmkId KEY_NAME_US_WEST_2_ARN_2 = AwsKmsCmkId.fromString(US_WEST_2_ARN_2); + private static final AwsKmsCmkId KEY_NAME_NON_ARN = AwsKmsCmkId.fromString(NON_ARN); + + private static final List CHILD_KEY_NAMES = Arrays.asList( + KEY_NAME_US_EAST_1, + KEY_NAME_US_WEST_2_ARN_1, + KEY_NAME_US_WEST_2_ARN_2, + KEY_NAME_NON_ARN); + + private static final List REGIONS = Arrays.asList("us-east-1", "us-west-2", null); + + private AwsKmsSymmetricMultiCmkKeyringBuilder builder; + + @BeforeEach + void setup() { + when(daoBuilder.clientConfiguration(clientConfiguration)).thenReturn(daoBuilder); + when(daoBuilder.credentialsProvider(credentialsProvider)).thenReturn(daoBuilder); + when(daoBuilder.grantTokens(GRANT_TOKENS)).thenReturn(daoBuilder); + for (String region : REGIONS) { + when(daoBuilder.regionId(region)).thenReturn(daoBuilder); + } + when(daoBuilder.build()).thenReturn(doa); + builder = new AwsKmsSymmetricMultiCmkKeyringBuilder(daoBuilder); + } + + @Test + void testBuildFullyConfigured() { + MultiKeyring keyring = builder.credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .grantTokens(GRANT_TOKENS) + .keyNames(CHILD_KEY_NAMES) + .generator(GENERATOR_KEY_NAME) + .build(); + + assertNotNull(keyring.generatorKeyring); + assertNotNull(keyring.childrenKeyrings); + assertEquals(4, keyring.childrenKeyrings.size()); + + verify(daoBuilder, times(REGIONS.size())).clientConfiguration(clientConfiguration); + verify(daoBuilder, times(REGIONS.size())).credentialsProvider(credentialsProvider); + verify(daoBuilder, times(REGIONS.size())).grantTokens(GRANT_TOKENS); + for (String region : REGIONS) { + verify(daoBuilder).regionId(region); + } + verify(daoBuilder, times(REGIONS.size())).build(); + } + + @Test + void testBuildNoGenerator() { + MultiKeyring keyring = builder.credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .grantTokens(GRANT_TOKENS) + .keyNames(CHILD_KEY_NAMES) + .build(); + + assertNull(keyring.generatorKeyring); + assertNotNull(keyring.childrenKeyrings); + assertEquals(4, keyring.childrenKeyrings.size()); + + verify(daoBuilder, times(REGIONS.size())).clientConfiguration(clientConfiguration); + verify(daoBuilder, times(REGIONS.size())).credentialsProvider(credentialsProvider); + verify(daoBuilder, times(REGIONS.size())).grantTokens(GRANT_TOKENS); + for (String region : REGIONS) { + verify(daoBuilder).regionId(region); + } + verify(daoBuilder, times(REGIONS.size())).build(); + } + + @Test + void testBuildNoChildren() { + MultiKeyring keyring = builder.credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .grantTokens(GRANT_TOKENS) + .generator(GENERATOR_KEY_NAME) + .build(); + + assertNotNull(keyring.generatorKeyring); + assertNotNull(keyring.childrenKeyrings); + assertEquals(0, keyring.childrenKeyrings.size()); + + verify(daoBuilder, times(1)).clientConfiguration(clientConfiguration); + verify(daoBuilder, times(1)).credentialsProvider(credentialsProvider); + verify(daoBuilder, times(1)).grantTokens(GRANT_TOKENS); + verify(daoBuilder).regionId("us-east-1"); + verify(daoBuilder, times(1)).build(); + } + + @Test + void testBuildNullKeyNames() { + MultiKeyring keyring = builder.credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .grantTokens(GRANT_TOKENS) + .generator(null) + .keyNames(Arrays.asList( + null, + KEY_NAME_US_EAST_1)) + .build(); + + assertNull(keyring.generatorKeyring); + assertNotNull(keyring.childrenKeyrings); + assertEquals(1, keyring.childrenKeyrings.size()); + + verify(daoBuilder, times(1)).clientConfiguration(clientConfiguration); + verify(daoBuilder, times(1)).credentialsProvider(credentialsProvider); + verify(daoBuilder, times(1)).grantTokens(GRANT_TOKENS); + verify(daoBuilder).regionId("us-east-1"); + verify(daoBuilder, times(1)).build(); + } + + @Test + void testBuildNoKeys() { + assertThrows(IllegalArgumentException.class, + () -> builder.build()); + } + + @Test + void testBuildNoCustomization() { + when(daoBuilder.clientConfiguration(null)).thenReturn(daoBuilder); + when(daoBuilder.credentialsProvider(null)).thenReturn(daoBuilder); + when(daoBuilder.grantTokens(null)).thenReturn(daoBuilder); + + MultiKeyring keyring = builder.keyNames(CHILD_KEY_NAMES) + .generator(GENERATOR_KEY_NAME) + .build(); + + assertNotNull(keyring.generatorKeyring); + assertNotNull(keyring.childrenKeyrings); + assertEquals(4, keyring.childrenKeyrings.size()); + + verify(daoBuilder, times(REGIONS.size())).clientConfiguration(null); + verify(daoBuilder, times(REGIONS.size())).credentialsProvider(null); + verify(daoBuilder, times(REGIONS.size())).grantTokens(null); + verify(daoBuilder).regionId("us-west-2"); + verify(daoBuilder).regionId("us-east-1"); + verify(daoBuilder, times(REGIONS.size())).build(); + } +} From 186b6e21352e3e60201fac16711c2e0cf7618fa9 Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Thu, 13 Aug 2020 16:09:59 -0700 Subject: [PATCH 13/15] Fix keyring.childrenKeyrings->keyring.childKeyrings after rebase --- ...msSymmetricMultiCmkKeyringBuilderTest.java | 20 +++++++++---------- ...ultiRegionDiscoveryKeyringBuilderTest.java | 16 +++++++-------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java index ba31521af..cb7119d3a 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java @@ -75,8 +75,8 @@ void testBuildFullyConfigured() { .build(); assertNotNull(keyring.generatorKeyring); - assertNotNull(keyring.childrenKeyrings); - assertEquals(4, keyring.childrenKeyrings.size()); + assertNotNull(keyring.childKeyrings); + assertEquals(4, keyring.childKeyrings.size()); verify(daoBuilder, times(REGIONS.size())).clientConfiguration(clientConfiguration); verify(daoBuilder, times(REGIONS.size())).credentialsProvider(credentialsProvider); @@ -96,8 +96,8 @@ void testBuildNoGenerator() { .build(); assertNull(keyring.generatorKeyring); - assertNotNull(keyring.childrenKeyrings); - assertEquals(4, keyring.childrenKeyrings.size()); + assertNotNull(keyring.childKeyrings); + assertEquals(4, keyring.childKeyrings.size()); verify(daoBuilder, times(REGIONS.size())).clientConfiguration(clientConfiguration); verify(daoBuilder, times(REGIONS.size())).credentialsProvider(credentialsProvider); @@ -117,8 +117,8 @@ void testBuildNoChildren() { .build(); assertNotNull(keyring.generatorKeyring); - assertNotNull(keyring.childrenKeyrings); - assertEquals(0, keyring.childrenKeyrings.size()); + assertNotNull(keyring.childKeyrings); + assertEquals(0, keyring.childKeyrings.size()); verify(daoBuilder, times(1)).clientConfiguration(clientConfiguration); verify(daoBuilder, times(1)).credentialsProvider(credentialsProvider); @@ -139,8 +139,8 @@ void testBuildNullKeyNames() { .build(); assertNull(keyring.generatorKeyring); - assertNotNull(keyring.childrenKeyrings); - assertEquals(1, keyring.childrenKeyrings.size()); + assertNotNull(keyring.childKeyrings); + assertEquals(1, keyring.childKeyrings.size()); verify(daoBuilder, times(1)).clientConfiguration(clientConfiguration); verify(daoBuilder, times(1)).credentialsProvider(credentialsProvider); @@ -166,8 +166,8 @@ void testBuildNoCustomization() { .build(); assertNotNull(keyring.generatorKeyring); - assertNotNull(keyring.childrenKeyrings); - assertEquals(4, keyring.childrenKeyrings.size()); + assertNotNull(keyring.childKeyrings); + assertEquals(4, keyring.childKeyrings.size()); verify(daoBuilder, times(REGIONS.size())).clientConfiguration(null); verify(daoBuilder, times(REGIONS.size())).credentialsProvider(null); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java index 479c18549..7bce168c0 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java @@ -57,8 +57,8 @@ void testBuildFullyConfigured() { .build(); assertNull(keyring.generatorKeyring); - assertNotNull(keyring.childrenKeyrings); - assertEquals(2, keyring.childrenKeyrings.size()); + assertNotNull(keyring.childKeyrings); + assertEquals(2, keyring.childKeyrings.size()); verify(daoBuilder, times(REGIONS.size())).clientConfiguration(clientConfiguration); verify(daoBuilder, times(REGIONS.size())).credentialsProvider(credentialsProvider); @@ -78,9 +78,9 @@ void testBuildMultipleSameRegion() { .build(); assertNull(keyring.generatorKeyring); - assertNotNull(keyring.childrenKeyrings); + assertNotNull(keyring.childKeyrings); // Regions are not de-duplicated - assertEquals(3, keyring.childrenKeyrings.size()); + assertEquals(3, keyring.childKeyrings.size()); verify(daoBuilder, times(2)).clientConfiguration(clientConfiguration); verify(daoBuilder, times(2)).credentialsProvider(credentialsProvider); @@ -99,8 +99,8 @@ void testBuildEmptyAndNullRegions() { .build(); assertNull(keyring.generatorKeyring); - assertNotNull(keyring.childrenKeyrings); - assertEquals(2, keyring.childrenKeyrings.size()); + assertNotNull(keyring.childKeyrings); + assertEquals(2, keyring.childKeyrings.size()); verify(daoBuilder, times(2)).clientConfiguration(clientConfiguration); verify(daoBuilder, times(2)).credentialsProvider(credentialsProvider); @@ -130,8 +130,8 @@ void testBuildNoCustomization() { .build(); assertNull(keyring.generatorKeyring); - assertNotNull(keyring.childrenKeyrings); - assertEquals(2, keyring.childrenKeyrings.size()); + assertNotNull(keyring.childKeyrings); + assertEquals(2, keyring.childKeyrings.size()); verify(daoBuilder, times(REGIONS.size())).clientConfiguration(null); verify(daoBuilder, times(REGIONS.size())).credentialsProvider(null); From 19669c2499278ba3511566843dce7e6ab9c0f17e Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Mon, 17 Aug 2020 11:39:08 -0700 Subject: [PATCH 14/15] Update based on PR feedback, more throws, test/example updates --- .../ActSimilarToAwsKmsMasterKeyProvider.java | 22 ++++---- .../awskms/DiscoveryDecryptInRegionOnly.java | 6 +-- .../DiscoveryDecryptWithPreferredRegions.java | 13 +++-- .../keyrings/AwsKmsSymmetricKeyring.java | 23 ++++---- ...AwsKmsSymmetricMultiCmkKeyringBuilder.java | 2 +- ...ricMultiRegionDiscoveryKeyringBuilder.java | 34 ++++++------ .../keyrings/StandardKeyrings.java | 2 +- .../kms/AwsKmsDataKeyEncryptionDao.java | 5 +- .../keyrings/AwsKmsSymmetricKeyringTest.java | 32 +++++++---- ...msSymmetricMultiCmkKeyringBuilderTest.java | 25 +++------ ...ultiRegionDiscoveryKeyringBuilderTest.java | 36 ++++++------- ...msSymmetricRegionDiscoveryKeyringTest.java | 53 ++++++++++--------- 12 files changed, 126 insertions(+), 127 deletions(-) diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActSimilarToAwsKmsMasterKeyProvider.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActSimilarToAwsKmsMasterKeyProvider.java index 4dfd3d9ae..e4d7ca213 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActSimilarToAwsKmsMasterKeyProvider.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/ActSimilarToAwsKmsMasterKeyProvider.java @@ -49,8 +49,8 @@ * However, as you migrate from master key providers to keyrings, * you might want a keyring that behaves similarly the AWS KMS master key provider. * - * Since the AWS KMS symmetric multi-region keyring cannot perform encryption, - * it cannot be combined with an AWS KMS symmetric multi-CMK in a multi-keyring, + * The AWS KMS symmetric multi-region keyring throws an error on encryption, + * so it cannot be combined with an AWS KMS symmetric multi-CMK in a multi-keyring, * if the encrypt operation is ever called. * Therefore, we have two separate keyrings. * One for encrypting with a specific list of CMKs @@ -112,12 +112,16 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final List awsK // it may communicate with. // // In production, if you need a keyring that attempts decryption in all AWS regions, - // you should call a service/API to get an updated list of AWS regions. - // This will prevent any AWS SDK-derived region-lists from potentially becoming stale over time. + // you should call a service/API to get an updated list of AWS regions + // and configure the keyring with that list. + // Although there are ways of getting a list of AWS regions directly from the AWS SDK, + // this is more prone to staleness + // than making a service/API call. // // In most cases, you should simply call StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery - // with the specific regions you need, - // and not attempt decryption in any AWS region. + // with the specific AWS regions you require for decryption + // and not attempt to configure the keyring with all available AWS regions. + // You should only provide the regions you need. // // This will provide flexibility for adding more regions over time, // without allowing unnecessary access to regions that are not currently required. @@ -129,8 +133,8 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final List awsK final Keyring discoveryKeyring = StandardKeyrings.awsKmsSymmetricMultiRegionDiscovery(allRegionIds); // Note that you cannot combine the AWS KMS symmetric multi-CMK and AWS KMS symmetric multi-region keyrings - // using a multi-keyring because the AWS KMS symmetric multi-region keyring is unable to perform - // an encryption operation. + // using a multi-keyring because the AWS KMS symmetric multi-region keyring throws an error + // when calling the encryption operation. // // Therefore, you should use these keyrings separately (one for encrypt and one for decrypt). // @@ -154,7 +158,7 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final List awsK // Demonstrate that the ciphertext and plaintext are different. assert !Arrays.equals(ciphertext, sourcePlaintext); - // Decrypt your encrypted data using the same keyring you used on encrypt. + // Decrypt your encrypted data using the AWS KMS symmetric multi-region keyring. // // You do not need to specify the encryption context on decrypt because // the header of the encrypted message includes the encryption context. diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java index d833491e3..86aff28be 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java @@ -24,14 +24,14 @@ * especially if you don't know which CMK was used to encrypt a message. * To address this need, you can use an AWS KMS symmetric multi-region discovery keyring. * The AWS KMS symmetric multi-region discovery keyring is a multi-keyring of AWS KMS symmetric region discovery keyrings. - * AWS KMS symmetric region discovery keyrings cannot encrypt. + * AWS KMS symmetric region discovery keyrings throw errors on encryption. * On decrypt each AWS KMS symmetric region discovery keyring reviews each encrypted data key (EDK). * If an EDK was encrypted under an AWS KMS CMK, * the AWS KMS symmetric region discovery keyring attempts to decrypt it if the EDK's region matches the region associated * with the AWS KMS symmetric region discovery keyring. * Whether decryption succeeds depends on permissions on the CMK. - * This continues until the AWS KMS symmetric region discovery keyring either runs out of EDKs - * or succeeds in decrypting an EDK. + * This continues until all child AWS KMS symmetric region discovery keyrings either run out of EDKs + * or a child succeeds in decrypting an EDK. *

* Each AWS KMS symmetric region discovery keyring is restricted to a single AWS region. * Additionally, an AWS KMS symmetric multi-region discovery keyring restricts communication to the configured regions, diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java index 3d3e569d3..d2aff6040 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java @@ -12,6 +12,7 @@ import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.regions.Regions; import com.amazonaws.services.kms.AWSKMSClientBuilder; +import org.apache.commons.lang3.StringUtils; import java.util.Arrays; import java.util.HashMap; @@ -24,14 +25,14 @@ * especially if you don't know which CMK was used to encrypt a message. * To address this need, you can use an AWS KMS symmetric multi-region discovery keyring. * The AWS KMS symmetric multi-region discovery keyring is a multi-keyring of AWS KMS symmetric region discovery keyrings. - * AWS KMS symmetric region discovery keyrings cannot encrypt. + * AWS KMS symmetric region discovery keyrings throw errors on encryption. * On decrypt each AWS KMS symmetric region discovery keyring reviews each encrypted data key (EDK). * If an EDK was encrypted under an AWS KMS CMK, * the AWS KMS symmetric region discovery keyring attempts to decrypt it if the EDK's region matches the region associated * with the AWS KMS symmetric region discovery keyring. * Whether decryption succeeds depends on permissions on the CMK. - * This continues until the AWS KMS symmetric region discovery keyring either runs out of EDKs - * or succeeds in decrypting an EDK. + * This continues until all child AWS KMS symmetric region discovery keyrings either run out of EDKs + * or a child succeeds in decrypting an EDK. *

* Each AWS KMS symmetric region discovery keyring is restricted to a single AWS region. * Additionally, an AWS KMS symmetric multi-region discovery keyring restricts communication to the configured regions, @@ -85,8 +86,10 @@ public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext // To create our decrypt keyring, we need to know our current default AWS region. // Please note that this *may* return a null region. - // As a result, we recommend specifying the region you are operating in directly. - final String localRegion = AWSKMSClientBuilder.standard().getRegion(); + // As a result, we recommend specifying the region you are operating in directly + // or having a fallback to prevent the keyring builder from failing. + String localRegion = AWSKMSClientBuilder.standard().getRegion(); + localRegion = StringUtils.isBlank(localRegion) ? Regions.US_EAST_1.getName() : localRegion; // Now, use that region name to create an AWS KMS symmetric multi-region discovery keyring. // The AWS KMS symmetric multi-region discovery keyring represents a multi-keying diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java index f7cb297b4..9b0e1b707 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java @@ -14,7 +14,6 @@ package com.amazonaws.encryptionsdk.keyrings; import com.amazonaws.encryptionsdk.EncryptedDataKey; -import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; import com.amazonaws.encryptionsdk.model.DecryptionMaterials; @@ -107,19 +106,15 @@ public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, Li for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { if (okToDecrypt(encryptedDataKey)) { - try { - final DataKeyEncryptionDao.DecryptDataKeyResult result = this.dataKeyEncryptionDao.decryptDataKey( - encryptedDataKey, decryptionMaterials.getAlgorithm(), decryptionMaterials.getEncryptionContext()); - - return decryptionMaterials.withCleartextDataKey(result.getPlaintextDataKey(), - new KeyringTraceEntry( - AWS_KMS_PROVIDER_ID, - result.getKeyArn(), - KeyringTraceFlag.DECRYPTED_DATA_KEY, - KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); - } catch (CannotUnwrapDataKeyException e) { - continue; - } + final DataKeyEncryptionDao.DecryptDataKeyResult result = this.dataKeyEncryptionDao.decryptDataKey( + encryptedDataKey, decryptionMaterials.getAlgorithm(), decryptionMaterials.getEncryptionContext()); + + return decryptionMaterials.withCleartextDataKey(result.getPlaintextDataKey(), + new KeyringTraceEntry( + AWS_KMS_PROVIDER_ID, + result.getKeyArn(), + KeyringTraceFlag.DECRYPTED_DATA_KEY, + KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java index ff0951456..5f05ae84e 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilder.java @@ -149,7 +149,7 @@ public MultiKeyring build() { if (this.childKeyNames != null) { for (final AwsKmsCmkId keyName : this.childKeyNames) { if (keyName == null) { - continue; + throw new IllegalArgumentException("AwsKmsSymmetricMultiCmkKeyringBuilder provided a null AwsKmsCmkId"); } // If we have an ARN, obtain the region from the ARN (to specify the region of the AWS SDK KMS service client) diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java index 000f251c4..a379bf736 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder.java @@ -119,23 +119,25 @@ public MultiKeyring build() { // Construct each AwsKmsSymmetricRegionDiscoveryKeyring List discoveryKeyrings = new ArrayList<>(); - if (this.regionIds != null) { - for (final String region : this.regionIds) { - if (StringUtils.isBlank(region)) { - continue; - } - - // Check if a dao already exists for the given region - // and use the existing dao or construct a new one and save it - final boolean discoveryDaoExists = clientMapping.containsKey(region); - final DataKeyEncryptionDao discoveryDao = discoveryDaoExists ? clientMapping.get(region) : constructDataKeyEncryptionDao(region); - if (!discoveryDaoExists) { - clientMapping.put(region, discoveryDao); - } - - final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(discoveryDao, region, this.awsAccountId); - discoveryKeyrings.add(discoveryKeyring); + if (this.regionIds == null) { + throw new IllegalArgumentException("AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder requires at least one region to build"); + } + + for (final String region : this.regionIds) { + if (StringUtils.isBlank(region)) { + throw new IllegalArgumentException("AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilder provided a null or blank region"); } + + // Check if a dao already exists for the given region + // and use the existing dao or construct a new one and save it + final boolean discoveryDaoExists = clientMapping.containsKey(region); + final DataKeyEncryptionDao discoveryDao = discoveryDaoExists ? clientMapping.get(region) : constructDataKeyEncryptionDao(region); + if (!discoveryDaoExists) { + clientMapping.put(region, discoveryDao); + } + + final Keyring discoveryKeyring = new AwsKmsSymmetricRegionDiscoveryKeyring(discoveryDao, region, this.awsAccountId); + discoveryKeyrings.add(discoveryKeyring); } // Finally, construct a multi-keyring diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java index e1eb1e940..03b18802d 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java @@ -64,7 +64,7 @@ public static Keyring awsKmsSymmetric(DataKeyEncryptionDao dataKeyEncryptionDao, } /** - * Constructs a {@code MultiKeyring} of {@code AwsKmsSymmetricKeyring}(s), + * Constructs a {@code MultiKeyring} of an {@code AwsKmsSymmetricKeyring}, * which interacts with AWS Key Management Service (KMS) to create, * encrypt, and decrypt data keys using the supplied AWS KMS defined Customer Master Keys (CMKs). * diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java index 4534f811e..b9f1ec92e 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java @@ -159,10 +159,7 @@ public DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, Cr } private T updateUserAgent(T request) { - // Only append the user agent string if the user agent string is the AWS SDK default - // and if we are allowed to append it - final String marker = request.getRequestClientOptions().getClientMarker(RequestClientOptions.Marker.USER_AGENT); - if (this.canAppendUserAgentString && marker == null) { + if (this.canAppendUserAgentString) { request.getRequestClientOptions().appendUserAgent(VersionInfo.USER_AGENT); } return request; diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java index aa51b9d29..4cd10679f 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java @@ -146,7 +146,7 @@ void testEncryptDecryptExistingDataKey() { } @Test - void testEncryptNullDataKey() { + void testGenerateEncryptDecryptDataKey() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) @@ -178,7 +178,7 @@ void testEncryptNullDataKey() { } @Test - void testDecryptFirstKeyIncorrectKeyName() { + void testDecryptFirstKeyFails() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) @@ -195,6 +195,21 @@ void testDecryptFirstKeyIncorrectKeyName() { assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } + @Test + void testDecryptIncorrectKeyName() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(FAILING_ENCRYPTED_KEY); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } + @Test void testDecryptMismatchedDataKeyException() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() @@ -208,7 +223,7 @@ void testDecryptMismatchedDataKeyException() { } @Test - void testDecryptFirstKeyWrongProvider() { + void testDecryptWrongProvider() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) @@ -218,13 +233,10 @@ void testDecryptFirstKeyWrongProvider() { List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(wrongProviderKey); - encryptedDataKeys.add(ENCRYPTED_KEY); decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -252,10 +264,8 @@ void testDecryptCannotUnwrapDataKey() { List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(ENCRYPTED_KEY); - decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + assertThrows(CannotUnwrapDataKeyException.class, () -> keyring.onDecrypt(decryptionMaterials, encryptedDataKeys)); - assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java index cb7119d3a..5d3771f63 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiCmkKeyringBuilderTest.java @@ -129,30 +129,17 @@ void testBuildNoChildren() { @Test void testBuildNullKeyNames() { - MultiKeyring keyring = builder.credentialsProvider(credentialsProvider) - .clientConfiguration(clientConfiguration) - .grantTokens(GRANT_TOKENS) - .generator(null) - .keyNames(Arrays.asList( - null, - KEY_NAME_US_EAST_1)) - .build(); - - assertNull(keyring.generatorKeyring); - assertNotNull(keyring.childKeyrings); - assertEquals(1, keyring.childKeyrings.size()); + when(daoBuilder.clientConfiguration(null)).thenReturn(daoBuilder); + when(daoBuilder.credentialsProvider(null)).thenReturn(daoBuilder); + when(daoBuilder.grantTokens(null)).thenReturn(daoBuilder); - verify(daoBuilder, times(1)).clientConfiguration(clientConfiguration); - verify(daoBuilder, times(1)).credentialsProvider(credentialsProvider); - verify(daoBuilder, times(1)).grantTokens(GRANT_TOKENS); - verify(daoBuilder).regionId("us-east-1"); - verify(daoBuilder, times(1)).build(); + builder = builder.keyNames(Arrays.asList(KEY_NAME_US_EAST_1, null)); + assertThrows(IllegalArgumentException.class, () -> builder.build()); } @Test void testBuildNoKeys() { - assertThrows(IllegalArgumentException.class, - () -> builder.build()); + assertThrows(IllegalArgumentException.class, () -> builder.build()); } @Test diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java index 7bce168c0..9145620c1 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricMultiRegionDiscoveryKeyringBuilderTest.java @@ -91,32 +91,28 @@ void testBuildMultipleSameRegion() { } @Test - void testBuildEmptyAndNullRegions() { - MultiKeyring keyring = builder.credentialsProvider(credentialsProvider) - .clientConfiguration(clientConfiguration) - .grantTokens(GRANT_TOKENS) - .regions(Arrays.asList("us-west-2", null, "", " ", "us-east-1")) - .build(); + void testBuildNullRegions() { + when(daoBuilder.clientConfiguration(null)).thenReturn(daoBuilder); + when(daoBuilder.credentialsProvider(null)).thenReturn(daoBuilder); + when(daoBuilder.grantTokens(null)).thenReturn(daoBuilder); - assertNull(keyring.generatorKeyring); - assertNotNull(keyring.childKeyrings); - assertEquals(2, keyring.childKeyrings.size()); + builder = builder.regions(Arrays.asList("us-west-2", null)); + assertThrows(IllegalArgumentException.class, () -> builder.build()); + } - verify(daoBuilder, times(2)).clientConfiguration(clientConfiguration); - verify(daoBuilder, times(2)).credentialsProvider(credentialsProvider); - verify(daoBuilder, times(2)).grantTokens(GRANT_TOKENS); - verify(daoBuilder).regionId("us-west-2"); - verify(daoBuilder).regionId("us-east-1"); - verify(daoBuilder, times(REGIONS.size())).build(); + @Test + void testBuildEmptyRegions() { + when(daoBuilder.clientConfiguration(null)).thenReturn(daoBuilder); + when(daoBuilder.credentialsProvider(null)).thenReturn(daoBuilder); + when(daoBuilder.grantTokens(null)).thenReturn(daoBuilder); + + builder = builder.regions(Arrays.asList("us-west-2", " ")); + assertThrows(IllegalArgumentException.class, () -> builder.build()); } @Test void testBuildNoRegions() { - assertThrows(IllegalArgumentException.class, - () -> builder.credentialsProvider(credentialsProvider) - .clientConfiguration(clientConfiguration) - .grantTokens(GRANT_TOKENS) - .build()); + assertThrows(IllegalArgumentException.class, () -> builder.build()); } @Test diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java index 7abbb48e5..6f5a8f1f3 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java @@ -171,21 +171,35 @@ void testDecryptFirstKeyFails() { } @Test - void testDecryptFirstKeyIncorrectAccountId() { + void testDecryptCannotUnwrap() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new CannotUnwrapDataKeyException()); + List encryptedDataKeys = new ArrayList<>(); - encryptedDataKeys.add(ENCRYPTED_DIFFERENT_AWS_ACCOUNT_ID_KEY); - encryptedDataKeys.add(ENCRYPTED_KEY_2); + encryptedDataKeys.add(ENCRYPTED_KEY_1); decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + @Test + void testDecryptIncorrectAccountId() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_DIFFERENT_AWS_ACCOUNT_ID_KEY); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -208,7 +222,7 @@ void testDecryptNoAwsAccountIdRequirement() { } @Test - void testDecryptFirstKeyIncorrectRegion() { + void testDecryptIncorrectRegion() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) @@ -216,13 +230,10 @@ void testDecryptFirstKeyIncorrectRegion() { List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(ENCRYPTED_DIFFERENT_REGION_KEY); - encryptedDataKeys.add(ENCRYPTED_KEY_2); decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -238,7 +249,7 @@ void testDecryptMismatchedDataKeyException() { } @Test - void testDecryptFirstKeyWrongProvider() { + void testDecryptWrongProvider() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) @@ -248,17 +259,14 @@ void testDecryptFirstKeyWrongProvider() { List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(wrongProviderKey); - encryptedDataKeys.add(ENCRYPTED_KEY_2); decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test - void testDecryptFirstKeyNotARN() { + void testDecryptNotARN() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) @@ -271,13 +279,10 @@ void testDecryptFirstKeyNotARN() { List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(notArnKey); - encryptedDataKeys.add(ENCRYPTED_KEY_2); decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertFalse(decryptionMaterials.hasCleartextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test From 44c6a2a4ae0a11baf0e04a533e31af9653f4b3de Mon Sep 17 00:00:00 2001 From: Alex Cioc Date: Tue, 18 Aug 2020 15:32:28 -0700 Subject: [PATCH 15/15] Remove keyring trace --- .../keyrings/AwsKmsSymmetricKeyring.java | 33 ++------------- ...AwsKmsSymmetricRegionDiscoveryKeyring.java | 9 +---- .../keyrings/AwsKmsSymmetricKeyringTest.java | 26 +----------- ...msSymmetricRegionDiscoveryKeyringTest.java | 40 ++++++------------- 4 files changed, 19 insertions(+), 89 deletions(-) diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java index 9b0e1b707..e1677c297 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyring.java @@ -65,34 +65,15 @@ public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { private EncryptionMaterials generateDataKey(final EncryptionMaterials encryptionMaterials) { final DataKeyEncryptionDao.GenerateDataKeyResult result = this.dataKeyEncryptionDao.generateDataKey( this.keyName, encryptionMaterials.getAlgorithm(), encryptionMaterials.getEncryptionContext()); - return encryptionMaterials - .withCleartextDataKey( - result.getPlaintextDataKey(), - new KeyringTraceEntry( - AWS_KMS_PROVIDER_ID, - this.keyName.toString(), - KeyringTraceFlag.GENERATED_DATA_KEY)) - .withEncryptedDataKey( - new KeyBlob(result.getEncryptedDataKey()), - new KeyringTraceEntry( - AWS_KMS_PROVIDER_ID, - this.keyName.toString(), - KeyringTraceFlag.ENCRYPTED_DATA_KEY, - KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + .withCleartextDataKey(result.getPlaintextDataKey()) + .withEncryptedDataKey(new KeyBlob(result.getEncryptedDataKey())); } private EncryptionMaterials encryptDataKey(final EncryptionMaterials encryptionMaterials) { final EncryptedDataKey encryptedDataKey = this.dataKeyEncryptionDao.encryptDataKey( this.keyName, encryptionMaterials.getCleartextDataKey(), encryptionMaterials.getEncryptionContext()); - - return encryptionMaterials.withEncryptedDataKey( - new KeyBlob(encryptedDataKey), - new KeyringTraceEntry( - AWS_KMS_PROVIDER_ID, - this.keyName.toString(), - KeyringTraceFlag.ENCRYPTED_DATA_KEY, - KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + return encryptionMaterials.withEncryptedDataKey(new KeyBlob(encryptedDataKey)); } @Override @@ -108,13 +89,7 @@ public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, Li if (okToDecrypt(encryptedDataKey)) { final DataKeyEncryptionDao.DecryptDataKeyResult result = this.dataKeyEncryptionDao.decryptDataKey( encryptedDataKey, decryptionMaterials.getAlgorithm(), decryptionMaterials.getEncryptionContext()); - - return decryptionMaterials.withCleartextDataKey(result.getPlaintextDataKey(), - new KeyringTraceEntry( - AWS_KMS_PROVIDER_ID, - result.getKeyArn(), - KeyringTraceFlag.DECRYPTED_DATA_KEY, - KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); + return decryptionMaterials.withCleartextDataKey(result.getPlaintextDataKey()); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java index 707864d2f..f8c0144d0 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyring.java @@ -67,14 +67,7 @@ public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, Li try { final DataKeyEncryptionDao.DecryptDataKeyResult result = this.dataKeyEncryptionDao.decryptDataKey( encryptedDataKey, decryptionMaterials.getAlgorithm(), decryptionMaterials.getEncryptionContext()); - - return decryptionMaterials.withCleartextDataKey( - result.getPlaintextDataKey(), - new KeyringTraceEntry( - AWS_KMS_PROVIDER_ID, - result.getKeyArn(), - KeyringTraceFlag.DECRYPTED_DATA_KEY, - KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); + return decryptionMaterials.withCleartextDataKey(result.getPlaintextDataKey()); } catch (CannotUnwrapDataKeyException e) { continue; } diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java index 4cd10679f..fde26c741 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricKeyringTest.java @@ -39,7 +39,6 @@ import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; -import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -56,10 +55,7 @@ class AwsKmsSymmetricKeyringTest { KEY_ARN.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); private static final EncryptedDataKey FAILING_ENCRYPTED_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, FAILING_KEY_ARN.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); - private static final KeyringTraceEntry ENCRYPTED_KEY_TRACE = - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); - private static final KeyringTraceEntry GENERATED_DATA_KEY_TRACE = - new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, GENERATED_DATA_KEY); + @Mock(lenient = true) private DataKeyEncryptionDao dataKeyEncryptionDao; private Keyring keyring; @@ -127,9 +123,6 @@ void testEncryptDecryptExistingDataKey() { assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); assertEncryptedDataKeyEquals(ENCRYPTED_KEY, encryptionMaterials.getEncryptedDataKeys().get(0)); - assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().size()); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_TRACE)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) @@ -140,9 +133,6 @@ void testEncryptDecryptExistingDataKey() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @Test @@ -158,10 +148,6 @@ void testGenerateEncryptDecryptDataKey() { assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getCleartextDataKey()); - assertEquals(2, encryptionMaterials.getKeyringTrace().getEntries().size()); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(GENERATED_DATA_KEY_TRACE)); - assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_TRACE)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) @@ -172,9 +158,6 @@ void testGenerateEncryptDecryptDataKey() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @Test @@ -190,9 +173,6 @@ void testDecryptFirstKeyFails() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ARN, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @Test @@ -207,7 +187,6 @@ void testDecryptIncorrectKeyName() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -236,7 +215,6 @@ void testDecryptWrongProvider() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -250,7 +228,6 @@ void testDecryptAlreadyDecryptedDataKey() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_KEY)); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -278,7 +255,6 @@ void testDecryptNoDataKey() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } private void assertEncryptedDataKeyEquals(EncryptedDataKey expected, EncryptedDataKey actual) { diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java index 6f5a8f1f3..47006c177 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsSymmetricRegionDiscoveryKeyringTest.java @@ -46,7 +46,9 @@ class AwsKmsSymmetricRegionDiscoveryKeyringTest { private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; - private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static final SecretKey PLAINTEXT_DATA_KEY_1 = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static final SecretKey PLAINTEXT_DATA_KEY_2 = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static final SecretKey PLAINTEXT_DATA_KEY_DIFFERENT_ACCOUNT_ID = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); private static final String KEY_NAME_1 = "arn:aws:kms:us-east-1:999999999999:key/key1-23bv-sdfs-werw-234323nfdsf"; private static final String KEY_NAME_2 = "arn:aws:kms:us-east-1:999999999999:key/key2-02ds-wvjs-aswe-a4923489273"; @@ -69,11 +71,11 @@ class AwsKmsSymmetricRegionDiscoveryKeyringTest { @BeforeEach void setup() { when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) - .thenReturn(new DecryptDataKeyResult(KEY_NAME_1, PLAINTEXT_DATA_KEY)); + .thenReturn(new DecryptDataKeyResult(KEY_NAME_1, PLAINTEXT_DATA_KEY_1)); when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_2, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) - .thenReturn(new DecryptDataKeyResult(KEY_NAME_2, PLAINTEXT_DATA_KEY)); + .thenReturn(new DecryptDataKeyResult(KEY_NAME_2, PLAINTEXT_DATA_KEY_2)); when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_DIFFERENT_AWS_ACCOUNT_ID_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) - .thenReturn(new DecryptDataKeyResult(DIFFERENT_AWS_ACCOUNT_ID_KEY_NAME, PLAINTEXT_DATA_KEY)); + .thenReturn(new DecryptDataKeyResult(DIFFERENT_AWS_ACCOUNT_ID_KEY_NAME, PLAINTEXT_DATA_KEY_DIFFERENT_ACCOUNT_ID)); keyring = new AwsKmsSymmetricRegionDiscoveryKeyring(dataKeyEncryptionDao, AWS_REGION, AWS_ACCOUNT_ID); } @@ -89,7 +91,7 @@ void testMalformedArns() { encryptedDataKeys.add(ENCRYPTED_KEY_1); decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY_1, decryptionMaterials.getCleartextDataKey()); decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) @@ -122,7 +124,7 @@ void testNullKeyName() { void testEncrypt() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) - .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setCleartextDataKey(PLAINTEXT_DATA_KEY_1) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); @@ -144,10 +146,7 @@ void testDecrypt() { encryptedDataKeys.add(ENCRYPTED_KEY_2); decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_1, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertEquals(PLAINTEXT_DATA_KEY_1, decryptionMaterials.getCleartextDataKey()); } @Test @@ -164,10 +163,7 @@ void testDecryptFirstKeyFails() { encryptedDataKeys.add(ENCRYPTED_KEY_2); decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_NAME_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertEquals(PLAINTEXT_DATA_KEY_2, decryptionMaterials.getCleartextDataKey()); } @Test @@ -184,7 +180,6 @@ void testDecryptCannotUnwrap() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -199,7 +194,6 @@ void testDecryptIncorrectAccountId() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -215,10 +209,7 @@ void testDecryptNoAwsAccountIdRequirement() { encryptedDataKeys.add(ENCRYPTED_KEY_2); decryptionMaterials = noAwsAccountIdCheckDiscoveryKeyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, DIFFERENT_AWS_ACCOUNT_ID_KEY_NAME, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertEquals(PLAINTEXT_DATA_KEY_DIFFERENT_ACCOUNT_ID, decryptionMaterials.getCleartextDataKey()); } @Test @@ -233,7 +224,6 @@ void testDecryptIncorrectRegion() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -262,7 +252,6 @@ void testDecryptWrongProvider() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test @@ -282,21 +271,19 @@ void testDecryptNotARN() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test void testDecryptAlreadyDecryptedDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) - .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setCleartextDataKey(PLAINTEXT_DATA_KEY_1) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_KEY_1)); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + assertEquals(PLAINTEXT_DATA_KEY_1, decryptionMaterials.getCleartextDataKey()); } @Test @@ -309,6 +296,5 @@ void testDecryptNoDataKey() { decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); assertFalse(decryptionMaterials.hasCleartextDataKey()); - assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } }