From ec8077cdc031bca3d22d6d785fd39e0d5713b214 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 11:10:04 -0800 Subject: [PATCH 01/33] copy code from internal --- .../encryption/DelegatedKey.java | 146 +++ .../encryption/DynamoDbEncryptor.java | 595 +++++++++++ .../encryption/DynamoDbSigner.java | 261 +++++ .../encryption/EncryptionContext.java | 187 ++++ .../encryption/EncryptionFlags.java | 23 + .../DynamoDbEncryptionException.java | 47 + .../materials/AbstractRawMaterials.java | 73 ++ .../materials/AsymmetricRawMaterials.java | 49 + .../materials/CryptographicMaterials.java | 24 + .../materials/DecryptionMaterials.java | 27 + .../materials/EncryptionMaterials.java | 27 + .../materials/SymmetricRawMaterials.java | 58 ++ .../materials/WrappedRawMaterials.java | 212 ++++ .../providers/AsymmetricStaticProvider.java | 46 + .../providers/CachingMostRecentProvider.java | 183 ++++ .../providers/DirectKmsMaterialsProvider.java | 296 ++++++ .../EncryptionMaterialsProvider.java | 71 ++ .../providers/KeyStoreMaterialsProvider.java | 199 ++++ .../providers/SymmetricStaticProvider.java | 130 +++ .../providers/WrappedMaterialsProvider.java | 163 +++ .../encryption/providers/store/MetaStore.java | 434 ++++++++ .../providers/store/ProviderStore.java | 84 ++ .../utils/EncryptionContextOperators.java | 81 ++ .../internal/AttributeValueMarshaller.java | 331 +++++++ .../internal/Base64.java | 48 + .../internal/ByteBufferInputStream.java | 56 ++ .../internal/Hkdf.java | 316 ++++++ .../internal/LRUCache.java | 107 ++ .../internal/MsClock.java | 19 + .../internal/TTLCache.java | 242 +++++ .../internal/Utils.java | 39 + .../HolisticIT.java | 932 ++++++++++++++++++ .../encryption/DelegatedEncryptionTest.java | 296 ++++++ .../DelegatedEnvelopeEncryptionTest.java | 280 ++++++ .../encryption/DynamoDbEncryptorTest.java | 591 +++++++++++ .../encryption/DynamoDbSignerTest.java | 567 +++++++++++ .../materials/AsymmetricRawMaterialsTest.java | 138 +++ .../materials/SymmetricRawMaterialsTest.java | 104 ++ .../AsymmetricStaticProviderTest.java | 130 +++ .../CachingMostRecentProviderTests.java | 610 ++++++++++++ .../DirectKmsMaterialsProviderTest.java | 449 +++++++++ .../KeyStoreMaterialsProviderTest.java | 315 ++++++ .../SymmetricStaticProviderTest.java | 182 ++++ .../WrappedMaterialsProviderTest.java | 414 ++++++++ .../providers/store/MetaStoreTests.java | 346 +++++++ .../utils/EncryptionContextOperatorsTest.java | 164 +++ .../AttributeValueMarshallerTest.java | 393 ++++++++ .../internal/Base64Tests.java | 93 ++ .../internal/ByteBufferInputStreamTest.java | 86 ++ .../internal/ConcurrentTTLCacheTest.java | 244 +++++ .../internal/HkdfTests.java | 209 ++++ .../internal/LRUCacheTest.java | 85 ++ .../internal/TTLCacheTest.java | 372 +++++++ .../testing/AttrMatcher.java | 125 +++ .../testing/AttributeValueBuilder.java | 67 ++ .../testing/AttributeValueDeserializer.java | 58 ++ .../testing/AttributeValueMatcher.java | 101 ++ .../testing/AttributeValueSerializer.java | 48 + .../testing/DdbRecordMatcher.java | 47 + .../testing/FakeKMS.java | 201 ++++ .../testing/LocalDynamoDb.java | 175 ++++ .../testing/ScenarioManifest.java | 77 ++ .../testing/TestDelegatedKey.java | 128 +++ 63 files changed, 12601 insertions(+) create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java new file mode 100644 index 0000000000..52e02f2e8e --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java @@ -0,0 +1,146 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; + +/** + * Identifies keys which should not be used directly with {@link Cipher} but + * instead contain their own cryptographic logic. This can be used to wrap more + * complex logic, HSM integration, or service-calls. + * + *

+ * Most delegated keys will only support a subset of these operations. (For + * example, AES keys will generally not support {@link #sign(byte[], String)} or + * {@link #verify(byte[], byte[], String)} and HMAC keys will generally not + * support anything except sign and verify.) + * {@link UnsupportedOperationException} should be thrown in these cases. + * + * @author Greg Rubin + */ +public interface DelegatedKey extends SecretKey { + /** + * Encrypts the provided plaintext and returns a byte-array containing the ciphertext. + * + * @param plainText + * @param additionalAssociatedData + * Optional additional data which must then also be provided for successful + * decryption. Both null and arrays of length 0 are treated identically. + * Not all keys will support this parameter. + * @param algorithm + * the transformation to be used when encrypting the data + * @return ciphertext the ciphertext produced by this encryption operation + * @throws UnsupportedOperationException + * if encryption is not supported or if additionalAssociatedData is + * provided, but not supported. + */ + byte[] encrypt(byte[] plainText, byte[] additionalAssociatedData, String algorithm) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, + NoSuchPaddingException; + + /** + * Decrypts the provided ciphertext and returns a byte-array containing the + * plaintext. + * + * @param cipherText + * @param additionalAssociatedData + * Optional additional data which was provided during encryption. + * Both null and arrays of length 0 are treated + * identically. Not all keys will support this parameter. + * @param algorithm + * the transformation to be used when decrypting the data + * @return plaintext the result of decrypting the input ciphertext + * @throws UnsupportedOperationException + * if decryption is not supported or if + * additionalAssociatedData is provided, but not + * supported. + */ + byte[] decrypt(byte[] cipherText, byte[] additionalAssociatedData, String algorithm) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, + NoSuchPaddingException, InvalidAlgorithmParameterException; + + /** + * Wraps (encrypts) the provided key to make it safe for + * storage or transmission. + * + * @param key + * @param additionalAssociatedData + * Optional additional data which must then also be provided for + * successful unwrapping. Both null and arrays of + * length 0 are treated identically. Not all keys will support + * this parameter. + * @param algorithm + * the transformation to be used when wrapping the key + * @return the wrapped key + * @throws UnsupportedOperationException + * if wrapping is not supported or if + * additionalAssociatedData is provided, but not + * supported. + */ + byte[] wrap(Key key, byte[] additionalAssociatedData, String algorithm) throws InvalidKeyException, + NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException; + + /** + * Unwraps (decrypts) the provided wrappedKey to recover the + * original key. + * + * @param wrappedKey + * @param additionalAssociatedData + * Optional additional data which was provided during wrapping. + * Both null and arrays of length 0 are treated + * identically. Not all keys will support this parameter. + * @param algorithm + * the transformation to be used when unwrapping the key + * @return the unwrapped key + * @throws UnsupportedOperationException + * if wrapping is not supported or if + * additionalAssociatedData is provided, but not + * supported. + */ + Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType, + byte[] additionalAssociatedData, String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException, + InvalidKeyException; + + /** + * Calculates and returns a signature for dataToSign. + * + * @param dataToSign + * @param algorithm + * @return the signature + * @throws UnsupportedOperationException if signing is not supported + */ + byte[] sign(byte[] dataToSign, String algorithm) throws GeneralSecurityException; + + /** + * Checks the provided signature for correctness. + * + * @param dataToSign + * @param signature + * @param algorithm + * @return true if and only if the signature matches the dataToSign. + * @throws UnsupportedOperationException if signature validation is not supported + */ + boolean verify(byte[] dataToSign, byte[] signature, String algorithm); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java new file mode 100644 index 0000000000..95e6ec73c7 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java @@ -0,0 +1,595 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.SignatureException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.ByteBufferInputStream; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +/** + * The low-level API for performing crypto operations on the record attributes. + * + * @author Greg Rubin + */ +public class DynamoDbEncryptor { + private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA"; + private static final String DEFAULT_METADATA_FIELD = "*amzn-ddb-map-desc*"; + private static final String DEFAULT_SIGNATURE_FIELD = "*amzn-ddb-map-sig*"; + private static final String DEFAULT_DESCRIPTION_BASE = "amzn-ddb-map-"; // Same as the Mapper + private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final String SYMMETRIC_ENCRYPTION_MODE = "/CBC/PKCS5Padding"; + private static final ConcurrentHashMap BLOCK_SIZE_CACHE = new ConcurrentHashMap<>(); + private static final Function BLOCK_SIZE_CALCULATOR = (transformation) -> { + try { + final Cipher c = Cipher.getInstance(transformation); + return c.getBlockSize(); + } catch (final GeneralSecurityException ex) { + throw new IllegalArgumentException("Algorithm does not exist", ex); + } + }; + + private static final int CURRENT_VERSION = 0; + + private String signatureFieldName = DEFAULT_SIGNATURE_FIELD; + private String materialDescriptionFieldName = DEFAULT_METADATA_FIELD; + + private EncryptionMaterialsProvider encryptionMaterialsProvider; + private final String descriptionBase; + private final String symmetricEncryptionModeHeader; + private final String signingAlgorithmHeader; + + static final String DEFAULT_SIGNING_ALGORITHM_HEADER = DEFAULT_DESCRIPTION_BASE + "signingAlg"; + + private Function encryptionContextOverrideOperator; + + protected DynamoDbEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { + this.encryptionMaterialsProvider = provider; + this.descriptionBase = descriptionBase; + symmetricEncryptionModeHeader = this.descriptionBase + "sym-mode"; + signingAlgorithmHeader = this.descriptionBase + "signingAlg"; + } + + public static DynamoDbEncryptor getInstance( + EncryptionMaterialsProvider provider, String descriptionbase) { + return new DynamoDbEncryptor(provider, descriptionbase); + } + + public static DynamoDbEncryptor getInstance(EncryptionMaterialsProvider provider) { + return getInstance(provider, DEFAULT_DESCRIPTION_BASE); + } + + /** + * Returns a decrypted version of the provided DynamoDb record. The signature is verified across + * all provided fields. All fields (except those listed in doNotEncrypt are + * decrypted. + * + * @param itemAttributes the DynamoDbRecord + * @param context additional information used to successfully select the encryption materials and + * decrypt the data. This should include (at least) the tableName and the materialDescription. + * @param doNotDecrypt those fields which should not be encrypted + * @return a plaintext version of the DynamoDb record + * @throws SignatureException if the signature is invalid or cannot be verified + * @throws GeneralSecurityException + */ + public Map decryptAllFieldsExcept( + Map itemAttributes, EncryptionContext context, String... doNotDecrypt) + throws GeneralSecurityException { + return decryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotDecrypt)); + } + + /** @see #decryptAllFieldsExcept(Map, EncryptionContext, String...) */ + public Map decryptAllFieldsExcept( + Map itemAttributes, + EncryptionContext context, + Collection doNotDecrypt) + throws GeneralSecurityException { + Map> attributeFlags = + allDecryptionFlagsExcept(itemAttributes, doNotDecrypt); + return decryptRecord(itemAttributes, attributeFlags, context); + } + + /** + * Returns the decryption flags for all item attributes except for those explicitly specified to + * be excluded. + * + * @param doNotDecrypt fields to be excluded + */ + public Map> allDecryptionFlagsExcept( + Map itemAttributes, String... doNotDecrypt) { + return allDecryptionFlagsExcept(itemAttributes, Arrays.asList(doNotDecrypt)); + } + + /** + * Returns the decryption flags for all item attributes except for those explicitly specified to + * be excluded. + * + * @param doNotDecrypt fields to be excluded + */ + public Map> allDecryptionFlagsExcept( + Map itemAttributes, Collection doNotDecrypt) { + Map> attributeFlags = new HashMap>(); + + for (String fieldName : doNotDecrypt) { + attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN)); + } + + for (String fieldName : itemAttributes.keySet()) { + if (!attributeFlags.containsKey(fieldName) + && !fieldName.equals(getMaterialDescriptionFieldName()) + && !fieldName.equals(getSignatureFieldName())) { + attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); + } + } + return attributeFlags; + } + + /** + * Returns an encrypted version of the provided DynamoDb record. All fields are signed. All fields + * (except those listed in doNotEncrypt) are encrypted. + * + * @param itemAttributes a DynamoDb Record + * @param context additional information used to successfully select the encryption materials and + * encrypt the data. This should include (at least) the tableName. + * @param doNotEncrypt those fields which should not be encrypted + * @return a ciphertext version of the DynamoDb record + * @throws GeneralSecurityException + */ + public Map encryptAllFieldsExcept( + Map itemAttributes, EncryptionContext context, String... doNotEncrypt) + throws GeneralSecurityException { + + return encryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotEncrypt)); + } + + public Map encryptAllFieldsExcept( + Map itemAttributes, + EncryptionContext context, + Collection doNotEncrypt) + throws GeneralSecurityException { + Map> attributeFlags = + allEncryptionFlagsExcept(itemAttributes, doNotEncrypt); + return encryptRecord(itemAttributes, attributeFlags, context); + } + + /** + * Returns the encryption flags for all item attributes except for those explicitly specified to + * be excluded. + * + * @param doNotEncrypt fields to be excluded + */ + public Map> allEncryptionFlagsExcept( + Map itemAttributes, String... doNotEncrypt) { + return allEncryptionFlagsExcept(itemAttributes, Arrays.asList(doNotEncrypt)); + } + + /** + * Returns the encryption flags for all item attributes except for those explicitly specified to + * be excluded. + * + * @param doNotEncrypt fields to be excluded + */ + public Map> allEncryptionFlagsExcept( + Map itemAttributes, Collection doNotEncrypt) { + Map> attributeFlags = new HashMap>(); + for (String fieldName : doNotEncrypt) { + attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN)); + } + + for (String fieldName : itemAttributes.keySet()) { + if (!attributeFlags.containsKey(fieldName)) { + attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); + } + } + return attributeFlags; + } + + public Map decryptRecord( + Map itemAttributes, + Map> attributeFlags, + EncryptionContext context) + throws GeneralSecurityException { + if (!itemContainsFieldsToDecryptOrSign(itemAttributes.keySet(), attributeFlags)) { + return itemAttributes; + } + // Copy to avoid changing anyone elses objects + itemAttributes = new HashMap(itemAttributes); + + Map materialDescription = Collections.emptyMap(); + DecryptionMaterials materials; + SecretKey decryptionKey; + + DynamoDbSigner signer = DynamoDbSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng()); + + if (itemAttributes.containsKey(materialDescriptionFieldName)) { + materialDescription = unmarshallDescription(itemAttributes.get(materialDescriptionFieldName)); + } + // Copy the material description and attribute values into the context + context = + new EncryptionContext.Builder(context) + .materialDescription(materialDescription) + .attributeValues(itemAttributes) + .build(); + + Function encryptionContextOverrideOperator = + getEncryptionContextOverrideOperator(); + if (encryptionContextOverrideOperator != null) { + context = encryptionContextOverrideOperator.apply(context); + } + + materials = encryptionMaterialsProvider.getDecryptionMaterials(context); + decryptionKey = materials.getDecryptionKey(); + if (materialDescription.containsKey(signingAlgorithmHeader)) { + String signingAlg = materialDescription.get(signingAlgorithmHeader); + signer = DynamoDbSigner.getInstance(signingAlg, Utils.getRng()); + } + + ByteBuffer signature; + if (!itemAttributes.containsKey(signatureFieldName) + || itemAttributes.get(signatureFieldName).b() == null) { + signature = ByteBuffer.allocate(0); + } else { + signature = itemAttributes.get(signatureFieldName).b().asByteBuffer().asReadOnlyBuffer(); + } + itemAttributes.remove(signatureFieldName); + + String associatedData = "TABLE>" + context.getTableName() + " attributeNamesToCheck, Map> attributeFlags) { + return attributeNamesToCheck.stream() + .filter(attributeFlags::containsKey) + .anyMatch(attributeName -> !attributeFlags.get(attributeName).isEmpty()); + } + + public Map encryptRecord( + Map itemAttributes, + Map> attributeFlags, + EncryptionContext context) { + if (attributeFlags.isEmpty()) { + return itemAttributes; + } + // Copy to avoid changing anyone elses objects + itemAttributes = new HashMap<>(itemAttributes); + + // Copy the attribute values into the context + context = context.toBuilder() + .attributeValues(itemAttributes) + .build(); + + Function encryptionContextOverrideOperator = + getEncryptionContextOverrideOperator(); + if (encryptionContextOverrideOperator != null) { + context = encryptionContextOverrideOperator.apply(context); + } + + EncryptionMaterials materials = encryptionMaterialsProvider.getEncryptionMaterials(context); + // We need to copy this because we modify it to record other encryption details + Map materialDescription = new HashMap<>( + materials.getMaterialDescription()); + SecretKey encryptionKey = materials.getEncryptionKey(); + + try { + actualEncryption(itemAttributes, attributeFlags, materialDescription, encryptionKey); + + // The description must be stored after encryption because its data + // is necessary for proper decryption. + final String signingAlgo = materialDescription.get(signingAlgorithmHeader); + DynamoDbSigner signer; + if (signingAlgo != null) { + signer = DynamoDbSigner.getInstance(signingAlgo, Utils.getRng()); + } else { + signer = DynamoDbSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng()); + } + + if (materials.getSigningKey() instanceof PrivateKey) { + materialDescription.put(signingAlgorithmHeader, signer.getSigningAlgorithm()); + } + if (! materialDescription.isEmpty()) { + itemAttributes.put(materialDescriptionFieldName, marshallDescription(materialDescription)); + } + + String associatedData = "TABLE>" + context.getTableName() + " itemAttributes, + Map> attributeFlags, SecretKey encryptionKey, + Map materialDescription) throws GeneralSecurityException { + final String encryptionMode = encryptionKey != null ? encryptionKey.getAlgorithm() + + materialDescription.get(symmetricEncryptionModeHeader) : null; + Cipher cipher = null; + int blockSize = -1; + + for (Map.Entry entry: itemAttributes.entrySet()) { + Set flags = attributeFlags.get(entry.getKey()); + if (flags != null && flags.contains(EncryptionFlags.ENCRYPT)) { + if (!flags.contains(EncryptionFlags.SIGN)) { + throw new IllegalArgumentException("All encrypted fields must be signed. Bad field: " + entry.getKey()); + } + ByteBuffer plainText; + ByteBuffer cipherText = entry.getValue().b().asByteBuffer(); + cipherText.rewind(); + if (encryptionKey instanceof DelegatedKey) { + plainText = ByteBuffer.wrap(((DelegatedKey)encryptionKey).decrypt(toByteArray(cipherText), null, encryptionMode)); + } else { + if (cipher == null) { + blockSize = getBlockSize(encryptionMode); + cipher = Cipher.getInstance(encryptionMode); + } + byte[] iv = new byte[blockSize]; + cipherText.get(iv); + cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng()); + plainText = ByteBuffer.allocate(cipher.getOutputSize(cipherText.remaining())); + cipher.doFinal(cipherText, plainText); + plainText.rewind(); + } + entry.setValue(AttributeValueMarshaller.unmarshall(plainText)); + } + } + } + + private static int getBlockSize(final String encryptionMode) { + return BLOCK_SIZE_CACHE.computeIfAbsent(encryptionMode, BLOCK_SIZE_CALCULATOR); + } + + /** + * This method has the side effect of replacing the plaintext + * attribute-values of "itemAttributes" with ciphertext attribute-values + * (which are always in the form of ByteBuffer) as per the corresponding + * attribute flags. + */ + private void actualEncryption(Map itemAttributes, + Map> attributeFlags, + Map materialDescription, + SecretKey encryptionKey) throws GeneralSecurityException { + String encryptionMode = null; + if (encryptionKey != null) { + materialDescription.put(this.symmetricEncryptionModeHeader, + SYMMETRIC_ENCRYPTION_MODE); + encryptionMode = encryptionKey.getAlgorithm() + SYMMETRIC_ENCRYPTION_MODE; + } + Cipher cipher = null; + int blockSize = -1; + + for (Map.Entry entry: itemAttributes.entrySet()) { + Set flags = attributeFlags.get(entry.getKey()); + if (flags != null && flags.contains(EncryptionFlags.ENCRYPT)) { + if (!flags.contains(EncryptionFlags.SIGN)) { + throw new IllegalArgumentException("All encrypted fields must be signed. Bad field: " + entry.getKey()); + } + ByteBuffer plainText = AttributeValueMarshaller.marshall(entry.getValue()); + plainText.rewind(); + ByteBuffer cipherText; + if (encryptionKey instanceof DelegatedKey) { + DelegatedKey dk = (DelegatedKey) encryptionKey; + cipherText = ByteBuffer.wrap( + dk.encrypt(toByteArray(plainText), null, encryptionMode)); + } else { + if (cipher == null) { + blockSize = getBlockSize(encryptionMode); + cipher = Cipher.getInstance(encryptionMode); + } + // Encryption format: + // Note a unique iv is generated per attribute + cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, Utils.getRng()); + cipherText = ByteBuffer.allocate(blockSize + cipher.getOutputSize(plainText.remaining())); + cipherText.position(blockSize); + cipher.doFinal(plainText, cipherText); + cipherText.flip(); + final byte[] iv = cipher.getIV(); + if (iv.length != blockSize) { + throw new IllegalStateException(String.format("Generated IV length (%d) not equal to block size (%d)", + iv.length, blockSize)); + } + cipherText.put(iv); + cipherText.rewind(); + } + // Replace the plaintext attribute value with the encrypted content + entry.setValue(AttributeValue.builder().b(SdkBytes.fromByteBuffer(cipherText)).build()); + } + } + } + + /** + * Get the name of the DynamoDB field used to store the signature. + * Defaults to {@link #DEFAULT_SIGNATURE_FIELD}. + * + * @return the name of the DynamoDB field used to store the signature + */ + String getSignatureFieldName() { + return signatureFieldName; + } + + /** + * Set the name of the DynamoDB field used to store the signature. + * + * @param signatureFieldName + */ + void setSignatureFieldName(final String signatureFieldName) { + this.signatureFieldName = signatureFieldName; + } + + /** + * Get the name of the DynamoDB field used to store metadata used by the + * DynamoDBEncryptedMapper. Defaults to {@link #DEFAULT_METADATA_FIELD}. + * + * @return the name of the DynamoDB field used to store metadata used by the + * DynamoDBEncryptedMapper + */ + String getMaterialDescriptionFieldName() { + return materialDescriptionFieldName; + } + + /** + * Set the name of the DynamoDB field used to store metadata used by the + * DynamoDBEncryptedMapper + * + * @param materialDescriptionFieldName + */ + void setMaterialDescriptionFieldName(final String materialDescriptionFieldName) { + this.materialDescriptionFieldName = materialDescriptionFieldName; + } + + /** + * Marshalls the description into a ByteBuffer by outputting + * each key (modified UTF-8) followed by its value (also in modified UTF-8). + * + * @param description + * @return the description encoded as an AttributeValue with a ByteBuffer value + * @see java.io.DataOutput#writeUTF(String) + */ + private static AttributeValue marshallDescription(Map description) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(bos); + out.writeInt(CURRENT_VERSION); + for (Map.Entry entry : description.entrySet()) { + byte[] bytes = entry.getKey().getBytes(UTF8); + out.writeInt(bytes.length); + out.write(bytes); + bytes = entry.getValue().getBytes(UTF8); + out.writeInt(bytes.length); + out.write(bytes); + } + out.close(); + return AttributeValue.builder().b(SdkBytes.fromByteArray(bos.toByteArray())).build(); + } catch (IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + /** + * @see #marshallDescription(Map) + */ + private static Map unmarshallDescription(AttributeValue attributeValue) { + try (DataInputStream in = new DataInputStream( + new ByteBufferInputStream(attributeValue.b().asByteBuffer())) ) { + Map result = new HashMap<>(); + int version = in.readInt(); + if (version != CURRENT_VERSION) { + throw new IllegalArgumentException("Unsupported description version"); + } + + String key, value; + int keyLength, valueLength; + try { + while(in.available() > 0) { + keyLength = in.readInt(); + byte[] bytes = new byte[keyLength]; + if (in.read(bytes) != keyLength) { + throw new IllegalArgumentException("Malformed description"); + } + key = new String(bytes, UTF8); + valueLength = in.readInt(); + bytes = new byte[valueLength]; + if (in.read(bytes) != valueLength) { + throw new IllegalArgumentException("Malformed description"); + } + value = new String(bytes, UTF8); + result.put(key, value); + } + } catch (EOFException eof) { + throw new IllegalArgumentException("Malformed description", eof); + } + return result; + } catch (IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + /** + * @param encryptionContextOverrideOperator the nullable operator which will be used to override + * the EncryptionContext. + * @see EncryptionContextOperators + */ + void setEncryptionContextOverrideOperator( + Function encryptionContextOverrideOperator) { + this.encryptionContextOverrideOperator = encryptionContextOverrideOperator; + } + + /** + * @return the operator used to override the EncryptionContext + * @see #setEncryptionContextOverrideOperator(Function) + */ + private Function getEncryptionContextOverrideOperator() { + return encryptionContextOverrideOperator; + } + + private static byte[] toByteArray(ByteBuffer buffer) { + buffer = buffer.duplicate(); + // We can only return the array directly if: + // 1. The ByteBuffer exposes an array + // 2. The ByteBuffer starts at the beginning of the array + // 3. The ByteBuffer uses the entire array + if (buffer.hasArray() && buffer.arrayOffset() == 0) { + byte[] result = buffer.array(); + if (buffer.remaining() == result.length) { + return result; + } + } + + byte[] result = new byte[buffer.remaining()]; + buffer.get(result); + return result; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java new file mode 100644 index 0000000000..d2998057b0 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java @@ -0,0 +1,261 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +/** + * @author Greg Rubin + */ +// NOTE: This class must remain thread-safe. +class DynamoDbSigner { + private static final ConcurrentHashMap cache = + new ConcurrentHashMap(); + + protected static final Charset UTF8 = Charset.forName("UTF-8"); + private final SecureRandom rnd; + private final SecretKey hmacComparisonKey; + private final String signingAlgorithm; + + /** + * @param signingAlgorithm is the algorithm used for asymmetric signing (ex: SHA256withRSA). This + * is ignored for symmetric HMACs as that algorithm is fully specified by the key. + */ + static DynamoDbSigner getInstance(String signingAlgorithm, SecureRandom rnd) { + DynamoDbSigner result = cache.get(signingAlgorithm); + if (result == null) { + result = new DynamoDbSigner(signingAlgorithm, rnd); + cache.putIfAbsent(signingAlgorithm, result); + } + return result; + } + + /** + * @param signingAlgorithm is the algorithm used for asymmetric signing (ex: SHA256withRSA). This + * is ignored for symmetric HMACs as that algorithm is fully specified by the key. + */ + private DynamoDbSigner(String signingAlgorithm, SecureRandom rnd) { + if (rnd == null) { + rnd = Utils.getRng(); + } + this.rnd = rnd; + this.signingAlgorithm = signingAlgorithm; + // Shorter than the output of SHA256 to avoid weak keys. + // http://cs.nyu.edu/~dodis/ps/h-of-h.pdf + // http://link.springer.com/chapter/10.1007%2F978-3-642-32009-5_21 + byte[] tmpKey = new byte[31]; + rnd.nextBytes(tmpKey); + hmacComparisonKey = new SecretKeySpec(tmpKey, "HmacSHA256"); + } + + void verifySignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + Key verificationKey, + ByteBuffer signature) + throws GeneralSecurityException { + if (verificationKey instanceof DelegatedKey) { + DelegatedKey dKey = (DelegatedKey) verificationKey; + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + if (!dKey.verify(stringToSign, toByteArray(signature), dKey.getAlgorithm())) { + throw new SignatureException("Bad signature"); + } + } else if (verificationKey instanceof SecretKey) { + byte[] calculatedSig = + calculateSignature( + itemAttributes, attributeFlags, associatedData, (SecretKey) verificationKey); + if (!safeEquals(signature, calculatedSig)) { + throw new SignatureException("Bad signature"); + } + } else if (verificationKey instanceof PublicKey) { + PublicKey integrityKey = (PublicKey) verificationKey; + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + Signature sig = Signature.getInstance(getSigningAlgorithm()); + sig.initVerify(integrityKey); + sig.update(stringToSign); + if (!sig.verify(toByteArray(signature))) { + throw new SignatureException("Bad signature"); + } + } else { + throw new IllegalArgumentException("No integrity key provided"); + } + } + + static byte[] calculateStringToSign( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData) + throws NoSuchAlgorithmException { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + List attrNames = new ArrayList(itemAttributes.keySet()); + Collections.sort(attrNames); + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + if (associatedData != null) { + out.write(sha256.digest(associatedData)); + } else { + out.write(sha256.digest()); + } + sha256.reset(); + + for (String name : attrNames) { + Set set = attributeFlags.get(name); + if (set != null && set.contains(EncryptionFlags.SIGN)) { + AttributeValue tmp = itemAttributes.get(name); + out.write(sha256.digest(name.getBytes(UTF8))); + sha256.reset(); + if (set.contains(EncryptionFlags.ENCRYPT)) { + sha256.update("ENCRYPTED".getBytes(UTF8)); + } else { + sha256.update("PLAINTEXT".getBytes(UTF8)); + } + out.write(sha256.digest()); + + sha256.reset(); + + sha256.update(AttributeValueMarshaller.marshall(tmp)); + out.write(sha256.digest()); + sha256.reset(); + } + } + return out.toByteArray(); + } catch (IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + /** The itemAttributes have already been encrypted, if necessary, before the signing. */ + byte[] calculateSignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + Key key) + throws GeneralSecurityException { + if (key instanceof DelegatedKey) { + return calculateSignature(itemAttributes, attributeFlags, associatedData, (DelegatedKey) key); + } else if (key instanceof SecretKey) { + return calculateSignature(itemAttributes, attributeFlags, associatedData, (SecretKey) key); + } else if (key instanceof PrivateKey) { + return calculateSignature(itemAttributes, attributeFlags, associatedData, (PrivateKey) key); + } else { + throw new IllegalArgumentException("No integrity key provided"); + } + } + + byte[] calculateSignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + DelegatedKey key) + throws GeneralSecurityException { + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + return key.sign(stringToSign, key.getAlgorithm()); + } + + byte[] calculateSignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + SecretKey key) + throws GeneralSecurityException { + if (key instanceof DelegatedKey) { + return calculateSignature(itemAttributes, attributeFlags, associatedData, (DelegatedKey) key); + } + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + Mac hmac = Mac.getInstance(key.getAlgorithm()); + hmac.init(key); + hmac.update(stringToSign); + return hmac.doFinal(); + } + + byte[] calculateSignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + PrivateKey key) + throws GeneralSecurityException { + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + Signature sig = Signature.getInstance(signingAlgorithm); + sig.initSign(key, rnd); + sig.update(stringToSign); + return sig.sign(); + } + + String getSigningAlgorithm() { + return signingAlgorithm; + } + + /** Constant-time equality check. */ + private boolean safeEquals(ByteBuffer signature, byte[] calculatedSig) { + try { + signature.rewind(); + Mac hmac = Mac.getInstance(hmacComparisonKey.getAlgorithm()); + hmac.init(hmacComparisonKey); + hmac.update(signature); + byte[] signatureHash = hmac.doFinal(); + + hmac.reset(); + hmac.update(calculatedSig); + byte[] calculatedHash = hmac.doFinal(); + + return MessageDigest.isEqual(signatureHash, calculatedHash); + } catch (GeneralSecurityException ex) { + // We've hardcoded these algorithms, so the error should not be possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + private static byte[] toByteArray(ByteBuffer buffer) { + if (buffer.hasArray()) { + byte[] result = buffer.array(); + buffer.rewind(); + return result; + } else { + byte[] result = new byte[buffer.remaining()]; + buffer.get(result); + buffer.rewind(); + return result; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java new file mode 100644 index 0000000000..9a78ad9b04 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java @@ -0,0 +1,187 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +/** + * This class serves to provide additional useful data to + * {@link EncryptionMaterialsProvider}s so they can more intelligently select + * the proper {@link EncryptionMaterials} or {@link DecryptionMaterials} for + * use. Any of the methods are permitted to return null. + *

+ * For the simplest cases, all a developer needs to provide in the context are: + *

+ * + * This class is immutable. + * + * @author Greg Rubin + */ +public final class EncryptionContext { + private final String tableName; + private final Map attributeValues; + private final Object developerContext; + private final String hashKeyName; + private final String rangeKeyName; + private final Map materialDescription; + + /** + * Return a new builder that can be used to construct an {@link EncryptionContext} + * @return A newly initialized {@link EncryptionContext.Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + private EncryptionContext(Builder builder) { + tableName = builder.tableName; + attributeValues = builder.attributeValues; + developerContext = builder.developerContext; + hashKeyName = builder.hashKeyName; + rangeKeyName = builder.rangeKeyName; + materialDescription = builder.materialDescription; + } + + /** + * Returns the name of the DynamoDB Table this record is associated with. + */ + public String getTableName() { + return tableName; + } + + /** + * Returns the DynamoDB record about to be encrypted/decrypted. + */ + public Map getAttributeValues() { + return attributeValues; + } + + /** + * This object has no meaning (and will not be set or examined) by any core libraries. + * It exists to allow custom object mappers and data access layers to pass + * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDbEncryptor}. + */ + public Object getDeveloperContext() { + return developerContext; + } + + /** + * Returns the name of the HashKey attribute for the record to be encrypted/decrypted. + */ + public String getHashKeyName() { + return hashKeyName; + } + + /** + * Returns the name of the RangeKey attribute for the record to be encrypted/decrypted. + */ + public String getRangeKeyName() { + return rangeKeyName; + } + + public Map getMaterialDescription() { + return materialDescription; + } + + /** + * Converts an existing {@link EncryptionContext} into a builder that can be used to mutate and make a new version. + * @return A new {@link EncryptionContext.Builder} with all the fields filled out to match the current object. + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Builder class for {@link EncryptionContext}. + * Mutable objects (other than developerContext) will undergo + * a defensive copy prior to being stored in the builder. + * + * This class is not thread-safe. + */ + public static final class Builder { + private String tableName = null; + private Map attributeValues = null; + private Object developerContext = null; + private String hashKeyName = null; + private String rangeKeyName = null; + private Map materialDescription = null; + + public Builder() { + } + + public Builder(EncryptionContext context) { + tableName = context.getTableName(); + attributeValues = context.getAttributeValues(); + hashKeyName = context.getHashKeyName(); + rangeKeyName = context.getRangeKeyName(); + developerContext = context.getDeveloperContext(); + materialDescription = context.getMaterialDescription(); + } + + public EncryptionContext build() { + return new EncryptionContext(this); + } + + public Builder tableName(String tableName) { + this.tableName = tableName; + return this; + } + + public Builder attributeValues(Map attributeValues) { + this.attributeValues = Collections.unmodifiableMap(new HashMap<>(attributeValues)); + return this; + } + + public Builder developerContext(Object developerContext) { + this.developerContext = developerContext; + return this; + } + + public Builder hashKeyName(String hashKeyName) { + this.hashKeyName = hashKeyName; + return this; + } + + public Builder rangeKeyName(String rangeKeyName) { + this.rangeKeyName = rangeKeyName; + return this; + } + + public Builder materialDescription(Map materialDescription) { + this.materialDescription = Collections.unmodifiableMap(new HashMap<>(materialDescription)); + return this; + } + } + + @Override + public String toString() { + return "EncryptionContext [tableName=" + tableName + ", attributeValues=" + attributeValues + + ", developerContext=" + developerContext + + ", hashKeyName=" + hashKeyName + ", rangeKeyName=" + rangeKeyName + + ", materialDescription=" + materialDescription + "]"; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java new file mode 100644 index 0000000000..47329f7128 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java @@ -0,0 +1,23 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +/** + * @author Greg Rubin + */ +public enum EncryptionFlags { + ENCRYPT, + SIGN +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java new file mode 100644 index 0000000000..f245d66e31 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java @@ -0,0 +1,47 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions; + +/** + * Generic exception thrown for any problem the DynamoDB encryption client has performing tasks + */ +public class DynamoDbEncryptionException extends RuntimeException { + private static final long serialVersionUID = - 7565904179772520868L; + + /** + * Standard constructor + * @param cause exception cause + */ + public DynamoDbEncryptionException(Throwable cause) { + super(cause); + } + + /** + * Standard constructor + * @param message exception message + */ + public DynamoDbEncryptionException(String message) { + super(message); + } + + /** + * Standard constructor + * @param message exception message + * @param cause exception cause + */ + public DynamoDbEncryptionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java new file mode 100644 index 0000000000..5dfbb19709 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.Key; +import java.security.KeyPair; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public abstract class AbstractRawMaterials implements DecryptionMaterials, EncryptionMaterials { + private Map description; + private final Key signingKey; + private final Key verificationKey; + + @SuppressWarnings("unchecked") + protected AbstractRawMaterials(KeyPair signingPair) { + this(signingPair, Collections.EMPTY_MAP); + } + + protected AbstractRawMaterials(KeyPair signingPair, Map description) { + this.signingKey = signingPair.getPrivate(); + this.verificationKey = signingPair.getPublic(); + setMaterialDescription(description); + } + + @SuppressWarnings("unchecked") + protected AbstractRawMaterials(SecretKey macKey) { + this(macKey, Collections.EMPTY_MAP); + } + + protected AbstractRawMaterials(SecretKey macKey, Map description) { + this.signingKey = macKey; + this.verificationKey = macKey; + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + } + + @Override + public Map getMaterialDescription() { + return new HashMap<>(description); + } + + public void setMaterialDescription(Map description) { + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + } + + @Override + public Key getSigningKey() { + return signingKey; + } + + @Override + public Key getVerificationKey() { + return verificationKey; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java new file mode 100644 index 0000000000..003d0b60cc --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public class AsymmetricRawMaterials extends WrappedRawMaterials { + @SuppressWarnings("unchecked") + public AsymmetricRawMaterials(KeyPair encryptionKey, KeyPair signingPair) + throws GeneralSecurityException { + this(encryptionKey, signingPair, Collections.EMPTY_MAP); + } + + public AsymmetricRawMaterials(KeyPair encryptionKey, KeyPair signingPair, Map description) + throws GeneralSecurityException { + super(encryptionKey.getPublic(), encryptionKey.getPrivate(), signingPair, description); + } + + @SuppressWarnings("unchecked") + public AsymmetricRawMaterials(KeyPair encryptionKey, SecretKey macKey) + throws GeneralSecurityException { + this(encryptionKey, macKey, Collections.EMPTY_MAP); + } + + public AsymmetricRawMaterials(KeyPair encryptionKey, SecretKey macKey, Map description) + throws GeneralSecurityException { + super(encryptionKey.getPublic(), encryptionKey.getPrivate(), macKey, description); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java new file mode 100644 index 0000000000..033d331f5b --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.util.Map; + +/** + * @author Greg Rubin + */ +public interface CryptographicMaterials { + Map getMaterialDescription(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java new file mode 100644 index 0000000000..00f8548bc7 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.Key; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public interface DecryptionMaterials extends CryptographicMaterials { + SecretKey getDecryptionKey(); + Key getVerificationKey(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java new file mode 100644 index 0000000000..ecef9e9fc8 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.Key; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public interface EncryptionMaterials extends CryptographicMaterials { + SecretKey getEncryptionKey(); + Key getSigningKey(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java new file mode 100644 index 0000000000..b3daab44ba --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.KeyPair; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public class SymmetricRawMaterials extends AbstractRawMaterials { + private final SecretKey cryptoKey; + + @SuppressWarnings("unchecked") + public SymmetricRawMaterials(SecretKey encryptionKey, KeyPair signingPair) { + this(encryptionKey, signingPair, Collections.EMPTY_MAP); + } + + public SymmetricRawMaterials(SecretKey encryptionKey, KeyPair signingPair, Map description) { + super(signingPair, description); + this.cryptoKey = encryptionKey; + } + + @SuppressWarnings("unchecked") + public SymmetricRawMaterials(SecretKey encryptionKey, SecretKey macKey) { + this(encryptionKey, macKey, Collections.EMPTY_MAP); + } + + public SymmetricRawMaterials(SecretKey encryptionKey, SecretKey macKey, Map description) { + super(macKey, description); + this.cryptoKey = encryptionKey; + } + + @Override + public SecretKey getEncryptionKey() { + return cryptoKey; + } + + @Override + public SecretKey getDecryptionKey() { + return cryptoKey; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java new file mode 100644 index 0000000000..fd17521ca1 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java @@ -0,0 +1,212 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DelegatedKey; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +/** + * Represents cryptographic materials used to manage unique record-level keys. + * This class specifically implements Envelope Encryption where a unique content + * key is randomly generated each time this class is constructed which is then + * encrypted with the Wrapping Key and then persisted in the Description. If a + * wrapped key is present in the Description, then that content key is unwrapped + * and used to decrypt the actual data in the record. + * + * Other possibly implementations might use a Key-Derivation Function to derive + * a unique key per record. + * + * @author Greg Rubin + */ +public class WrappedRawMaterials extends AbstractRawMaterials { + /** + * The key-name in the Description which contains the algorithm use to wrap + * content key. Example values are "AESWrap", or + * "RSA/ECB/OAEPWithSHA-256AndMGF1Padding". + */ + public static final String KEY_WRAPPING_ALGORITHM = "amzn-ddb-wrap-alg"; + /** + * The key-name in the Description which contains the algorithm used by the + * content key. Example values are "AES", or "Blowfish". + */ + public static final String CONTENT_KEY_ALGORITHM = "amzn-ddb-env-alg"; + /** + * The key-name in the Description which which contains the wrapped content + * key. + */ + public static final String ENVELOPE_KEY = "amzn-ddb-env-key"; + + private static final String DEFAULT_ALGORITHM = "AES/256"; + + protected final Key wrappingKey; + protected final Key unwrappingKey; + private final SecretKey envelopeKey; + + public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, KeyPair signingPair) + throws GeneralSecurityException { + this(wrappingKey, unwrappingKey, signingPair, Collections.emptyMap()); + } + + public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, KeyPair signingPair, + Map description) throws GeneralSecurityException { + super(signingPair, description); + this.wrappingKey = wrappingKey; + this.unwrappingKey = unwrappingKey; + this.envelopeKey = initEnvelopeKey(); + } + + public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, SecretKey macKey) + throws GeneralSecurityException { + this(wrappingKey, unwrappingKey, macKey, Collections.emptyMap()); + } + + public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, SecretKey macKey, + Map description) throws GeneralSecurityException { + super(macKey, description); + this.wrappingKey = wrappingKey; + this.unwrappingKey = unwrappingKey; + this.envelopeKey = initEnvelopeKey(); + } + + @Override + public SecretKey getDecryptionKey() { + return envelopeKey; + } + + @Override + public SecretKey getEncryptionKey() { + return envelopeKey; + } + + /** + * Called by the constructors. If there is already a key associated with + * this record (usually signified by a value stored in the description in + * the key {@link #ENVELOPE_KEY}) it extracts it and returns it. Otherwise + * it generates a new key, stores a wrapped version in the Description, and + * returns the key to the caller. + * + * @return the content key (which is returned by both + * {@link #getDecryptionKey()} and {@link #getEncryptionKey()}. + * @throws GeneralSecurityException if there is a problem + */ + protected SecretKey initEnvelopeKey() throws GeneralSecurityException { + Map description = getMaterialDescription(); + if (description.containsKey(ENVELOPE_KEY)) { + if (unwrappingKey == null) { + throw new IllegalStateException("No private decryption key provided."); + } + byte[] encryptedKey = Base64.decode(description.get(ENVELOPE_KEY)); + String wrappingAlgorithm = unwrappingKey.getAlgorithm(); + if (description.containsKey(KEY_WRAPPING_ALGORITHM)) { + wrappingAlgorithm = description.get(KEY_WRAPPING_ALGORITHM); + } + return unwrapKey(description, encryptedKey, wrappingAlgorithm); + } else { + SecretKey key = description.containsKey(CONTENT_KEY_ALGORITHM) ? + generateContentKey(description.get(CONTENT_KEY_ALGORITHM)) : + generateContentKey(DEFAULT_ALGORITHM); + + String wrappingAlg = description.containsKey(KEY_WRAPPING_ALGORITHM) ? + description.get(KEY_WRAPPING_ALGORITHM) : + getTransformation(wrappingKey.getAlgorithm()); + byte[] encryptedKey = wrapKey(key, wrappingAlg); + description.put(ENVELOPE_KEY, Base64.encodeToString(encryptedKey)); + description.put(CONTENT_KEY_ALGORITHM, key.getAlgorithm()); + description.put(KEY_WRAPPING_ALGORITHM, wrappingAlg); + setMaterialDescription(description); + return key; + } + } + + public byte[] wrapKey(SecretKey key, String wrappingAlg) throws NoSuchAlgorithmException, NoSuchPaddingException, + InvalidKeyException, IllegalBlockSizeException { + if (wrappingKey instanceof DelegatedKey) { + return ((DelegatedKey)wrappingKey).wrap(key, null, wrappingAlg); + } else { + Cipher cipher = Cipher.getInstance(wrappingAlg); + cipher.init(Cipher.WRAP_MODE, wrappingKey, Utils.getRng()); + return cipher.wrap(key); + } + } + + protected SecretKey unwrapKey( + Map description, byte[] encryptedKey, String wrappingAlgorithm) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { + if (unwrappingKey instanceof DelegatedKey) { + return (SecretKey) + ((DelegatedKey) unwrappingKey) + .unwrap( + encryptedKey, + description.get(CONTENT_KEY_ALGORITHM), + Cipher.SECRET_KEY, + null, + wrappingAlgorithm); + } else { + Cipher cipher = Cipher.getInstance(wrappingAlgorithm); + + // This can be of the form "AES/256" as well as "AES" e.g., + // but we want to set the SecretKey with just "AES" in either case + String[] algPieces = description.get(CONTENT_KEY_ALGORITHM).split("/", 2); + String contentKeyAlgorithm = algPieces[0]; + + cipher.init(Cipher.UNWRAP_MODE, unwrappingKey, Utils.getRng()); + return (SecretKey) cipher.unwrap(encryptedKey, contentKeyAlgorithm, Cipher.SECRET_KEY); + } + } + + protected SecretKey generateContentKey(final String algorithm) throws NoSuchAlgorithmException { + String[] pieces = algorithm.split("/", 2); + KeyGenerator kg = KeyGenerator.getInstance(pieces[0]); + int keyLen = 0; + if (pieces.length == 2) { + try { + keyLen = Integer.parseInt(pieces[1]); + } catch (NumberFormatException ignored) { + } + } + + if (keyLen > 0) { + kg.init(keyLen, Utils.getRng()); + } else { + kg.init(Utils.getRng()); + } + return kg.generateKey(); + } + + private static String getTransformation(final String algorithm) { + if (algorithm.equalsIgnoreCase("RSA")) { + return "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; + } else if (algorithm.equalsIgnoreCase("AES")) { + return "AESWrap"; + } else { + return algorithm; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java new file mode 100644 index 0000000000..b49e2b9a20 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import java.security.KeyPair; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; + +/** + * This is a thin wrapper around the {@link WrappedMaterialsProvider}, using + * the provided encryptionKey for wrapping and unwrapping the + * record key. Please see that class for detailed documentation. + * + * @author Greg Rubin + */ +public class AsymmetricStaticProvider extends WrappedMaterialsProvider { + public AsymmetricStaticProvider(KeyPair encryptionKey, KeyPair signingPair) { + this(encryptionKey, signingPair, Collections.emptyMap()); + } + + public AsymmetricStaticProvider(KeyPair encryptionKey, SecretKey macKey) { + this(encryptionKey, macKey, Collections.emptyMap()); + } + + public AsymmetricStaticProvider(KeyPair encryptionKey, KeyPair signingPair, Map description) { + super(encryptionKey.getPublic(), encryptionKey.getPrivate(), signingPair, description); + } + + public AsymmetricStaticProvider(KeyPair encryptionKey, SecretKey macKey, Map description) { + super(encryptionKey.getPublic(), encryptionKey.getPrivate(), macKey, description); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java new file mode 100644 index 0000000000..653e754c26 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java @@ -0,0 +1,183 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import io.netty.util.internal.ObjectUtil; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.TTLCache; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.TTLCache.EntryLoader; +import java.util.concurrent.TimeUnit; + +/** + * This meta-Provider encrypts data with the most recent version of keying materials from a {@link + * ProviderStore} and decrypts using whichever version is appropriate. It also caches the results + * from the {@link ProviderStore} to avoid excessive load on the backing systems. + */ +public class CachingMostRecentProvider implements EncryptionMaterialsProvider { + private static final long INITIAL_VERSION = 0; + private static final String PROVIDER_CACHE_KEY_DELIM = "#"; + private static final int DEFAULT_CACHE_MAX_SIZE = 1000; + + private final long ttlInNanos; + private final ProviderStore keystore; + protected final String defaultMaterialName; + private final TTLCache providerCache; + private final TTLCache versionCache; + + private final EntryLoader versionLoader = + new EntryLoader() { + @Override + public Long load(String entryKey) { + return keystore.getMaxVersion(entryKey); + } + }; + private final EntryLoader providerLoader = + new EntryLoader() { + @Override + public EncryptionMaterialsProvider load(String entryKey) { + final String[] parts = entryKey.split(PROVIDER_CACHE_KEY_DELIM, 2); + if (parts.length != 2) { + throw new IllegalStateException("Invalid cache key for provider cache: " + entryKey); + } + return keystore.getProvider(parts[0], Long.parseLong(parts[1])); + } + }; + + /** + * Creates a new {@link CachingMostRecentProvider}. + * + * @param keystore The key store that this provider will use to determine which material and which + * version of material to use + * @param materialName The name of the materials associated with this provider + * @param ttlInMillis The length of time in milliseconds to cache the most recent provider + */ + public CachingMostRecentProvider( + final ProviderStore keystore, final String materialName, final long ttlInMillis) { + this(keystore, materialName, ttlInMillis, DEFAULT_CACHE_MAX_SIZE); + } + + /** + * Creates a new {@link CachingMostRecentProvider}. + * + * @param keystore The key store that this provider will use to determine which material and which + * version of material to use + * @param materialName The name of the materials associated with this provider + * @param ttlInMillis The length of time in milliseconds to cache the most recent provider + * @param maxCacheSize The maximum size of the underlying caches this provider uses. Entries will + * be evicted from the cache once this size is exceeded. + */ + public CachingMostRecentProvider( + final ProviderStore keystore, + final String materialName, + final long ttlInMillis, + final int maxCacheSize) { + this.keystore = ObjectUtil.checkNotNull(keystore, "keystore must not be null"); + this.defaultMaterialName = materialName; + this.ttlInNanos = TimeUnit.MILLISECONDS.toNanos(ttlInMillis); + + this.providerCache = new TTLCache<>(maxCacheSize, ttlInMillis, providerLoader); + this.versionCache = new TTLCache<>(maxCacheSize, ttlInMillis, versionLoader); + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + final long version = + keystore.getVersionFromMaterialDescription(context.getMaterialDescription()); + final String materialName = getMaterialName(context); + final String cacheKey = buildCacheKey(materialName, version); + + EncryptionMaterialsProvider provider = providerCache.load(cacheKey); + return provider.getDecryptionMaterials(context); + } + + + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + final String materialName = getMaterialName(context); + final long currentVersion = versionCache.load(materialName); + + if (currentVersion < 0) { + // The material hasn't been created yet, so specify a loading function + // to create the first version of materials and update both caches. + // We want this to be done as part of the cache load to ensure that this logic + // only happens once in a multithreaded environment, + // in order to limit calls to the keystore's dependencies. + final String cacheKey = buildCacheKey(materialName, INITIAL_VERSION); + EncryptionMaterialsProvider newProvider = + providerCache.load( + cacheKey, + s -> { + // Create the new material in the keystore + final String[] parts = s.split(PROVIDER_CACHE_KEY_DELIM, 2); + if (parts.length != 2) { + throw new IllegalStateException("Invalid cache key for provider cache: " + s); + } + EncryptionMaterialsProvider provider = + keystore.getOrCreate(parts[0], Long.parseLong(parts[1])); + + // We now should have version 0 in our keystore. + // Update the version cache for this material as a side effect + versionCache.put(materialName, INITIAL_VERSION); + + // Return the new materials to be put into the cache + return provider; + }); + + return newProvider.getEncryptionMaterials(context); + } else { + final String cacheKey = buildCacheKey(materialName, currentVersion); + return providerCache.load(cacheKey).getEncryptionMaterials(context); + } + } + + @Override + public void refresh() { + versionCache.clear(); + providerCache.clear(); + } + + public String getMaterialName() { + return defaultMaterialName; + } + + public long getTtlInMills() { + return TimeUnit.NANOSECONDS.toMillis(ttlInNanos); + } + + /** + * The current version of the materials being used for encryption. Returns -1 if we do not + * currently have a current version. + */ + public long getCurrentVersion() { + return versionCache.load(getMaterialName()); + } + + /** + * The last time the current version was updated. Returns 0 if we do not currently have a current + * version. + */ + public long getLastUpdated() { + // We cache a version of -1 to mean that there is not a current version + if (versionCache.load(getMaterialName()) < 0) { + return 0; + } + // Otherwise, return the last update time of that entry + return TimeUnit.NANOSECONDS.toMillis(versionCache.getLastUpdated(getMaterialName())); + } + + protected String getMaterialName(final EncryptionContext context) { + return defaultMaterialName; + } + + private static String buildCacheKey(final String materialName, final long version) { + StringBuilder result = new StringBuilder(materialName); + result.append(PROVIDER_CACHE_KEY_DELIM); + result.append(version); + return result.toString(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java new file mode 100644 index 0000000000..425a4119f2 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java @@ -0,0 +1,296 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.CONTENT_KEY_ALGORITHM; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.ENVELOPE_KEY; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.KEY_WRAPPING_ALGORITHM; + +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Hkdf; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.DecryptRequest; +import software.amazon.awssdk.services.kms.model.DecryptResponse; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; + +/** + * Generates a unique data key for each record in DynamoDB and protects that key + * using {@link KmsClient}. Currently, the HashKey, RangeKey, and TableName will be + * included in the KMS EncryptionContext for wrapping/unwrapping the key. This + * means that records cannot be copied/moved between tables without re-encryption. + * + * @see KMS Encryption Context + */ +public class DirectKmsMaterialsProvider implements EncryptionMaterialsProvider { + private static final String COVERED_ATTR_CTX_KEY = "aws-kms-ec-attr"; + private static final String SIGNING_KEY_ALGORITHM = "amzn-ddb-sig-alg"; + private static final String TABLE_NAME_EC_KEY = "*aws-kms-table*"; + + private static final String DEFAULT_ENC_ALG = "AES/256"; + private static final String DEFAULT_SIG_ALG = "HmacSHA256/256"; + private static final String KEY_COVERAGE = "*keys*"; + private static final String KDF_ALG = "HmacSHA256"; + private static final String KDF_SIG_INFO = "Signing"; + private static final String KDF_ENC_INFO = "Encryption"; + + private final KmsClient kms; + private final String encryptionKeyId; + private final Map description; + private final String dataKeyAlg; + private final int dataKeyLength; + private final String dataKeyDesc; + private final String sigKeyAlg; + private final int sigKeyLength; + private final String sigKeyDesc; + + public DirectKmsMaterialsProvider(KmsClient kms) { + this(kms, null); + } + + public DirectKmsMaterialsProvider(KmsClient kms, String encryptionKeyId, Map materialDescription) { + this.kms = kms; + this.encryptionKeyId = encryptionKeyId; + this.description = materialDescription != null ? + Collections.unmodifiableMap(new HashMap<>(materialDescription)) : + Collections.emptyMap(); + + dataKeyDesc = description.getOrDefault(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, DEFAULT_ENC_ALG); + + String[] parts = dataKeyDesc.split("/", 2); + this.dataKeyAlg = parts[0]; + this.dataKeyLength = parts.length == 2 ? Integer.parseInt(parts[1]) : 256; + + sigKeyDesc = description.getOrDefault(SIGNING_KEY_ALGORITHM, DEFAULT_SIG_ALG); + + parts = sigKeyDesc.split("/", 2); + this.sigKeyAlg = parts[0]; + this.sigKeyLength = parts.length == 2 ? Integer.parseInt(parts[1]) : 256; + } + + public DirectKmsMaterialsProvider(KmsClient kms, String encryptionKeyId) { + this(kms, encryptionKeyId, Collections.emptyMap()); + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + final Map materialDescription = context.getMaterialDescription(); + + final Map ec = new HashMap<>(); + final String providedEncAlg = materialDescription.get(CONTENT_KEY_ALGORITHM); + final String providedSigAlg = materialDescription.get(SIGNING_KEY_ALGORITHM); + + ec.put("*" + CONTENT_KEY_ALGORITHM + "*", providedEncAlg); + ec.put("*" + SIGNING_KEY_ALGORITHM + "*", providedSigAlg); + + populateKmsEcFromEc(context, ec); + + DecryptRequest.Builder request = DecryptRequest.builder(); + request.ciphertextBlob(SdkBytes.fromByteArray(Base64.decode(materialDescription.get(ENVELOPE_KEY)))); + request.encryptionContext(ec); + final DecryptResponse decryptResponse = decrypt(request.build(), context); + validateEncryptionKeyId(decryptResponse.keyId(), context); + + final Hkdf kdf; + try { + kdf = Hkdf.getInstance(KDF_ALG); + } catch (NoSuchAlgorithmException e) { + throw new DynamoDbEncryptionException(e); + } + kdf.init(decryptResponse.plaintext().asByteArray()); + + final String[] encAlgParts = providedEncAlg.split("/", 2); + int encLength = encAlgParts.length == 2 ? Integer.parseInt(encAlgParts[1]) : 256; + final String[] sigAlgParts = providedSigAlg.split("/", 2); + int sigLength = sigAlgParts.length == 2 ? Integer.parseInt(sigAlgParts[1]) : 256; + + final SecretKey encryptionKey = new SecretKeySpec(kdf.deriveKey(KDF_ENC_INFO, encLength / 8), encAlgParts[0]); + final SecretKey macKey = new SecretKeySpec(kdf.deriveKey(KDF_SIG_INFO, sigLength / 8), sigAlgParts[0]); + + return new SymmetricRawMaterials(encryptionKey, macKey, materialDescription); + } + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + final Map ec = new HashMap<>(); + ec.put("*" + CONTENT_KEY_ALGORITHM + "*", dataKeyDesc); + ec.put("*" + SIGNING_KEY_ALGORITHM + "*", sigKeyDesc); + populateKmsEcFromEc(context, ec); + + final String keyId = selectEncryptionKeyId(context); + if (keyId == null || keyId.isEmpty()) { + throw new DynamoDbEncryptionException("Encryption key id is empty."); + } + + final GenerateDataKeyRequest.Builder req = GenerateDataKeyRequest.builder(); + req.keyId(keyId); + // NumberOfBytes parameter is used because we're not using this key as an AES-256 key, + // we're using it as an HKDF-SHA256 key. + req.numberOfBytes(256 / 8); + req.encryptionContext(ec); + + final GenerateDataKeyResponse dataKeyResult = generateDataKey(req.build(), context); + + final Map materialDescription = new HashMap<>(description); + materialDescription.put(COVERED_ATTR_CTX_KEY, KEY_COVERAGE); + materialDescription.put(KEY_WRAPPING_ALGORITHM, "kms"); + materialDescription.put(CONTENT_KEY_ALGORITHM, dataKeyDesc); + materialDescription.put(SIGNING_KEY_ALGORITHM, sigKeyDesc); + materialDescription.put(ENVELOPE_KEY, + Base64.encodeToString(dataKeyResult.ciphertextBlob().asByteArray())); + + final Hkdf kdf; + try { + kdf = Hkdf.getInstance(KDF_ALG); + } catch (NoSuchAlgorithmException e) { + throw new DynamoDbEncryptionException(e); + } + + kdf.init(dataKeyResult.plaintext().asByteArray()); + + final SecretKey encryptionKey = new SecretKeySpec(kdf.deriveKey(KDF_ENC_INFO, dataKeyLength / 8), dataKeyAlg); + final SecretKey signatureKey = new SecretKeySpec(kdf.deriveKey(KDF_SIG_INFO, sigKeyLength / 8), sigKeyAlg); + return new SymmetricRawMaterials(encryptionKey, signatureKey, materialDescription); + } + + /** + * Get encryption key id that is used to create the {@link EncryptionMaterials}. + * + * @return encryption key id. + */ + protected String getEncryptionKeyId() { + return this.encryptionKeyId; + } + + /** + * Select encryption key id to be used to generate data key. The default implementation of this method returns + * {@link DirectKmsMaterialsProvider#encryptionKeyId}. + * + * @param context encryption context. + * @return the encryptionKeyId. + * @throws DynamoDbEncryptionException when we fails to select a valid encryption key id. + */ + protected String selectEncryptionKeyId(EncryptionContext context) throws DynamoDbEncryptionException { + return getEncryptionKeyId(); + } + + /** + * Validate the encryption key id. The default implementation of this method does not validate + * encryption key id. + * + * @param encryptionKeyId encryption key id from {@link DecryptResponse}. + * @param context encryption context. + * @throws DynamoDbEncryptionException when encryptionKeyId is invalid. + */ + protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext context) + throws DynamoDbEncryptionException { + // No action taken. + } + + /** + * Decrypts ciphertext. The default implementation calls KMS to decrypt the ciphertext using the parameters + * provided in the {@link DecryptRequest}. Subclass can override the default implementation to provide + * additional request parameters using attributes within the {@link EncryptionContext}. + * + * @param request request parameters to decrypt the given ciphertext. + * @param context additional useful data to decrypt the ciphertext. + * @return the decrypted plaintext for the given ciphertext. + */ + protected DecryptResponse decrypt(final DecryptRequest request, final EncryptionContext context) { + return kms.decrypt(request); + } + + /** + * Returns a data encryption key that you can use in your application to encrypt data locally. The default + * implementation calls KMS to generate the data key using the parameters provided in the + * {@link GenerateDataKeyRequest}. Subclass can override the default implementation to provide additional + * request parameters using attributes within the {@link EncryptionContext}. + * + * @param request request parameters to generate the data key. + * @param context additional useful data to generate the data key. + * @return the newly generated data key which includes both the plaintext and ciphertext. + */ + protected GenerateDataKeyResponse generateDataKey(final GenerateDataKeyRequest request, + final EncryptionContext context) { + return kms.generateDataKey(request); + } + + /** + * Extracts relevant information from {@code context} and uses it to populate fields in + * {@code kmsEc}. Currently, these fields are: + *
+ *
{@code HashKeyName}
+ *
{@code HashKeyValue}
+ *
{@code RangeKeyName}
+ *
{@code RangeKeyValue}
+ *
{@link #TABLE_NAME_EC_KEY}
+ *
{@code TableName}
+ */ + private static void populateKmsEcFromEc(EncryptionContext context, Map kmsEc) { + final String hashKeyName = context.getHashKeyName(); + if (hashKeyName != null) { + final AttributeValue hashKey = context.getAttributeValues().get(hashKeyName); + if (hashKey.n() != null) { + kmsEc.put(hashKeyName, hashKey.n()); + } else if (hashKey.s() != null) { + kmsEc.put(hashKeyName, hashKey.s()); + } else if (hashKey.b() != null) { + kmsEc.put(hashKeyName, Base64.encodeToString(hashKey.b().asByteArray())); + } else { + throw new UnsupportedOperationException("DirectKmsMaterialsProvider only supports String, Number, and Binary HashKeys"); + } + } + final String rangeKeyName = context.getRangeKeyName(); + if (rangeKeyName != null) { + final AttributeValue rangeKey = context.getAttributeValues().get(rangeKeyName); + if (rangeKey.n() != null) { + kmsEc.put(rangeKeyName, rangeKey.n()); + } else if (rangeKey.s() != null) { + kmsEc.put(rangeKeyName, rangeKey.s()); + } else if (rangeKey.b() != null) { + kmsEc.put(rangeKeyName, Base64.encodeToString(rangeKey.b().asByteArray())); + } else { + throw new UnsupportedOperationException("DirectKmsMaterialsProvider only supports String, Number, and Binary RangeKeys"); + } + } + + final String tableName = context.getTableName(); + if (tableName != null) { + kmsEc.put(TABLE_NAME_EC_KEY, tableName); + } + } + + @Override + public void refresh() { + // No action needed + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java new file mode 100644 index 0000000000..b60fee3ee0 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; + +/** + * Interface for providing encryption materials. + * Implementations are free to use any strategy for providing encryption + * materials, such as simply providing static material that doesn't change, + * or more complicated implementations, such as integrating with existing + * key management systems. + * + * @author Greg Rubin + */ +public interface EncryptionMaterialsProvider { + + /** + * Retrieves encryption materials matching the specified description from some source. + * + * @param context + * Information to assist in selecting a the proper return value. The implementation + * is free to determine the minimum necessary for successful processing. + * + * @return + * The encryption materials that match the description, or null if no matching encryption materials found. + */ + DecryptionMaterials getDecryptionMaterials(EncryptionContext context); + + /** + * Returns EncryptionMaterials which the caller can use for encryption. + * Each implementation of EncryptionMaterialsProvider can choose its own + * strategy for loading encryption material. For example, an + * implementation might load encryption material from an existing key + * management system, or load new encryption material when keys are + * rotated. + * + * @param context + * Information to assist in selecting a the proper return value. The implementation + * is free to determine the minimum necessary for successful processing. + * + * @return EncryptionMaterials which the caller can use to encrypt or + * decrypt data. + */ + EncryptionMaterials getEncryptionMaterials(EncryptionContext context); + + /** + * Forces this encryption materials provider to refresh its encryption + * material. For many implementations of encryption materials provider, + * this may simply be a no-op, such as any encryption materials provider + * implementation that vends static/non-changing encryption material. + * For other implementations that vend different encryption material + * throughout their lifetime, this method should force the encryption + * materials provider to refresh its encryption material. + */ + void refresh(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java new file mode 100644 index 0000000000..483b81b51a --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java @@ -0,0 +1,199 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStore.Entry; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.ProtectionParameter; +import java.security.KeyStore.SecretKeyEntry; +import java.security.KeyStore.TrustedCertificateEntry; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableEntryException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.AsymmetricRawMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; + +/** + * @author Greg Rubin + */ +public class KeyStoreMaterialsProvider implements EncryptionMaterialsProvider { + private final Map description; + private final String encryptionAlias; + private final String signingAlias; + private final ProtectionParameter encryptionProtection; + private final ProtectionParameter signingProtection; + private final KeyStore keyStore; + private final AtomicReference currMaterials = + new AtomicReference<>(); + + public KeyStoreMaterialsProvider(KeyStore keyStore, String encryptionAlias, String signingAlias, Map description) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { + this(keyStore, encryptionAlias, signingAlias, null, null, description); + } + + public KeyStoreMaterialsProvider(KeyStore keyStore, String encryptionAlias, String signingAlias, + ProtectionParameter encryptionProtection, ProtectionParameter signingProtection, + Map description) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { + super(); + this.keyStore = keyStore; + this.encryptionAlias = encryptionAlias; + this.signingAlias = signingAlias; + this.encryptionProtection = encryptionProtection; + this.signingProtection = signingProtection; + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + + validateKeys(); + loadKeys(); + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + CurrentMaterials materials = currMaterials.get(); + if (context.getMaterialDescription().entrySet().containsAll(description.entrySet())) { + if (materials.encryptionEntry instanceof SecretKeyEntry) { + return materials.symRawMaterials; + } else { + try { + return makeAsymMaterials(materials, context.getMaterialDescription()); + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to decrypt envelope key", ex); + } + } + } else { + return null; + } + } + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + CurrentMaterials materials = currMaterials.get(); + if (materials.encryptionEntry instanceof SecretKeyEntry) { + return materials.symRawMaterials; + } else { + try { + return makeAsymMaterials(materials, description); + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to encrypt envelope key", ex); + } + } + } + + private AsymmetricRawMaterials makeAsymMaterials(CurrentMaterials materials, + Map description) throws GeneralSecurityException { + KeyPair encryptionPair = entry2Pair(materials.encryptionEntry); + if (materials.signingEntry instanceof SecretKeyEntry) { + return new AsymmetricRawMaterials(encryptionPair, + ((SecretKeyEntry) materials.signingEntry).getSecretKey(), description); + } else { + return new AsymmetricRawMaterials(encryptionPair, entry2Pair(materials.signingEntry), + description); + } + } + + private static KeyPair entry2Pair(Entry entry) { + PublicKey pub = null; + PrivateKey priv = null; + + if (entry instanceof PrivateKeyEntry) { + PrivateKeyEntry pk = (PrivateKeyEntry) entry; + if (pk.getCertificate() != null) { + pub = pk.getCertificate().getPublicKey(); + } + priv = pk.getPrivateKey(); + } else if (entry instanceof TrustedCertificateEntry) { + TrustedCertificateEntry tc = (TrustedCertificateEntry) entry; + pub = tc.getTrustedCertificate().getPublicKey(); + } else { + throw new IllegalArgumentException( + "Only entry types PrivateKeyEntry and TrustedCertificateEntry are supported."); + } + return new KeyPair(pub, priv); + } + + /** + * Reloads the keys from the underlying keystore by calling + * {@link KeyStore#getEntry(String, ProtectionParameter)} again for each of them. + */ + @Override + public void refresh() { + try { + loadKeys(); + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to load keys from keystore", ex); + } + } + + private void validateKeys() throws KeyStoreException { + if (!keyStore.containsAlias(encryptionAlias)) { + throw new IllegalArgumentException("Keystore does not contain alias: " + + encryptionAlias); + } + if (!keyStore.containsAlias(signingAlias)) { + throw new IllegalArgumentException("Keystore does not contain alias: " + + signingAlias); + } + } + + private void loadKeys() throws NoSuchAlgorithmException, UnrecoverableEntryException, + KeyStoreException { + Entry encryptionEntry = keyStore.getEntry(encryptionAlias, encryptionProtection); + Entry signingEntry = keyStore.getEntry(signingAlias, signingProtection); + CurrentMaterials newMaterials = new CurrentMaterials(encryptionEntry, signingEntry); + currMaterials.set(newMaterials); + } + + private class CurrentMaterials { + public final Entry encryptionEntry; + public final Entry signingEntry; + public final SymmetricRawMaterials symRawMaterials; + + public CurrentMaterials(Entry encryptionEntry, Entry signingEntry) { + super(); + this.encryptionEntry = encryptionEntry; + this.signingEntry = signingEntry; + + if (encryptionEntry instanceof SecretKeyEntry) { + if (signingEntry instanceof SecretKeyEntry) { + this.symRawMaterials = new SymmetricRawMaterials( + ((SecretKeyEntry) encryptionEntry).getSecretKey(), + ((SecretKeyEntry) signingEntry).getSecretKey(), + description); + } else { + this.symRawMaterials = new SymmetricRawMaterials( + ((SecretKeyEntry) encryptionEntry).getSecretKey(), + entry2Pair(signingEntry), + description); + } + } else { + this.symRawMaterials = null; + } + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java new file mode 100644 index 0000000000..8a63a0328c --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java @@ -0,0 +1,130 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import java.security.KeyPair; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.CryptographicMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; + +/** + * A provider which always returns the same provided symmetric + * encryption/decryption key and the same signing/verification key(s). + * + * @author Greg Rubin + */ +public class SymmetricStaticProvider implements EncryptionMaterialsProvider { + private final SymmetricRawMaterials materials; + + /** + * @param encryptionKey + * the value to be returned by + * {@link #getEncryptionMaterials(EncryptionContext)} and + * {@link #getDecryptionMaterials(EncryptionContext)} + * @param signingPair + * the keypair used to sign/verify the data stored in Dynamo. If + * only the public key is provided, then this provider may be + * used for decryption, but not encryption. + */ + public SymmetricStaticProvider(SecretKey encryptionKey, KeyPair signingPair) { + this(encryptionKey, signingPair, Collections.emptyMap()); + } + + /** + * @param encryptionKey + * the value to be returned by + * {@link #getEncryptionMaterials(EncryptionContext)} and + * {@link #getDecryptionMaterials(EncryptionContext)} + * @param signingPair + * the keypair used to sign/verify the data stored in Dynamo. If + * only the public key is provided, then this provider may be + * used for decryption, but not encryption. + * @param description + * the value to be returned by + * {@link CryptographicMaterials#getMaterialDescription()} for + * any {@link CryptographicMaterials} returned by this object. + */ + public SymmetricStaticProvider(SecretKey encryptionKey, + KeyPair signingPair, Map description) { + materials = new SymmetricRawMaterials(encryptionKey, signingPair, + description); + } + + /** + * @param encryptionKey + * the value to be returned by + * {@link #getEncryptionMaterials(EncryptionContext)} and + * {@link #getDecryptionMaterials(EncryptionContext)} + * @param macKey + * the key used to sign/verify the data stored in Dynamo. + */ + public SymmetricStaticProvider(SecretKey encryptionKey, SecretKey macKey) { + this(encryptionKey, macKey, Collections.emptyMap()); + } + + /** + * @param encryptionKey + * the value to be returned by + * {@link #getEncryptionMaterials(EncryptionContext)} and + * {@link #getDecryptionMaterials(EncryptionContext)} + * @param macKey + * the key used to sign/verify the data stored in Dynamo. + * @param description + * the value to be returned by + * {@link CryptographicMaterials#getMaterialDescription()} for + * any {@link CryptographicMaterials} returned by this object. + */ + public SymmetricStaticProvider(SecretKey encryptionKey, SecretKey macKey, Map description) { + materials = new SymmetricRawMaterials(encryptionKey, macKey, description); + } + + /** + * Returns the encryptionKey provided to the constructor if and only if + * materialDescription is a super-set (may be equal) to the + * description provided to the constructor. + */ + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + if (context.getMaterialDescription().entrySet().containsAll(materials.getMaterialDescription().entrySet())) { + return materials; + } + else { + return null; + } + } + + /** + * Returns the encryptionKey provided to the constructor. + */ + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + return materials; + } + + /** + * Does nothing. + */ + @Override + public void refresh() { + // Do Nothing + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java new file mode 100644 index 0000000000..1c92fb3f4a --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java @@ -0,0 +1,163 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyPair; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.SecretKey; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.CryptographicMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; + +/** + * This provider will use create a unique (random) symmetric key upon each call to + * {@link #getEncryptionMaterials(EncryptionContext)}. Practically, this means each record in DynamoDB will be + * encrypted under a unique record key. A wrapped/encrypted copy of this record key is stored in the + * MaterialsDescription field of that record and is unwrapped/decrypted upon reading that record. + * + * This is generally a more secure way of encrypting data than with the + * {@link SymmetricStaticProvider}. + * + * @see WrappedRawMaterials + * + * @author Greg Rubin + */ +public class WrappedMaterialsProvider implements EncryptionMaterialsProvider { + private final Key wrappingKey; + private final Key unwrappingKey; + private final KeyPair sigPair; + private final SecretKey macKey; + private final Map description; + + /** + * @param wrappingKey + * The key used to wrap/encrypt the symmetric record key. (May be the same as the + * unwrappingKey.) + * @param unwrappingKey + * The key used to unwrap/decrypt the symmetric record key. (May be the same as the + * wrappingKey.) If null, then this provider may only be used for + * decryption, but not encryption. + * @param signingPair + * the keypair used to sign/verify the data stored in Dynamo. If only the public key + * is provided, then this provider may only be used for decryption, but not + * encryption. + */ + public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, KeyPair signingPair) { + this(wrappingKey, unwrappingKey, signingPair, Collections.emptyMap()); + } + + /** + * @param wrappingKey + * The key used to wrap/encrypt the symmetric record key. (May be the same as the + * unwrappingKey.) + * @param unwrappingKey + * The key used to unwrap/decrypt the symmetric record key. (May be the same as the + * wrappingKey.) If null, then this provider may only be used for + * decryption, but not encryption. + * @param signingPair + * the keypair used to sign/verify the data stored in Dynamo. If only the public key + * is provided, then this provider may only be used for decryption, but not + * encryption. + * @param description + * description the value to be returned by + * {@link CryptographicMaterials#getMaterialDescription()} for any + * {@link CryptographicMaterials} returned by this object. + */ + public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, KeyPair signingPair, Map description) { + this.wrappingKey = wrappingKey; + this.unwrappingKey = unwrappingKey; + this.sigPair = signingPair; + this.macKey = null; + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + } + + /** + * @param wrappingKey + * The key used to wrap/encrypt the symmetric record key. (May be the same as the + * unwrappingKey.) + * @param unwrappingKey + * The key used to unwrap/decrypt the symmetric record key. (May be the same as the + * wrappingKey.) If null, then this provider may only be used for + * decryption, but not encryption. + * @param macKey + * the key used to sign/verify the data stored in Dynamo. + */ + public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, SecretKey macKey) { + this(wrappingKey, unwrappingKey, macKey, Collections.emptyMap()); + } + + /** + * @param wrappingKey + * The key used to wrap/encrypt the symmetric record key. (May be the same as the + * unwrappingKey.) + * @param unwrappingKey + * The key used to unwrap/decrypt the symmetric record key. (May be the same as the + * wrappingKey.) If null, then this provider may only be used for + * decryption, but not encryption. + * @param macKey + * the key used to sign/verify the data stored in Dynamo. + * @param description + * description the value to be returned by + * {@link CryptographicMaterials#getMaterialDescription()} for any + * {@link CryptographicMaterials} returned by this object. + */ + public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, SecretKey macKey, Map description) { + this.wrappingKey = wrappingKey; + this.unwrappingKey = unwrappingKey; + this.sigPair = null; + this.macKey = macKey; + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + try { + if (macKey != null) { + return new WrappedRawMaterials(wrappingKey, unwrappingKey, macKey, context.getMaterialDescription()); + } else { + return new WrappedRawMaterials(wrappingKey, unwrappingKey, sigPair, context.getMaterialDescription()); + } + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to decrypt envelope key", ex); + } + } + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + try { + if (macKey != null) { + return new WrappedRawMaterials(wrappingKey, unwrappingKey, macKey, description); + } else { + return new WrappedRawMaterials(wrappingKey, unwrappingKey, sigPair, description); + } + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to encrypt envelope key", ex); + } + } + + @Override + public void refresh() { + // Do nothing + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java new file mode 100644 index 0000000000..c0fbe5e06f --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java @@ -0,0 +1,434 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; + +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ComparisonOperator; +import software.amazon.awssdk.services.dynamodb.model.Condition; +import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse; +import software.amazon.awssdk.services.dynamodb.model.ExpectedAttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + + +/** + * Provides a simple collection of EncryptionMaterialProviders backed by an encrypted DynamoDB + * table. This can be used to build key hierarchies or meta providers. + * + * Currently, this only supports AES-256 in AESWrap mode and HmacSHA256 for the providers persisted + * in the table. + * + * @author rubin + */ +public class MetaStore extends ProviderStore { + private static final String INTEGRITY_ALGORITHM_FIELD = "intAlg"; + private static final String INTEGRITY_KEY_FIELD = "int"; + private static final String ENCRYPTION_ALGORITHM_FIELD = "encAlg"; + private static final String ENCRYPTION_KEY_FIELD = "enc"; + private static final Pattern COMBINED_PATTERN = Pattern.compile("([^#]+)#(\\d*)"); + private static final String DEFAULT_INTEGRITY = "HmacSHA256"; + private static final String DEFAULT_ENCRYPTION = "AES"; + private static final String MATERIAL_TYPE_VERSION = "t"; + private static final String META_ID = "amzn-ddb-meta-id"; + + private static final String DEFAULT_HASH_KEY = "N"; + private static final String DEFAULT_RANGE_KEY = "V"; + + /** Default no-op implementation of {@link ExtraDataSupplier}. */ + private static final EmptyExtraDataSupplier EMPTY_EXTRA_DATA_SUPPLIER + = new EmptyExtraDataSupplier(); + + /** DDB fields that must be encrypted. */ + private static final Set ENCRYPTED_FIELDS; + static { + final Set tempEncryptedFields = new HashSet<>(); + tempEncryptedFields.add(MATERIAL_TYPE_VERSION); + tempEncryptedFields.add(ENCRYPTION_KEY_FIELD); + tempEncryptedFields.add(ENCRYPTION_ALGORITHM_FIELD); + tempEncryptedFields.add(INTEGRITY_KEY_FIELD); + tempEncryptedFields.add(INTEGRITY_ALGORITHM_FIELD); + ENCRYPTED_FIELDS = tempEncryptedFields; + } + + private final Map doesNotExist; + private final Set doNotEncrypt; +// private final DynamoDbEncryptionConfiguration encryptionConfiguration; + private final String tableName; + private final DynamoDbClient ddb; + private final DynamoDbEncryptor encryptor; + private final EncryptionContext ddbCtx; + private final ExtraDataSupplier extraDataSupplier; + + /** + * Provides extra data that should be persisted along with the standard material data. + */ + public interface ExtraDataSupplier { + + /** + * Gets the extra data attributes for the specified material name. + * + * @param materialName material name. + * @param version version number. + * @return plain text of the extra data. + */ + Map getAttributes(final String materialName, final long version); + + /** + * Gets the extra data field names that should be signed only but not encrypted. + * + * @return signed only fields. + */ + Set getSignedOnlyFieldNames(); + } + + /** + * Create a new MetaStore with specified table name. + * + * @param ddb Interface for accessing DynamoDB. + * @param tableName DynamoDB table name for this {@link MetaStore}. + * @param encryptor used to perform crypto operations on the record attributes. + */ + public MetaStore(final DynamoDbClient ddb, final String tableName, + final DynamoDbEncryptor encryptor) { + this(ddb, tableName, encryptor, EMPTY_EXTRA_DATA_SUPPLIER); + } + + /** + * Create a new MetaStore with specified table name and extra data supplier. + * + * @param ddb Interface for accessing DynamoDB. + * @param tableName DynamoDB table name for this {@link MetaStore}. + * @param encryptor used to perform crypto operations on the record attributes + * @param extraDataSupplier provides extra data that should be stored along with the material. + */ + public MetaStore(final DynamoDbClient ddb, final String tableName, + final DynamoDbEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { + this.ddb = checkNotNull(ddb, "ddb must not be null"); + this.tableName = checkNotNull(tableName, "tableName must not be null"); + this.encryptor = checkNotNull(encryptor, "encryptor must not be null"); + this.extraDataSupplier = checkNotNull(extraDataSupplier, "extraDataSupplier must not be null"); + this.ddbCtx = + new EncryptionContext.Builder() + .tableName(this.tableName) + .hashKeyName(DEFAULT_HASH_KEY) + .rangeKeyName(DEFAULT_RANGE_KEY) + .build(); + + final Map tmpExpected = new HashMap<>(); + tmpExpected.put(DEFAULT_HASH_KEY, ExpectedAttributeValue.builder().exists(false).build()); + tmpExpected.put(DEFAULT_RANGE_KEY, ExpectedAttributeValue.builder().exists(false).build()); + doesNotExist = Collections.unmodifiableMap(tmpExpected); + + this.doNotEncrypt = getSignedOnlyFields(extraDataSupplier); + } + + @Override + public EncryptionMaterialsProvider getProvider(final String materialName, final long version) { + final Map item = getMaterialItem(materialName, version); + return decryptProvider(item); + } + + @Override + public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) { + final Map plaintext = createMaterialItem(materialName, nextId); + final Map ciphertext = conditionalPut(getEncryptedText(plaintext)); + return decryptProvider(ciphertext); + } + + @Override + public long getMaxVersion(final String materialName) { + + final List> items = + ddb.query( + QueryRequest.builder() + .tableName(tableName) + .consistentRead(Boolean.TRUE) + .keyConditions( + Collections.singletonMap( + DEFAULT_HASH_KEY, + Condition.builder() + .comparisonOperator(ComparisonOperator.EQ) + .attributeValueList(AttributeValue.builder().s(materialName).build()) + .build())) + .limit(1) + .scanIndexForward(false) + .attributesToGet(DEFAULT_RANGE_KEY) + .build()) + .items(); + + if (items.isEmpty()) { + return -1L; + } else { + return Long.parseLong(items.get(0).get(DEFAULT_RANGE_KEY).n()); + } + } + + @Override + public long getVersionFromMaterialDescription(final Map description) { + final Matcher m = COMBINED_PATTERN.matcher(description.get(META_ID)); + if (m.matches()) { + return Long.parseLong(m.group(2)); + } else { + throw new IllegalArgumentException("No meta id found"); + } + } + + /** + * This API retrieves the intermediate keys from the source region and replicates it in the target region. + * + * @param materialName material name of the encryption material. + * @param version version of the encryption material. + * @param targetMetaStore target MetaStore where the encryption material to be stored. + */ + public void replicate(final String materialName, final long version, final MetaStore targetMetaStore) { + try { + final Map item = getMaterialItem(materialName, version); + + final Map plainText = getPlainText(item); + final Map encryptedText = targetMetaStore.getEncryptedText(plainText); + final PutItemRequest put = PutItemRequest.builder() + .tableName(targetMetaStore.tableName) + .item(encryptedText) + .expected(doesNotExist) + .build(); + targetMetaStore.ddb.putItem(put); + } catch (ConditionalCheckFailedException e) { + //Item already present. + } + } + + /** + * Creates a DynamoDB Table with the correct properties to be used with a ProviderStore. + * + * @param ddb interface for accessing DynamoDB + * @param tableName name of table that stores the meta data of the material. + * @param provisionedThroughput required provisioned throughput of the this table. + * @return result of create table request. + */ + public static CreateTableResponse createTable(final DynamoDbClient ddb, final String tableName, + final ProvisionedThroughput provisionedThroughput) { + return ddb.createTable( + CreateTableRequest.builder() + .tableName(tableName) + .attributeDefinitions(Arrays.asList( + AttributeDefinition.builder() + .attributeName(DEFAULT_HASH_KEY) + .attributeType(ScalarAttributeType.S) + .build(), + AttributeDefinition.builder() + .attributeName(DEFAULT_RANGE_KEY) + .attributeType(ScalarAttributeType.N).build())) + .keySchema(Arrays.asList( + KeySchemaElement.builder() + .attributeName(DEFAULT_HASH_KEY) + .keyType(KeyType.HASH) + .build(), + KeySchemaElement.builder() + .attributeName(DEFAULT_RANGE_KEY) + .keyType(KeyType.RANGE) + .build())) + .provisionedThroughput(provisionedThroughput).build()); + } + + private Map getMaterialItem(final String materialName, final long version) { + final Map ddbKey = new HashMap<>(); + ddbKey.put(DEFAULT_HASH_KEY, AttributeValue.builder().s(materialName).build()); + ddbKey.put(DEFAULT_RANGE_KEY, AttributeValue.builder().n(Long.toString(version)).build()); + final Map item = ddbGet(ddbKey); + if (item == null || item.isEmpty()) { + throw new IndexOutOfBoundsException("No material found: " + materialName + "#" + version); + } + return item; + } + + + /** + * Empty extra data supplier. This default class is intended to simplify the default + * implementation of {@link MetaStore}. + */ + private static class EmptyExtraDataSupplier implements ExtraDataSupplier { + @Override + public Map getAttributes(String materialName, long version) { + return Collections.emptyMap(); + } + + @Override + public Set getSignedOnlyFieldNames() { + return Collections.emptySet(); + } + } + + /** + * Get a set of fields that must be signed but not encrypted. + * + * @param extraDataSupplier extra data supplier that is used to return sign only field names. + * @return fields that must be signed. + */ + private static Set getSignedOnlyFields(final ExtraDataSupplier extraDataSupplier) { + final Set signedOnlyFields = extraDataSupplier.getSignedOnlyFieldNames(); + for (final String signedOnlyField : signedOnlyFields) { + if (ENCRYPTED_FIELDS.contains(signedOnlyField)) { + throw new IllegalArgumentException(signedOnlyField + " must be encrypted"); + } + } + + // fields that should not be encrypted + final Set doNotEncryptFields = new HashSet<>(); + doNotEncryptFields.add(DEFAULT_HASH_KEY); + doNotEncryptFields.add(DEFAULT_RANGE_KEY); + doNotEncryptFields.addAll(signedOnlyFields); + return Collections.unmodifiableSet(doNotEncryptFields); + } + + private Map conditionalPut(final Map item) { + try { + final PutItemRequest put = PutItemRequest.builder().tableName(tableName).item(item) + .expected(doesNotExist).build(); + ddb.putItem(put); + return item; + } catch (final ConditionalCheckFailedException ex) { + final Map ddbKey = new HashMap<>(); + ddbKey.put(DEFAULT_HASH_KEY, item.get(DEFAULT_HASH_KEY)); + ddbKey.put(DEFAULT_RANGE_KEY, item.get(DEFAULT_RANGE_KEY)); + return ddbGet(ddbKey); + } + } + + private Map ddbGet(final Map ddbKey) { + return ddb.getItem( + GetItemRequest.builder().tableName(tableName).consistentRead(true) + .key(ddbKey).build()).item(); + } + + /** + * Build an material item for a given material name and version with newly generated + * encryption and integrity keys. + * + * @param materialName material name. + * @param version version of the material. + * @return newly generated plaintext material item. + */ + private Map createMaterialItem(final String materialName, final long version) { + final SecretKeySpec encryptionKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_ENCRYPTION); + final SecretKeySpec integrityKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_INTEGRITY); + + final Map plaintext = new HashMap<>(); + plaintext.put(DEFAULT_HASH_KEY, AttributeValue.builder().s(materialName).build()); + plaintext.put(DEFAULT_RANGE_KEY, AttributeValue.builder().n(Long.toString(version)).build()); + plaintext.put(MATERIAL_TYPE_VERSION, AttributeValue.builder().s("0").build()); + plaintext.put(ENCRYPTION_KEY_FIELD, + AttributeValue.builder().b(SdkBytes.fromByteArray(encryptionKey.getEncoded())).build()); + plaintext.put(ENCRYPTION_ALGORITHM_FIELD, AttributeValue.builder().s(encryptionKey.getAlgorithm()).build()); + plaintext.put(INTEGRITY_KEY_FIELD, + AttributeValue.builder().b(SdkBytes.fromByteArray(integrityKey.getEncoded())).build()); + plaintext.put(INTEGRITY_ALGORITHM_FIELD, AttributeValue.builder().s(integrityKey.getAlgorithm()).build()); + plaintext.putAll(extraDataSupplier.getAttributes(materialName, version)); + + return plaintext; + } + + private EncryptionMaterialsProvider decryptProvider(final Map item) { + final Map plaintext = getPlainText(item); + + final String type = plaintext.get(MATERIAL_TYPE_VERSION).s(); + final SecretKey encryptionKey; + final SecretKey integrityKey; + // This switch statement is to make future extensibility easier and more obvious + switch (type) { + case "0": // Only currently supported type + encryptionKey = new SecretKeySpec(plaintext.get(ENCRYPTION_KEY_FIELD).b().asByteArray(), + plaintext.get(ENCRYPTION_ALGORITHM_FIELD).s()); + integrityKey = new SecretKeySpec(plaintext.get(INTEGRITY_KEY_FIELD).b().asByteArray(), plaintext + .get(INTEGRITY_ALGORITHM_FIELD).s()); + break; + default: + throw new IllegalStateException("Unsupported material type: " + type); + } + return new WrappedMaterialsProvider(encryptionKey, encryptionKey, integrityKey, + buildDescription(plaintext)); + } + + /** + * Decrypts attributes in the ciphertext item using {@link DynamoDbEncryptor}. except the + * attribute names specified in doNotEncrypt. + * + * @param ciphertext the ciphertext to be decrypted. + * @throws SdkClientException when failed to decrypt material item. + * @return decrypted item. + */ + private Map getPlainText(final Map ciphertext) { + try { + return encryptor.decryptAllFieldsExcept(ciphertext, ddbCtx, doNotEncrypt); + } catch (final GeneralSecurityException e) { + throw SdkClientException.create("Error retrieving PlainText", e); + } + } + + /** + * Encrypts attributes in the plaintext item using {@link DynamoDbEncryptor}. except the attribute + * names specified in doNotEncrypt. + * + * @throws SdkClientException when failed to encrypt material item. + * @param plaintext plaintext to be encrypted. + */ + private Map getEncryptedText(Map plaintext) { + try { + return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, doNotEncrypt); + } catch (final GeneralSecurityException e) { + throw SdkClientException.create("Error retrieving PlainText", e); + } + } + + private Map buildDescription(final Map plaintext) { + return Collections.singletonMap(META_ID, plaintext.get(DEFAULT_HASH_KEY).s() + "#" + + plaintext.get(DEFAULT_RANGE_KEY).n()); + } + + private static V checkNotNull(final V ref, final String errMsg) { + if (ref == null) { + throw new NullPointerException(errMsg); + } else { + return ref; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java new file mode 100644 index 0000000000..a29fe9b34d --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; + +import java.util.Map; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; + +/** + * Provides a standard way to retrieve and optionally create {@link EncryptionMaterialsProvider}s + * backed by some form of persistent storage. + * + * @author rubin + * + */ +public abstract class ProviderStore { + + /** + * Returns the most recent provider with the specified name. If there are no providers with this + * name, it will create one with version 0. + */ + public EncryptionMaterialsProvider getProvider(final String materialName) { + final long currVersion = getMaxVersion(materialName); + if (currVersion >= 0) { + return getProvider(materialName, currVersion); + } else { + return getOrCreate(materialName, 0); + } + } + + /** + * Returns the provider with the specified name and version. + * + * @throws IndexOutOfBoundsException + * if {@code version} is not a valid version + */ + public abstract EncryptionMaterialsProvider getProvider(final String materialName, final long version); + + /** + * Creates a new provider with a version one greater than the current max version. If multiple + * clients attempt to create a provider with this same version simultaneously, they will + * properly coordinate and the result will be that a single provider is created and that all + * ProviderStores return the same one. + */ + public EncryptionMaterialsProvider newProvider(final String materialName) { + final long nextId = getMaxVersion(materialName) + 1; + return getOrCreate(materialName, nextId); + } + + /** + * Returns the provider with the specified name and version and creates it if it doesn't exist. + * + * @throws UnsupportedOperationException + * if a new provider cannot be created + */ + public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) { + try { + return getProvider(materialName, nextId); + } catch (final IndexOutOfBoundsException ex) { + throw new UnsupportedOperationException("This ProviderStore does not support creation.", ex); + } + } + + /** + * Returns the maximum version number associated with {@code materialName}. If there are no + * versions, returns -1. + */ + public abstract long getMaxVersion(final String materialName); + + /** + * Extracts the material version from {@code description}. + */ + public abstract long getVersionFromMaterialDescription(final Map description); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java new file mode 100644 index 0000000000..d29bb818cb --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java @@ -0,0 +1,81 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; + +import java.util.Map; +import java.util.function.UnaryOperator; + +/** + * Implementations of common operators for overriding the EncryptionContext + */ +public class EncryptionContextOperators { + + // Prevent instantiation + private EncryptionContextOperators() { + } + + /** + * An operator for overriding EncryptionContext's table name for a specific DynamoDbEncryptor. If any table names or + * the encryption context itself is null, then it returns the original EncryptionContext. + * + * @param originalTableName the name of the table that should be overridden in the Encryption Context + * @param newTableName the table name that should be used in the Encryption Context + * @return A UnaryOperator that produces a new EncryptionContext with the supplied table name + */ + public static UnaryOperator overrideEncryptionContextTableName( + String originalTableName, + String newTableName) { + return encryptionContext -> { + if (encryptionContext == null + || encryptionContext.getTableName() == null + || originalTableName == null + || newTableName == null) { + return encryptionContext; + } + if (originalTableName.equals(encryptionContext.getTableName())) { + return encryptionContext.toBuilder().tableName(newTableName).build(); + } else { + return encryptionContext; + } + }; + } + + /** + * An operator for mapping multiple table names in the Encryption Context to a new table name. If the table name for + * a given EncryptionContext is missing, then it returns the original EncryptionContext. Similarly, it returns the + * original EncryptionContext if the value it is overridden to is null, or if the original table name is null. + * + * @param tableNameOverrideMap a map specifying the names of tables that should be overridden, + * and the values to which they should be overridden. If the given table name + * corresponds to null, or isn't in the map, then the table name won't be overridden. + * @return A UnaryOperator that produces a new EncryptionContext with the supplied table name + */ + public static UnaryOperator overrideEncryptionContextTableNameUsingMap( + Map tableNameOverrideMap) { + return encryptionContext -> { + if (tableNameOverrideMap == null || encryptionContext == null || encryptionContext.getTableName() == null) { + return encryptionContext; + } + String newTableName = tableNameOverrideMap.get(encryptionContext.getTableName()); + if (newTableName != null) { + return encryptionContext.toBuilder().tableName(newTableName).build(); + } else { + return encryptionContext; + } + }; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java new file mode 100644 index 0000000000..e9348af05d --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java @@ -0,0 +1,331 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import software.amazon.awssdk.core.BytesWrapper; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.util.DefaultSdkAutoConstructList; +import software.amazon.awssdk.core.util.DefaultSdkAutoConstructMap; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + + +/** + * @author Greg Rubin + */ +public class AttributeValueMarshaller { + private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final int TRUE_FLAG = 1; + private static final int FALSE_FLAG = 0; + + private AttributeValueMarshaller() { + // Prevent instantiation + } + + /** + * Marshalls the data using a TLV (Tag-Length-Value) encoding. The tag may be 'b', 'n', 's', + * '?', '\0' to represent a ByteBuffer, Number, String, Boolean, or Null respectively. The tag + * may also be capitalized (for 'b', 'n', and 's',) to represent an array of that type. If an + * array is stored, then a four-byte big-endian integer is written representing the number of + * array elements. If a ByteBuffer is stored, the length of the buffer is stored as a four-byte + * big-endian integer and the buffer then copied directly. Both Numbers and Strings are treated + * identically and are stored as UTF8 encoded Unicode, proceeded by the length of the encoded + * string (in bytes) as a four-byte big-endian integer. Boolean is encoded as a single byte, 0 + * for false and 1 for true (and so has no Length parameter). The + * Null tag ('\0') takes neither a Length nor a Value parameter. + * + * The tags 'L' and 'M' are for the document types List and Map respectively. These are encoded + * recursively with the Length being the size of the collection. In the case of List, the value + * is a Length number of marshalled AttributeValues. If the case of Map, the value is a Length + * number of AttributeValue Pairs where the first must always have a String value. + * + * This implementation does not recognize loops. If an AttributeValue contains itself + * (even indirectly) this code will recurse infinitely. + * + * @param attributeValue an AttributeValue instance + * @return the serialized AttributeValue + * @see java.io.DataInput + */ + public static ByteBuffer marshall(final AttributeValue attributeValue) { + try (ByteArrayOutputStream resultBytes = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(resultBytes);) { + marshall(attributeValue, out); + out.close(); + resultBytes.close(); + return ByteBuffer.wrap(resultBytes.toByteArray()); + } catch (final IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + private static void marshall(final AttributeValue attributeValue, final DataOutputStream out) + throws IOException { + + if (attributeValue.b() != null) { + out.writeChar('b'); + writeBytes(attributeValue.b().asByteBuffer(), out); + } else if (hasAttributeValueSet(attributeValue.bs())) { + out.writeChar('B'); + writeBytesList(attributeValue.bs().stream() + .map(BytesWrapper::asByteBuffer).collect(Collectors.toList()), out); + } else if (attributeValue.n() != null) { + out.writeChar('n'); + writeString(trimZeros(attributeValue.n()), out); + } else if (hasAttributeValueSet(attributeValue.ns())) { + out.writeChar('N'); + + final List ns = new ArrayList<>(attributeValue.ns().size()); + for (final String n : attributeValue.ns()) { + ns.add(trimZeros(n)); + } + writeStringList(ns, out); + } else if (attributeValue.s() != null) { + out.writeChar('s'); + writeString(attributeValue.s(), out); + } else if (hasAttributeValueSet(attributeValue.ss())) { + out.writeChar('S'); + writeStringList(attributeValue.ss(), out); + } else if (attributeValue.bool() != null) { + out.writeChar('?'); + out.writeByte((attributeValue.bool() ? TRUE_FLAG : FALSE_FLAG)); + } else if (Boolean.TRUE.equals(attributeValue.nul())) { + out.writeChar('\0'); + } else if (hasAttributeValueSet(attributeValue.l())) { + final List l = attributeValue.l(); + out.writeChar('L'); + out.writeInt(l.size()); + for (final AttributeValue attr : l) { + if (attr == null) { + throw new NullPointerException( + "Encountered null list entry value while marshalling attribute value " + + attributeValue); + } + marshall(attr, out); + } + } else if (hasAttributeValueMap(attributeValue.m())) { + final Map m = attributeValue.m(); + final List mKeys = new ArrayList<>(m.keySet()); + Collections.sort(mKeys); + out.writeChar('M'); + out.writeInt(m.size()); + for (final String mKey : mKeys) { + marshall(AttributeValue.builder().s(mKey).build(), out); + + final AttributeValue mValue = m.get(mKey); + + if (mValue == null) { + throw new NullPointerException( + "Encountered null map value for key " + + mKey + + " while marshalling attribute value " + + attributeValue); + } + marshall(mValue, out); + } + } else { + throw new IllegalArgumentException("A seemingly empty AttributeValue is indicative of invalid input or potential errors"); + } + } + + /** + * @see #marshall(AttributeValue) + */ + public static AttributeValue unmarshall(final ByteBuffer plainText) { + try (final DataInputStream in = new DataInputStream( + new ByteBufferInputStream(plainText.asReadOnlyBuffer()))) { + return unmarshall(in); + } catch (IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + private static AttributeValue unmarshall(final DataInputStream in) throws IOException { + char type = in.readChar(); + AttributeValue.Builder result = AttributeValue.builder(); + switch (type) { + case '\0': + result.nul(Boolean.TRUE); + break; + case 'b': + result.b(SdkBytes.fromByteBuffer(readBytes(in))); + break; + case 'B': + result.bs(readBytesList(in).stream().map(SdkBytes::fromByteBuffer).collect(Collectors.toList())); + break; + case 'n': + result.n(readString(in)); + break; + case 'N': + result.ns(readStringList(in)); + break; + case 's': + result.s(readString(in)); + break; + case 'S': + result.ss(readStringList(in)); + break; + case '?': + final byte boolValue = in.readByte(); + + if (boolValue == TRUE_FLAG) { + result.bool(Boolean.TRUE); + } else if (boolValue == FALSE_FLAG) { + result.bool(Boolean.FALSE); + } else { + throw new IllegalArgumentException("Improperly formatted data"); + } + break; + case 'L': + final int lCount = in.readInt(); + final List l = new ArrayList<>(lCount); + for (int lIdx = 0; lIdx < lCount; lIdx++) { + l.add(unmarshall(in)); + } + result.l(l); + break; + case 'M': + final int mCount = in.readInt(); + final Map m = new HashMap<>(); + for (int mIdx = 0; mIdx < mCount; mIdx++) { + final AttributeValue key = unmarshall(in); + if (key.s() == null) { + throw new IllegalArgumentException("Improperly formatted data"); + } + AttributeValue value = unmarshall(in); + m.put(key.s(), value); + } + result.m(m); + break; + default: + throw new IllegalArgumentException("Unsupported data encoding"); + } + + return result.build(); + } + + private static String trimZeros(final String n) { + BigDecimal number = new BigDecimal(n); + if (number.compareTo(BigDecimal.ZERO) == 0) { + return "0"; + } + return number.stripTrailingZeros().toPlainString(); + } + + private static void writeStringList(List values, final DataOutputStream out) throws IOException { + final List sorted = new ArrayList<>(values); + Collections.sort(sorted); + out.writeInt(sorted.size()); + for (final String v : sorted) { + writeString(v, out); + } + } + + private static List readStringList(final DataInputStream in) throws IOException, + IllegalArgumentException { + final int nCount = in.readInt(); + List ns = new ArrayList<>(nCount); + for (int nIdx = 0; nIdx < nCount; nIdx++) { + ns.add(readString(in)); + } + return ns; + } + + private static void writeString(String value, final DataOutputStream out) throws IOException { + final byte[] bytes = value.getBytes(UTF8); + out.writeInt(bytes.length); + out.write(bytes); + } + + private static String readString(final DataInputStream in) throws IOException, + IllegalArgumentException { + byte[] bytes; + int length; + length = in.readInt(); + bytes = new byte[length]; + if(in.read(bytes) != length) { + throw new IllegalArgumentException("Improperly formatted data"); + } + return new String(bytes, UTF8); + } + + private static void writeBytesList(List values, final DataOutputStream out) throws IOException { + final List sorted = new ArrayList<>(values); + Collections.sort(sorted); + out.writeInt(sorted.size()); + for (final ByteBuffer v : sorted) { + writeBytes(v, out); + } + } + + private static List readBytesList(final DataInputStream in) throws IOException { + final int bCount = in.readInt(); + List bs = new ArrayList<>(bCount); + for (int bIdx = 0; bIdx < bCount; bIdx++) { + bs.add(readBytes(in)); + } + return bs; + } + + private static void writeBytes(ByteBuffer value, final DataOutputStream out) throws IOException { + value = value.asReadOnlyBuffer(); + value.rewind(); + out.writeInt(value.remaining()); + while (value.hasRemaining()) { + out.writeByte(value.get()); + } + } + + private static ByteBuffer readBytes(final DataInputStream in) throws IOException { + final int length = in.readInt(); + final byte[] buf = new byte[length]; + in.readFully(buf); + return ByteBuffer.wrap(buf); + } + + /** + * Determines if the value of a 'set' type AttributeValue (various S types) has been explicitly set or not. + * @param value the actual value portion of an AttributeValue of the appropriate type + * @return true if the value of this type field has been explicitly set, false if it has not + */ + private static boolean hasAttributeValueSet(Collection value) { + return value != null && value != DefaultSdkAutoConstructList.getInstance(); + } + + /** + * Determines if the value of a 'map' type AttributeValue (M type) has been explicitly set or not. + * @param value the actual value portion of a AttributeValue of the appropriate type + * @return true if the value of this type field has been explicitly set, false if it has not + */ + private static boolean hasAttributeValueMap(Map value) { + return value != null && value != DefaultSdkAutoConstructMap.getInstance(); + } + +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java new file mode 100644 index 0000000000..ee94a86a02 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java @@ -0,0 +1,48 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static java.util.Base64.*; + +/** + * A class for decoding Base64 strings and encoding bytes as Base64 strings. + */ +public class Base64 { + private static final Decoder DECODER = getMimeDecoder(); + private static final Encoder ENCODER = getEncoder(); + + private Base64() { } + + /** + * Encode the bytes as a Base64 string. + *

+ * See the Basic encoder in {@link java.util.Base64} + */ + public static String encodeToString(byte[] bytes) { + return ENCODER.encodeToString(bytes); + } + + /** + * Decode the Base64 string as bytes, ignoring illegal characters. + *

+ * See the Mime Decoder in {@link java.util.Base64} + */ + public static byte[] decode(String str) { + if(str == null) { + return null; + } + return DECODER.decode(str); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java new file mode 100644 index 0000000000..ff70306841 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * @author Greg Rubin + */ +public class ByteBufferInputStream extends InputStream { + private final ByteBuffer buffer; + + public ByteBufferInputStream(ByteBuffer buffer) { + this.buffer = buffer; + } + + @Override + public int read() { + if (buffer.hasRemaining()) { + int tmp = buffer.get(); + if (tmp < 0) { + tmp += 256; + } + return tmp; + } else { + return -1; + } + } + + @Override + public int read(byte[] b, int off, int len) { + if (available() < len) { + len = available(); + } + buffer.get(b, off, len); + return len; + } + + @Override + public int available() { + return buffer.remaining(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java new file mode 100644 index 0000000000..15422aaab7 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java @@ -0,0 +1,316 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.SecretKeySpec; + +/** + * HMAC-based Key Derivation Function. + * + * @see RFC 5869 + */ +public final class Hkdf { + private static final byte[] EMPTY_ARRAY = new byte[0]; + private final String algorithm; + private final Provider provider; + + private SecretKey prk = null; + + /** + * Returns an Hkdf object using the specified algorithm. + * + * @param algorithm + * the standard name of the requested MAC algorithm. See the Mac + * section in the Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm + * names. + * @return the new Hkdf object + * @throws NoSuchAlgorithmException + * if no Provider supports a MacSpi implementation for the + * specified algorithm. + */ + public static Hkdf getInstance(final String algorithm) + throws NoSuchAlgorithmException { + // Constructed specifically to sanity-test arguments. + Mac mac = Mac.getInstance(algorithm); + return new Hkdf(algorithm, mac.getProvider()); + } + + /** + * Returns an Hkdf object using the specified algorithm. + * + * @param algorithm + * the standard name of the requested MAC algorithm. See the Mac + * section in the Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm + * names. + * @param provider + * the name of the provider + * @return the new Hkdf object + * @throws NoSuchAlgorithmException + * if a MacSpi implementation for the specified algorithm is not + * available from the specified provider. + * @throws NoSuchProviderException + * if the specified provider is not registered in the security + * provider list. + */ + public static Hkdf getInstance(final String algorithm, final String provider) + throws NoSuchAlgorithmException, NoSuchProviderException { + // Constructed specifically to sanity-test arguments. + Mac mac = Mac.getInstance(algorithm, provider); + return new Hkdf(algorithm, mac.getProvider()); + } + + /** + * Returns an Hkdf object using the specified algorithm. + * + * @param algorithm + * the standard name of the requested MAC algorithm. See the Mac + * section in the Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm + * names. + * @param provider + * the provider + * @return the new Hkdf object + * @throws NoSuchAlgorithmException + * if a MacSpi implementation for the specified algorithm is not + * available from the specified provider. + */ + public static Hkdf getInstance(final String algorithm, + final Provider provider) throws NoSuchAlgorithmException { + // Constructed specifically to sanity-test arguments. + Mac mac = Mac.getInstance(algorithm, provider); + return new Hkdf(algorithm, mac.getProvider()); + } + + /** + * Initializes this Hkdf with input keying material. A default salt of + * HashLen zeros will be used (where HashLen is the length of the return + * value of the supplied algorithm). + * + * @param ikm + * the Input Keying Material + */ + public void init(final byte[] ikm) { + init(ikm, null); + } + + /** + * Initializes this Hkdf with input keying material and a salt. If + * salt is null or of length 0, then a default salt of + * HashLen zeros will be used (where HashLen is the length of the return + * value of the supplied algorithm). + * + * @param salt + * the salt used for key extraction (optional) + * @param ikm + * the Input Keying Material + */ + public void init(final byte[] ikm, final byte[] salt) { + byte[] realSalt = (salt == null) ? EMPTY_ARRAY : salt.clone(); + byte[] rawKeyMaterial = EMPTY_ARRAY; + try { + Mac extractionMac = Mac.getInstance(algorithm, provider); + if (realSalt.length == 0) { + realSalt = new byte[extractionMac.getMacLength()]; + Arrays.fill(realSalt, (byte) 0); + } + extractionMac.init(new SecretKeySpec(realSalt, algorithm)); + rawKeyMaterial = extractionMac.doFinal(ikm); + SecretKeySpec key = new SecretKeySpec(rawKeyMaterial, algorithm); + Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array + unsafeInitWithoutKeyExtraction(key); + } catch (GeneralSecurityException e) { + // We've already checked all of the parameters so no exceptions + // should be possible here. + throw new RuntimeException("Unexpected exception", e); + } finally { + Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array + } + } + + /** + * Initializes this Hkdf to use the provided key directly for creation of + * new keys. If rawKey is not securely generated and uniformly + * distributed over the total key-space, then this will result in an + * insecure key derivation function (KDF). DO NOT USE THIS UNLESS YOU + * ARE ABSOLUTELY POSITIVE THIS IS THE CORRECT THING TO DO. + * + * @param rawKey + * the pseudorandom key directly used to derive keys + * @throws InvalidKeyException + * if the algorithm for rawKey does not match the + * algorithm this Hkdf was created with + */ + public void unsafeInitWithoutKeyExtraction(final SecretKey rawKey) + throws InvalidKeyException { + if (!rawKey.getAlgorithm().equals(algorithm)) { + throw new InvalidKeyException( + "Algorithm for the provided key must match the algorithm for this Hkdf. Expected " + + algorithm + " but found " + rawKey.getAlgorithm()); + } + + this.prk = rawKey; + } + + private Hkdf(final String algorithm, final Provider provider) { + if (!algorithm.startsWith("Hmac")) { + throw new IllegalArgumentException("Invalid algorithm " + algorithm + + ". Hkdf may only be used with Hmac algorithms."); + } + this.algorithm = algorithm; + this.provider = provider; + } + + /** + * Returns a pseudorandom key of length bytes. + * + * @param info + * optional context and application specific information (can be + * a zero-length string). This will be treated as UTF-8. + * @param length + * the length of the output key in bytes + * @return a pseudorandom key of length bytes. + * @throws IllegalStateException + * if this object has not been initialized + */ + public byte[] deriveKey(final String info, final int length) throws IllegalStateException { + return deriveKey((info != null ? info.getBytes(StandardCharsets.UTF_8) : null), length); + } + + /** + * Returns a pseudorandom key of length bytes. + * + * @param info + * optional context and application specific information (can be + * a zero-length array). + * @param length + * the length of the output key in bytes + * @return a pseudorandom key of length bytes. + * @throws IllegalStateException + * if this object has not been initialized + */ + public byte[] deriveKey(final byte[] info, final int length) throws IllegalStateException { + byte[] result = new byte[length]; + try { + deriveKey(info, length, result, 0); + } catch (ShortBufferException ex) { + // This exception is impossible as we ensure the buffer is long + // enough + throw new RuntimeException(ex); + } + return result; + } + + /** + * Derives a pseudorandom key of length bytes and stores the + * result in output. + * + * @param info + * optional context and application specific information (can be + * a zero-length array). + * @param length + * the length of the output key in bytes + * @param output + * the buffer where the pseudorandom key will be stored + * @param offset + * the offset in output where the key will be stored + * @throws ShortBufferException + * if the given output buffer is too small to hold the result + * @throws IllegalStateException + * if this object has not been initialized + */ + public void deriveKey(final byte[] info, final int length, + final byte[] output, final int offset) throws ShortBufferException, + IllegalStateException { + assertInitialized(); + if (length < 0) { + throw new IllegalArgumentException("Length must be a non-negative value."); + } + if (output.length < offset + length) { + throw new ShortBufferException(); + } + Mac mac = createMac(); + + if (length > 255 * mac.getMacLength()) { + throw new IllegalArgumentException( + "Requested keys may not be longer than 255 times the underlying HMAC length."); + } + + byte[] t = EMPTY_ARRAY; + try { + int loc = 0; + byte i = 1; + while (loc < length) { + mac.update(t); + mac.update(info); + mac.update(i); + t = mac.doFinal(); + + for (int x = 0; x < t.length && loc < length; x++, loc++) { + output[loc] = t[x]; + } + + i++; + } + } finally { + Arrays.fill(t, (byte) 0); // Zeroize temporary array + } + } + + private Mac createMac() { + try { + Mac mac = Mac.getInstance(algorithm, provider); + mac.init(prk); + return mac; + } catch (NoSuchAlgorithmException ex) { + // We've already validated that this algorithm is correct. + throw new RuntimeException(ex); + } catch (InvalidKeyException ex) { + // We've already validated that this key is correct. + throw new RuntimeException(ex); + } + } + + /** + * Throws an IllegalStateException if this object has not been + * initialized. + * + * @throws IllegalStateException + * if this object has not been initialized + */ + private void assertInitialized() throws IllegalStateException { + if (prk == null) { + throw new IllegalStateException("Hkdf has not been initialized"); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java new file mode 100644 index 0000000000..e191a84215 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import software.amazon.awssdk.annotations.ThreadSafe; + +/** + * A bounded cache that has a LRU eviction policy when the cache is full. + * + * @param + * value type + */ +@ThreadSafe +public final class LRUCache { + /** + * Used for the internal cache. + */ + private final Map map; + + /** + * Maximum size of the cache. + */ + private final int maxSize; + + /** + * @param maxSize + * the maximum number of entries of the cache + */ + public LRUCache(final int maxSize) { + if (maxSize < 1) { + throw new IllegalArgumentException("maxSize " + maxSize + " must be at least 1"); + } + this.maxSize = maxSize; + map = Collections.synchronizedMap(new LRUHashMap(maxSize)); + } + + /** + * Adds an entry to the cache, evicting the earliest entry if necessary. + */ + public T add(final String key, final T value) { + return map.put(key, value); + } + + /** Returns the value of the given key; or null of no such entry exists. */ + public T get(final String key) { + return map.get(key); + } + + /** + * Returns the current size of the cache. + */ + public int size() { + return map.size(); + } + + /** + * Returns the maximum size of the cache. + */ + public int getMaxSize() { + return maxSize; + } + + public void clear() { + map.clear(); + } + + public T remove(String key) { + return map.remove(key); + } + + @Override + public String toString() { + return map.toString(); + } + + @SuppressWarnings("serial") + private static class LRUHashMap extends LinkedHashMap { + private final int maxSize; + + private LRUHashMap(final int maxSize) { + super(10, 0.75F, true); + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(final Entry eldest) { + return size() > maxSize; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java new file mode 100644 index 0000000000..3d776c0dc8 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java @@ -0,0 +1,19 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +interface MsClock { + MsClock WALLCLOCK = System::nanoTime; + + public long timestampNano(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java new file mode 100644 index 0000000000..f529047c8a --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java @@ -0,0 +1,242 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import io.netty.util.internal.ObjectUtil; +import software.amazon.awssdk.annotations.ThreadSafe; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; + +/** + * A cache, backed by an LRUCache, that uses a loader to calculate values on cache miss or expired + * TTL. + * + *

Note that this cache does not proactively evict expired entries, however will immediately + * evict entries discovered to be expired on load. + * + * @param value type + */ +@ThreadSafe +public final class TTLCache { + /** Used for the internal cache. */ + private final LRUCache> cache; + + /** Time to live for entries in the cache. */ + private final long ttlInNanos; + + /** Used for loading new values into the cache on cache miss or expiration. */ + private final EntryLoader defaultLoader; + + // Mockable time source, to allow us to test TTL behavior. + // package access for tests + MsClock clock = MsClock.WALLCLOCK; + + private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); + + /** + * @param maxSize the maximum number of entries of the cache + * @param ttlInMillis the time to live value for entries of the cache, in milliseconds + */ + public TTLCache(final int maxSize, final long ttlInMillis, final EntryLoader loader) { + if (maxSize < 1) { + throw new IllegalArgumentException("maxSize " + maxSize + " must be at least 1"); + } + if (ttlInMillis < 1) { + throw new IllegalArgumentException("ttlInMillis " + maxSize + " must be at least 1"); + } + this.ttlInNanos = TimeUnit.MILLISECONDS.toNanos(ttlInMillis); + this.cache = new LRUCache<>(maxSize); + this.defaultLoader = ObjectUtil.checkNotNull(loader, "loader must not be null"); + } + + /** + * Uses the default loader to calculate the value at key and insert it into the cache, if it + * doesn't already exist or is expired according to the TTL. + * + *

This immediately evicts entries past the TTL such that a load failure results in the removal + * of the entry. + * + *

Entries that are not expired according to the TTL are returned without recalculating the + * value. + * + *

Within a grace period past the TTL, the cache may either return the cached value without + * recalculating or use the loader to recalculate the value. This is implemented such that, in a + * multi-threaded environment, only one thread per cache key uses the loader to recalculate the + * value at one time. + * + * @param key The cache key to load the value at + * @return The value of the given value (already existing or re-calculated). + */ + public T load(final String key) { + return load(key, defaultLoader::load); + } + + /** + * Uses the inputted function to calculate the value at key and insert it into the cache, if it + * doesn't already exist or is expired according to the TTL. + * + *

This immediately evicts entries past the TTL such that a load failure results in the removal + * of the entry. + * + *

Entries that are not expired according to the TTL are returned without recalculating the + * value. + * + *

Within a grace period past the TTL, the cache may either return the cached value without + * recalculating or use the loader to recalculate the value. This is implemented such that, in a + * multi-threaded environment, only one thread per cache key uses the loader to recalculate the + * value at one time. + * + *

Returns the value of the given key (already existing or re-calculated). + * + * @param key The cache key to load the value at + * @param f The function to use to load the value, given key as input + * @return The value of the given value (already existing or re-calculated). + */ + public T load(final String key, Function f) { + final LockedState ls = cache.get(key); + + if (ls == null) { + // The entry doesn't exist yet, so load a new one. + return loadNewEntryIfAbsent(key, f); + } else if (clock.timestampNano() - ls.getState().lastUpdatedNano + > ttlInNanos + TTL_GRACE_IN_NANO) { + // The data has expired past the grace period. + // Evict the old entry and load a new entry. + cache.remove(key); + return loadNewEntryIfAbsent(key, f); + } else if (clock.timestampNano() - ls.getState().lastUpdatedNano <= ttlInNanos) { + // The data hasn't expired. Return as-is from the cache. + return ls.getState().data; + } else if (!ls.tryLock()) { + // We are in the TTL grace period. If we couldn't grab the lock, then some other + // thread is currently loading the new value. Because we are in the grace period, + // use the cached data instead of waiting for the lock. + return ls.getState().data; + } + + // We are in the grace period and have acquired a lock. + // Update the cache with the value determined by the loading function. + try { + T loadedData = f.apply(key); + ls.update(loadedData, clock.timestampNano()); + return ls.getState().data; + } finally { + ls.unlock(); + } + } + + // Synchronously calculate the value for a new entry in the cache if it doesn't already exist. + // Otherwise return the cached value. + // It is important that this is the only place where we use the loader for a new entry, + // given that we don't have the entry yet to lock on. + // This ensures that the loading function is only called once if multiple threads + // attempt to add a new entry for the same key at the same time. + private synchronized T loadNewEntryIfAbsent(final String key, Function f) { + // If the entry already exists in the cache, return it + final LockedState cachedState = cache.get(key); + if (cachedState != null) { + return cachedState.getState().data; + } + + // Otherwise, load the data and create a new entry + T loadedData = f.apply(key); + LockedState ls = new LockedState<>(loadedData, clock.timestampNano()); + cache.add(key, ls); + return loadedData; + } + + + /** + * Put a new entry in the cache. Returns the value previously at that key in the cache, or null if + * the entry previously didn't exist or is expired. + */ + public synchronized T put(final String key, final T value) { + LockedState ls = new LockedState<>(value, clock.timestampNano()); + LockedState oldLockedState = cache.add(key, ls); + if (oldLockedState == null + || clock.timestampNano() - oldLockedState.getState().lastUpdatedNano + > ttlInNanos + TTL_GRACE_IN_NANO) { + return null; + } + return oldLockedState.getState().data; + } + + /** + * Get when the entry at this key was last updated. Returns 0 if the entry doesn't exist at key. + */ + public long getLastUpdated(String key) { + LockedState ls = cache.get(key); + if (ls == null) { + return 0; + } + return ls.getState().lastUpdatedNano; + } + + /** Returns the current size of the cache. */ + public int size() { + return cache.size(); + } + + /** Returns the maximum size of the cache. */ + public int getMaxSize() { + return cache.getMaxSize(); + } + + /** Clears all entries from the cache. */ + public void clear() { + cache.clear(); + } + + @Override + public String toString() { + return cache.toString(); + } + + public interface EntryLoader { + T load(String entryKey); + } + + // An object which stores a state alongside a lock, + // and performs updates to that state atomically. + // The state may only be updated if the lock is acquired by the current thread. + private static class LockedState { + private final ReentrantLock lock = new ReentrantLock(true); + private final AtomicReference> state; + + public LockedState(T data, long createTimeNano) { + state = new AtomicReference<>(new State<>(data, createTimeNano)); + } + + public State getState() { + return state.get(); + } + + public void unlock() { + lock.unlock(); + } + + public boolean tryLock() { + return lock.tryLock(); + } + + public void update(T data, long createTimeNano) { + if (!lock.isHeldByCurrentThread()) { + throw new IllegalStateException("Lock not held by current thread"); + } + state.set(new State<>(data, createTimeNano)); + } + } + + // An object that holds some data and the time at which this object was created + private static class State { + public final T data; + public final long lastUpdatedNano; + + public State(T data, long lastUpdatedNano) { + this.data = data; + this.lastUpdatedNano = lastUpdatedNano; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java new file mode 100644 index 0000000000..6d092cc06b --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java @@ -0,0 +1,39 @@ +/* + * 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. + */ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.security.SecureRandom; + +public class Utils { + private static final ThreadLocal RND = ThreadLocal.withInitial(() -> { + final SecureRandom result = new SecureRandom(); + result.nextBoolean(); // Force seeding + return result; + }); + + private Utils() { + // Prevent instantiation + } + + public static SecureRandom getRng() { + return RND.get(); + } + + public static byte[] getRandom(int len) { + final byte[] result = new byte[len]; + getRng().nextBytes(result); + return result; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java new file mode 100644 index 0000000000..b9906bade0 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java @@ -0,0 +1,932 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + +import com.amazonaws.util.Base64; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.*; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.*; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.AsymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.CachingMostRecentProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.*; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.KeyData; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.Keys; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.Scenario; + +public class HolisticIT { + + private static final SecretKey aesKey = + new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "AES"); + private static final SecretKey hmacKey = + new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); + private static final String rsaEncPub = + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtiNSLSvT9cExXOcD0dGZ" + + "9DFEMHw8895gAZcCdSppDrxbD7XgZiQYTlgt058i5fS+l11guAUJtKt5sZ2u8Fx0" + + "K9pxMdlczGtvQJdx/LQETEnLnfzAijvHisJ8h6dQOVczM7t01KIkS24QZElyO+kY" + + "qMWLytUV4RSHnrnIuUtPHCe6LieDWT2+1UBguxgtFt1xdXlquACLVv/Em3wp40Xc" + + "bIwzhqLitb98rTY/wqSiGTz1uvvBX46n+f2j3geZKCEDGkWcXYw3dH4lRtDWTbqw" + + "eRcaNDT/MJswQlBk/Up9KCyN7gjX67gttiCO6jMoTNDejGeJhG4Dd2o0vmn8WJlr" + + "5wIDAQAB"; + private static final String rsaEncPriv = + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2I1ItK9P1wTFc" + + "5wPR0Zn0MUQwfDzz3mABlwJ1KmkOvFsPteBmJBhOWC3TnyLl9L6XXWC4BQm0q3mx" + + "na7wXHQr2nEx2VzMa29Al3H8tARMScud/MCKO8eKwnyHp1A5VzMzu3TUoiRLbhBk" + + "SXI76RioxYvK1RXhFIeeuci5S08cJ7ouJ4NZPb7VQGC7GC0W3XF1eWq4AItW/8Sb" + + "fCnjRdxsjDOGouK1v3ytNj/CpKIZPPW6+8Ffjqf5/aPeB5koIQMaRZxdjDd0fiVG" + + "0NZNurB5Fxo0NP8wmzBCUGT9Sn0oLI3uCNfruC22II7qMyhM0N6MZ4mEbgN3ajS+" + + "afxYmWvnAgMBAAECggEBAIIU293zDWDZZ73oJ+w0fHXQsdjHAmlRitPX3CN99KZX" + + "k9m2ldudL9bUV3Zqk2wUzgIg6LDEuFfWmAVojsaP4VBopKtriEFfAYfqIbjPgLpT" + + "gh8FoyWW6D6MBJCFyGALjUAHQ7uRScathvt5ESMEqV3wKJTmdsfX97w/B8J+rLN3" + + "3fT3ZJUck5duZ8XKD+UtX1Y3UE1hTWo3Ae2MFND964XyUqy+HaYXjH0x6dhZzqyJ" + + "/OJ/MPGeMJgxp+nUbMWerwxrLQceNFVgnQgHj8e8k4fd04rkowkkPua912gNtmz7" + + "DuIEvcMnY64z585cn+cnXUPJwtu3JbAmn/AyLsV9FLECgYEA798Ut/r+vORB16JD" + + "KFu38pQCgIbdCPkXeI0DC6u1cW8JFhgRqi+AqSrEy5SzY3IY7NVMSRsBI9Y026Bl" + + "R9OQwTrOzLRAw26NPSDvbTkeYXlY9+hX7IovHjGkho/OxyTJ7bKRDYLoNCz56BC1" + + "khIWvECpcf/fZU0nqOFVFqF3H/UCgYEAwmJ4rjl5fksTNtNRL6ivkqkHIPKXzk5w" + + "C+L90HKNicic9bqyX8K4JRkGKSNYN3mkjrguAzUlEld390qNBw5Lu7PwATv0e2i+" + + "6hdwJsjTKNpj7Nh4Mieq6d7lWe1L8FLyHEhxgIeQ4BgqrVtPPOH8IBGpuzVZdWwI" + + "dgOvEvAi/usCgYBdfk3NB/+SEEW5jn0uldE0s4vmHKq6fJwxWIT/X4XxGJ4qBmec" + + "NbeoOAtMbkEdWbNtXBXHyMbA+RTRJctUG5ooNou0Le2wPr6+PMAVilXVGD8dIWpj" + + "v9htpFvENvkZlbU++IKhCY0ICR++3ARpUrOZ3Hou/NRN36y9nlZT48tSoQKBgES2" + + "Bi6fxmBsLUiN/f64xAc1lH2DA0I728N343xRYdK4hTMfYXoUHH+QjurvwXkqmI6S" + + "cEFWAdqv7IoPYjaCSSb6ffYRuWP+LK4WxuAO0QV53SSViDdCalntHmlhRhyXVVnG" + + "CckDIqT0JfHNev7savDzDWpNe2fUXlFJEBPDqrstAoGBAOpd5+QBHF/tP5oPILH4" + + "aD/zmqMH7VtB+b/fOPwtIM+B/WnU7hHLO5t2lJYu18Be3amPkfoQIB7bpkM3Cer2" + + "G7Jw+TcHrY+EtIziDB5vwau1fl4VcbA9SfWpBojJ5Ifo9ELVxGiK95WxeQNSmLUy" + + "7AJzhK1Gwey8a/v+xfqiu9sE"; + private static final PrivateKey rsaPriv; + private static final PublicKey rsaPub; + private static final KeyPair rsaPair; + private static final EncryptionMaterialsProvider symProv; + private static final EncryptionMaterialsProvider asymProv; + private static final EncryptionMaterialsProvider symWrappedProv; + private static final String HASH_KEY = "hashKey"; + private static final String RANGE_KEY = "rangeKey"; + private static final String RSA = "RSA"; + private static final String tableName = "TableName"; + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = + EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + + private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); + private DynamoDbClient client; + private static KmsClient kmsClient = KmsClient.builder().build(); + + private static Map keyDataMap = new HashMap<>(); + + private static final Map ENCRYPTED_TEST_VALUE = new HashMap<>(); + private static final Map MIXED_TEST_VALUE = new HashMap<>(); + private static final Map SIGNED_TEST_VALUE = new HashMap<>(); + private static final Map UNTOUCHED_TEST_VALUE = new HashMap<>(); + + private static final Map ENCRYPTED_TEST_VALUE_2 = new HashMap<>(); + private static final Map MIXED_TEST_VALUE_2 = new HashMap<>(); + private static final Map SIGNED_TEST_VALUE_2 = new HashMap<>(); + private static final Map UNTOUCHED_TEST_VALUE_2 = new HashMap<>(); + + private static final String TEST_VECTOR_MANIFEST_DIR = "/vectors/encrypted_item/"; + private static final String SCENARIO_MANIFEST_PATH = TEST_VECTOR_MANIFEST_DIR + "scenarios.json"; + private static final String JAVA_DIR = "java"; + + static { + try { + KeyFactory rsaFact = KeyFactory.getInstance("RSA"); + rsaPub = rsaFact.generatePublic(new X509EncodedKeySpec(Base64.decode(rsaEncPub))); + rsaPriv = rsaFact.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(rsaEncPriv))); + rsaPair = new KeyPair(rsaPub, rsaPriv); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + symProv = new SymmetricStaticProvider(aesKey, hmacKey); + asymProv = new AsymmetricStaticProvider(rsaPair, rsaPair); + symWrappedProv = new WrappedMaterialsProvider(aesKey, aesKey, hmacKey); + + ENCRYPTED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("5").build()); + ENCRYPTED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("7").build()); + ENCRYPTED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); + ENCRYPTED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); + ENCRYPTED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + ENCRYPTED_TEST_VALUE.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + ENCRYPTED_TEST_VALUE.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + ENCRYPTED_TEST_VALUE.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + + MIXED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("6").build()); + MIXED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("8").build()); + MIXED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); + MIXED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); + MIXED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + MIXED_TEST_VALUE.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + MIXED_TEST_VALUE.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + MIXED_TEST_VALUE.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + + SIGNED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("8").build()); + SIGNED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("10").build()); + SIGNED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); + SIGNED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); + SIGNED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + SIGNED_TEST_VALUE.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + SIGNED_TEST_VALUE.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + SIGNED_TEST_VALUE.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + + UNTOUCHED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("7").build()); + UNTOUCHED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("9").build()); + UNTOUCHED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); + UNTOUCHED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); + UNTOUCHED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + UNTOUCHED_TEST_VALUE.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + UNTOUCHED_TEST_VALUE.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + UNTOUCHED_TEST_VALUE.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + + // STORING DOUBLES + ENCRYPTED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("5").build()); + ENCRYPTED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("7").build()); + ENCRYPTED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); + ENCRYPTED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); + ENCRYPTED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + ENCRYPTED_TEST_VALUE_2.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + ENCRYPTED_TEST_VALUE_2.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + ENCRYPTED_TEST_VALUE_2.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + ENCRYPTED_TEST_VALUE_2.put( + "doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); + ENCRYPTED_TEST_VALUE_2.put( + "doubleSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) + .build()); + + MIXED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("6").build()); + MIXED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("8").build()); + MIXED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); + MIXED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); + MIXED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + MIXED_TEST_VALUE_2.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + MIXED_TEST_VALUE_2.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + MIXED_TEST_VALUE_2.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + MIXED_TEST_VALUE_2.put("doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); + MIXED_TEST_VALUE_2.put( + "doubleSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) + .build()); + + SIGNED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("8").build()); + SIGNED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("10").build()); + SIGNED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); + SIGNED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); + SIGNED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + SIGNED_TEST_VALUE_2.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + SIGNED_TEST_VALUE_2.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + SIGNED_TEST_VALUE_2.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + SIGNED_TEST_VALUE_2.put("doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); + SIGNED_TEST_VALUE_2.put( + "doubleSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) + .build()); + + UNTOUCHED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("7").build()); + UNTOUCHED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("9").build()); + UNTOUCHED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); + UNTOUCHED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); + UNTOUCHED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + UNTOUCHED_TEST_VALUE_2.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + UNTOUCHED_TEST_VALUE_2.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + UNTOUCHED_TEST_VALUE_2.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + UNTOUCHED_TEST_VALUE_2.put( + "doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); + UNTOUCHED_TEST_VALUE_2.put( + "doubleSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) + .build()); + } + + @DataProvider(name = "getEncryptTestVectors") + public static Object[][] getEncryptTestVectors() throws IOException { + ScenarioManifest scenarioManifest = + getManifestFromFile(SCENARIO_MANIFEST_PATH, new TypeReference() {}); + loadKeyData(scenarioManifest.keyDataPath); + + // Only use Java generated test vectors to dedupe the scenarios for encrypt, + // we only care that we are able to generate data using the different provider configurations + return scenarioManifest.scenarios.stream() + .filter(s -> s.ciphertextPath.contains(JAVA_DIR)) + .map(s -> new Object[] {s}) + .toArray(Object[][]::new); + } + + @DataProvider(name = "getDecryptTestVectors") + public static Object[][] getDecryptTestVectors() throws IOException { + ScenarioManifest scenarioManifest = + getManifestFromFile(SCENARIO_MANIFEST_PATH, new TypeReference() {}); + loadKeyData(scenarioManifest.keyDataPath); + + return scenarioManifest.scenarios.stream().map(s -> new Object[] {s}).toArray(Object[][]::new); + } + + @Test(dataProvider = "getDecryptTestVectors") + public void decryptTestVector(Scenario scenario) throws IOException, GeneralSecurityException { + localDynamoDb.start(); + client = localDynamoDb.createLimitedWrappedClient(); + + // load data into ciphertext tables + createCiphertextTables(client); + + // load data from vector file + putDataFromFile(client, scenario.ciphertextPath); + + // create and load metastore table if necessary + ProviderStore metastore = null; + if (scenario.metastore != null) { + MetaStore.createTable( + client, + scenario.metastore.tableName, + ProvisionedThroughput.builder().readCapacityUnits(100L).writeCapacityUnits(100L).build()); + putDataFromFile(client, scenario.metastore.path); + EncryptionMaterialsProvider metaProvider = + createProvider( + scenario.metastore.providerName, + scenario.materialName, + scenario.metastore.keys, + null); + metastore = + new MetaStore( + client, scenario.metastore.tableName, DynamoDbEncryptor.getInstance(metaProvider)); + } + + // Create the mapper with the provider under test + EncryptionMaterialsProvider provider = + createProvider(scenario.providerName, scenario.materialName, scenario.keys, metastore); + + // Verify successful decryption + switch (scenario.version) { + case "v0": + assertVersionCompatibility(provider, tableName); + break; + case "v1": + assertVersionCompatibility_2(provider, tableName); + break; + default: + throw new IllegalStateException( + "Version " + scenario.version + " not yet implemented in test vector runner"); + } + client.close(); + localDynamoDb.stop(); + } + + @Test(dataProvider = "getEncryptTestVectors") + public void encryptWithTestVector(Scenario scenario) throws IOException { + localDynamoDb.start(); + client = localDynamoDb.createLimitedWrappedClient(); + + // load data into ciphertext tables + createCiphertextTables(client); + + // create and load metastore table if necessary + ProviderStore metastore = null; + if (scenario.metastore != null) { + MetaStore.createTable( + client, + scenario.metastore.tableName, + ProvisionedThroughput.builder().readCapacityUnits(100L).writeCapacityUnits(100L).build()); + putDataFromFile(client, scenario.metastore.path); + EncryptionMaterialsProvider metaProvider = + createProvider( + scenario.metastore.providerName, + scenario.materialName, + scenario.metastore.keys, + null); + metastore = + new MetaStore( + client, scenario.metastore.tableName, DynamoDbEncryptor.getInstance(metaProvider)); + } + + // Encrypt data with the provider under test, only ensure that no exception is thrown + EncryptionMaterialsProvider provider = + createProvider(scenario.providerName, scenario.materialName, scenario.keys, metastore); + generateStandardData(provider); + client.close(); + localDynamoDb.stop(); + } + + private EncryptionMaterialsProvider createProvider( + String providerName, String materialName, Keys keys, ProviderStore metastore) { + switch (providerName) { + case ScenarioManifest.MOST_RECENT_PROVIDER_NAME: + return new CachingMostRecentProvider(metastore, materialName, 1000); + case ScenarioManifest.STATIC_PROVIDER_NAME: + KeyData decryptKeyData = keyDataMap.get(keys.decryptName); + KeyData verifyKeyData = keyDataMap.get(keys.verifyName); + SecretKey decryptKey = + new SecretKeySpec(Base64.decode(decryptKeyData.material), decryptKeyData.algorithm); + SecretKey verifyKey = + new SecretKeySpec(Base64.decode(verifyKeyData.material), verifyKeyData.algorithm); + return new SymmetricStaticProvider(decryptKey, verifyKey); + case ScenarioManifest.WRAPPED_PROVIDER_NAME: + decryptKeyData = keyDataMap.get(keys.decryptName); + verifyKeyData = keyDataMap.get(keys.verifyName); + + // This can be either the asymmetric provider, where we should test using it's explicit + // constructor, + // or a wrapped symmetric where we use the wrapped materials constructor. + if (decryptKeyData.keyType.equals(ScenarioManifest.SYMMETRIC_KEY_TYPE)) { + decryptKey = + new SecretKeySpec(Base64.decode(decryptKeyData.material), decryptKeyData.algorithm); + verifyKey = + new SecretKeySpec(Base64.decode(verifyKeyData.material), verifyKeyData.algorithm); + return new WrappedMaterialsProvider(decryptKey, decryptKey, verifyKey); + } else { + KeyData encryptKeyData = keyDataMap.get(keys.encryptName); + KeyData signKeyData = keyDataMap.get(keys.signName); + try { + // Hardcoded to use RSA for asymmetric keys. If we include vectors with a different + // asymmetric scheme this will need to be updated. + KeyFactory rsaFact = KeyFactory.getInstance(RSA); + + PublicKey encryptMaterial = + rsaFact.generatePublic( + new X509EncodedKeySpec(Base64.decode(encryptKeyData.material))); + PrivateKey decryptMaterial = + rsaFact.generatePrivate( + new PKCS8EncodedKeySpec(Base64.decode(decryptKeyData.material))); + KeyPair decryptPair = new KeyPair(encryptMaterial, decryptMaterial); + + PublicKey verifyMaterial = + rsaFact.generatePublic( + new X509EncodedKeySpec(Base64.decode(verifyKeyData.material))); + PrivateKey signingMaterial = + rsaFact.generatePrivate( + new PKCS8EncodedKeySpec(Base64.decode(signKeyData.material))); + KeyPair sigPair = new KeyPair(verifyMaterial, signingMaterial); + + return new AsymmetricStaticProvider(decryptPair, sigPair); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } + case ScenarioManifest.AWS_KMS_PROVIDER_NAME: + return new DirectKmsMaterialsProvider(kmsClient, keyDataMap.get(keys.decryptName).keyId); + default: + throw new IllegalStateException( + "Provider " + providerName + " not yet implemented in test vector runner"); + } + } + + // Create empty tables for the ciphertext. + // The underlying structure to these tables is hardcoded, + // and we run all test vectors assuming the ciphertext matches the key schema for these tables. + private void createCiphertextTables(DynamoDbClient localDynamoDb) { + // TableName Setup + ArrayList attrDef = new ArrayList<>(); + attrDef.add( + AttributeDefinition.builder() + .attributeName(HASH_KEY) + .attributeType(ScalarAttributeType.N) + .build()); + + attrDef.add( + AttributeDefinition.builder() + .attributeName(RANGE_KEY) + .attributeType(ScalarAttributeType.N) + .build()); + ArrayList keySchema = new ArrayList<>(); + keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); + keySchema.add( + KeySchemaElement.builder().attributeName(RANGE_KEY).keyType(KeyType.RANGE).build()); + + localDynamoDb.createTable( + CreateTableRequest.builder() + .tableName("TableName") + .attributeDefinitions(attrDef) + .keySchema(keySchema) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(100L) + .writeCapacityUnits(100L) + .build()) + .build()); + + // HashKeyOnly SetUp + attrDef = new ArrayList<>(); + attrDef.add( + AttributeDefinition.builder() + .attributeName(HASH_KEY) + .attributeType(ScalarAttributeType.S) + .build()); + + keySchema = new ArrayList<>(); + keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); + + localDynamoDb.createTable( + CreateTableRequest.builder() + .tableName("HashKeyOnly") + .attributeDefinitions(attrDef) + .keySchema(keySchema) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(100L) + .writeCapacityUnits(100L) + .build()) + .build()); + + // DeterministicTable SetUp + attrDef = new ArrayList<>(); + attrDef.add( + AttributeDefinition.builder() + .attributeName(HASH_KEY) + .attributeType(ScalarAttributeType.B) + .build()); + attrDef.add( + AttributeDefinition.builder() + .attributeName(RANGE_KEY) + .attributeType(ScalarAttributeType.N) + .build()); + + keySchema = new ArrayList<>(); + keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); + keySchema.add( + KeySchemaElement.builder().attributeName(RANGE_KEY).keyType(KeyType.RANGE).build()); + + localDynamoDb.createTable( + CreateTableRequest.builder() + .tableName("DeterministicTable") + .attributeDefinitions(attrDef) + .keySchema(keySchema) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(100L) + .writeCapacityUnits(100L) + .build()) + .build()); + } + + // Given a file in the test vector ciphertext format, put those entries into their tables. + // This assumes the expected tables have already been created. + private void putDataFromFile(DynamoDbClient localDynamoDb, String filename) throws IOException { + Map>> manifest = + getCiphertextManifestFromFile(filename); + for (String tableName : manifest.keySet()) { + for (Map attributes : manifest.get(tableName)) { + localDynamoDb.putItem( + PutItemRequest.builder().tableName(tableName).item(attributes).build()); + } + } + } + + private Map>> getCiphertextManifestFromFile( + String filename) throws IOException { + return getManifestFromFile( + TEST_VECTOR_MANIFEST_DIR + stripFilePath(filename), + new TypeReference>>>() {}); + } + + private static T getManifestFromFile(String filename, TypeReference typeRef) + throws IOException { + final URL url = HolisticIT.class.getResource(filename); + if (url == null) { + throw new IllegalStateException("Missing file " + filename + " in src/test/resources."); + } + final File manifestFile = new File(url.getPath()); + final ObjectMapper manifestMapper = new ObjectMapper(); + return (T) manifestMapper.readValue(manifestFile, typeRef); + } + + private static void loadKeyData(String filename) throws IOException { + keyDataMap = + getManifestFromFile( + TEST_VECTOR_MANIFEST_DIR + stripFilePath(filename), + new TypeReference>() {}); + } + + public void generateStandardData(EncryptionMaterialsProvider prov) { + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + Map encryptedRecord; + Map> actions; + EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + Map hashKey1 = new HashMap<>(); + Map hashKey2 = new HashMap<>(); + Map hashKey3 = new HashMap<>(); + + hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); + hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); + hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); + + // encrypted record + actions = new HashMap<>(); + for (final String attr : ENCRYPTED_TEST_VALUE_2.keySet()) { + switch (attr) { + case "hashKey": + case "rangeKey": + case "version": + actions.put(attr, signOnly); + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + encryptedRecord = encryptor.encryptRecord(ENCRYPTED_TEST_VALUE_2, actions, encryptionContext); + putItems(encryptedRecord, tableName); + + // mixed test record + actions = new HashMap<>(); + for (final String attr : MIXED_TEST_VALUE_2.keySet()) { + switch (attr) { + case "rangeKey": + case "hashKey": + case "version": + case "stringValue": + case "doubleValue": + case "doubleSet": + actions.put(attr, signOnly); + break; + case "intValue": + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + encryptedRecord = encryptor.encryptRecord(MIXED_TEST_VALUE_2, actions, encryptionContext); + putItems(encryptedRecord, tableName); + + // sign only record + actions = new HashMap<>(); + for (final String attr : SIGNED_TEST_VALUE_2.keySet()) { + actions.put(attr, signOnly); + } + encryptedRecord = encryptor.encryptRecord(SIGNED_TEST_VALUE_2, actions, encryptionContext); + putItems(encryptedRecord, tableName); + + // untouched record + putItems(UNTOUCHED_TEST_VALUE_2, tableName); + } + + private void putItems(Map map, String tableName) { + PutItemRequest request = PutItemRequest.builder().item(map).tableName(tableName).build(); + client.putItem(request); + } + + private Map getItems(Map map, String tableName) { + GetItemRequest request = GetItemRequest.builder().key(map).tableName(tableName).build(); + return client.getItem(request).item(); + } + + private void assertVersionCompatibility(EncryptionMaterialsProvider provider, String tableName) + throws GeneralSecurityException { + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + Map response; + Map decryptedRecord; + EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + + // Set up maps for table items + HashMap untouched = new HashMap<>(); + HashMap signed = new HashMap<>(); + HashMap mixed = new HashMap<>(); + HashMap encrypted = new HashMap<>(); + HashMap hashKey1 = new HashMap<>(); + HashMap hashKey2 = new HashMap<>(); + HashMap hashKey3 = new HashMap<>(); + untouched.put("hashKey", UNTOUCHED_TEST_VALUE.get("hashKey")); + untouched.put("rangeKey", UNTOUCHED_TEST_VALUE.get("rangeKey")); + + signed.put("hashKey", SIGNED_TEST_VALUE.get("hashKey")); + signed.put("rangeKey", SIGNED_TEST_VALUE.get("rangeKey")); + + mixed.put("hashKey", MIXED_TEST_VALUE.get("hashKey")); + mixed.put("rangeKey", MIXED_TEST_VALUE.get("rangeKey")); + + encrypted.put("hashKey", ENCRYPTED_TEST_VALUE.get("hashKey")); + encrypted.put("rangeKey", ENCRYPTED_TEST_VALUE.get("rangeKey")); + + hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); + hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); + hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); + + // check untouched attr + assertTrue( + new DdbRecordMatcher(UNTOUCHED_TEST_VALUE, false).matches(getItems(untouched, tableName))); + + // check signed attr + // Describe what actions need to be taken for each attribute + Map> actions = new HashMap<>(); + for (final String attr : SIGNED_TEST_VALUE.keySet()) { + actions.put(attr, signOnly); + } + response = getItems(signed, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(SIGNED_TEST_VALUE, false).matches(decryptedRecord)); + + // check mixed attr + actions = new HashMap<>(); + for (final String attr : MIXED_TEST_VALUE.keySet()) { + switch (attr) { + case "rangeKey": + case "hashKey": + case "version": + case "stringValue": + actions.put(attr, signOnly); + break; + case "intValue": + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + response = getItems(mixed, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(MIXED_TEST_VALUE, false).matches(decryptedRecord)); + + // check encrypted attr + actions = new HashMap<>(); + for (final String attr : ENCRYPTED_TEST_VALUE.keySet()) { + switch (attr) { + case "hashKey": + case "rangeKey": + case "version": + actions.put(attr, signOnly); + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + response = getItems(encrypted, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(ENCRYPTED_TEST_VALUE, false).matches(decryptedRecord)); + + assertEquals("Foo", getItems(hashKey1, "HashKeyOnly").get("hashKey").s()); + assertEquals("Bar", getItems(hashKey2, "HashKeyOnly").get("hashKey").s()); + assertEquals("Baz", getItems(hashKey3, "HashKeyOnly").get("hashKey").s()); + + Map key = new HashMap<>(); + for (int i = 1; i <= 3; ++i) { + key.put("hashKey", AttributeValue.builder().n("0").build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, "TableName"); + assertEquals(0, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + + key.put("hashKey", AttributeValue.builder().n("1").build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, "TableName"); + assertEquals(1, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + + key.put("hashKey", AttributeValue.builder().n(String.valueOf(4 + i)).build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, "TableName"); + assertEquals(4 + i, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + } + } + + private void assertVersionCompatibility_2(EncryptionMaterialsProvider provider, String tableName) + throws GeneralSecurityException { + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + Map response; + Map decryptedRecord; + EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + + // Set up maps for table items + HashMap untouched = new HashMap<>(); + HashMap signed = new HashMap<>(); + HashMap mixed = new HashMap<>(); + HashMap encrypted = new HashMap<>(); + HashMap hashKey1 = new HashMap<>(); + HashMap hashKey2 = new HashMap<>(); + HashMap hashKey3 = new HashMap<>(); + + untouched.put("hashKey", UNTOUCHED_TEST_VALUE_2.get("hashKey")); + untouched.put("rangeKey", UNTOUCHED_TEST_VALUE_2.get("rangeKey")); + + signed.put("hashKey", SIGNED_TEST_VALUE_2.get("hashKey")); + signed.put("rangeKey", SIGNED_TEST_VALUE_2.get("rangeKey")); + + mixed.put("hashKey", MIXED_TEST_VALUE_2.get("hashKey")); + mixed.put("rangeKey", MIXED_TEST_VALUE_2.get("rangeKey")); + + encrypted.put("hashKey", ENCRYPTED_TEST_VALUE_2.get("hashKey")); + encrypted.put("rangeKey", ENCRYPTED_TEST_VALUE_2.get("rangeKey")); + + hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); + hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); + hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); + + // check untouched attr + assert new DdbRecordMatcher(UNTOUCHED_TEST_VALUE_2, false) + .matches(getItems(untouched, tableName)); + + // check signed attr + // Describe what actions need to be taken for each attribute + Map> actions = new HashMap<>(); + for (final String attr : SIGNED_TEST_VALUE_2.keySet()) { + actions.put(attr, signOnly); + } + response = getItems(signed, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(SIGNED_TEST_VALUE_2, false).matches(decryptedRecord)); + + // check mixed attr + actions = new HashMap<>(); + for (final String attr : MIXED_TEST_VALUE_2.keySet()) { + switch (attr) { + case "rangeKey": + case "hashKey": + case "version": + case "stringValue": + case "doubleValue": + case "doubleSet": + actions.put(attr, signOnly); + break; + case "intValue": + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + response = getItems(mixed, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(MIXED_TEST_VALUE_2, false).matches(decryptedRecord)); + + // check encrypted attr + actions = new HashMap<>(); + for (final String attr : ENCRYPTED_TEST_VALUE_2.keySet()) { + switch (attr) { + case "hashKey": + case "rangeKey": + case "version": + actions.put(attr, signOnly); + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + response = getItems(encrypted, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(ENCRYPTED_TEST_VALUE_2, false).matches(decryptedRecord)); + + // check HashKey Table + assertEquals("Foo", getItems(hashKey1, "HashKeyOnly").get("hashKey").s()); + assertEquals("Bar", getItems(hashKey2, "HashKeyOnly").get("hashKey").s()); + assertEquals("Baz", getItems(hashKey3, "HashKeyOnly").get("hashKey").s()); + + // Check Hash and Range Key Values + Map key = new HashMap<>(); + for (int i = 1; i <= 3; ++i) { + key.put("hashKey", AttributeValue.builder().n("0").build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, tableName); + assertEquals(0, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + + key.put("hashKey", AttributeValue.builder().n("1").build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, tableName); + assertEquals(1, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + + key.put("hashKey", AttributeValue.builder().n(String.valueOf(4 + i)).build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, tableName); + assertEquals(4 + i, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + } + } + + private static String stripFilePath(String path) { + return path.replaceFirst("file://", ""); + } + + @JsonDeserialize(using = AttributeValueDeserializer.class) + public abstract static class DeserializedAttributeValue implements AttributeValue.Builder {} +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java new file mode 100644 index 0000000000..fd3bf37ace --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java @@ -0,0 +1,296 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.SignatureException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.crypto.spec.SecretKeySpec; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.TestDelegatedKey; + +public class DelegatedEncryptionTest { + private static SecretKeySpec rawEncryptionKey; + private static SecretKeySpec rawMacKey; + private static DelegatedKey encryptionKey; + private static DelegatedKey macKey; + + private EncryptionMaterialsProvider prov; + private DynamoDbEncryptor encryptor; + private Map attribs; + private EncryptionContext context; + + @BeforeClass + public static void setupClass() { + rawEncryptionKey = new SecretKeySpec(Utils.getRandom(32), "AES"); + encryptionKey = new TestDelegatedKey(rawEncryptionKey); + + rawMacKey = new SecretKeySpec(Utils.getRandom(32), "HmacSHA256"); + macKey = new TestDelegatedKey(rawMacKey); + } + + @BeforeMethod + public void setUp() { + prov = new SymmetricStaticProvider(encryptionKey, macKey, + Collections.emptyMap()); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + + attribs = new HashMap<>(); + attribs.put("intValue", AttributeValue.builder().n("123").build()); + attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + attribs.put("byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); + attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); + attribs.put("hashKey", AttributeValue.builder().n("5").build()); + attribs.put("rangeKey", AttributeValue.builder().n("7").build()); + attribs.put("version", AttributeValue.builder().n("0").build()); + + context = EncryptionContext.builder() + .tableName("TableName") + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + } + + @Test + public void testSetSignatureFieldName() { + assertNotNull(encryptor.getSignatureFieldName()); + encryptor.setSignatureFieldName("A different value"); + assertEquals("A different value", encryptor.getSignatureFieldName()); + } + + @Test + public void testSetMaterialDescriptionFieldName() { + assertNotNull(encryptor.getMaterialDescriptionFieldName()); + encryptor.setMaterialDescriptionFieldName("A different value"); + assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); + } + + @Test + public void fullEncryption() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has been encrypted (we'll assume the others are correct as well) + assertTrue(encryptedAttributes.containsKey("stringValue")); + assertNull(encryptedAttributes.get("stringValue").s()); + assertNotNull(encryptedAttributes.get("stringValue").b()); + } + + @Test(expectedExceptions = SignatureException.class) + public void fullEncryptionBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void badVersionNumber() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + SdkBytes materialDescription = + encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); + byte[] rawArray = materialDescription.asByteArray(); + assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. + rawArray[0] = 100; + encryptedAttributes.put( + encryptor.getMaterialDescriptionFieldName(), + AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test + public void signedOnly() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test + public void signedOnlyNullCryptoKey() throws GeneralSecurityException { + prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyNoSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.remove(encryptor.getSignatureFieldName()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test + public void RsaSignedOnly() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-" + ); + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-" + ); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.replace("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { + assertEquals(o1.b(), o2.b()); + assertSetsEqual(o1.bs(), o2.bs()); + assertEquals(o1.n(), o2.n()); + assertSetsEqual(o1.ns(), o2.ns()); + assertEquals(o1.s(), o2.s()); + assertSetsEqual(o1.ss(), o2.ss()); + } + + private void assertSetsEqual(Collection c1, Collection c2) { + assertFalse(c1 == null ^ c2 == null); + if (c1 != null) { + Set s1 = new HashSet<>(c1); + Set s2 = new HashSet<>(c2); + assertEquals(s1, s2); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java new file mode 100644 index 0000000000..ce22c396fa --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java @@ -0,0 +1,280 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.SignatureException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.crypto.spec.SecretKeySpec; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.TestDelegatedKey; + +public class DelegatedEnvelopeEncryptionTest { + private static SecretKeySpec rawEncryptionKey; + private static SecretKeySpec rawMacKey; + private static DelegatedKey encryptionKey; + private static DelegatedKey macKey; + + private EncryptionMaterialsProvider prov; + private DynamoDbEncryptor encryptor; + private Map attribs; + private EncryptionContext context; + + @BeforeClass + public static void setupClass() { + rawEncryptionKey = new SecretKeySpec(Utils.getRandom(32), "AES"); + encryptionKey = new TestDelegatedKey(rawEncryptionKey); + + rawMacKey = new SecretKeySpec(Utils.getRandom(32), "HmacSHA256"); + macKey = new TestDelegatedKey(rawMacKey); + } + + @BeforeMethod + public void setUp() throws Exception { + prov = + new WrappedMaterialsProvider( + encryptionKey, encryptionKey, macKey, Collections.emptyMap()); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + + attribs = new HashMap(); + attribs.put("intValue", AttributeValue.builder().n("123").build()); + attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + attribs.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5}))).build()); + attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); + attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); + attribs.put("hashKey", AttributeValue.builder().n("5").build()); + attribs.put("rangeKey", AttributeValue.builder().n("7").build()); + attribs.put("version", AttributeValue.builder().n("0").build()); + + context = + new EncryptionContext.Builder() + .tableName("TableName") + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + } + + @Test + public void testSetSignatureFieldName() { + assertNotNull(encryptor.getSignatureFieldName()); + encryptor.setSignatureFieldName("A different value"); + assertEquals("A different value", encryptor.getSignatureFieldName()); + } + + @Test + public void testSetMaterialDescriptionFieldName() { + assertNotNull(encryptor.getMaterialDescriptionFieldName()); + encryptor.setMaterialDescriptionFieldName("A different value"); + assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); + } + + @Test + public void fullEncryption() throws GeneralSecurityException{ + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has been encrypted (we'll assume the others are correct as well) + assertTrue(encryptedAttributes.containsKey("stringValue")); + assertNull(encryptedAttributes.get("stringValue").s()); + assertNotNull(encryptedAttributes.get("stringValue").b()); + } + + @Test(expectedExceptions = SignatureException.class) + public void fullEncryptionBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void badVersionNumber() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + SdkBytes materialDescription = + encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); + byte[] rawArray = materialDescription.asByteArray(); + assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. + rawArray[0] = 100; + encryptedAttributes.put( + encryptor.getMaterialDescriptionFieldName(), + AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test + public void signedOnlyNullCryptoKey() throws GeneralSecurityException { + prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyNoSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.remove(encryptor.getSignatureFieldName()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test + public void RsaSignedOnly() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-"); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-"); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.replace("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { + assertEquals(o1.b(), o2.b()); + assertSetsEqual(o1.bs(), o2.bs()); + assertEquals(o1.n(), o2.n()); + assertSetsEqual(o1.ns(), o2.ns()); + assertEquals(o1.s(), o2.s()); + assertSetsEqual(o1.ss(), o2.ss()); + } + + private void assertSetsEqual(Collection c1, Collection c2) { + assertFalse(c1 == null ^ c2 == null); + if (c1 != null) { + Set s1 = new HashSet<>(c1); + Set s2 = new HashSet<>(c2); + assertEquals(s1, s2); + } + } + +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java new file mode 100644 index 0000000000..87fb8353bb --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java @@ -0,0 +1,591 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import static java.util.stream.Collectors.toMap; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; +import static org.testng.collections.Sets.newHashSet; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableName; + +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.security.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.mockito.internal.util.collections.Sets; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; + +public class DynamoDbEncryptorTest { + private static SecretKey encryptionKey; + private static SecretKey macKey; + + private InstrumentedEncryptionMaterialsProvider prov; + private DynamoDbEncryptor encryptor; + private Map attribs; + private EncryptionContext context; + private static final String OVERRIDDEN_TABLE_NAME = "TheBestTableName"; + + @BeforeClass + public static void setUpClass() throws Exception { + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, Utils.getRng()); + encryptionKey = aesGen.generateKey(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + prov = new InstrumentedEncryptionMaterialsProvider( + new SymmetricStaticProvider(encryptionKey, macKey, + Collections.emptyMap())); + encryptor = DynamoDbEncryptor.getInstance(prov, "enryptor-"); + + attribs = new HashMap<>(); + attribs.put("intValue", AttributeValue.builder().n("123").build()); + attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + attribs.put("byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); + attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); + attribs.put("hashKey", AttributeValue.builder().n("5").build()); + attribs.put("rangeKey", AttributeValue.builder().n("7").build()); + attribs.put("version", AttributeValue.builder().n("0").build()); + + // New(er) data types + attribs.put("booleanTrue", AttributeValue.builder().bool(true).build()); + attribs.put("booleanFalse", AttributeValue.builder().bool(false).build()); + attribs.put("nullValue", AttributeValue.builder().nul(true).build()); + Map tmpMap = new HashMap<>(attribs); + attribs.put("listValue", AttributeValue.builder().l( + AttributeValue.builder().s("I'm a string").build(), + AttributeValue.builder().n("42").build(), + AttributeValue.builder().s("Another string").build(), + AttributeValue.builder().ns("1", "4", "7").build(), + AttributeValue.builder().m(tmpMap).build(), + AttributeValue.builder().l( + AttributeValue.builder().n("123").build(), + AttributeValue.builder().ns("1", "200", "10", "15", "0").build(), + AttributeValue.builder().ss("Goodbye", "Cruel", "World", "!").build() + ).build()).build()); + tmpMap = new HashMap<>(); + tmpMap.put("another string", AttributeValue.builder().s("All around the cobbler's bench").build()); + tmpMap.put("next line", AttributeValue.builder().ss("the monkey", "chased", "the weasel").build()); + tmpMap.put("more lyrics", AttributeValue.builder().l( + AttributeValue.builder().s("the monkey").build(), + AttributeValue.builder().s("thought twas").build(), + AttributeValue.builder().s("all in fun").build() + ).build()); + tmpMap.put("weasel", AttributeValue.builder().m(Collections.singletonMap("pop", AttributeValue.builder().bool(true).build())).build()); + attribs.put("song", AttributeValue.builder().m(tmpMap).build()); + + context = EncryptionContext.builder() + .tableName("TableName") + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + } + + @Test + public void testSetSignatureFieldName() { + assertNotNull(encryptor.getSignatureFieldName()); + encryptor.setSignatureFieldName("A different value"); + assertEquals("A different value", encryptor.getSignatureFieldName()); + } + + @Test + public void testSetMaterialDescriptionFieldName() { + assertNotNull(encryptor.getMaterialDescriptionFieldName()); + encryptor.setMaterialDescriptionFieldName("A different value"); + assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); + } + + @Test + public void fullEncryption() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has been encrypted (we'll assume the others are correct as well) + assertTrue(encryptedAttributes.containsKey("stringValue")); + assertNull(encryptedAttributes.get("stringValue").s()); + assertNotNull(encryptedAttributes.get("stringValue").b()); + + // Make sure we're calling the proper getEncryptionMaterials method + assertEquals( + "Wrong getEncryptionMaterials() called", + 1, + prov.getCallCount("getEncryptionMaterials(EncryptionContext context)")); + } + + @Test + public void ensureEncryptedAttributesUnmodified() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + String encryptedString = encryptedAttributes.toString(); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + + assertEquals(encryptedString, encryptedAttributes.toString()); + } + + @Test(expectedExceptions = SignatureException.class) + public void fullEncryptionBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void badVersionNumber() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + SdkBytes materialDescription = + encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); + byte[] rawArray = materialDescription.asByteArray(); + assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. + rawArray[0] = 100; + encryptedAttributes.put( + encryptor.getMaterialDescriptionFieldName(), + AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test + public void signedOnly() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test + public void signedOnlyNullCryptoKey() throws GeneralSecurityException { + prov = + new InstrumentedEncryptionMaterialsProvider( + new SymmetricStaticProvider(null, macKey, Collections.emptyMap())); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyNoSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.remove(encryptor.getSignatureFieldName()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test + public void RsaSignedOnly() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-"); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-"); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + /** + * Tests that no exception is thrown when the encryption context override operator is null + * + * @throws GeneralSecurityException + */ + @Test + public void testNullEncryptionContextOperator() throws GeneralSecurityException { + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + encryptor.setEncryptionContextOverrideOperator(null); + encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); + } + + /** + * Tests decrypt and encrypt with an encryption context override operator + */ + @Test + public void testTableNameOverriddenEncryptionContextOperator() throws GeneralSecurityException { + // Ensure that the table name is different from what we override the table to. + assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + encryptor.setEncryptionContextOverrideOperator( + overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); + Map encryptedItems = + encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); + Map decryptedItems = + encryptor.decryptAllFieldsExcept(encryptedItems, context, Collections.emptyList()); + assertThat(decryptedItems, AttrMatcher.match(attribs)); + } + + + /** + * Tests encrypt with an encryption context override operator, and a second encryptor without an override + */ + @Test + public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor() + throws GeneralSecurityException { + // Ensure that the table name is different from what we override the table to. + assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + encryptor.setEncryptionContextOverrideOperator( + overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); + Map encryptedItems = + encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); + + EncryptionContext expectedOverriddenContext = + new EncryptionContext.Builder(context).tableName("TheBestTableName").build(); + Map decryptedItems = + encryptorWithoutOverride.decryptAllFieldsExcept( + encryptedItems, expectedOverriddenContext, Collections.emptyList()); + assertThat(decryptedItems, AttrMatcher.match(attribs)); + } + + /** + * Tests encrypt with an encryption context override operator, and a second encryptor without an override + */ + @Test(expectedExceptions = SignatureException.class) + public void + testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptorButTheOriginalEncryptionContext() + throws GeneralSecurityException { + // Ensure that the table name is different from what we override the table to. + assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + encryptor.setEncryptionContextOverrideOperator( + overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); + Map encryptedItems = + encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); + + // Use the original encryption context, and expect a signature failure + Map decryptedItems = + encryptorWithoutOverride.decryptAllFieldsExcept( + encryptedItems, context, Collections.emptyList()); + } + + @Test + public void EcdsaSignedOnly() throws GeneralSecurityException { + + encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void EcdsaSignedOnlyBadSignature() throws GeneralSecurityException { + + encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test + public void toByteArray() throws ReflectiveOperationException { + final byte[] expected = new byte[] {0, 1, 2, 3, 4, 5}; + assertToByteArray("Wrap", expected, ByteBuffer.wrap(expected)); + assertToByteArray("Wrap-RO", expected, ByteBuffer.wrap(expected).asReadOnlyBuffer()); + + assertToByteArray("Wrap-Truncated-Sliced", expected, ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 6).slice()); + assertToByteArray("Wrap-Offset-Sliced", expected, ByteBuffer.wrap(new byte[] {6, 0, 1, 2, 3, 4, 5, 6}, 1, 6).slice()); + assertToByteArray("Wrap-Truncated", expected, ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 6)); + assertToByteArray("Wrap-Offset", expected, ByteBuffer.wrap(new byte[] {6, 0, 1, 2, 3, 4, 5, 6}, 1, 6)); + + ByteBuffer buff = ByteBuffer.allocate(expected.length + 10); + buff.put(expected); + buff.flip(); + assertToByteArray("Normal", expected, buff); + + buff = ByteBuffer.allocateDirect(expected.length + 10); + buff.put(expected); + buff.flip(); + assertToByteArray("Direct", expected, buff); + } + + @Test + public void testDecryptWithPlaintextItem() throws GeneralSecurityException { + Map> attributeWithEmptyEncryptionFlags = + attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet())); + + Map decryptedAttributes = + encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + } + + /* + Test decrypt with a map that contains a new key (not included in attribs) with an encryption flag set that contains ENCRYPT and SIGN. + */ + @Test + public void testDecryptWithPlainTextItemAndAdditionNewAttributeHavingEncryptionFlag() + throws GeneralSecurityException { + Map> attributeWithEmptyEncryptionFlags = + attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet())); + attributeWithEmptyEncryptionFlags.put( + "newAttribute", Sets.newSet(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); + + Map decryptedAttributes = + encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + } + private void assertToByteArray( + final String msg, final byte[] expected, final ByteBuffer testValue) + throws ReflectiveOperationException { + Method m = DynamoDbEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); + m.setAccessible(true); + + int oldPosition = testValue.position(); + int oldLimit = testValue.limit(); + + assertThat(m.invoke(null, testValue), is(expected)); + assertEquals(msg + ":Position", oldPosition, testValue.position()); + assertEquals(msg + ":Limit", oldLimit, testValue.limit()); + } + + private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { + assertEquals(o1.b(), o2.b()); + assertSetsEqual(o1.bs(), o2.bs()); + assertEquals(o1.n(), o2.n()); + assertSetsEqual(o1.ns(), o2.ns()); + assertEquals(o1.s(), o2.s()); + assertSetsEqual(o1.ss(), o2.ss()); + } + + private void assertSetsEqual(Collection c1, Collection c2) { + assertFalse(c1 == null ^ c2 == null); + if (c1 != null) { + Set s1 = new HashSet<>(c1); + Set s2 = new HashSet<>(c2); + assertEquals(s1, s2); + } + } + + private EncryptionMaterialsProvider getMaterialProviderwithECDSA() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException { + Security.addProvider(new BouncyCastleProvider()); + ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp384r1"); + KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC"); + g.initialize(ecSpec, Utils.getRng()); + KeyPair keypair = g.generateKeyPair(); + Map description = new HashMap<>(); + description.put(DynamoDbEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); + return new SymmetricStaticProvider(null, keypair, description); + } + + private static final class InstrumentedEncryptionMaterialsProvider implements EncryptionMaterialsProvider { + private final EncryptionMaterialsProvider delegate; + private final ConcurrentHashMap calls = new ConcurrentHashMap<>(); + + InstrumentedEncryptionMaterialsProvider(EncryptionMaterialsProvider delegate) { + this.delegate = delegate; + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + incrementMethodCount("getDecryptionMaterials()"); + return delegate.getDecryptionMaterials(context); + } + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + incrementMethodCount("getEncryptionMaterials(EncryptionContext context)"); + return delegate.getEncryptionMaterials(context); + } + + @Override + public void refresh() { + incrementMethodCount("refresh()"); + delegate.refresh(); + } + + int getCallCount(String method) { + AtomicInteger count = calls.get(method); + if (count != null) { + return count.intValue(); + } else { + return 0; + } + } + + @SuppressWarnings("unused") + public void resetCallCounts() { + calls.clear(); + } + + private void incrementMethodCount(String method) { + AtomicInteger oldValue = calls.putIfAbsent(method, new AtomicInteger(1)); + if (oldValue != null) { + oldValue.incrementAndGet(); + } + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java new file mode 100644 index 0000000000..8320e79526 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java @@ -0,0 +1,567 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Security; +import java.security.SignatureException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.crypto.KeyGenerator; + +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class DynamoDbSignerTest { + // These use the Key type (rather than PublicKey, PrivateKey, and SecretKey) + // to test the routing logic within the signer. + private static Key pubKeyRsa; + private static Key privKeyRsa; + private static Key macKey; + private DynamoDbSigner signerRsa; + private DynamoDbSigner signerEcdsa; + private static Key pubKeyEcdsa; + private static Key privKeyEcdsa; + + @BeforeClass + public static void setUpClass() throws Exception { + + // RSA key generation + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + pubKeyRsa = sigPair.getPublic(); + privKeyRsa = sigPair.getPrivate(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + + Security.addProvider(new BouncyCastleProvider()); + ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp384r1"); + KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC"); + g.initialize(ecSpec, Utils.getRng()); + KeyPair keypair = g.generateKeyPair(); + pubKeyEcdsa = keypair.getPublic(); + privKeyEcdsa = keypair.getPrivate(); + } + + @BeforeMethod + public void setUp() { + signerRsa = DynamoDbSigner.getInstance("SHA256withRSA", Utils.getRng()); + signerEcdsa = DynamoDbSigner.getInstance("SHA384withECDSA", Utils.getRng()); + } + + @Test + public void mac() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", + AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void macLists() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().ss("Value1", "Value2", "Value3").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().ns("100", "200", "300").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", + AttributeValue.builder() + .bs( + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})), + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void macListsUnsorted() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().ss("Value3", "Value1", "Value2").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().ns("100", "300", "200").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", + AttributeValue.builder() + .bs( + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1})), + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + Map scrambledAttributes = new HashMap(); + scrambledAttributes.put("Key1", AttributeValue.builder().ss("Value1", "Value2", "Value3").build()); + scrambledAttributes.put("Key2", AttributeValue.builder().ns("100", "200", "300").build()); + scrambledAttributes.put( + "Key3", + AttributeValue.builder() + .bs( + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})), + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1}))) + .build()); + + signerRsa.verifySignature( + scrambledAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void macNoAdMatchesEmptyAd() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = signerRsa.calculateSignature(itemAttributes, attributeFlags, null, macKey); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void macWithIgnoredChange() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void macChangedValue() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void macChangedFlag() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void macChangedAssociatedData() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[] {3, 2, 1}, macKey); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[] {1, 2, 3}, macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void sig() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test + public void sigWithReadOnlySignature() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + signerRsa.verifySignature( + itemAttributes, + attributeFlags, + new byte[0], + pubKeyRsa, + ByteBuffer.wrap(signature).asReadOnlyBuffer()); + } + + @Test + public void sigNoAdMatchesEmptyAd() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, null, privKeyRsa); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test + public void sigWithIgnoredChange() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigChangedValue() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigChangedFlag() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigChangedAssociatedData() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + signerRsa.verifySignature( + itemAttributes, + attributeFlags, + new byte[] {1, 2, 3}, + pubKeyRsa, + ByteBuffer.wrap(signature)); + } + + @Test + public void sigEcdsa() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + signerEcdsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); + } + + @Test + public void sigEcdsaWithReadOnlySignature() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + signerEcdsa.verifySignature( + itemAttributes, + attributeFlags, + new byte[0], + pubKeyEcdsa, + ByteBuffer.wrap(signature).asReadOnlyBuffer()); + } + + @Test + public void sigEcdsaNoAdMatchesEmptyAd() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, null, privKeyEcdsa); + + signerEcdsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); + } + + @Test + public void sigEcdsaWithIgnoredChange() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); + signerEcdsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigEcdsaChangedValue() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); + signerEcdsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigEcdsaChangedAssociatedData() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + signerEcdsa.verifySignature( + itemAttributes, + attributeFlags, + new byte[] {1, 2, 3}, + pubKeyEcdsa, + ByteBuffer.wrap(signature)); + } +} \ No newline at end of file diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java new file mode 100644 index 0000000000..b9258c5f83 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class AsymmetricRawMaterialsTest { + private static SecureRandom rnd; + private static KeyPair encryptionPair; + private static SecretKey macKey; + private static KeyPair sigPair; + private Map description; + + @BeforeClass + public static void setUpClass() throws NoSuchAlgorithmException { + rnd = new SecureRandom(); + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, rnd); + encryptionPair = rsaGen.generateKeyPair(); + sigPair = rsaGen.generateKeyPair(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, rnd); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + } + + @Test + public void macNoDescription() throws GeneralSecurityException { + AsymmetricRawMaterials matEncryption = new AsymmetricRawMaterials(encryptionPair, macKey); + assertEquals(macKey, matEncryption.getSigningKey()); + assertEquals(macKey, matEncryption.getVerificationKey()); + assertFalse(matEncryption.getMaterialDescription().isEmpty()); + + SecretKey envelopeKey = matEncryption.getEncryptionKey(); + assertEquals(envelopeKey, matEncryption.getDecryptionKey()); + + AsymmetricRawMaterials matDecryption = + new AsymmetricRawMaterials(encryptionPair, macKey, matEncryption.getMaterialDescription()); + assertEquals(macKey, matDecryption.getSigningKey()); + assertEquals(macKey, matDecryption.getVerificationKey()); + assertEquals(envelopeKey, matDecryption.getEncryptionKey()); + assertEquals(envelopeKey, matDecryption.getDecryptionKey()); + } + + @Test + public void macWithDescription() throws GeneralSecurityException { + AsymmetricRawMaterials matEncryption = + new AsymmetricRawMaterials(encryptionPair, macKey, description); + assertEquals(macKey, matEncryption.getSigningKey()); + assertEquals(macKey, matEncryption.getVerificationKey()); + assertFalse(matEncryption.getMaterialDescription().isEmpty()); + assertEquals("test value", matEncryption.getMaterialDescription().get("TestKey")); + + SecretKey envelopeKey = matEncryption.getEncryptionKey(); + assertEquals(envelopeKey, matEncryption.getDecryptionKey()); + + AsymmetricRawMaterials matDecryption = + new AsymmetricRawMaterials(encryptionPair, macKey, matEncryption.getMaterialDescription()); + assertEquals(macKey, matDecryption.getSigningKey()); + assertEquals(macKey, matDecryption.getVerificationKey()); + assertEquals(envelopeKey, matDecryption.getEncryptionKey()); + assertEquals(envelopeKey, matDecryption.getDecryptionKey()); + assertEquals("test value", matDecryption.getMaterialDescription().get("TestKey")); + } + + @Test + public void sigNoDescription() throws GeneralSecurityException { + AsymmetricRawMaterials matEncryption = new AsymmetricRawMaterials(encryptionPair, sigPair); + assertEquals(sigPair.getPrivate(), matEncryption.getSigningKey()); + assertEquals(sigPair.getPublic(), matEncryption.getVerificationKey()); + assertFalse(matEncryption.getMaterialDescription().isEmpty()); + + SecretKey envelopeKey = matEncryption.getEncryptionKey(); + assertEquals(envelopeKey, matEncryption.getDecryptionKey()); + + AsymmetricRawMaterials matDecryption = + new AsymmetricRawMaterials(encryptionPair, sigPair, matEncryption.getMaterialDescription()); + assertEquals(sigPair.getPrivate(), matDecryption.getSigningKey()); + assertEquals(sigPair.getPublic(), matDecryption.getVerificationKey()); + assertEquals(envelopeKey, matDecryption.getEncryptionKey()); + assertEquals(envelopeKey, matDecryption.getDecryptionKey()); + } + + @Test + public void sigWithDescription() throws GeneralSecurityException { + AsymmetricRawMaterials matEncryption = + new AsymmetricRawMaterials(encryptionPair, sigPair, description); + assertEquals(sigPair.getPrivate(), matEncryption.getSigningKey()); + assertEquals(sigPair.getPublic(), matEncryption.getVerificationKey()); + assertFalse(matEncryption.getMaterialDescription().isEmpty()); + assertEquals("test value", matEncryption.getMaterialDescription().get("TestKey")); + + SecretKey envelopeKey = matEncryption.getEncryptionKey(); + assertEquals(envelopeKey, matEncryption.getDecryptionKey()); + + AsymmetricRawMaterials matDecryption = + new AsymmetricRawMaterials(encryptionPair, sigPair, matEncryption.getMaterialDescription()); + assertEquals(sigPair.getPrivate(), matDecryption.getSigningKey()); + assertEquals(sigPair.getPublic(), matDecryption.getVerificationKey()); + assertEquals(envelopeKey, matDecryption.getEncryptionKey()); + assertEquals(envelopeKey, matDecryption.getDecryptionKey()); + assertEquals("test value", matDecryption.getMaterialDescription().get("TestKey")); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java new file mode 100644 index 0000000000..a6987ce792 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class SymmetricRawMaterialsTest { + private static SecretKey encryptionKey; + private static SecretKey macKey; + private static KeyPair sigPair; + private static SecureRandom rnd; + private Map description; + + @BeforeClass + public static void setUpClass() throws NoSuchAlgorithmException { + rnd = new SecureRandom(); + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, rnd); + sigPair = rsaGen.generateKeyPair(); + + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, rnd); + encryptionKey = aesGen.generateKey(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, rnd); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + } + + @Test + public void macNoDescription() throws NoSuchAlgorithmException { + SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, macKey); + assertEquals(encryptionKey, mat.getEncryptionKey()); + assertEquals(encryptionKey, mat.getDecryptionKey()); + assertEquals(macKey, mat.getSigningKey()); + assertEquals(macKey, mat.getVerificationKey()); + assertTrue(mat.getMaterialDescription().isEmpty()); + } + + @Test + public void macWithDescription() throws NoSuchAlgorithmException { + SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, macKey, description); + assertEquals(encryptionKey, mat.getEncryptionKey()); + assertEquals(encryptionKey, mat.getDecryptionKey()); + assertEquals(macKey, mat.getSigningKey()); + assertEquals(macKey, mat.getVerificationKey()); + assertEquals(description, mat.getMaterialDescription()); + assertEquals("test value", mat.getMaterialDescription().get("TestKey")); + } + + @Test + public void sigNoDescription() throws NoSuchAlgorithmException { + SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, sigPair); + assertEquals(encryptionKey, mat.getEncryptionKey()); + assertEquals(encryptionKey, mat.getDecryptionKey()); + assertEquals(sigPair.getPrivate(), mat.getSigningKey()); + assertEquals(sigPair.getPublic(), mat.getVerificationKey()); + assertTrue(mat.getMaterialDescription().isEmpty()); + } + + @Test + public void sigWithDescription() throws NoSuchAlgorithmException { + SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, sigPair, description); + assertEquals(encryptionKey, mat.getEncryptionKey()); + assertEquals(encryptionKey, mat.getDecryptionKey()); + assertEquals(sigPair.getPrivate(), mat.getSigningKey()); + assertEquals(sigPair.getPublic(), mat.getVerificationKey()); + assertEquals(description, mat.getMaterialDescription()); + assertEquals("test value", mat.getMaterialDescription().get("TestKey")); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java new file mode 100644 index 0000000000..8f71ac7b28 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +public class AsymmetricStaticProviderTest { + private static KeyPair encryptionPair; + private static SecretKey macKey; + private static KeyPair sigPair; + private Map description; + private EncryptionContext ctx; + + @BeforeClass + public static void setUpClass() throws Exception { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + sigPair = rsaGen.generateKeyPair(); + encryptionPair = rsaGen.generateKeyPair(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + description = Collections.unmodifiableMap(description); + ctx = new EncryptionContext.Builder().build(); + } + + @Test + public void constructWithMac() throws GeneralSecurityException { + AsymmetricStaticProvider prov = + new AsymmetricStaticProvider( + encryptionPair, macKey, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void constructWithSigPair() throws GeneralSecurityException { + AsymmetricStaticProvider prov = + new AsymmetricStaticProvider( + encryptionPair, sigPair, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void randomEnvelopeKeys() throws GeneralSecurityException { + AsymmetricStaticProvider prov = + new AsymmetricStaticProvider( + encryptionPair, macKey, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey2 = eMat2.getEncryptionKey(); + assertEquals(macKey, eMat.getSigningKey()); + + assertFalse("Envelope keys must be different", encryptionKey.equals(encryptionKey2)); + } + + @Test + public void testRefresh() { + // This does nothing, make sure we don't throw and exception. + AsymmetricStaticProvider prov = + new AsymmetricStaticProvider(encryptionPair, macKey, description); + prov.refresh(); + } + + private static EncryptionContext ctx(EncryptionMaterials mat) { + return new EncryptionContext.Builder() + .materialDescription(mat.getMaterialDescription()) + .build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java new file mode 100644 index 0000000000..f286648332 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java @@ -0,0 +1,610 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; +import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; + +public class CachingMostRecentProviderTests { + private static final String TABLE_NAME = "keystoreTable"; + private static final String MATERIAL_NAME = "material"; + private static final String MATERIAL_PARAM = "materialName"; + private static final SecretKey AES_KEY = + new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "AES"); + private static final SecretKey HMAC_KEY = + new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); + private static final EncryptionMaterialsProvider BASE_PROVIDER = + new SymmetricStaticProvider(AES_KEY, HMAC_KEY); + private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); + + private DynamoDbClient client; + private Map methodCalls; + private ProvisionedThroughput throughput; + private ProviderStore store; + private EncryptionContext ctx; + + @BeforeMethod + public void setup() { + methodCalls = new HashMap(); + throughput = ProvisionedThroughput.builder().readCapacityUnits(1L).writeCapacityUnits(1L).build(); + + client = instrument(DynamoDBEmbedded.create().dynamoDbClient(), DynamoDbClient.class, methodCalls); + MetaStore.createTable(client, TABLE_NAME, throughput); + store = new MetaStore(client, TABLE_NAME, ENCRYPTOR); + ctx = new EncryptionContext.Builder().build(); + methodCalls.clear(); + } + + @Test + public void testConstructors() { + final CachingMostRecentProvider prov = + new CachingMostRecentProvider(store, MATERIAL_NAME, 100, 1000); + assertEquals(MATERIAL_NAME, prov.getMaterialName()); + assertEquals(100, prov.getTtlInMills()); + assertEquals(-1, prov.getCurrentVersion()); + assertEquals(0, prov.getLastUpdated()); + + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + assertEquals(MATERIAL_NAME, prov2.getMaterialName()); + assertEquals(500, prov2.getTtlInMills()); + assertEquals(-1, prov2.getCurrentVersion()); + assertEquals(0, prov2.getLastUpdated()); + } + + + @Test + public void testSmallMaxCacheSize() { + final Map attr1 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); + final EncryptionContext ctx1 = ctx(attr1); + final Map attr2 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); + final EncryptionContext ctx2 = ctx(attr2); + + final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 1); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the two materials are, in fact, different + assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); + + // Ensure the second set of materials are cached + final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + + // Ensure the first set of materials are no longer cached, due to being the LRU + final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); + } + + @Test + public void testSingleVersion() throws InterruptedException { + final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the cache is working + final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + // Let the TTL be exceeded + Thread.sleep(500); + final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); + assertEquals(2, methodCalls.size()); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); // To get provider + assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); + + assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); + assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey()); + // Check algorithms. Right now we only support AES and HmacSHA256 + assertEquals("AES", eMat1.getEncryptionKey().getAlgorithm()); + assertEquals("HmacSHA256", eMat1.getSigningKey().getAlgorithm()); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); + methodCalls.clear(); + assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); + assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); + final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); + assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); + assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); + final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); + assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); + assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + } + + @Test + public void testSingleVersionWithRefresh() throws InterruptedException { + final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the cache is working + final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + prov.refresh(); + final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); + prov.refresh(); + + assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); + assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey()); + + // Ensure that after cache refresh we only get one more hit as opposed to multiple + prov.getEncryptionMaterials(ctx); + Thread.sleep(700); + // Force refresh + prov.getEncryptionMaterials(ctx); + methodCalls.clear(); + // Check to ensure no more hits + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); + methodCalls.clear(); + assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); + assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); + final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); + assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); + assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); + final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); + assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); + assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + } + + @Test + public void testTwoVersions() throws InterruptedException { + final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Create the new material + store.newProvider(MATERIAL_NAME); + methodCalls.clear(); + + // Ensure the cache is working + final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + // Let the TTL be exceeded + Thread.sleep(500); + final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); + + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); // To retrieve current version + assertNull(methodCalls.get("putItem")); // No attempt to create a new item + assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); + + assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); + assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey())); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); + methodCalls.clear(); + assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); + assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); + final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); + assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); + assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); + final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); + assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); + assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); + // Get item will be hit once for the one old key + assertEquals(1, methodCalls.size()); + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + } + + @Test + public void testTwoVersionsWithRefresh() throws InterruptedException { + final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 100); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Create the new material + store.newProvider(MATERIAL_NAME); + methodCalls.clear(); + // Ensure the cache is working + final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + prov.refresh(); + final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); + + assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); + assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey())); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); + methodCalls.clear(); + assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); + assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); + final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); + assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); + assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); + final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); + assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); + assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); + // Get item will be hit once for the one old key + assertEquals(1, methodCalls.size()); + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + } + + @Test + public void testSingleVersionTwoMaterials() throws InterruptedException { + final Map attr1 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); + final EncryptionContext ctx1 = ctx(attr1); + final Map attr2 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); + final EncryptionContext ctx2 = ctx(attr2); + + final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the two materials are, in fact, different + assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); + + // Ensure the cache is working + final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); + final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); + + // Let the TTL be exceeded + Thread.sleep(500); + final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); + assertEquals(2, methodCalls.size()); + assertEquals(1, (int) methodCalls.get("query")); // To find current version + assertEquals(1, (int) methodCalls.get("getItem")); // To get the provider + assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); + methodCalls.clear(); + final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); + assertEquals(2, methodCalls.size()); + assertEquals(1, (int) methodCalls.get("query")); // To find current version + assertEquals(1, (int) methodCalls.get("getItem")); // To get the provider + assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); + + assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey()); + // Check algorithms. Right now we only support AES and HmacSHA256 + assertEquals("AES", eMat1_1.getEncryptionKey().getAlgorithm()); + assertEquals("AES", eMat1_2.getEncryptionKey().getAlgorithm()); + assertEquals("HmacSHA256", eMat1_1.getSigningKey().getAlgorithm()); + assertEquals("HmacSHA256", eMat1_2.getSigningKey().getAlgorithm()); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); + final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); + final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); + methodCalls.clear(); + assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); + assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); + assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); + assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); + final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); + final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); + assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); + assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); + assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); + assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); + final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); + final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); + assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); + assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); + assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); + assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + } + + @Test + public void testSingleVersionWithTwoMaterialsWithRefresh() throws InterruptedException { + final Map attr1 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); + final EncryptionContext ctx1 = ctx(attr1); + final Map attr2 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); + final EncryptionContext ctx2 = ctx(attr2); + + final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the two materials are, in fact, different + assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); + + // Ensure the cache is working + final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); + final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); + + prov.refresh(); + final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); + assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); + prov.refresh(); + + assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey()); + + // Ensure that after cache refresh we only get one more hit as opposed to multiple + prov.getEncryptionMaterials(ctx1); + prov.getEncryptionMaterials(ctx2); + Thread.sleep(700); + // Force refresh + prov.getEncryptionMaterials(ctx1); + prov.getEncryptionMaterials(ctx2); + methodCalls.clear(); + // Check to ensure no more hits + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); + final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); + final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); + methodCalls.clear(); + assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); + assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); + assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); + assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); + final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); + final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); + assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); + assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); + assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); + assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); + final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); + final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); + assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); + assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); + assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); + assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + } + + @Test + public void testTwoVersionsWithTwoMaterialsWithRefresh() throws InterruptedException { + final Map attr1 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); + final EncryptionContext ctx1 = ctx(attr1); + final Map attr2 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); + final EncryptionContext ctx2 = ctx(attr2); + + final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Create the new material + store.newProvider("material1"); + store.newProvider("material2"); + methodCalls.clear(); + // Ensure the cache is working + final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); + final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); + prov.refresh(); + final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); + final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); + assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); + assertEquals(1, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); + assertEquals(1, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); + + assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); + assertFalse(eMat1_1.getSigningKey().equals(eMat3_1.getSigningKey())); + assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); + assertFalse(eMat1_2.getSigningKey().equals(eMat3_2.getSigningKey())); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); + final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); + final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); + methodCalls.clear(); + assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); + assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); + assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); + assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); + final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); + final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); + assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); + assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); + assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); + assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); + final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); + final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); + assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); + assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); + assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); + assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); + // Get item will be hit twice, once for each old key + assertEquals(1, methodCalls.size()); + assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); + } + + private static EncryptionContext ctx(final Map attr) { + return new EncryptionContext.Builder().attributeValues(attr).build(); + } + + private static EncryptionContext ctx( + final EncryptionMaterials mat, Map attr) { + return new EncryptionContext.Builder() + .attributeValues(attr) + .materialDescription(mat.getMaterialDescription()) + .build(); + } + + private static EncryptionContext ctx(final EncryptionMaterials mat) { + return new EncryptionContext.Builder() + .materialDescription(mat.getMaterialDescription()) + .build(); + } + + private static class ExtendedProvider extends CachingMostRecentProvider { + public ExtendedProvider(ProviderStore keystore, long ttlInMillis, int maxCacheSize) { + super(keystore, null, ttlInMillis, maxCacheSize); + } + + @Override + public long getCurrentVersion() { + throw new UnsupportedOperationException(); + } + + @Override + protected String getMaterialName(final EncryptionContext context) { + return context.getAttributeValues().get(MATERIAL_PARAM).s(); + } + } + + @SuppressWarnings("unchecked") + private static T instrument( + final T obj, final Class clazz, final Map map) { + return (T) + Proxy.newProxyInstance( + clazz.getClassLoader(), + new Class[] {clazz}, + new InvocationHandler() { + private final Object lock = new Object(); + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) + throws Throwable { + synchronized (lock) { + try { + final Integer oldCount = map.get(method.getName()); + if (oldCount != null) { + map.put(method.getName(), oldCount + 1); + } else { + map.put(method.getName(), 1); + } + return method.invoke(obj, args); + } catch (final InvocationTargetException ex) { + throw ex.getCause(); + } + } + } + }); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java new file mode 100644 index 0000000000..f5832a1e62 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java @@ -0,0 +1,449 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.DecryptRequest; +import software.amazon.awssdk.services.kms.model.DecryptResponse; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.FakeKMS; + +public class DirectKmsMaterialsProviderTest { + private FakeKMS kms; + private String keyId; + private Map description; + private EncryptionContext ctx; + + @BeforeMethod + public void setUp() { + description = new HashMap<>(); + description.put("TestKey", "test value"); + description = Collections.unmodifiableMap(description); + ctx = new EncryptionContext.Builder().build(); + kms = new FakeKMS(); + keyId = kms.createKey().keyMetadata().keyId(); + } + + @Test + public void simple() { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + + String expectedEncAlg = + encryptionKey.getAlgorithm() + "/" + (encryptionKey.getEncoded().length * 8); + String expectedSigAlg = signingKey.getAlgorithm() + "/" + (signingKey.getEncoded().length * 8); + + Map kmsCtx = kms.getSingleEc(); + assertEquals(expectedEncAlg, kmsCtx.get("*" + WrappedRawMaterials.CONTENT_KEY_ALGORITHM + "*")); + assertEquals(expectedSigAlg, kmsCtx.get("*amzn-ddb-sig-alg*")); + } + + @Test + public void simpleWithKmsEc() { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().s("HashKeyValue").build()); + attrVals.put("rk", AttributeValue.builder().s("RangeKeyValue").build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + Map kmsCtx = kms.getSingleEc(); + assertEquals("HashKeyValue", kmsCtx.get("hk")); + assertEquals("RangeKeyValue", kmsCtx.get("rk")); + assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + } + + @Test + public void simpleWithKmsEc2() throws GeneralSecurityException { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().n("10").build()); + attrVals.put("rk", AttributeValue.builder().n("20").build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + Map kmsCtx = kms.getSingleEc(); + assertEquals("10", kmsCtx.get("hk")); + assertEquals("20", kmsCtx.get("rk")); + assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + } + + @Test + public void simpleWithKmsEc3() throws GeneralSecurityException { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + Map attrVals = new HashMap<>(); + attrVals.put( + "hk", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap("Foo".getBytes(StandardCharsets.UTF_8)))) + .build()); + attrVals.put( + "rk", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap("Bar".getBytes(StandardCharsets.UTF_8)))) + .build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + assertNotNull(signingKey); + Map kmsCtx = kms.getSingleEc(); + assertEquals(Base64.encodeToString("Foo".getBytes(StandardCharsets.UTF_8)), kmsCtx.get("hk")); + assertEquals(Base64.encodeToString("Bar".getBytes(StandardCharsets.UTF_8)), kmsCtx.get("rk")); + assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + } + + @Test + public void randomEnvelopeKeys() throws GeneralSecurityException { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey2 = eMat2.getEncryptionKey(); + + assertFalse("Envelope keys must be different", encryptionKey.equals(encryptionKey2)); + } + + @Test + public void testRefresh() { + // This does nothing, make sure we don't throw and exception. + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + prov.refresh(); + } + + @Test + public void explicitContentKeyAlgorithm() throws GeneralSecurityException { + Map desc = new HashMap<>(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); + + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + } + + @Test + public void explicitContentKeyLength128() throws GeneralSecurityException { + Map desc = new HashMap<>(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(16, encryptionKey.getEncoded().length); // 128 Bits + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES/128", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", eMat.getEncryptionKey().getAlgorithm()); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + } + + @Test + public void explicitContentKeyLength256() throws GeneralSecurityException { + Map desc = new HashMap<>(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(32, encryptionKey.getEncoded().length); // 256 Bits + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES/256", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", eMat.getEncryptionKey().getAlgorithm()); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + } + + @Test + public void extendedWithDerivedEncryptionKeyId() { + ExtendedKmsMaterialsProvider prov = + new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); + String customKeyId = kms.createKey().keyMetadata().keyId(); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().n("10").build()); + attrVals.put("rk", AttributeValue.builder().n("20").build()); + attrVals.put("encryptionKeyId", AttributeValue.builder().s(customKeyId).build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + Map kmsCtx = kms.getSingleEc(); + assertEquals("10", kmsCtx.get("hk")); + assertEquals("20", kmsCtx.get("rk")); + assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + } + + @Test(expectedExceptions = SdkException.class) + public void encryptionKeyIdMismatch() throws SdkException { + DirectKmsMaterialsProvider directProvider = new DirectKmsMaterialsProvider(kms, keyId); + String customKeyId = kms.createKey().keyMetadata().keyId(); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().n("10").build()); + attrVals.put("rk", AttributeValue.builder().n("20").build()); + attrVals.put("encryptionKeyId", AttributeValue.builder().s(customKeyId).build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = directProvider.getEncryptionMaterials(ctx); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + ExtendedKmsMaterialsProvider extendedProvider = + new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); + + extendedProvider.getDecryptionMaterials(dCtx); + } + + @Test(expectedExceptions = SdkException.class) + public void missingEncryptionKeyId() throws SdkException { + ExtendedKmsMaterialsProvider prov = + new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().n("10").build()); + attrVals.put("rk", AttributeValue.builder().n("20").build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + prov.getEncryptionMaterials(ctx); + } + + @Test + public void generateDataKeyIsCalledWith256NumberOfBits() { + final AtomicBoolean gdkCalled = new AtomicBoolean(false); + KmsClient kmsSpy = + new FakeKMS() { + @Override + public GenerateDataKeyResponse generateDataKey(GenerateDataKeyRequest r) { + gdkCalled.set(true); + assertEquals((Integer) 32, r.numberOfBytes()); + assertNull(r.keySpec()); + return super.generateDataKey(r); + } + }; + assertFalse(gdkCalled.get()); + new DirectKmsMaterialsProvider(kmsSpy, keyId).getEncryptionMaterials(ctx); + assertTrue(gdkCalled.get()); + } + + private static class ExtendedKmsMaterialsProvider extends DirectKmsMaterialsProvider { + private final String encryptionKeyIdAttributeName; + + public ExtendedKmsMaterialsProvider( + KmsClient kms, String encryptionKeyId, String encryptionKeyIdAttributeName) { + super(kms, encryptionKeyId); + + this.encryptionKeyIdAttributeName = encryptionKeyIdAttributeName; + } + + @Override + protected String selectEncryptionKeyId(EncryptionContext context) + throws DynamoDbException { + if (!context.getAttributeValues().containsKey(encryptionKeyIdAttributeName)) { + throw DynamoDbException.create("encryption key attribute is not provided", new Exception()); + } + + return context.getAttributeValues().get(encryptionKeyIdAttributeName).s(); + } + + @Override + protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext context) + throws DynamoDbException { + if (!context.getAttributeValues().containsKey(encryptionKeyIdAttributeName)) { + throw DynamoDbException.create("encryption key attribute is not provided", new Exception()); + } + + String customEncryptionKeyId = + context.getAttributeValues().get(encryptionKeyIdAttributeName).s(); + if (!customEncryptionKeyId.equals(encryptionKeyId)) { + throw DynamoDbException.create("encryption key ids do not match.", new Exception()); + } + } + + @Override + protected DecryptResponse decrypt(DecryptRequest request, EncryptionContext context) { + return super.decrypt(request, context); + } + + @Override + protected GenerateDataKeyResponse generateDataKey( + GenerateDataKeyRequest request, EncryptionContext context) { + return super.generateDataKey(request, context); + } + } + + private static EncryptionContext ctx(EncryptionMaterials mat) { + return new EncryptionContext.Builder() + .materialDescription(mat.getMaterialDescription()) + .build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java new file mode 100644 index 0000000000..406052452e --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java @@ -0,0 +1,315 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.fail; + +import java.io.ByteArrayInputStream; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStore.PasswordProtection; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.SecretKeyEntry; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +public class KeyStoreMaterialsProviderTest { + private static final String certPem = + "MIIDbTCCAlWgAwIBAgIJANdRvzVsW1CIMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV" + + "BAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMQwwCgYDVQQKDANBV1MxGzAZBgNV" + + "BAMMEktleVN0b3JlIFRlc3QgQ2VydDAeFw0xMzA1MDgyMzMyMjBaFw0xMzA2MDcy" + + "MzMyMjBaME0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMQwwCgYD" + + "VQQKDANBV1MxGzAZBgNVBAMMEktleVN0b3JlIFRlc3QgQ2VydDCCASIwDQYJKoZI" + + "hvcNAQEBBQADggEPADCCAQoCggEBAJ8+umOX8x/Ma4OZishtYpcA676bwK5KScf3" + + "w+YGM37L12KTdnOyieiGtRW8p0fS0YvnhmVTvaky09I33bH+qy9gliuNL2QkyMxp" + + "uu1IwkTKKuB67CaKT6osYJLFxV/OwHcaZnTszzDgbAVg/Z+8IZxhPgxMzMa+7nDn" + + "hEm9Jd+EONq3PnRagnFeLNbMIePprdJzXHyNNiZKRRGQ/Mo9rr7mqMLSKnFNsmzB" + + "OIfeZM8nXeg+cvlmtXl72obwnGGw2ksJfaxTPm4eEhzRoAgkbjPPLHbwiJlc+GwF" + + "i8kh0Y3vQTj/gOFE4nzipkm7ux1lsGHNRVpVDWpjNd8Fl9JFELkCAwEAAaNQME4w" + + "HQYDVR0OBBYEFM0oGUuFAWlLXZaMXoJgGZxWqfOxMB8GA1UdIwQYMBaAFM0oGUuF" + + "AWlLXZaMXoJgGZxWqfOxMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB" + + "AAXCsXeC8ZRxovP0Wc6C5qv3d7dtgJJVzHwoIRt2YR3yScBa1XI40GKT80jP3MYH" + + "8xMu3mBQtcYrgRKZBy4GpHAyxoFTnPcuzq5Fg7dw7fx4E4OKIbWOahdxwtbVxQfZ" + + "UHnGG88Z0bq2twj7dALGyJhUDdiccckJGmJPOFMzjqsvoAu0n/p7eS6y5WZ5ewqw" + + "p7VwYOP3N9wVV7Podmkh1os+eCcp9GoFf0MHBMFXi2Ps2azKx8wHRIA5D1MZv/Va" + + "4L4/oTBKCjORpFlP7EhMksHBYnjqXLDP6awPMAgQNYB5J9zX6GfJsAgly3t4Rjr5" + + "cLuNYBmRuByFGo+SOdrj6D8="; + private static final String keyPem = + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCfPrpjl/MfzGuD" + + "mYrIbWKXAOu+m8CuSknH98PmBjN+y9dik3ZzsonohrUVvKdH0tGL54ZlU72pMtPS" + + "N92x/qsvYJYrjS9kJMjMabrtSMJEyirgeuwmik+qLGCSxcVfzsB3GmZ07M8w4GwF" + + "YP2fvCGcYT4MTMzGvu5w54RJvSXfhDjatz50WoJxXizWzCHj6a3Sc1x8jTYmSkUR" + + "kPzKPa6+5qjC0ipxTbJswTiH3mTPJ13oPnL5ZrV5e9qG8JxhsNpLCX2sUz5uHhIc" + + "0aAIJG4zzyx28IiZXPhsBYvJIdGN70E4/4DhROJ84qZJu7sdZbBhzUVaVQ1qYzXf" + + "BZfSRRC5AgMBAAECggEBAJMwx9eGe5LIwBfDtCPN93LbxwtHq7FtuQS8XrYexTpN" + + "76eN5c7LF+11lauh1HzuwAEw32iJHqVl9aQ5PxFm85O3ExbuSP+ngHJwx/bLacVr" + + "mHYlKGH3Net1WU5Qvz7vO7bbEBjDSj9DMJVIMSWUHv0MZO25jw2lLX/ufrgpvPf7" + + "KXSgXg/8uV7PbnTbBDNlg02u8eOc+IbH4O8XDKAhD+YQ8AE3pxtopJbb912U/cJs" + + "Y0hQ01zbkWYH7wL9BeQmR7+TEjjtr/IInNjnXmaOmSX867/rTSTuozaVrl1Ce7r8" + + "EmUDg9ZLZeKfoNYovMy08wnxWVX2J+WnNDjNiSOm+IECgYEA0v3jtGrOnKbd0d9E" + + "dbyIuhjgnwp+UsgALIiBeJYjhFS9NcWgs+02q/0ztqOK7g088KBBQOmiA+frLIVb" + + "uNCt/3jF6kJvHYkHMZ0eBEstxjVSM2UcxzJ6ceHZ68pmrru74382TewVosxccNy0" + + "glsUWNN0t5KQDcetaycRYg50MmcCgYEAwTb8klpNyQE8AWxVQlbOIEV24iarXxex" + + "7HynIg9lSeTzquZOXjp0m5omQ04psil2gZ08xjiudG+Dm7QKgYQcxQYUtZPQe15K" + + "m+2hQM0jA7tRfM1NAZHoTmUlYhzRNX6GWAqQXOgjOqBocT4ySBXRaSQq9zuZu36s" + + "fI17knap798CgYArDa2yOf0xEAfBdJqmn7MSrlLfgSenwrHuZGhu78wNi7EUUOBq" + + "9qOqUr+DrDmEO+VMgJbwJPxvaZqeehPuUX6/26gfFjFQSI7UO+hNHf4YLPc6D47g" + + "wtcjd9+c8q8jRqGfWWz+V4dOsf7G9PJMi0NKoNN3RgvpE+66J72vUZ26TwKBgEUq" + + "DdfGA7pEetp3kT2iHT9oHlpuRUJRFRv2s015/WQqVR+EOeF5Q2zADZpiTIK+XPGg" + + "+7Rpbem4UYBXPruGM1ZECv3E4AiJhGO0+Nhdln8reswWIc7CEEqf4nXwouNnW2gA" + + "wBTB9Hp0GW8QOKedR80/aTH/X9TCT7R2YRnY6JQ5AoGBAKjgPySgrNDhlJkW7jXR" + + "WiGpjGSAFPT9NMTvEHDo7oLTQ8AcYzcGQ7ISMRdVXR6GJOlFVsH4NLwuHGtcMTPK" + + "zoHbPHJyOn1SgC5tARD/1vm5CsG2hATRpWRQCTJFg5VRJ4R7Pz+HuxY4SoABcPQd" + + "K+MP8GlGqTldC6NaB1s7KuAX"; + + private static SecretKey encryptionKey; + private static SecretKey macKey; + private static KeyStore keyStore; + private static final String password = "Password"; + private static final PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray()); + + private Map description; + private EncryptionContext ctx; + private static PrivateKey privateKey; + private static Certificate certificate; + + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, Utils.getRng()); + encryptionKey = aesGen.generateKey(); + + keyStore = KeyStore.getInstance("jceks"); + keyStore.load(null, password.toCharArray()); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec rsaSpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyPem)); + privateKey = kf.generatePrivate(rsaSpec); + CertificateFactory cf = CertificateFactory.getInstance("X509"); + certificate = cf.generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(certPem))); + + keyStore.setEntry("enc", new SecretKeyEntry(encryptionKey), passwordProtection); + keyStore.setEntry("sig", new SecretKeyEntry(macKey), passwordProtection); + keyStore.setEntry( + "enc-a", + new PrivateKeyEntry(privateKey, new Certificate[] {certificate}), + passwordProtection); + keyStore.setEntry( + "sig-a", + new PrivateKeyEntry(privateKey, new Certificate[] {certificate}), + passwordProtection); + keyStore.setCertificateEntry("trustedCert", certificate); + } + + @BeforeMethod + public void setUp() { + description = new HashMap<>(); + description.put("TestKey", "test value"); + description = Collections.unmodifiableMap(description); + ctx = EncryptionContext.builder().build(); + } + + + @Test + @SuppressWarnings("unchecked") + public void simpleSymMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, Collections.EMPTY_MAP); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); + } + + @Test + @SuppressWarnings("unchecked") + public void simpleSymSig() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig-a", passwordProtection, passwordProtection, Collections.EMPTY_MAP); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(privateKey, encryptionMaterials.getSigningKey()); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); + assertEquals(certificate.getPublicKey(), prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); + } + + @Test + public void equalSymDescMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); + } + + @Test + public void superSetSymDescMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + Map tmpDesc = + new HashMap<>(encryptionMaterials.getMaterialDescription()); + tmpDesc.put("randomValue", "random"); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(tmpDesc)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(tmpDesc)).getVerificationKey()); + } + + @Test + @SuppressWarnings("unchecked") + public void subSetSymDescMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + + assertNull(prov.getDecryptionMaterials(ctx(Collections.EMPTY_MAP))); + } + + + @Test + public void noMatchSymDescMac() throws Exception { + KeyStoreMaterialsProvider prov = new + KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + Map tmpDesc = new HashMap<>(); + tmpDesc.put("randomValue", "random"); + + assertNull(prov.getDecryptionMaterials(ctx(tmpDesc))); + } + + @Test + public void testRefresh() throws Exception { + // Mostly make sure we don't throw an exception + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + prov.refresh(); + } + + @Test + public void asymSimpleMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc-a", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void asymSimpleSig() throws Exception { + KeyStoreMaterialsProvider prov = new KeyStoreMaterialsProvider(keyStore, "enc-a", "sig-a", passwordProtection, passwordProtection, description); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(privateKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(certificate.getPublicKey(), dMat.getVerificationKey()); + } + + @Test + public void asymSigVerifyOnly() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc-a", "trustedCert", passwordProtection, null, description); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertNull(eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(certificate.getPublicKey(), dMat.getVerificationKey()); + } + + @Test + public void asymSigEncryptOnly() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "trustedCert", "sig-a", null, passwordProtection, description); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(privateKey, eMat.getSigningKey()); + + try { + prov.getDecryptionMaterials(ctx(eMat)); + fail("Expected exception"); + } catch (IllegalStateException ex) { + assertEquals("No private decryption key provided.", ex.getMessage()); + } + } + + private static EncryptionContext ctx(EncryptionMaterials mat) { + return ctx(mat.getMaterialDescription()); + } + + private static EncryptionContext ctx(Map desc) { + return EncryptionContext.builder() + .materialDescription(desc).build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java new file mode 100644 index 0000000000..0485d4dff7 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java @@ -0,0 +1,182 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +public class SymmetricStaticProviderTest { + private static SecretKey encryptionKey; + private static SecretKey macKey; + private static KeyPair sigPair; + private Map description; + private EncryptionContext ctx; + + @BeforeClass + public static void setUpClass() throws Exception { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + sigPair = rsaGen.generateKeyPair(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, Utils.getRng()); + encryptionKey = aesGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + description = Collections.unmodifiableMap(description); + ctx = new EncryptionContext.Builder().build(); + } + + @Test + public void simpleMac() { + SymmetricStaticProvider prov = + new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap()); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + + assertEquals( + encryptionKey, + prov.getDecryptionMaterials(ctx(Collections.emptyMap())) + .getDecryptionKey()); + assertEquals( + macKey, + prov.getDecryptionMaterials(ctx(Collections.emptyMap())) + .getVerificationKey()); + } + + @Test + public void simpleSig() { + SymmetricStaticProvider prov = + new SymmetricStaticProvider(encryptionKey, sigPair, Collections.emptyMap()); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(sigPair.getPrivate(), prov.getEncryptionMaterials(ctx).getSigningKey()); + + assertEquals( + encryptionKey, + prov.getDecryptionMaterials(ctx(Collections.emptyMap())) + .getDecryptionKey()); + assertEquals( + sigPair.getPublic(), + prov.getDecryptionMaterials(ctx(Collections.emptyMap())) + .getVerificationKey()); + } + + @Test + public void equalDescMac() { + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue( + prov.getEncryptionMaterials(ctx) + .getMaterialDescription() + .entrySet() + .containsAll(description.entrySet())); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(description)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(description)).getVerificationKey()); + } + + @Test + public void supersetDescMac() { + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue( + prov.getEncryptionMaterials(ctx) + .getMaterialDescription() + .entrySet() + .containsAll(description.entrySet())); + + Map superSet = new HashMap(description); + superSet.put("NewValue", "super!"); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(superSet)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(superSet)).getVerificationKey()); + } + + @Test + public void subsetDescMac() { + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue( + prov.getEncryptionMaterials(ctx) + .getMaterialDescription() + .entrySet() + .containsAll(description.entrySet())); + + assertNull(prov.getDecryptionMaterials(ctx(Collections.emptyMap()))); + } + + @Test + public void noMatchDescMac() { + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue( + prov.getEncryptionMaterials(ctx) + .getMaterialDescription() + .entrySet() + .containsAll(description.entrySet())); + + Map noMatch = new HashMap(); + noMatch.put("NewValue", "no match!"); + + assertNull(prov.getDecryptionMaterials(ctx(noMatch))); + } + + @Test + public void testRefresh() { + // This does nothing, make sure we don't throw and exception. + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + prov.refresh(); + } + + @SuppressWarnings("unused") + private static EncryptionContext ctx(EncryptionMaterials mat) { + return ctx(mat.getMaterialDescription()); + } + + private static EncryptionContext ctx(Map desc) { + return EncryptionContext.builder() + .materialDescription(desc).build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java new file mode 100644 index 0000000000..5f82b47dd8 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java @@ -0,0 +1,414 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class WrappedMaterialsProviderTest { + private static SecretKey symEncryptionKey; + private static SecretKey macKey; + private static KeyPair sigPair; + private static KeyPair encryptionPair; + private static SecureRandom rnd; + private Map description; + private EncryptionContext ctx; + + @BeforeClass + public static void setUpClass() throws NoSuchAlgorithmException { + rnd = new SecureRandom(); + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, rnd); + sigPair = rsaGen.generateKeyPair(); + encryptionPair = rsaGen.generateKeyPair(); + + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, rnd); + symEncryptionKey = aesGen.generateKey(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, rnd); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + ctx = new EncryptionContext.Builder().build(); + } + + @Test + public void simpleMac() { + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void simpleSigPair() { + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + symEncryptionKey, symEncryptionKey, sigPair, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void randomEnvelopeKeys() { + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey2 = eMat2.getEncryptionKey(); + assertEquals(macKey, eMat.getSigningKey()); + + assertFalse( + "Envelope keys must be different", contentEncryptionKey.equals(contentEncryptionKey2)); + } + + @Test + public void testRefresh() { + // This does nothing, make sure we don't throw an exception. + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); + prov.refresh(); + } + + @Test + public void wrapUnwrapAsymMatExplicitWrappingAlgorithmPkcs1() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM, "RSA/ECB/PKCS1Padding"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "RSA/ECB/PKCS1Padding", + eMat.getMaterialDescription().get(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapAsymMatExplicitWrappingAlgorithmPkcs2() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM, "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", + eMat.getMaterialDescription().get(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapAsymMatExplicitContentKeyAlgorithm() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), + encryptionPair.getPrivate(), + sigPair, + Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapAsymMatExplicitContentKeyLength128() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(16, contentEncryptionKey.getEncoded().length); // 128 Bits + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapAsymMatExplicitContentKeyLength256() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(32, contentEncryptionKey.getEncoded().length); // 256 Bits + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void unwrapAsymMatExplicitEncAlgAes128() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + // Get materials we can test unwrapping on + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + + // Ensure "AES/128" on the created materials creates the expected key + Map aes128Desc = eMat.getMaterialDescription(); + aes128Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + EncryptionContext aes128Ctx = + new EncryptionContext.Builder().materialDescription(aes128Desc).build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(aes128Ctx); + assertEquals( + "AES/128", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void unwrapAsymMatExplicitEncAlgAes256() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + // Get materials we can test unwrapping on + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + + // Ensure "AES/256" on the created materials creates the expected key + Map aes256Desc = eMat.getMaterialDescription(); + aes256Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + EncryptionContext aes256Ctx = + new EncryptionContext.Builder().materialDescription(aes256Desc).build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(aes256Ctx); + assertEquals( + "AES/256", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapSymMatExplicitContentKeyAlgorithm() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapSymMatExplicitContentKeyLength128() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(16, contentEncryptionKey.getEncoded().length); // 128 Bits + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapSymMatExplicitContentKeyLength256() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(32, contentEncryptionKey.getEncoded().length); // 256 Bits + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void unwrapSymMatExplicitEncAlgAes128() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + // Get materials we can test unwrapping on + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + + // Ensure "AES/128" on the created materials creates the expected key + Map aes128Desc = eMat.getMaterialDescription(); + aes128Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + EncryptionContext aes128Ctx = + new EncryptionContext.Builder().materialDescription(aes128Desc).build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(aes128Ctx); + assertEquals( + "AES/128", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void unwrapSymMatExplicitEncAlgAes256() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + + Map aes256Desc = eMat.getMaterialDescription(); + aes256Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + EncryptionContext aes256Ctx = + new EncryptionContext.Builder().materialDescription(aes256Desc).build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(aes256Ctx); + assertEquals( + "AES/256", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + private static EncryptionContext ctx(EncryptionMaterials mat) { + return new EncryptionContext.Builder() + .materialDescription(mat.getMaterialDescription()) + .build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java new file mode 100644 index 0000000000..3449908a6d --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java @@ -0,0 +1,346 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.fail; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttributeValueBuilder; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.LocalDynamoDb; + +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; + +public class MetaStoreTests { + private static final String SOURCE_TABLE_NAME = "keystoreTable"; + private static final String DESTINATION_TABLE_NAME = "keystoreDestinationTable"; + private static final String MATERIAL_NAME = "material"; + private static final SecretKey AES_KEY = new SecretKeySpec(new byte[] { 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, "AES"); + private static final SecretKey TARGET_AES_KEY = new SecretKeySpec(new byte[] { 0, + 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, "AES"); + private static final SecretKey HMAC_KEY = new SecretKeySpec(new byte[] { 0, + 1, 2, 3, 4, 5, 6, 7 }, "HmacSHA256"); + private static final SecretKey TARGET_HMAC_KEY = new SecretKeySpec(new byte[] { 0, + 2, 4, 6, 8, 10, 12, 14 }, "HmacSHA256"); + private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); + private static final EncryptionMaterialsProvider TARGET_BASE_PROVIDER = new SymmetricStaticProvider(TARGET_AES_KEY, TARGET_HMAC_KEY); + private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); + private static final DynamoDbEncryptor TARGET_ENCRYPTOR = DynamoDbEncryptor.getInstance(TARGET_BASE_PROVIDER); + + private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); + private final LocalDynamoDb targetLocalDynamoDb = new LocalDynamoDb(); + private DynamoDbClient client; + private DynamoDbClient targetClient; + private MetaStore store; + private MetaStore targetStore; + private EncryptionContext ctx; + + private static class TestExtraDataSupplier implements MetaStore.ExtraDataSupplier { + + private final Map attributeValueMap; + private final Set signedOnlyFieldNames; + + TestExtraDataSupplier(final Map attributeValueMap, + final Set signedOnlyFieldNames) { + this.attributeValueMap = attributeValueMap; + this.signedOnlyFieldNames = signedOnlyFieldNames; + } + + @Override + public Map getAttributes(String materialName, long version) { + return this.attributeValueMap; + } + + @Override + public Set getSignedOnlyFieldNames() { + return this.signedOnlyFieldNames; + } + } + + @BeforeMethod + public void setup() { + localDynamoDb.start(); + targetLocalDynamoDb.start(); + client = localDynamoDb.createClient(); + targetClient = targetLocalDynamoDb.createClient(); + + MetaStore.createTable(client, SOURCE_TABLE_NAME, ProvisionedThroughput.builder() + .readCapacityUnits(1L) + .writeCapacityUnits(1L) + .build()); + //Creating Targeted DynamoDB Object + MetaStore.createTable(targetClient, DESTINATION_TABLE_NAME, ProvisionedThroughput.builder() + .readCapacityUnits(1L) + .writeCapacityUnits(1L) + .build()); + store = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR); + targetStore = new MetaStore(targetClient, DESTINATION_TABLE_NAME, TARGET_ENCRYPTOR); + ctx = EncryptionContext.builder().build(); + } + + @AfterMethod + public void stopLocalDynamoDb() { + localDynamoDb.stop(); + targetLocalDynamoDb.stop(); + } + + @Test + public void testNoMaterials() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + } + + @Test + public void singleMaterial() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov = store.newProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void singleMaterialExplicitAccess() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = store.getProvider(MATERIAL_NAME); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void singleMaterialExplicitAccessWithVersion() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = store.getProvider(MATERIAL_NAME, 0); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void singleMaterialWithImplicitCreation() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov = store.getProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void twoDifferentMaterials() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = store.newProvider(MATERIAL_NAME); + assertEquals(1, store.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + try { + prov2.getDecryptionMaterials(ctx(eMat)); + fail("Missing expected exception"); + } catch (final DynamoDbEncryptionException ex) { + // Expected Exception + } + final EncryptionMaterials eMat2 = prov2.getEncryptionMaterials(ctx); + assertEquals(1, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + } + + @Test + public void getOrCreateCollision() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = store.getOrCreate(MATERIAL_NAME, 0); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = store.getOrCreate(MATERIAL_NAME, 0); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void getOrCreateWithContextSupplier() { + final Map attributeValueMap = new HashMap<>(); + attributeValueMap.put("CustomKeyId", AttributeValueBuilder.ofS("testCustomKeyId")); + attributeValueMap.put("KeyToken", AttributeValueBuilder.ofS("testKeyToken")); + + final Set signedOnlyAttributes = new HashSet<>(); + signedOnlyAttributes.add("CustomKeyId"); + + final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( + attributeValueMap, signedOnlyAttributes); + + final MetaStore metaStore = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); + + assertEquals(-1, metaStore.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = metaStore.getOrCreate(MATERIAL_NAME, 0); + assertEquals(0, metaStore.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = metaStore.getOrCreate(MATERIAL_NAME, 0); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void replicateIntermediateKeysTest() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterialsProvider prov1 = store.getOrCreate(MATERIAL_NAME, 0); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + + store.replicate(MATERIAL_NAME, 0, targetStore); + assertEquals(0, targetStore.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final DecryptionMaterials dMat = targetStore.getProvider(MATERIAL_NAME, 0).getDecryptionMaterials(ctx(eMat)); + + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test(expectedExceptions = IndexOutOfBoundsException.class) + public void replicateIntermediateKeysWhenMaterialNotFoundTest() { + store.replicate(MATERIAL_NAME, 0, targetStore); + } + + @Test + public void newProviderCollision() throws InterruptedException { + final SlowNewProvider slowProv = new SlowNewProvider(); + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + assertEquals(-1, slowProv.slowStore.getMaxVersion(MATERIAL_NAME)); + + slowProv.start(); + Thread.sleep(100); + final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); + slowProv.join(); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + assertEquals(0, slowProv.slowStore.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = slowProv.result; + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test(expectedExceptions= IndexOutOfBoundsException.class) + public void invalidVersion() { + store.getProvider(MATERIAL_NAME, 1000); + } + + @Test(expectedExceptions= IllegalArgumentException.class) + public void invalidSignedOnlyField() { + final Map attributeValueMap = new HashMap<>(); + attributeValueMap.put("enc", AttributeValueBuilder.ofS("testEncryptionKey")); + + final Set signedOnlyAttributes = new HashSet<>(); + signedOnlyAttributes.add("enc"); + + final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( + attributeValueMap, signedOnlyAttributes); + + new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); + } + + private static EncryptionContext ctx(final EncryptionMaterials mat) { + return EncryptionContext.builder() + .materialDescription(mat.getMaterialDescription()).build(); + } + + private class SlowNewProvider extends Thread { + public volatile EncryptionMaterialsProvider result; + public ProviderStore slowStore = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR) { + @Override + public EncryptionMaterialsProvider newProvider(final String materialName) { + final long nextId = getMaxVersion(materialName) + 1; + try { + Thread.sleep(1000); + } catch (final InterruptedException e) { + // Ignored + } + return getOrCreate(materialName, nextId); + } + }; + + @Override + public void run() { + result = slowStore.newProvider(MATERIAL_NAME); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java new file mode 100644 index 0000000000..2ed128e9d3 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java @@ -0,0 +1,164 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils; + +import static org.testng.AssertJUnit.assertEquals; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableName; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableNameUsingMap; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.testng.annotations.Test; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; + +public class EncryptionContextOperatorsTest { + + @Test + public void testCreateEncryptionContextTableNameOverride_expectedOverride() { + Function myNewTableName = overrideEncryptionContextTableName("OriginalTableName", "MyNewTableName"); + + EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName").build(); + + EncryptionContext newContext = myNewTableName.apply(context); + + assertEquals("OriginalTableName", context.getTableName()); + assertEquals("MyNewTableName", newContext.getTableName()); + } + + /** + * Some pretty clear repetition in null cases. May make sense to replace with data providers or parameterized + * classes for null cases + */ + @Test + public void testNullCasesCreateEncryptionContextTableNameOverride_nullOriginalTableName() { + assertEncryptionContextUnchanged(EncryptionContext.builder().tableName("example").build(), + null, + "MyNewTableName"); + } + + @Test + public void testCreateEncryptionContextTableNameOverride_differentOriginalTableName() { + assertEncryptionContextUnchanged(EncryptionContext.builder().tableName("example").build(), + "DifferentTableName", + "MyNewTableName"); + } + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverride_nullEncryptionContext() { + assertEncryptionContextUnchanged(null, + "DifferentTableName", + "MyNewTableName"); + } + + @Test + public void testCreateEncryptionContextTableNameOverrideMap_expectedOverride() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("OriginalTableName", "MyNewTableName"); + + + Function nameOverrideMap = + overrideEncryptionContextTableNameUsingMap(tableNameOverrides); + + EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName").build(); + + EncryptionContext newContext = nameOverrideMap.apply(context); + + assertEquals("OriginalTableName", context.getTableName()); + assertEquals("MyNewTableName", newContext.getTableName()); + } + + @Test + public void testCreateEncryptionContextTableNameOverrideMap_multipleOverrides() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("OriginalTableName1", "MyNewTableName1"); + tableNameOverrides.put("OriginalTableName2", "MyNewTableName2"); + + + Function overrideOperator = + overrideEncryptionContextTableNameUsingMap(tableNameOverrides); + + EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName1").build(); + + EncryptionContext newContext = overrideOperator.apply(context); + + assertEquals("OriginalTableName1", context.getTableName()); + assertEquals("MyNewTableName1", newContext.getTableName()); + + EncryptionContext context2 = EncryptionContext.builder().tableName("OriginalTableName2").build(); + + EncryptionContext newContext2 = overrideOperator.apply(context2); + + assertEquals("OriginalTableName2", context2.getTableName()); + assertEquals("MyNewTableName2", newContext2.getTableName()); + + } + + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullEncryptionContextTableName() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("DifferentTableName", "MyNewTableName"); + assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().build(), + tableNameOverrides); + } + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullEncryptionContext() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("DifferentTableName", "MyNewTableName"); + assertEncryptionContextUnchangedFromMap(null, + tableNameOverrides); + } + + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullOriginalTableName() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put(null, "MyNewTableName"); + assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("example").build(), + tableNameOverrides); + } + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullNewTableName() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("MyOriginalTableName", null); + assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("MyOriginalTableName").build(), + tableNameOverrides); + } + + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullMap() { + assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("MyOriginalTableName").build(), + null); + } + + + private void assertEncryptionContextUnchanged(EncryptionContext encryptionContext, String originalTableName, String newTableName) { + Function encryptionContextTableNameOverride = overrideEncryptionContextTableName(originalTableName, newTableName); + EncryptionContext newEncryptionContext = encryptionContextTableNameOverride.apply(encryptionContext); + assertEquals(encryptionContext, newEncryptionContext); + } + + + private void assertEncryptionContextUnchangedFromMap(EncryptionContext encryptionContext, Map overrideMap) { + Function encryptionContextTableNameOverrideFromMap = overrideEncryptionContextTableNameUsingMap(overrideMap); + EncryptionContext newEncryptionContext = encryptionContextTableNameOverrideFromMap.apply(encryptionContext); + assertEquals(encryptionContext, newEncryptionContext); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java new file mode 100644 index 0000000000..e098816275 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java @@ -0,0 +1,393 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.fail; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller.marshall; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller.unmarshall; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttributeValueBuilder; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class AttributeValueMarshallerTest { + @Test(expectedExceptions = IllegalArgumentException.class) + public void testEmpty() { + AttributeValue av = AttributeValue.builder().build(); + marshall(av); + } + + @Test + public void testNumber() { + AttributeValue av = AttributeValue.builder().n("1337").build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testString() { + AttributeValue av = AttributeValue.builder().s("1337").build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testByteBuffer() { + AttributeValue av = AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + // We can't use straight .equals for comparison because Attribute Values represents Sets + // as Lists and so incorrectly does an ordered comparison + + @Test + public void testNumberS() { + AttributeValue av = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1337", "1", "5"))).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testNumberSOrdering() { + AttributeValue av1 = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1337", "1", "5"))).build(); + AttributeValue av2 = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1", "5", "1337"))).build(); + assertAttributesAreEqual(av1, av2); + ByteBuffer buff1 = marshall(av1); + ByteBuffer buff2 = marshall(av2); + assertEquals(buff1, buff2); + } + + @Test + public void testStringS() { + AttributeValue av = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Bob", "Ann", "5"))).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testStringSOrdering() { + AttributeValue av1 = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Bob", "Ann", "5"))).build(); + AttributeValue av2 = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Ann", "Bob", "5"))).build(); + assertAttributesAreEqual(av1, av2); + ByteBuffer buff1 = marshall(av1); + ByteBuffer buff2 = marshall(av2); + assertEquals(buff1, buff2); + } + + @Test + public void testByteBufferS() { + AttributeValue av = AttributeValue.builder().bs(unmodifiableList( + Arrays.asList(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5}), + SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7})))).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testByteBufferSOrdering() { + AttributeValue av1 = AttributeValue.builder().bs(unmodifiableList( + Arrays.asList(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5}), + SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7})))).build(); + AttributeValue av2 = AttributeValue.builder().bs(unmodifiableList( + Arrays.asList(SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7}), + SdkBytes.fromByteArray(new byte[]{0, 1, 2, 3, 4, 5})))).build(); + + assertAttributesAreEqual(av1, av2); + ByteBuffer buff1 = marshall(av1); + ByteBuffer buff2 = marshall(av2); + assertEquals(buff1, buff2); + } + + @Test + public void testBoolTrue() { + AttributeValue av = AttributeValue.builder().bool(Boolean.TRUE).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testBoolFalse() { + AttributeValue av = AttributeValue.builder().bool(Boolean.FALSE).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testNULL() { + AttributeValue av = AttributeValue.builder().nul(Boolean.TRUE).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testActualNULL() { + unmarshall(marshall(null)); + } + + @Test + public void testEmptyList() { + AttributeValue av = AttributeValue.builder().l(emptyList()).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testListOfString() { + AttributeValue av = + AttributeValue.builder().l(singletonList(AttributeValue.builder().s("StringValue").build())).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testList() { + AttributeValue av = AttributeValueBuilder.ofL( + AttributeValueBuilder.ofS("StringValue"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofBool(Boolean.TRUE)); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testListWithNull() { + final AttributeValue av = AttributeValueBuilder.ofL( + AttributeValueBuilder.ofS("StringValue"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofBool(Boolean.TRUE), + null); + + try { + marshall(av); + } catch (NullPointerException e) { + assertThat(e.getMessage(), + startsWith("Encountered null list entry value while marshalling attribute value")); + } + } + + @Test + public void testListDuplicates() { + AttributeValue av = AttributeValueBuilder.ofL( + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofN("1000")); + AttributeValue result = unmarshall(marshall(av)); + assertAttributesAreEqual(av, result); + assertEquals(4, result.l().size()); + } + + @Test + public void testComplexList() { + final List list1 = Arrays.asList( + AttributeValueBuilder.ofS("StringValue"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofBool(Boolean.TRUE)); + final List list22 = Arrays.asList( + AttributeValueBuilder.ofS("AWS"), + AttributeValueBuilder.ofN("-3700"), + AttributeValueBuilder.ofBool(Boolean.FALSE)); + final List list2 = Arrays.asList( + AttributeValueBuilder.ofL(list22), + AttributeValueBuilder.ofNull()); + AttributeValue av = AttributeValueBuilder.ofL( + AttributeValueBuilder.ofS("StringValue1"), + AttributeValueBuilder.ofL(list1), + AttributeValueBuilder.ofN("50"), + AttributeValueBuilder.ofL(list2)); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testEmptyMap() { + Map map = new HashMap<>(); + AttributeValue av = AttributeValueBuilder.ofM(map); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testSimpleMap() { + Map map = new HashMap<>(); + map.put("KeyValue", AttributeValueBuilder.ofS("ValueValue")); + AttributeValue av = AttributeValueBuilder.ofM(map); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testSimpleMapWithNull() { + final Map map = new HashMap<>(); + map.put("KeyValue", AttributeValueBuilder.ofS("ValueValue")); + map.put("NullKeyValue", null); + + final AttributeValue av = AttributeValueBuilder.ofM(map); + + try { + marshall(av); + fail("NullPointerException should have been thrown"); + } catch (NullPointerException e) { + assertThat(e.getMessage(), startsWith("Encountered null map value for key NullKeyValue while marshalling " + + "attribute value")); + } + } + + @Test + public void testMapOrdering() { + LinkedHashMap m1 = new LinkedHashMap<>(); + LinkedHashMap m2 = new LinkedHashMap<>(); + + m1.put("Value1", AttributeValueBuilder.ofN("1")); + m1.put("Value2", AttributeValueBuilder.ofBool(Boolean.TRUE)); + + m2.put("Value2", AttributeValueBuilder.ofBool(Boolean.TRUE)); + m2.put("Value1", AttributeValueBuilder.ofN("1")); + + AttributeValue av1 = AttributeValueBuilder.ofM(m1); + AttributeValue av2 = AttributeValueBuilder.ofM(m2); + + ByteBuffer buff1 = marshall(av1); + ByteBuffer buff2 = marshall(av2); + assertEquals(buff1, buff2); + assertAttributesAreEqual(av1, unmarshall(buff1)); + assertAttributesAreEqual(av1, unmarshall(buff2)); + assertAttributesAreEqual(av2, unmarshall(buff1)); + assertAttributesAreEqual(av2, unmarshall(buff2)); + } + + @Test + public void testComplexMap() { + AttributeValue av = buildComplexAttributeValue(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + // This test ensures that an AttributeValue marshalled by an older + // version of this library still unmarshalls correctly. It also + // ensures that old and new marshalling is identical. + @Test + public void testVersioningCompatibility() { + AttributeValue newObject = buildComplexAttributeValue(); + byte[] oldBytes = Base64.getDecoder().decode(COMPLEX_ATTRIBUTE_MARSHALLED); + byte[] newBytes = marshall(newObject).array(); + assertThat(oldBytes, is(newBytes)); + + AttributeValue oldObject = unmarshall(ByteBuffer.wrap(oldBytes)); + assertAttributesAreEqual(oldObject, newObject); + } + + private static final String COMPLEX_ATTRIBUTE_MARSHALLED = "AE0AAAADAHM" + + "AAAAJSW5uZXJMaXN0AEwAAAAGAHMAAAALQ29tcGxleExpc3QAbgAAAAE1AGIAA" + + "AAGAAECAwQFAEwAAAAFAD8BAAAAAABMAAAAAQA/AABNAAAAAwBzAAAABFBpbms" + + "AcwAAAAVGbG95ZABzAAAABFRlc3QAPwEAcwAAAAdWZXJzaW9uAG4AAAABMQAAA" + + "E0AAAADAHMAAAAETGlzdABMAAAABQBuAAAAATUAbgAAAAE0AG4AAAABMwBuAAA" + + "AATIAbgAAAAExAHMAAAADTWFwAE0AAAABAHMAAAAGTmVzdGVkAD8BAHMAAAAEV" + + "HJ1ZQA/AQBzAAAACVNpbmdsZU1hcABNAAAAAQBzAAAAA0ZPTwBzAAAAA0JBUgB" + + "zAAAACVN0cmluZ1NldABTAAAAAwAAAANiYXIAAAADYmF6AAAAA2Zvbw=="; + + private static AttributeValue buildComplexAttributeValue() { + Map floydMap = new HashMap<>(); + floydMap.put("Pink", AttributeValueBuilder.ofS("Floyd")); + floydMap.put("Version", AttributeValueBuilder.ofN("1")); + floydMap.put("Test", AttributeValueBuilder.ofBool(Boolean.TRUE)); + List floydList = Arrays.asList( + AttributeValueBuilder.ofBool(Boolean.TRUE), + AttributeValueBuilder.ofNull(), + AttributeValueBuilder.ofNull(), + AttributeValueBuilder.ofL(AttributeValueBuilder.ofBool(Boolean.FALSE)), + AttributeValueBuilder.ofM(floydMap) + ); + + List nestedList = Arrays.asList( + AttributeValueBuilder.ofN("5"), + AttributeValueBuilder.ofN("4"), + AttributeValueBuilder.ofN("3"), + AttributeValueBuilder.ofN("2"), + AttributeValueBuilder.ofN("1") + ); + Map nestedMap = new HashMap<>(); + nestedMap.put("True", AttributeValueBuilder.ofBool(Boolean.TRUE)); + nestedMap.put("List", AttributeValueBuilder.ofL(nestedList)); + nestedMap.put("Map", AttributeValueBuilder.ofM( + Collections.singletonMap("Nested", + AttributeValueBuilder.ofBool(Boolean.TRUE)))); + + List innerList = Arrays.asList( + AttributeValueBuilder.ofS("ComplexList"), + AttributeValueBuilder.ofN("5"), + AttributeValueBuilder.ofB(new byte[] {0, 1, 2, 3, 4, 5}), + AttributeValueBuilder.ofL(floydList), + AttributeValueBuilder.ofNull(), + AttributeValueBuilder.ofM(nestedMap) + ); + + Map result = new HashMap<>(); + result.put("SingleMap", AttributeValueBuilder.ofM( + Collections.singletonMap("FOO", AttributeValueBuilder.ofS("BAR")))); + result.put("InnerList", AttributeValueBuilder.ofL(innerList)); + result.put("StringSet", AttributeValueBuilder.ofSS("foo", "bar", "baz")); + return AttributeValue.builder().m(Collections.unmodifiableMap(result)).build(); + } + + private void assertAttributesAreEqual(AttributeValue o1, AttributeValue o2) { + assertEquals(o1.b(), o2.b()); + assertSetsEqual(o1.bs(), o2.bs()); + assertEquals(o1.n(), o2.n()); + assertSetsEqual(o1.ns(), o2.ns()); + assertEquals(o1.s(), o2.s()); + assertSetsEqual(o1.ss(), o2.ss()); + assertEquals(o1.bool(), o2.bool()); + assertEquals(o1.nul(), o2.nul()); + + if (o1.l() != null) { + assertNotNull(o2.l()); + final List l1 = o1.l(); + final List l2 = o2.l(); + assertEquals(l1.size(), l2.size()); + for (int x = 0; x < l1.size(); ++x) { + assertAttributesAreEqual(l1.get(x), l2.get(x)); + } + } + + if (o1.m() != null) { + assertNotNull(o2.m()); + final Map m1 = o1.m(); + final Map m2 = o2.m(); + assertEquals(m1.size(), m2.size()); + for (Map.Entry entry : m1.entrySet()) { + assertAttributesAreEqual(entry.getValue(), m2.get(entry.getKey())); + } + } + } + + private void assertSetsEqual(Collection c1, Collection c2) { + assertFalse(c1 == null ^ c2 == null); + if (c1 != null) { + Set s1 = new HashSet<>(c1); + Set s2 = new HashSet<>(c2); + assertEquals(s1, s2); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java new file mode 100644 index 0000000000..4ec9c03ae4 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java @@ -0,0 +1,93 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.quicktheories.QuickTheory.qt; +import static org.quicktheories.generators.Generate.byteArrays; +import static org.quicktheories.generators.Generate.bytes; +import static org.quicktheories.generators.SourceDSL.integers; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import org.apache.commons.lang3.StringUtils; +import org.testng.annotations.Test; + +public class Base64Tests { + @Test + public void testBase64EncodeEquivalence() { + qt().forAll( + byteArrays( + integers().between(0, 1000000), bytes(Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0))) + .check( + (a) -> { + // Base64 encode using both implementations and check for equality of output + // in case one version produces different output + String sdkV1Base64 = com.amazonaws.util.Base64.encodeAsString(a); + String encryptionClientBase64 = Base64.encodeToString(a); + return StringUtils.equals(sdkV1Base64, encryptionClientBase64); + }); + } + + @Test + public void testBase64DecodeEquivalence() { + qt().forAll( + byteArrays( + integers().between(0, 10000), bytes(Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0))) + .as((b) -> java.util.Base64.getMimeEncoder().encodeToString(b)) + .check( + (s) -> { + // Check for equality using the MimeEncoder, which inserts newlines + // The encryptionClient's decoder is expected to ignore them + byte[] sdkV1Bytes = com.amazonaws.util.Base64.decode(s); + byte[] encryptionClientBase64 = Base64.decode(s); + return Arrays.equals(sdkV1Bytes, encryptionClientBase64); + }); + } + + @Test + public void testNullDecodeBehavior() { + byte[] decoded = Base64.decode(null); + assertThat(decoded, equalTo(null)); + } + + @Test + public void testNullDecodeBehaviorSdk1() { + byte[] decoded = com.amazonaws.util.Base64.decode((String) null); + assertThat(decoded, equalTo(null)); + + byte[] decoded2 = com.amazonaws.util.Base64.decode((byte[]) null); + assertThat(decoded2, equalTo(null)); + } + + @Test + public void testBase64PaddingBehavior() { + String testInput = "another one bites the dust"; + String expectedEncoding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q="; + assertThat( + Base64.encodeToString(testInput.getBytes(StandardCharsets.UTF_8)), + equalTo(expectedEncoding)); + + String encodingWithoutPadding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q"; + assertThat(Base64.decode(encodingWithoutPadding), equalTo(testInput.getBytes())); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testBase64PaddingBehaviorSdk1() { + String testInput = "another one bites the dust"; + String encodingWithoutPadding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q"; + com.amazonaws.util.Base64.decode(encodingWithoutPadding); + } + + @Test + public void rfc4648TestVectors() { + assertThat(Base64.encodeToString("".getBytes(StandardCharsets.UTF_8)), equalTo("")); + assertThat(Base64.encodeToString("f".getBytes(StandardCharsets.UTF_8)), equalTo("Zg==")); + assertThat(Base64.encodeToString("fo".getBytes(StandardCharsets.UTF_8)), equalTo("Zm8=")); + assertThat(Base64.encodeToString("foo".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9v")); + assertThat(Base64.encodeToString("foob".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYg==")); + assertThat( + Base64.encodeToString("fooba".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYmE=")); + assertThat( + Base64.encodeToString("foobar".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYmFy")); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java new file mode 100644 index 0000000000..71b90c195e --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.testng.annotations.Test; + +public class ByteBufferInputStreamTest { + + @Test + public void testRead() throws IOException { + ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + for (int x = 0; x < 10; ++x) { + assertEquals(10 - x, bis.available()); + assertEquals(x, bis.read()); + } + assertEquals(0, bis.available()); + bis.close(); + } + + @Test + public void testReadByteArray() throws IOException { + ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + assertEquals(10, bis.available()); + + byte[] buff = new byte[4]; + + int len = bis.read(buff); + assertEquals(4, len); + assertEquals(6, bis.available()); + assertThat(buff, is(new byte[] {0, 1, 2, 3})); + + len = bis.read(buff); + assertEquals(4, len); + assertEquals(2, bis.available()); + assertThat(buff, is(new byte[] {4, 5, 6, 7})); + + len = bis.read(buff); + assertEquals(2, len); + assertEquals(0, bis.available()); + assertThat(buff, is(new byte[] {8, 9, 6, 7})); + bis.close(); + } + + @Test + public void testSkip() throws IOException { + ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{(byte) 0xFA, 15, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + assertEquals(13, bis.available()); + assertEquals(0xFA, bis.read()); + assertEquals(12, bis.available()); + bis.skip(2); + assertEquals(10, bis.available()); + for (int x = 0; x < 10; ++x) { + assertEquals(x, bis.read()); + } + assertEquals(0, bis.available()); + assertEquals(-1, bis.read()); + bis.close(); + } + + @Test + public void testMarkSupported() throws IOException { + try (ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.allocate(0))) { + assertFalse(bis.markSupported()); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java new file mode 100644 index 0000000000..7fcb5b89ab --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java @@ -0,0 +1,244 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import edu.umd.cs.mtc.MultithreadedTestCase; +import edu.umd.cs.mtc.TestFramework; +import java.util.concurrent.TimeUnit; +import org.testng.annotations.Test; + +/* Test specific thread interleavings with behaviors we care about in the + * TTLCache. + */ +public class ConcurrentTTLCacheTest { + + private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); + private static final long ttlInMillis = 1000; + + @Test + public void testGracePeriodCase() throws Throwable { + TestFramework.runOnce(new GracePeriodCase()); + } + + @Test + public void testExpiredCase() throws Throwable { + TestFramework.runOnce(new ExpiredCase()); + } + + @Test + public void testNewEntryCase() throws Throwable { + TestFramework.runOnce(new NewEntryCase()); + } + + @Test + public void testPutLoadCase() throws Throwable { + TestFramework.runOnce(new PutLoadCase()); + } + + // Ensure the loader is only called once if two threads attempt to load during the grace period + class GracePeriodCase extends MultithreadedTestCase { + TTLCache cache; + TTLCache.EntryLoader loader; + MsClock clock = mock(MsClock.class); + + @Override + public void initialize() { + loader = + spy( + new TTLCache.EntryLoader() { + @Override + public String load(String entryKey) { + // Wait until thread2 finishes to complete load + waitForTick(2); + return "loadedValue"; + } + }); + when(clock.timestampNano()).thenReturn((long) 0); + cache = new TTLCache<>(3, ttlInMillis, loader); + cache.clock = clock; + + // Put an initial value into the cache at time 0 + cache.put("k1", "v1"); + } + + // The thread that first calls load in the grace period and acquires the lock + public void thread1() { + when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); + String loadedValue = cache.load("k1"); + assertTick(2); + // Expect to get back the value calculated from load + assertEquals("loadedValue", loadedValue); + } + + // The thread that calls load in the grace period after the lock has been acquired + public void thread2() { + // Wait until the first thread acquires the lock and starts load + waitForTick(1); + when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); + String loadedValue = cache.load("k1"); + // Expect to get back the original value in the cache + assertEquals("v1", loadedValue); + } + + @Override + public void finish() { + // Ensure the loader was only called once + verify(loader, times(1)).load("k1"); + } + } + + // Ensure the loader is only called once if two threads attempt to load an expired entry. + class ExpiredCase extends MultithreadedTestCase { + TTLCache cache; + TTLCache.EntryLoader loader; + MsClock clock = mock(MsClock.class); + + @Override + public void initialize() { + loader = + spy( + new TTLCache.EntryLoader() { + @Override + public String load(String entryKey) { + // Wait until thread2 is waiting for the lock to complete load + waitForTick(2); + return "loadedValue"; + } + }); + when(clock.timestampNano()).thenReturn((long) 0); + cache = new TTLCache<>(3, ttlInMillis, loader); + cache.clock = clock; + + // Put an initial value into the cache at time 0 + cache.put("k1", "v1"); + } + + // The thread that first calls load after expiration + public void thread1() { + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + String loadedValue = cache.load("k1"); + assertTick(2); + // Expect to get back the value calculated from load + assertEquals("loadedValue", loadedValue); + } + + // The thread that calls load after expiration, + // after the first thread calls load, but before + // the new value is put into the cache. + public void thread2() { + // Wait until the first thread acquires the lock and starts load + waitForTick(1); + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + String loadedValue = cache.load("k1"); + // Expect to get back the newly loaded value + assertEquals("loadedValue", loadedValue); + // assert that this thread only finishes once the first thread's load does + assertTick(2); + } + + @Override + public void finish() { + // Ensure the loader was only called once + verify(loader, times(1)).load("k1"); + } + } + + // Ensure the loader is only called once if two threads attempt to load the same new entry. + class NewEntryCase extends MultithreadedTestCase { + TTLCache cache; + TTLCache.EntryLoader loader; + MsClock clock = mock(MsClock.class); + + @Override + public void initialize() { + loader = + spy( + new TTLCache.EntryLoader() { + @Override + public String load(String entryKey) { + // Wait until thread2 is blocked to complete load + waitForTick(2); + return "loadedValue"; + } + }); + when(clock.timestampNano()).thenReturn((long) 0); + cache = new TTLCache<>(3, ttlInMillis, loader); + cache.clock = clock; + } + + // The thread that first calls load + public void thread1() { + String loadedValue = cache.load("k1"); + assertTick(2); + // Expect to get back the value calculated from load + assertEquals("loadedValue", loadedValue); + } + + // The thread that calls load after the first thread calls load, + // but before the new value is put into the cache. + public void thread2() { + // Wait until the first thread acquires the lock and starts load + waitForTick(1); + String loadedValue = cache.load("k1"); + // Expect to get back the newly loaded value + assertEquals("loadedValue", loadedValue); + // assert that this thread only finishes once the first thread's load does + assertTick(2); + } + + @Override + public void finish() { + // Ensure the loader was only called once + verify(loader, times(1)).load("k1"); + } + } + + // Ensure the loader blocks put on load/put of the same new entry + class PutLoadCase extends MultithreadedTestCase { + TTLCache cache; + TTLCache.EntryLoader loader; + MsClock clock = mock(MsClock.class); + + @Override + public void initialize() { + loader = + spy( + new TTLCache.EntryLoader() { + @Override + public String load(String entryKey) { + // Wait until the put blocks to complete load + waitForTick(2); + return "loadedValue"; + } + }); + when(clock.timestampNano()).thenReturn((long) 0); + cache = new TTLCache<>(3, ttlInMillis, loader); + cache.clock = clock; + } + + // The thread that first calls load + public void thread1() { + String loadedValue = cache.load("k1"); + // Expect to get back the value calculated from load + assertEquals("loadedValue", loadedValue); + verify(loader, times(1)).load("k1"); + } + + // The thread that calls put during the first thread's load + public void thread2() { + // Wait until the first thread is loading + waitForTick(1); + String previousValue = cache.put("k1", "v1"); + // Expect to get back the value loaded into the cache by thread1 + assertEquals("loadedValue", previousValue); + // assert that this thread was blocked by the first thread + assertTick(2); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java new file mode 100644 index 0000000000..b9fdcb1d09 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java @@ -0,0 +1,209 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.testng.AssertJUnit.assertArrayEquals; + +import org.testng.annotations.Test; + +public class HkdfTests { + private static final testCase[] testCases = + new testCase[] { + new testCase( + "HmacSHA256", + fromCHex( + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" + + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), + fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), + fromHex( + "3CB25F25FAACD57A90434F64D0362F2A2D2D0A90CF1A5A4C5DB02D56ECC4C5BF34007208D5B887185865")), + new testCase( + "HmacSHA256", + fromCHex( + "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" + + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" + + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" + + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" + + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" + + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), + fromCHex( + "\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d" + + "\\x6e\\x6f\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b" + + "\\x7c\\x7d\\x7e\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" + + "\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" + + "\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5" + + "\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf"), + fromCHex( + "\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd" + + "\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb" + + "\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9" + + "\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7" + + "\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5" + + "\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff"), + fromHex( + "B11E398DC80327A1C8E7F78C596A4934" + + "4F012EDA2D4EFAD8A050CC4C19AFA97C" + + "59045A99CAC7827271CB41C65E590E09" + + "DA3275600C2F09B8367793A9ACA3DB71" + + "CC30C58179EC3E87C14C01D5C1F3434F" + + "1D87")), + new testCase( + "HmacSHA256", + fromCHex( + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" + + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + new byte[0], + new byte[0], + fromHex( + "8DA4E775A563C18F715F802A063C5A31" + + "B8A11F5C5EE1879EC3454E5F3C738D2D" + + "9D201395FAA4B61A96C8")), + new testCase( + "HmacSHA1", + fromCHex("\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), + fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), + fromHex( + "085A01EA1B10F36933068B56EFA5AD81" + + "A4F14B822F5B091568A9CDD4F155FDA2" + + "C22E422478D305F3F896")), + new testCase( + "HmacSHA1", + fromCHex( + "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" + + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" + + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" + + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" + + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" + + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), + fromCHex( + "\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6A\\x6B\\x6C\\x6D" + + "\\x6E\\x6F\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7A\\x7B" + + "\\x7C\\x7D\\x7E\\x7F\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" + + "\\x8A\\x8B\\x8C\\x8D\\x8E\\x8F\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" + + "\\x98\\x99\\x9A\\x9B\\x9C\\x9D\\x9E\\x9F\\xA0\\xA1\\xA2\\xA3\\xA4\\xA5" + + "\\xA6\\xA7\\xA8\\xA9\\xAA\\xAB\\xAC\\xAD\\xAE\\xAF"), + fromCHex( + "\\xB0\\xB1\\xB2\\xB3\\xB4\\xB5\\xB6\\xB7\\xB8\\xB9\\xBA\\xBB\\xBC\\xBD" + + "\\xBE\\xBF\\xC0\\xC1\\xC2\\xC3\\xC4\\xC5\\xC6\\xC7\\xC8\\xC9\\xCA\\xCB" + + "\\xCC\\xCD\\xCE\\xCF\\xD0\\xD1\\xD2\\xD3\\xD4\\xD5\\xD6\\xD7\\xD8\\xD9" + + "\\xDA\\xDB\\xDC\\xDD\\xDE\\xDF\\xE0\\xE1\\xE2\\xE3\\xE4\\xE5\\xE6\\xE7" + + "\\xE8\\xE9\\xEA\\xEB\\xEC\\xED\\xEE\\xEF\\xF0\\xF1\\xF2\\xF3\\xF4\\xF5" + + "\\xF6\\xF7\\xF8\\xF9\\xFA\\xFB\\xFC\\xFD\\xFE\\xFF"), + fromHex( + "0BD770A74D1160F7C9F12CD5912A06EB" + + "FF6ADCAE899D92191FE4305673BA2FFE" + + "8FA3F1A4E5AD79F3F334B3B202B2173C" + + "486EA37CE3D397ED034C7F9DFEB15C5E" + + "927336D0441F4C4300E2CFF0D0900B52D3B4")), + new testCase( + "HmacSHA1", + fromCHex( + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" + + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + new byte[0], + new byte[0], + fromHex("0AC1AF7002B3D761D1E55298DA9D0506" + "B9AE52057220A306E07B6B87E8DF21D0")), + new testCase( + "HmacSHA1", + fromCHex( + "\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c" + + "\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c"), + null, + new byte[0], + fromHex( + "2C91117204D745F3500D636A62F64F0A" + + "B3BAE548AA53D423B0D1F27EBBA6F5E5" + + "673A081D70CCE7ACFC48")) + }; + + @Test + public void rfc5869Tests() throws Exception { + for (int x = 0; x < testCases.length; x++) { + testCase trial = testCases[x]; + System.out.println("Test case A." + (x + 1)); + Hkdf kdf = Hkdf.getInstance(trial.algo); + kdf.init(trial.ikm, trial.salt); + byte[] result = kdf.deriveKey(trial.info, trial.expected.length); + assertArrayEquals("Trial A." + x, trial.expected, result); + } + } + + @Test + public void nullTests() throws Exception { + testCase trial = testCases[0]; + Hkdf kdf = Hkdf.getInstance(trial.algo); + kdf.init(trial.ikm, trial.salt); + // Just ensuring no exceptions are thrown + kdf.deriveKey((String) null, 16); + kdf.deriveKey((byte[]) null, 16); + } + + @Test + public void defaultSalt() throws Exception { + // Tests all the different ways to get the default salt + + testCase trial = testCases[0]; + Hkdf kdf1 = Hkdf.getInstance(trial.algo); + kdf1.init(trial.ikm, null); + Hkdf kdf2 = Hkdf.getInstance(trial.algo); + kdf2.init(trial.ikm, new byte[0]); + Hkdf kdf3 = Hkdf.getInstance(trial.algo); + kdf3.init(trial.ikm); + Hkdf kdf4 = Hkdf.getInstance(trial.algo); + kdf4.init(trial.ikm, new byte[32]); + + byte[] key1 = kdf1.deriveKey("Test", 16); + byte[] key2 = kdf2.deriveKey("Test", 16); + byte[] key3 = kdf3.deriveKey("Test", 16); + byte[] key4 = kdf4.deriveKey("Test", 16); + + assertArrayEquals(key1, key2); + assertArrayEquals(key1, key3); + assertArrayEquals(key1, key4); + } + + private static byte[] fromHex(String data) { + byte[] result = new byte[data.length() / 2]; + for (int x = 0; x < result.length; x++) { + result[x] = (byte) Integer.parseInt(data.substring(2 * x, 2 * x + 2), 16); + } + return result; + } + + private static byte[] fromCHex(String data) { + byte[] result = new byte[data.length() / 4]; + for (int x = 0; x < result.length; x++) { + result[x] = (byte) Integer.parseInt(data.substring(4 * x + 2, 4 * x + 4), 16); + } + return result; + } + + private static class testCase { + public final String algo; + public final byte[] ikm; + public final byte[] salt; + public final byte[] info; + public final byte[] expected; + + public testCase(String algo, byte[] ikm, byte[] salt, byte[] info, byte[] expected) { + super(); + this.algo = algo; + this.ikm = ikm; + this.salt = salt; + this.info = info; + this.expected = expected; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java new file mode 100644 index 0000000000..8f56d35b96 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import org.testng.annotations.Test; + +public class LRUCacheTest { + @Test + public void test() { + final LRUCache cache = new LRUCache(3); + assertEquals(0, cache.size()); + assertEquals(3, cache.getMaxSize()); + cache.add("k1", "v1"); + assertTrue(cache.size() == 1); + cache.add("k1", "v11"); + assertTrue(cache.size() == 1); + cache.add("k2", "v2"); + assertTrue(cache.size() == 2); + cache.add("k3", "v3"); + assertTrue(cache.size() == 3); + assertEquals("v11", cache.get("k1")); + assertEquals("v2", cache.get("k2")); + assertEquals("v3", cache.get("k3")); + cache.add("k4", "v4"); + assertTrue(cache.size() == 3); + assertNull(cache.get("k1")); + assertEquals("v4", cache.get("k4")); + assertEquals("v2", cache.get("k2")); + assertEquals("v3", cache.get("k3")); + assertTrue(cache.size() == 3); + cache.add("k5", "v5"); + assertNull(cache.get("k4")); + assertEquals("v5", cache.get("k5")); + assertEquals("v2", cache.get("k2")); + assertEquals("v3", cache.get("k3")); + cache.clear(); + assertEquals(0, cache.size()); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testZeroSize() { + new LRUCache(0); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testIllegalArgument() { + new LRUCache(-1); + } + + @Test + public void testSingleEntry() { + final LRUCache cache = new LRUCache(1); + assertTrue(cache.size() == 0); + cache.add("k1", "v1"); + assertTrue(cache.size() == 1); + cache.add("k1", "v11"); + assertTrue(cache.size() == 1); + assertEquals("v11", cache.get("k1")); + + cache.add("k2", "v2"); + assertTrue(cache.size() == 1); + assertEquals("v2", cache.get("k2")); + assertNull(cache.get("k1")); + + cache.add("k3", "v3"); + assertTrue(cache.size() == 1); + assertEquals("v3", cache.get("k3")); + assertNull(cache.get("k2")); + } + +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java new file mode 100644 index 0000000000..55e246f391 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java @@ -0,0 +1,372 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertThrows; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import org.testng.annotations.Test; + +public class TTLCacheTest { + + private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidSize() { + final TTLCache cache = new TTLCache(0, 1000, mock(TTLCache.EntryLoader.class)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidTTL() { + final TTLCache cache = new TTLCache(3, 0, mock(TTLCache.EntryLoader.class)); + } + + + @Test(expectedExceptions = NullPointerException.class) + public void testNullLoader() { + final TTLCache cache = new TTLCache(3, 1000, null); + } + + @Test + public void testConstructor() { + final TTLCache cache = + new TTLCache(1000, 1000, mock(TTLCache.EntryLoader.class)); + assertEquals(0, cache.size()); + assertEquals(1000, cache.getMaxSize()); + } + + @Test + public void testLoadPastMaxSize() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 1; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + cache.load("k1"); + verify(loader, times(1)).load("k1"); + assertTrue(cache.size() == 1); + + String result = cache.load("k2"); + verify(loader, times(1)).load("k2"); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + + // to verify result is in the cache, load one more time + // and expect the loader to not be called + String cachedValue = cache.load("k2"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, cachedValue); + } + + @Test + public void testLoadNoExistingEntry() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + String result = cache.load("k1"); + verify(loader, times(1)).load("k1"); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + + // to verify result is in the cache, load one more time + // and expect the loader to not be called + String cachedValue = cache.load("k1"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, cachedValue); + } + + @Test + public void testLoadNotExpired() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // when first creating the entry, time is 0 + when(clock.timestampNano()).thenReturn((long) 0); + cache.load("k1"); + assertTrue(cache.size() == 1); + verify(loader, times(1)).load("k1"); + + // on load, time is within TTL + when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis)); + String result = cache.load("k1"); + + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + } + + @Test + public void testLoadInGrace() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // when first creating the entry, time is zero + when(clock.timestampNano()).thenReturn((long) 0); + cache.load("k1"); + assertTrue(cache.size() == 1); + verify(loader, times(1)).load("k1"); + + // on load, time is past TTL but within the grace period + when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); + String result = cache.load("k1"); + + // Because this is tested in a single thread, + // this is expected to obtain the lock and load the new value + verify(loader, times(2)).load("k1"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + } + + @Test + public void testLoadExpired() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // when first creating the entry, time is zero + when(clock.timestampNano()).thenReturn((long) 0); + cache.load("k1"); + assertTrue(cache.size() == 1); + verify(loader, times(1)).load("k1"); + + // on load, time is past TTL and grace period + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + String result = cache.load("k1"); + + verify(loader, times(2)).load("k1"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + } + + @Test + public void testLoadExpiredEviction() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())) + .thenReturn(loadedValue) + .thenThrow(new IllegalStateException("This loader is mocked to throw a failure.")); + MsClock clock = mock(MsClock.class); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // when first creating the entry, time is zero + when(clock.timestampNano()).thenReturn((long) 0); + cache.load("k1"); + verify(loader, times(1)).load("k1"); + assertTrue(cache.size() == 1); + + // on load, time is past TTL and grace period + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + assertThrows(IllegalStateException.class, () -> cache.load("k1")); + + verify(loader, times(2)).load("k1"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 0); + } + + @Test + public void testLoadWithFunction() { + final String loadedValue = "loaded value"; + final String functionValue = "function value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + final Function function = spy(Function.class); + when(function.apply(any())).thenReturn(functionValue); + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())) + .thenReturn(loadedValue) + .thenThrow(new IllegalStateException("This loader is mocked to throw a failure.")); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + String result = cache.load("k1", function); + verify(function, times(1)).apply("k1"); + assertTrue(cache.size() == 1); + assertEquals(functionValue, result); + + // to verify result is in the cache, load one more time + // and expect the loader to not be called + String cachedValue = cache.load("k1"); + verifyNoMoreInteractions(function); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(functionValue, cachedValue); + } + + @Test + public void testClear() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + + assertTrue(cache.size() == 0); + cache.load("k1"); + cache.load("k2"); + assertTrue(cache.size() == 2); + + cache.clear(); + assertTrue(cache.size() == 0); + } + + @Test + public void testPut() { + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + String oldValue = cache.put("k1", "v1"); + assertNull(oldValue); + assertTrue(cache.size() == 1); + + String oldValue2 = cache.put("k1", "v11"); + assertEquals("v1", oldValue2); + assertTrue(cache.size() == 1); + } + + @Test + public void testExpiredPut() { + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // First put is at time 0 + String oldValue = cache.put("k1", "v1"); + assertNull(oldValue); + assertTrue(cache.size() == 1); + + // Second put is at time past TTL and grace period + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + String oldValue2 = cache.put("k1", "v11"); + assertNull(oldValue2); + assertTrue(cache.size() == 1); + } + + @Test + public void testPutPastMaxSize() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 1; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + cache.put("k1", "v1"); + assertTrue(cache.size() == 1); + + cache.put("k2", "v2"); + assertTrue(cache.size() == 1); + + // to verify put value is in the cache, load + // and expect the loader to not be called + String cachedValue = cache.load("k2"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(cachedValue, "v2"); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java new file mode 100644 index 0000000000..122364e6db --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java @@ -0,0 +1,125 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class AttrMatcher extends BaseMatcher> { + private final Map expected; + private final boolean invert; + + public static AttrMatcher invert(Map expected) { + return new AttrMatcher(expected, true); + } + + public static AttrMatcher match(Map expected) { + return new AttrMatcher(expected, false); + } + + public AttrMatcher(Map expected, boolean invert) { + this.expected = expected; + this.invert = invert; + } + + @Override + public boolean matches(Object item) { + @SuppressWarnings("unchecked") + Map actual = (Map)item; + if (!expected.keySet().equals(actual.keySet())) { + return invert; + } + for (String key: expected.keySet()) { + AttributeValue e = expected.get(key); + AttributeValue a = actual.get(key); + if (!attrEquals(a, e)) { + return invert; + } + } + return !invert; + } + + public static boolean attrEquals(AttributeValue e, AttributeValue a) { + if (!isEqual(e.b(), a.b()) || + !isEqual(e.bool(), a.bool()) || + !isSetEqual(e.bs(), a.bs()) || + !isEqual(e.n(), a.n()) || + !isSetEqual(e.ns(), a.ns()) || + !isEqual(e.nul(), a.nul()) || + !isEqual(e.s(), a.s()) || + !isSetEqual(e.ss(), a.ss())) { + return false; + } + // Recursive types need special handling + if (e.m() == null ^ a.m() == null) { + return false; + } else if (e.m() != null) { + if (!e.m().keySet().equals(a.m().keySet())) { + return false; + } + for (final String key : e.m().keySet()) { + if (!attrEquals(e.m().get(key), a.m().get(key))) { + return false; + } + } + } + if (e.l() == null ^ a.l() == null) { + return false; + } else if (e.l() != null) { + if (e.l().size() != a.l().size()) { + return false; + } + for (int x = 0; x < e.l().size(); x++) { + if (!attrEquals(e.l().get(x), a.l().get(x))) { + return false; + } + } + } + return true; + } + + @Override + public void describeTo(Description description) { } + + private static boolean isEqual(Object o1, Object o2) { + if(o1 == null ^ o2 == null) { + return false; + } + if (o1 == o2) + return true; + return o1.equals(o2); + } + + private static boolean isSetEqual(Collection c1, Collection c2) { + if(c1 == null ^ c2 == null) { + return false; + } + if (c1 != null) { + Set s1 = new HashSet(c1); + Set s2 = new HashSet(c2); + if(!s1.equals(s2)) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java new file mode 100644 index 0000000000..3ff7e4dff8 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java @@ -0,0 +1,67 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.util.List; +import java.util.Map; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +/** + * Static helper methods to construct standard AttributeValues in a more compact way than specifying the full builder + * chain. + */ +public final class AttributeValueBuilder { + private AttributeValueBuilder() { + // Static helper class + } + + public static AttributeValue ofS(String value) { + return AttributeValue.builder().s(value).build(); + } + + public static AttributeValue ofN(String value) { + return AttributeValue.builder().n(value).build(); + } + + public static AttributeValue ofB(byte [] value) { + return AttributeValue.builder().b(SdkBytes.fromByteArray(value)).build(); + } + + public static AttributeValue ofBool(Boolean value) { + return AttributeValue.builder().bool(value).build(); + } + + public static AttributeValue ofNull() { + return AttributeValue.builder().nul(true).build(); + } + + public static AttributeValue ofL(List values) { + return AttributeValue.builder().l(values).build(); + } + + public static AttributeValue ofL(AttributeValue ...values) { + return AttributeValue.builder().l(values).build(); + } + + public static AttributeValue ofM(Map valueMap) { + return AttributeValue.builder().m(valueMap).build(); + } + + public static AttributeValue ofSS(String ...values) { + return AttributeValue.builder().ss(values).build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java new file mode 100644 index 0000000000..42e479ba70 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java @@ -0,0 +1,58 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class AttributeValueDeserializer extends JsonDeserializer { + @Override + public AttributeValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode attribute = jp.getCodec().readTree(jp); + + for (Iterator> iter = attribute.fields(); iter.hasNext(); ) { + Map.Entry rawAttribute = iter.next(); + // If there is more than one entry in this map, there is an error with our test data + if (iter.hasNext()) { + throw new IllegalStateException("Attribute value JSON has more than one value mapped."); + } + String typeString = rawAttribute.getKey(); + JsonNode value = rawAttribute.getValue(); + switch (typeString) { + case "S": + return AttributeValue.builder().s(value.asText()).build(); + case "B": + SdkBytes b = SdkBytes.fromByteArray(java.util.Base64.getDecoder().decode(value.asText())); + return AttributeValue.builder().b(b).build(); + case "N": + return AttributeValue.builder().n(value.asText()).build(); + case "SS": + final Set stringSet = + objectMapper.readValue( + objectMapper.treeAsTokens(value), new TypeReference>() {}); + return AttributeValue.builder().ss(stringSet).build(); + case "NS": + final Set numSet = + objectMapper.readValue( + objectMapper.treeAsTokens(value), new TypeReference>() {}); + return AttributeValue.builder().ns(numSet).build(); + default: + throw new IllegalStateException( + "DDB JSON type " + + typeString + + " not implemented for test attribute value deserialization."); + } + } + return null; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java new file mode 100644 index 0000000000..0dbea38209 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java @@ -0,0 +1,101 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class AttributeValueMatcher extends BaseMatcher { + private final AttributeValue expected; + private final boolean invert; + + public static AttributeValueMatcher invert(AttributeValue expected) { + return new AttributeValueMatcher(expected, true); + } + + public static AttributeValueMatcher match(AttributeValue expected) { + return new AttributeValueMatcher(expected, false); + } + + public AttributeValueMatcher(AttributeValue expected, boolean invert) { + this.expected = expected; + this.invert = invert; + } + + @Override + public boolean matches(Object item) { + AttributeValue other = (AttributeValue) item; + return invert ^ attrEquals(expected, other); + } + + @Override + public void describeTo(Description description) {} + + public static boolean attrEquals(AttributeValue e, AttributeValue a) { + if (!isEqual(e.b(), a.b()) + || !isNumberEqual(e.n(), a.n()) + || !isEqual(e.s(), a.s()) + || !isEqual(e.bs(), a.bs()) + || !isNumberEqual(e.ns(), a.ns()) + || !isEqual(e.ss(), a.ss())) { + return false; + } + return true; + } + + private static boolean isNumberEqual(String o1, String o2) { + if (o1 == null ^ o2 == null) { + return false; + } + if (o1 == o2) return true; + BigDecimal d1 = new BigDecimal(o1); + BigDecimal d2 = new BigDecimal(o2); + return d1.equals(d2); + } + + private static boolean isEqual(Object o1, Object o2) { + if (o1 == null ^ o2 == null) { + return false; + } + if (o1 == o2) return true; + return o1.equals(o2); + } + + private static boolean isNumberEqual(Collection c1, Collection c2) { + if (c1 == null ^ c2 == null) { + return false; + } + if (c1 != null) { + Set s1 = new HashSet(); + Set s2 = new HashSet(); + for (String s : c1) { + s1.add(new BigDecimal(s)); + } + for (String s : c2) { + s2.add(new BigDecimal(s)); + } + if (!s1.equals(s2)) { + return false; + } + } + return true; + } + + private static boolean isEqual(Collection c1, Collection c2) { + if (c1 == null ^ c2 == null) { + return false; + } + if (c1 != null) { + Set s1 = new HashSet(c1); + Set s2 = new HashSet(c2); + if (!s1.equals(s2)) { + return false; + } + } + return true; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java new file mode 100644 index 0000000000..95abc5471d --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java @@ -0,0 +1,48 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import com.amazonaws.util.Base64; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class AttributeValueSerializer extends JsonSerializer { + @Override + public void serialize(AttributeValue.Builder value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { + if (value != null) { + jgen.writeStartObject(); + if (value.build().s() != null) { + jgen.writeStringField("S", value.build().s()); + } else if (value.build().b() != null) { + ByteBuffer valueBytes = value.build().b().asByteBuffer(); + byte[] arr = new byte[valueBytes.remaining()]; + valueBytes.get(arr); + jgen.writeStringField("B", Base64.encodeAsString(arr)); + } else if (value.build().n() != null) { + jgen.writeStringField("N", value.build().n()); + } else if (value.build().ss() != null) { + jgen.writeFieldName("SS"); + jgen.writeStartArray(); + for (String s : value.build().ss()) { + jgen.writeString(s); + } + jgen.writeEndArray(); + } else if (value.build().ns() != null) { + jgen.writeFieldName("NS"); + jgen.writeStartArray(); + for (String num : value.build().ns()) { + jgen.writeString(num); + } + jgen.writeEndArray(); + } else { + throw new IllegalStateException( + "AttributeValue has no value or type not implemented for serialization."); + } + jgen.writeEndObject(); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java new file mode 100644 index 0000000000..75cc574a1c --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java @@ -0,0 +1,47 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import java.util.Map; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +public class DdbRecordMatcher extends BaseMatcher>{ + private final Map expected; + private final boolean invert; + + public static DdbRecordMatcher invert(Map expected) { + return new DdbRecordMatcher(expected, true); + } + + public static DdbRecordMatcher match(Map expected) { + return new DdbRecordMatcher(expected, false); + } + + public DdbRecordMatcher(Map expected, boolean invert) { + this.expected = expected; + this.invert = invert; + } + + @Override + public boolean matches(Object item) { + @SuppressWarnings("unchecked") + Map actual = (Map) item; + if (!expected.keySet().equals(actual.keySet())) { + return invert; + } + for (String key : expected.keySet()) { + if (key.equals("version")) continue; + AttributeValue e = expected.get(key); + AttributeValue a = actual.get(key); + if (!AttributeValueMatcher.attrEquals(a, e)) { + return invert; + } + } + return !invert; + } + + @Override + public void describeTo(Description description) { + + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java new file mode 100644 index 0000000000..d05aff4113 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java @@ -0,0 +1,201 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.CreateKeyRequest; +import software.amazon.awssdk.services.kms.model.CreateKeyResponse; +import software.amazon.awssdk.services.kms.model.DecryptRequest; +import software.amazon.awssdk.services.kms.model.DecryptResponse; +import software.amazon.awssdk.services.kms.model.EncryptRequest; +import software.amazon.awssdk.services.kms.model.EncryptResponse; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyWithoutPlaintextRequest; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyWithoutPlaintextResponse; +import software.amazon.awssdk.services.kms.model.InvalidCiphertextException; +import software.amazon.awssdk.services.kms.model.KeyMetadata; +import software.amazon.awssdk.services.kms.model.KeyUsageType; + +public class FakeKMS implements KmsClient { + private static final SecureRandom rnd = new SecureRandom(); + private static final String ACCOUNT_ID = "01234567890"; + private final Map results_ = new HashMap<>(); + + @Override + public CreateKeyResponse createKey(CreateKeyRequest createKeyRequest) { + String keyId = UUID.randomUUID().toString(); + String arn = "arn:aws:testing:kms:" + ACCOUNT_ID + ":key/" + keyId; + return CreateKeyResponse.builder() + .keyMetadata(KeyMetadata.builder().awsAccountId(ACCOUNT_ID) + .creationDate(Instant.now()) + .description(createKeyRequest.description()) + .enabled(true) + .keyId(keyId) + .keyUsage(KeyUsageType.ENCRYPT_DECRYPT) + .arn(arn) + .build()) + .build(); + } + + @Override + public DecryptResponse decrypt(DecryptRequest decryptRequest) { + DecryptResponse result = results_.get(new DecryptMapKey(decryptRequest)); + if (result != null) { + return result; + } else { + throw InvalidCiphertextException.create("Invalid Ciphertext", new RuntimeException()); + } + } + + @Override + public EncryptResponse encrypt(EncryptRequest encryptRequest) { + final byte[] cipherText = new byte[512]; + rnd.nextBytes(cipherText); + DecryptResponse.Builder dec = DecryptResponse.builder(); + dec.keyId(encryptRequest.keyId()) + .plaintext(SdkBytes.fromByteBuffer(encryptRequest.plaintext().asByteBuffer().asReadOnlyBuffer())); + ByteBuffer ctBuff = ByteBuffer.wrap(cipherText); + + results_.put(new DecryptMapKey(ctBuff, encryptRequest.encryptionContext()), dec.build()); + + return EncryptResponse.builder() + .ciphertextBlob(SdkBytes.fromByteBuffer(ctBuff)) + .keyId(encryptRequest.keyId()) + .build(); + } + + @Override + public GenerateDataKeyResponse generateDataKey(GenerateDataKeyRequest generateDataKeyRequest) { + byte[] pt; + if (generateDataKeyRequest.keySpec() != null) { + if (generateDataKeyRequest.keySpec().toString().contains("256")) { + pt = new byte[32]; + } else if (generateDataKeyRequest.keySpec().toString().contains("128")) { + pt = new byte[16]; + } else { + throw new UnsupportedOperationException(); + } + } else { + pt = new byte[generateDataKeyRequest.numberOfBytes()]; + } + rnd.nextBytes(pt); + ByteBuffer ptBuff = ByteBuffer.wrap(pt); + EncryptResponse encryptresponse = encrypt(EncryptRequest.builder() + .keyId(generateDataKeyRequest.keyId()) + .plaintext(SdkBytes.fromByteBuffer(ptBuff)) + .encryptionContext(generateDataKeyRequest.encryptionContext()) + .build()); + return GenerateDataKeyResponse.builder().keyId(generateDataKeyRequest.keyId()) + .ciphertextBlob(encryptresponse.ciphertextBlob()) + .plaintext(SdkBytes.fromByteBuffer(ptBuff)) + .build(); + } + + @Override + public GenerateDataKeyWithoutPlaintextResponse generateDataKeyWithoutPlaintext( + GenerateDataKeyWithoutPlaintextRequest req) { + GenerateDataKeyResponse generateDataKey = generateDataKey(GenerateDataKeyRequest.builder() + .encryptionContext(req.encryptionContext()).numberOfBytes(req.numberOfBytes()).build()); + return GenerateDataKeyWithoutPlaintextResponse.builder().ciphertextBlob( + generateDataKey.ciphertextBlob()).keyId(req.keyId()).build(); + } + + public Map getSingleEc() { + if (results_.size() != 1) { + throw new IllegalStateException("Unexpected number of ciphertexts"); + } + for (final DecryptMapKey k : results_.keySet()) { + return k.ec; + } + throw new IllegalStateException("Unexpected number of ciphertexts"); + } + + @Override + public String serviceName() { + return KmsClient.SERVICE_NAME; + } + + @Override + public void close() { + // do nothing + } + + private static class DecryptMapKey { + private final ByteBuffer cipherText; + private final Map ec; + + public DecryptMapKey(DecryptRequest req) { + cipherText = req.ciphertextBlob().asByteBuffer(); + if (req.encryptionContext() != null) { + ec = Collections.unmodifiableMap(new HashMap<>(req.encryptionContext())); + } else { + ec = Collections.emptyMap(); + } + } + + public DecryptMapKey(ByteBuffer ctBuff, Map ec) { + cipherText = ctBuff.asReadOnlyBuffer(); + if (ec != null) { + this.ec = Collections.unmodifiableMap(new HashMap<>(ec)); + } else { + this.ec = Collections.emptyMap(); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((cipherText == null) ? 0 : cipherText.hashCode()); + result = prime * result + ((ec == null) ? 0 : ec.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DecryptMapKey other = (DecryptMapKey) obj; + if (cipherText == null) { + if (other.cipherText != null) + return false; + } else if (!cipherText.equals(other.cipherText)) + return false; + if (ec == null) { + if (other.ec != null) + return false; + } else if (!ec.equals(other.ec)) + return false; + return true; + } + + @Override + public String toString() { + return "DecryptMapKey [cipherText=" + cipherText + ", ec=" + ec + "]"; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java new file mode 100644 index 0000000000..fe293a0d02 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java @@ -0,0 +1,175 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.URI; + +import com.amazonaws.services.dynamodbv2.local.main.ServerRunner; +import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer; + +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.*; + +/** + * Wrapper for a local DynamoDb server used in testing. Each instance of this class will find a new port to run on, + * so multiple instances can be safely run simultaneously. Each instance of this service uses memory as a storage medium + * and is thus completely ephemeral; no data will be persisted between stops and starts. + * + * LocalDynamoDb localDynamoDb = new LocalDynamoDb(); + * localDynamoDb.start(); // Start the service running locally on host + * DynamoDbClient dynamoDbClient = localDynamoDb.createClient(); + * ... // Do your testing with the client + * localDynamoDb.stop(); // Stop the service and free up resources + * + * If possible it's recommended to keep a single running instance for all your tests, as it can be slow to teardown + * and create new servers for every test, but there have been observed problems when dropping tables between tests for + * this scenario, so it's best to write your tests to be resilient to tables that already have data in them. + */ +public class LocalDynamoDb { + private static DynamoDBProxyServer server; + private static int port; + + /** + * Start the local DynamoDb service and run in background + */ + public void start() { + port = getFreePort(); + String portString = Integer.toString(port); + + try { + server = createServer(portString); + server.start(); + } catch (Exception e) { + throw propagate(e); + } + } + + /** + * Create a standard AWS v2 SDK client pointing to the local DynamoDb instance + * @return A DynamoDbClient pointing to the local DynamoDb instance + */ + public DynamoDbClient createClient() { + start(); + String endpoint = String.format("http://localhost:%d", port); + return DynamoDbClient.builder() + .endpointOverride(URI.create(endpoint)) + // The region is meaningless for local DynamoDb but required for client builder validation + .region(Region.US_EAST_1) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("dummy-key", "dummy-secret"))) + .build(); + } + + /** + * If you require a client object that can be mocked or spied using standard mocking frameworks, then you must call + * this method to create the client instead. Only some methods are supported by this client, but it is easy to add + * new ones. + * @return A mockable/spyable DynamoDbClient pointing to the Local DynamoDB service. + */ + public DynamoDbClient createLimitedWrappedClient() { + return new WrappedDynamoDbClient(createClient()); + } + + /** + * Stops the local DynamoDb service and frees up resources it is using. + */ + public void stop() { + try { + server.stop(); + } catch (Exception e) { + throw propagate(e); + } + } + + private static DynamoDBProxyServer createServer(String portString) throws Exception { + return ServerRunner.createServerFromCommandLineArgs( + new String[]{ + "-inMemory", + "-port", portString + }); + } + + private static int getFreePort() { + try { + ServerSocket socket = new ServerSocket(0); + int port = socket.getLocalPort(); + socket.close(); + return port; + } catch (IOException ioe) { + throw propagate(ioe); + } + } + + private static RuntimeException propagate(Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } + throw new RuntimeException(e); + } + + /** + * This class can wrap any other implementation of a DynamoDbClient. The default implementation of the real + * DynamoDbClient is a final class, therefore it cannot be easily spied upon unless you first wrap it in a class + * like this. If there's a method you need it to support, just add it to the wrapper here. + */ + private static class WrappedDynamoDbClient implements DynamoDbClient { + private final DynamoDbClient wrappedClient; + + private WrappedDynamoDbClient(DynamoDbClient wrappedClient) { + this.wrappedClient = wrappedClient; + } + + @Override + public String serviceName() { + return wrappedClient.serviceName(); + } + + @Override + public void close() { + wrappedClient.close(); + } + + @Override + public PutItemResponse putItem(PutItemRequest putItemRequest) { + return wrappedClient.putItem(putItemRequest); + } + + @Override + public GetItemResponse getItem(GetItemRequest getItemRequest) { + return wrappedClient.getItem(getItemRequest); + } + + @Override + public QueryResponse query(QueryRequest queryRequest) { + return wrappedClient.query(queryRequest); + } + + @Override + public ListTablesResponse listTables(ListTablesRequest listTablesRequest) { return wrappedClient.listTables(listTablesRequest); } + + @Override + public ScanResponse scan(ScanRequest scanRequest) { return wrappedClient.scan(scanRequest); } + + @Override + public CreateTableResponse createTable(CreateTableRequest createTableRequest) { + return wrappedClient.createTable(createTableRequest); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java new file mode 100644 index 0000000000..14c84d8605 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java @@ -0,0 +1,77 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ScenarioManifest { + + public static final String MOST_RECENT_PROVIDER_NAME = "most_recent"; + public static final String WRAPPED_PROVIDER_NAME = "wrapped"; + public static final String STATIC_PROVIDER_NAME = "static"; + public static final String AWS_KMS_PROVIDER_NAME = "awskms"; + public static final String SYMMETRIC_KEY_TYPE = "symmetric"; + + public List scenarios; + + @JsonProperty("keys") + public String keyDataPath; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Scenario { + @JsonProperty("ciphertext") + public String ciphertextPath; + + @JsonProperty("provider") + public String providerName; + + public String version; + + @JsonProperty("material_name") + public String materialName; + + public Metastore metastore; + public Keys keys; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Metastore { + @JsonProperty("ciphertext") + public String path; + + @JsonProperty("table_name") + public String tableName; + + @JsonProperty("provider") + public String providerName; + + public Keys keys; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Keys { + @JsonProperty("encrypt") + public String encryptName; + + @JsonProperty("sign") + public String signName; + + @JsonProperty("decrypt") + public String decryptName; + + @JsonProperty("verify") + public String verifyName; + } + + public static class KeyData { + public String material; + public String algorithm; + public String encoding; + + @JsonProperty("type") + public String keyType; + + public String keyId; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java new file mode 100644 index 0000000000..c19c5565b3 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java @@ -0,0 +1,128 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DelegatedKey; + +public class TestDelegatedKey implements DelegatedKey { + private static final long serialVersionUID = 1L; + + private final Key realKey; + + public TestDelegatedKey(Key key) { + this.realKey = key; + } + + @Override + public String getAlgorithm() { + return "DELEGATED:" + realKey.getAlgorithm(); + } + + @Override + public byte[] getEncoded() { + return realKey.getEncoded(); + } + + @Override + public String getFormat() { + return realKey.getFormat(); + } + + @Override + public byte[] encrypt(byte[] plainText, byte[] additionalAssociatedData, String algorithm) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, + NoSuchPaddingException { + Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); + cipher.init(Cipher.ENCRYPT_MODE, realKey); + byte[] iv = cipher.getIV(); + byte[] result = new byte[cipher.getOutputSize(plainText.length) + iv.length + 1]; + result[0] = (byte) iv.length; + System.arraycopy(iv, 0, result, 1, iv.length); + try { + cipher.doFinal(plainText, 0, plainText.length, result, iv.length + 1); + } catch (ShortBufferException e) { + throw new RuntimeException(e); + } + return result; + } + + @Override + public byte[] decrypt(byte[] cipherText, byte[] additionalAssociatedData, String algorithm) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, + NoSuchPaddingException, InvalidAlgorithmParameterException { + final byte ivLength = cipherText[0]; + IvParameterSpec iv = new IvParameterSpec(cipherText, 1, ivLength); + Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); + cipher.init(Cipher.DECRYPT_MODE, realKey, iv); + return cipher.doFinal(cipherText, ivLength + 1, cipherText.length - ivLength - 1); + } + + @Override + public byte[] wrap(Key key, byte[] additionalAssociatedData, String algorithm) throws InvalidKeyException, + NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException { + Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); + cipher.init(Cipher.WRAP_MODE, realKey); + return cipher.wrap(key); + } + + @Override + public Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType, + byte[] additionalAssociatedData, String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException, + InvalidKeyException { + Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); + cipher.init(Cipher.UNWRAP_MODE, realKey); + return cipher.unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType); + } + + @Override + public byte[] sign(byte[] dataToSign, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException { + Mac mac = Mac.getInstance(extractAlgorithm(algorithm)); + mac.init(realKey); + return mac.doFinal(dataToSign); + } + + @Override + public boolean verify(byte[] dataToSign, byte[] signature, String algorithm) { + try { + byte[] expected = sign(dataToSign, extractAlgorithm(algorithm)); + return MessageDigest.isEqual(expected, signature); + } catch (GeneralSecurityException ex) { + return false; + } + } + + private String extractAlgorithm(String alg) { + if (alg.startsWith(getAlgorithm())) { + return alg.substring(10); + } else { + return alg; + } + } +} From e89541cbbfb5e0bb514fbb5dbb754478d35022ca Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 14:07:23 -0800 Subject: [PATCH 02/33] m --- DynamoDbEncryption/runtimes/java/build.gradle.kts | 7 ++++++- .../runtimes/java/dynamodb-local-metadata.json | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json diff --git a/DynamoDbEncryption/runtimes/java/build.gradle.kts b/DynamoDbEncryption/runtimes/java/build.gradle.kts index 2bbd345882..77065d5ec1 100644 --- a/DynamoDbEncryption/runtimes/java/build.gradle.kts +++ b/DynamoDbEncryption/runtimes/java/build.gradle.kts @@ -33,6 +33,7 @@ java { srcDir("src/main/dafny-generated") srcDir("src/main/smithy-generated") srcDir("src/main/sdkv1") + srcDir("src/main/sdkv2") } sourceSets["test"].java { srcDir("src/test") @@ -91,8 +92,12 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.4") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.4") - // For the DDB-EC v1 + // For the DDB-EC with SDK v1 implementation("com.amazonaws:aws-java-sdk-dynamodb:1.12.780") + // For the DDB-EC with SDK V2 + implementation("io.netty:netty-common:4.2.9.Final") + + testImplementation("software.amazon.awssdk:url-connection-client:2.41.17") // https://mvnrepository.com/artifact/org.testng/testng testImplementation("org.testng:testng:7.5") // https://mvnrepository.com/artifact/com.amazonaws/DynamoDBLocal diff --git a/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json b/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json new file mode 100644 index 0000000000..b041173a8c --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json @@ -0,0 +1 @@ +{"installationId":"64477908-a49f-45a3-b4b3-e44905cf8ec6","telemetryEnabled":"true"} \ No newline at end of file From f6f27e46dddb26dfc1c4882d9141a723b5eff118 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 14:07:23 -0800 Subject: [PATCH 03/33] m --- DynamoDbEncryption/runtimes/java/build.gradle.kts | 7 ++++++- .../runtimes/java/dynamodb-local-metadata.json | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json diff --git a/DynamoDbEncryption/runtimes/java/build.gradle.kts b/DynamoDbEncryption/runtimes/java/build.gradle.kts index 2bbd345882..77065d5ec1 100644 --- a/DynamoDbEncryption/runtimes/java/build.gradle.kts +++ b/DynamoDbEncryption/runtimes/java/build.gradle.kts @@ -33,6 +33,7 @@ java { srcDir("src/main/dafny-generated") srcDir("src/main/smithy-generated") srcDir("src/main/sdkv1") + srcDir("src/main/sdkv2") } sourceSets["test"].java { srcDir("src/test") @@ -91,8 +92,12 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.4") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.4") - // For the DDB-EC v1 + // For the DDB-EC with SDK v1 implementation("com.amazonaws:aws-java-sdk-dynamodb:1.12.780") + // For the DDB-EC with SDK V2 + implementation("io.netty:netty-common:4.2.9.Final") + + testImplementation("software.amazon.awssdk:url-connection-client:2.41.17") // https://mvnrepository.com/artifact/org.testng/testng testImplementation("org.testng:testng:7.5") // https://mvnrepository.com/artifact/com.amazonaws/DynamoDBLocal diff --git a/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json b/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json new file mode 100644 index 0000000000..b041173a8c --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json @@ -0,0 +1 @@ +{"installationId":"64477908-a49f-45a3-b4b3-e44905cf8ec6","telemetryEnabled":"true"} \ No newline at end of file From e5ea0959dbd711292f6b708d3abe91cfe65f0c6e Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 14:18:19 -0800 Subject: [PATCH 04/33] Revert "copy code from internal" This reverts commit ec8077cdc031bca3d22d6d785fd39e0d5713b214. --- .../encryption/DelegatedKey.java | 146 --- .../encryption/DynamoDbEncryptor.java | 595 ----------- .../encryption/DynamoDbSigner.java | 261 ----- .../encryption/EncryptionContext.java | 187 ---- .../encryption/EncryptionFlags.java | 23 - .../DynamoDbEncryptionException.java | 47 - .../materials/AbstractRawMaterials.java | 73 -- .../materials/AsymmetricRawMaterials.java | 49 - .../materials/CryptographicMaterials.java | 24 - .../materials/DecryptionMaterials.java | 27 - .../materials/EncryptionMaterials.java | 27 - .../materials/SymmetricRawMaterials.java | 58 -- .../materials/WrappedRawMaterials.java | 212 ---- .../providers/AsymmetricStaticProvider.java | 46 - .../providers/CachingMostRecentProvider.java | 183 ---- .../providers/DirectKmsMaterialsProvider.java | 296 ------ .../EncryptionMaterialsProvider.java | 71 -- .../providers/KeyStoreMaterialsProvider.java | 199 ---- .../providers/SymmetricStaticProvider.java | 130 --- .../providers/WrappedMaterialsProvider.java | 163 --- .../encryption/providers/store/MetaStore.java | 434 -------- .../providers/store/ProviderStore.java | 84 -- .../utils/EncryptionContextOperators.java | 81 -- .../internal/AttributeValueMarshaller.java | 331 ------- .../internal/Base64.java | 48 - .../internal/ByteBufferInputStream.java | 56 -- .../internal/Hkdf.java | 316 ------ .../internal/LRUCache.java | 107 -- .../internal/MsClock.java | 19 - .../internal/TTLCache.java | 242 ----- .../internal/Utils.java | 39 - .../HolisticIT.java | 932 ------------------ .../encryption/DelegatedEncryptionTest.java | 296 ------ .../DelegatedEnvelopeEncryptionTest.java | 280 ------ .../encryption/DynamoDbEncryptorTest.java | 591 ----------- .../encryption/DynamoDbSignerTest.java | 567 ----------- .../materials/AsymmetricRawMaterialsTest.java | 138 --- .../materials/SymmetricRawMaterialsTest.java | 104 -- .../AsymmetricStaticProviderTest.java | 130 --- .../CachingMostRecentProviderTests.java | 610 ------------ .../DirectKmsMaterialsProviderTest.java | 449 --------- .../KeyStoreMaterialsProviderTest.java | 315 ------ .../SymmetricStaticProviderTest.java | 182 ---- .../WrappedMaterialsProviderTest.java | 414 -------- .../providers/store/MetaStoreTests.java | 346 ------- .../utils/EncryptionContextOperatorsTest.java | 164 --- .../AttributeValueMarshallerTest.java | 393 -------- .../internal/Base64Tests.java | 93 -- .../internal/ByteBufferInputStreamTest.java | 86 -- .../internal/ConcurrentTTLCacheTest.java | 244 ----- .../internal/HkdfTests.java | 209 ---- .../internal/LRUCacheTest.java | 85 -- .../internal/TTLCacheTest.java | 372 ------- .../testing/AttrMatcher.java | 125 --- .../testing/AttributeValueBuilder.java | 67 -- .../testing/AttributeValueDeserializer.java | 58 -- .../testing/AttributeValueMatcher.java | 101 -- .../testing/AttributeValueSerializer.java | 48 - .../testing/DdbRecordMatcher.java | 47 - .../testing/FakeKMS.java | 201 ---- .../testing/LocalDynamoDb.java | 175 ---- .../testing/ScenarioManifest.java | 77 -- .../testing/TestDelegatedKey.java | 128 --- 63 files changed, 12601 deletions(-) delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java deleted file mode 100644 index 52e02f2e8e..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -/** - * Identifies keys which should not be used directly with {@link Cipher} but - * instead contain their own cryptographic logic. This can be used to wrap more - * complex logic, HSM integration, or service-calls. - * - *

- * Most delegated keys will only support a subset of these operations. (For - * example, AES keys will generally not support {@link #sign(byte[], String)} or - * {@link #verify(byte[], byte[], String)} and HMAC keys will generally not - * support anything except sign and verify.) - * {@link UnsupportedOperationException} should be thrown in these cases. - * - * @author Greg Rubin - */ -public interface DelegatedKey extends SecretKey { - /** - * Encrypts the provided plaintext and returns a byte-array containing the ciphertext. - * - * @param plainText - * @param additionalAssociatedData - * Optional additional data which must then also be provided for successful - * decryption. Both null and arrays of length 0 are treated identically. - * Not all keys will support this parameter. - * @param algorithm - * the transformation to be used when encrypting the data - * @return ciphertext the ciphertext produced by this encryption operation - * @throws UnsupportedOperationException - * if encryption is not supported or if additionalAssociatedData is - * provided, but not supported. - */ - byte[] encrypt(byte[] plainText, byte[] additionalAssociatedData, String algorithm) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, - NoSuchPaddingException; - - /** - * Decrypts the provided ciphertext and returns a byte-array containing the - * plaintext. - * - * @param cipherText - * @param additionalAssociatedData - * Optional additional data which was provided during encryption. - * Both null and arrays of length 0 are treated - * identically. Not all keys will support this parameter. - * @param algorithm - * the transformation to be used when decrypting the data - * @return plaintext the result of decrypting the input ciphertext - * @throws UnsupportedOperationException - * if decryption is not supported or if - * additionalAssociatedData is provided, but not - * supported. - */ - byte[] decrypt(byte[] cipherText, byte[] additionalAssociatedData, String algorithm) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, - NoSuchPaddingException, InvalidAlgorithmParameterException; - - /** - * Wraps (encrypts) the provided key to make it safe for - * storage or transmission. - * - * @param key - * @param additionalAssociatedData - * Optional additional data which must then also be provided for - * successful unwrapping. Both null and arrays of - * length 0 are treated identically. Not all keys will support - * this parameter. - * @param algorithm - * the transformation to be used when wrapping the key - * @return the wrapped key - * @throws UnsupportedOperationException - * if wrapping is not supported or if - * additionalAssociatedData is provided, but not - * supported. - */ - byte[] wrap(Key key, byte[] additionalAssociatedData, String algorithm) throws InvalidKeyException, - NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException; - - /** - * Unwraps (decrypts) the provided wrappedKey to recover the - * original key. - * - * @param wrappedKey - * @param additionalAssociatedData - * Optional additional data which was provided during wrapping. - * Both null and arrays of length 0 are treated - * identically. Not all keys will support this parameter. - * @param algorithm - * the transformation to be used when unwrapping the key - * @return the unwrapped key - * @throws UnsupportedOperationException - * if wrapping is not supported or if - * additionalAssociatedData is provided, but not - * supported. - */ - Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType, - byte[] additionalAssociatedData, String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException, - InvalidKeyException; - - /** - * Calculates and returns a signature for dataToSign. - * - * @param dataToSign - * @param algorithm - * @return the signature - * @throws UnsupportedOperationException if signing is not supported - */ - byte[] sign(byte[] dataToSign, String algorithm) throws GeneralSecurityException; - - /** - * Checks the provided signature for correctness. - * - * @param dataToSign - * @param signature - * @param algorithm - * @return true if and only if the signature matches the dataToSign. - * @throws UnsupportedOperationException if signature validation is not supported - */ - boolean verify(byte[] dataToSign, byte[] signature, String algorithm); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java deleted file mode 100644 index 95e6ec73c7..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.security.GeneralSecurityException; -import java.security.PrivateKey; -import java.security.SignatureException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.ByteBufferInputStream; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -/** - * The low-level API for performing crypto operations on the record attributes. - * - * @author Greg Rubin - */ -public class DynamoDbEncryptor { - private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA"; - private static final String DEFAULT_METADATA_FIELD = "*amzn-ddb-map-desc*"; - private static final String DEFAULT_SIGNATURE_FIELD = "*amzn-ddb-map-sig*"; - private static final String DEFAULT_DESCRIPTION_BASE = "amzn-ddb-map-"; // Same as the Mapper - private static final Charset UTF8 = Charset.forName("UTF-8"); - private static final String SYMMETRIC_ENCRYPTION_MODE = "/CBC/PKCS5Padding"; - private static final ConcurrentHashMap BLOCK_SIZE_CACHE = new ConcurrentHashMap<>(); - private static final Function BLOCK_SIZE_CALCULATOR = (transformation) -> { - try { - final Cipher c = Cipher.getInstance(transformation); - return c.getBlockSize(); - } catch (final GeneralSecurityException ex) { - throw new IllegalArgumentException("Algorithm does not exist", ex); - } - }; - - private static final int CURRENT_VERSION = 0; - - private String signatureFieldName = DEFAULT_SIGNATURE_FIELD; - private String materialDescriptionFieldName = DEFAULT_METADATA_FIELD; - - private EncryptionMaterialsProvider encryptionMaterialsProvider; - private final String descriptionBase; - private final String symmetricEncryptionModeHeader; - private final String signingAlgorithmHeader; - - static final String DEFAULT_SIGNING_ALGORITHM_HEADER = DEFAULT_DESCRIPTION_BASE + "signingAlg"; - - private Function encryptionContextOverrideOperator; - - protected DynamoDbEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { - this.encryptionMaterialsProvider = provider; - this.descriptionBase = descriptionBase; - symmetricEncryptionModeHeader = this.descriptionBase + "sym-mode"; - signingAlgorithmHeader = this.descriptionBase + "signingAlg"; - } - - public static DynamoDbEncryptor getInstance( - EncryptionMaterialsProvider provider, String descriptionbase) { - return new DynamoDbEncryptor(provider, descriptionbase); - } - - public static DynamoDbEncryptor getInstance(EncryptionMaterialsProvider provider) { - return getInstance(provider, DEFAULT_DESCRIPTION_BASE); - } - - /** - * Returns a decrypted version of the provided DynamoDb record. The signature is verified across - * all provided fields. All fields (except those listed in doNotEncrypt are - * decrypted. - * - * @param itemAttributes the DynamoDbRecord - * @param context additional information used to successfully select the encryption materials and - * decrypt the data. This should include (at least) the tableName and the materialDescription. - * @param doNotDecrypt those fields which should not be encrypted - * @return a plaintext version of the DynamoDb record - * @throws SignatureException if the signature is invalid or cannot be verified - * @throws GeneralSecurityException - */ - public Map decryptAllFieldsExcept( - Map itemAttributes, EncryptionContext context, String... doNotDecrypt) - throws GeneralSecurityException { - return decryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotDecrypt)); - } - - /** @see #decryptAllFieldsExcept(Map, EncryptionContext, String...) */ - public Map decryptAllFieldsExcept( - Map itemAttributes, - EncryptionContext context, - Collection doNotDecrypt) - throws GeneralSecurityException { - Map> attributeFlags = - allDecryptionFlagsExcept(itemAttributes, doNotDecrypt); - return decryptRecord(itemAttributes, attributeFlags, context); - } - - /** - * Returns the decryption flags for all item attributes except for those explicitly specified to - * be excluded. - * - * @param doNotDecrypt fields to be excluded - */ - public Map> allDecryptionFlagsExcept( - Map itemAttributes, String... doNotDecrypt) { - return allDecryptionFlagsExcept(itemAttributes, Arrays.asList(doNotDecrypt)); - } - - /** - * Returns the decryption flags for all item attributes except for those explicitly specified to - * be excluded. - * - * @param doNotDecrypt fields to be excluded - */ - public Map> allDecryptionFlagsExcept( - Map itemAttributes, Collection doNotDecrypt) { - Map> attributeFlags = new HashMap>(); - - for (String fieldName : doNotDecrypt) { - attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN)); - } - - for (String fieldName : itemAttributes.keySet()) { - if (!attributeFlags.containsKey(fieldName) - && !fieldName.equals(getMaterialDescriptionFieldName()) - && !fieldName.equals(getSignatureFieldName())) { - attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); - } - } - return attributeFlags; - } - - /** - * Returns an encrypted version of the provided DynamoDb record. All fields are signed. All fields - * (except those listed in doNotEncrypt) are encrypted. - * - * @param itemAttributes a DynamoDb Record - * @param context additional information used to successfully select the encryption materials and - * encrypt the data. This should include (at least) the tableName. - * @param doNotEncrypt those fields which should not be encrypted - * @return a ciphertext version of the DynamoDb record - * @throws GeneralSecurityException - */ - public Map encryptAllFieldsExcept( - Map itemAttributes, EncryptionContext context, String... doNotEncrypt) - throws GeneralSecurityException { - - return encryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotEncrypt)); - } - - public Map encryptAllFieldsExcept( - Map itemAttributes, - EncryptionContext context, - Collection doNotEncrypt) - throws GeneralSecurityException { - Map> attributeFlags = - allEncryptionFlagsExcept(itemAttributes, doNotEncrypt); - return encryptRecord(itemAttributes, attributeFlags, context); - } - - /** - * Returns the encryption flags for all item attributes except for those explicitly specified to - * be excluded. - * - * @param doNotEncrypt fields to be excluded - */ - public Map> allEncryptionFlagsExcept( - Map itemAttributes, String... doNotEncrypt) { - return allEncryptionFlagsExcept(itemAttributes, Arrays.asList(doNotEncrypt)); - } - - /** - * Returns the encryption flags for all item attributes except for those explicitly specified to - * be excluded. - * - * @param doNotEncrypt fields to be excluded - */ - public Map> allEncryptionFlagsExcept( - Map itemAttributes, Collection doNotEncrypt) { - Map> attributeFlags = new HashMap>(); - for (String fieldName : doNotEncrypt) { - attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN)); - } - - for (String fieldName : itemAttributes.keySet()) { - if (!attributeFlags.containsKey(fieldName)) { - attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); - } - } - return attributeFlags; - } - - public Map decryptRecord( - Map itemAttributes, - Map> attributeFlags, - EncryptionContext context) - throws GeneralSecurityException { - if (!itemContainsFieldsToDecryptOrSign(itemAttributes.keySet(), attributeFlags)) { - return itemAttributes; - } - // Copy to avoid changing anyone elses objects - itemAttributes = new HashMap(itemAttributes); - - Map materialDescription = Collections.emptyMap(); - DecryptionMaterials materials; - SecretKey decryptionKey; - - DynamoDbSigner signer = DynamoDbSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng()); - - if (itemAttributes.containsKey(materialDescriptionFieldName)) { - materialDescription = unmarshallDescription(itemAttributes.get(materialDescriptionFieldName)); - } - // Copy the material description and attribute values into the context - context = - new EncryptionContext.Builder(context) - .materialDescription(materialDescription) - .attributeValues(itemAttributes) - .build(); - - Function encryptionContextOverrideOperator = - getEncryptionContextOverrideOperator(); - if (encryptionContextOverrideOperator != null) { - context = encryptionContextOverrideOperator.apply(context); - } - - materials = encryptionMaterialsProvider.getDecryptionMaterials(context); - decryptionKey = materials.getDecryptionKey(); - if (materialDescription.containsKey(signingAlgorithmHeader)) { - String signingAlg = materialDescription.get(signingAlgorithmHeader); - signer = DynamoDbSigner.getInstance(signingAlg, Utils.getRng()); - } - - ByteBuffer signature; - if (!itemAttributes.containsKey(signatureFieldName) - || itemAttributes.get(signatureFieldName).b() == null) { - signature = ByteBuffer.allocate(0); - } else { - signature = itemAttributes.get(signatureFieldName).b().asByteBuffer().asReadOnlyBuffer(); - } - itemAttributes.remove(signatureFieldName); - - String associatedData = "TABLE>" + context.getTableName() + " attributeNamesToCheck, Map> attributeFlags) { - return attributeNamesToCheck.stream() - .filter(attributeFlags::containsKey) - .anyMatch(attributeName -> !attributeFlags.get(attributeName).isEmpty()); - } - - public Map encryptRecord( - Map itemAttributes, - Map> attributeFlags, - EncryptionContext context) { - if (attributeFlags.isEmpty()) { - return itemAttributes; - } - // Copy to avoid changing anyone elses objects - itemAttributes = new HashMap<>(itemAttributes); - - // Copy the attribute values into the context - context = context.toBuilder() - .attributeValues(itemAttributes) - .build(); - - Function encryptionContextOverrideOperator = - getEncryptionContextOverrideOperator(); - if (encryptionContextOverrideOperator != null) { - context = encryptionContextOverrideOperator.apply(context); - } - - EncryptionMaterials materials = encryptionMaterialsProvider.getEncryptionMaterials(context); - // We need to copy this because we modify it to record other encryption details - Map materialDescription = new HashMap<>( - materials.getMaterialDescription()); - SecretKey encryptionKey = materials.getEncryptionKey(); - - try { - actualEncryption(itemAttributes, attributeFlags, materialDescription, encryptionKey); - - // The description must be stored after encryption because its data - // is necessary for proper decryption. - final String signingAlgo = materialDescription.get(signingAlgorithmHeader); - DynamoDbSigner signer; - if (signingAlgo != null) { - signer = DynamoDbSigner.getInstance(signingAlgo, Utils.getRng()); - } else { - signer = DynamoDbSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng()); - } - - if (materials.getSigningKey() instanceof PrivateKey) { - materialDescription.put(signingAlgorithmHeader, signer.getSigningAlgorithm()); - } - if (! materialDescription.isEmpty()) { - itemAttributes.put(materialDescriptionFieldName, marshallDescription(materialDescription)); - } - - String associatedData = "TABLE>" + context.getTableName() + " itemAttributes, - Map> attributeFlags, SecretKey encryptionKey, - Map materialDescription) throws GeneralSecurityException { - final String encryptionMode = encryptionKey != null ? encryptionKey.getAlgorithm() + - materialDescription.get(symmetricEncryptionModeHeader) : null; - Cipher cipher = null; - int blockSize = -1; - - for (Map.Entry entry: itemAttributes.entrySet()) { - Set flags = attributeFlags.get(entry.getKey()); - if (flags != null && flags.contains(EncryptionFlags.ENCRYPT)) { - if (!flags.contains(EncryptionFlags.SIGN)) { - throw new IllegalArgumentException("All encrypted fields must be signed. Bad field: " + entry.getKey()); - } - ByteBuffer plainText; - ByteBuffer cipherText = entry.getValue().b().asByteBuffer(); - cipherText.rewind(); - if (encryptionKey instanceof DelegatedKey) { - plainText = ByteBuffer.wrap(((DelegatedKey)encryptionKey).decrypt(toByteArray(cipherText), null, encryptionMode)); - } else { - if (cipher == null) { - blockSize = getBlockSize(encryptionMode); - cipher = Cipher.getInstance(encryptionMode); - } - byte[] iv = new byte[blockSize]; - cipherText.get(iv); - cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng()); - plainText = ByteBuffer.allocate(cipher.getOutputSize(cipherText.remaining())); - cipher.doFinal(cipherText, plainText); - plainText.rewind(); - } - entry.setValue(AttributeValueMarshaller.unmarshall(plainText)); - } - } - } - - private static int getBlockSize(final String encryptionMode) { - return BLOCK_SIZE_CACHE.computeIfAbsent(encryptionMode, BLOCK_SIZE_CALCULATOR); - } - - /** - * This method has the side effect of replacing the plaintext - * attribute-values of "itemAttributes" with ciphertext attribute-values - * (which are always in the form of ByteBuffer) as per the corresponding - * attribute flags. - */ - private void actualEncryption(Map itemAttributes, - Map> attributeFlags, - Map materialDescription, - SecretKey encryptionKey) throws GeneralSecurityException { - String encryptionMode = null; - if (encryptionKey != null) { - materialDescription.put(this.symmetricEncryptionModeHeader, - SYMMETRIC_ENCRYPTION_MODE); - encryptionMode = encryptionKey.getAlgorithm() + SYMMETRIC_ENCRYPTION_MODE; - } - Cipher cipher = null; - int blockSize = -1; - - for (Map.Entry entry: itemAttributes.entrySet()) { - Set flags = attributeFlags.get(entry.getKey()); - if (flags != null && flags.contains(EncryptionFlags.ENCRYPT)) { - if (!flags.contains(EncryptionFlags.SIGN)) { - throw new IllegalArgumentException("All encrypted fields must be signed. Bad field: " + entry.getKey()); - } - ByteBuffer plainText = AttributeValueMarshaller.marshall(entry.getValue()); - plainText.rewind(); - ByteBuffer cipherText; - if (encryptionKey instanceof DelegatedKey) { - DelegatedKey dk = (DelegatedKey) encryptionKey; - cipherText = ByteBuffer.wrap( - dk.encrypt(toByteArray(plainText), null, encryptionMode)); - } else { - if (cipher == null) { - blockSize = getBlockSize(encryptionMode); - cipher = Cipher.getInstance(encryptionMode); - } - // Encryption format: - // Note a unique iv is generated per attribute - cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, Utils.getRng()); - cipherText = ByteBuffer.allocate(blockSize + cipher.getOutputSize(plainText.remaining())); - cipherText.position(blockSize); - cipher.doFinal(plainText, cipherText); - cipherText.flip(); - final byte[] iv = cipher.getIV(); - if (iv.length != blockSize) { - throw new IllegalStateException(String.format("Generated IV length (%d) not equal to block size (%d)", - iv.length, blockSize)); - } - cipherText.put(iv); - cipherText.rewind(); - } - // Replace the plaintext attribute value with the encrypted content - entry.setValue(AttributeValue.builder().b(SdkBytes.fromByteBuffer(cipherText)).build()); - } - } - } - - /** - * Get the name of the DynamoDB field used to store the signature. - * Defaults to {@link #DEFAULT_SIGNATURE_FIELD}. - * - * @return the name of the DynamoDB field used to store the signature - */ - String getSignatureFieldName() { - return signatureFieldName; - } - - /** - * Set the name of the DynamoDB field used to store the signature. - * - * @param signatureFieldName - */ - void setSignatureFieldName(final String signatureFieldName) { - this.signatureFieldName = signatureFieldName; - } - - /** - * Get the name of the DynamoDB field used to store metadata used by the - * DynamoDBEncryptedMapper. Defaults to {@link #DEFAULT_METADATA_FIELD}. - * - * @return the name of the DynamoDB field used to store metadata used by the - * DynamoDBEncryptedMapper - */ - String getMaterialDescriptionFieldName() { - return materialDescriptionFieldName; - } - - /** - * Set the name of the DynamoDB field used to store metadata used by the - * DynamoDBEncryptedMapper - * - * @param materialDescriptionFieldName - */ - void setMaterialDescriptionFieldName(final String materialDescriptionFieldName) { - this.materialDescriptionFieldName = materialDescriptionFieldName; - } - - /** - * Marshalls the description into a ByteBuffer by outputting - * each key (modified UTF-8) followed by its value (also in modified UTF-8). - * - * @param description - * @return the description encoded as an AttributeValue with a ByteBuffer value - * @see java.io.DataOutput#writeUTF(String) - */ - private static AttributeValue marshallDescription(Map description) { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(bos); - out.writeInt(CURRENT_VERSION); - for (Map.Entry entry : description.entrySet()) { - byte[] bytes = entry.getKey().getBytes(UTF8); - out.writeInt(bytes.length); - out.write(bytes); - bytes = entry.getValue().getBytes(UTF8); - out.writeInt(bytes.length); - out.write(bytes); - } - out.close(); - return AttributeValue.builder().b(SdkBytes.fromByteArray(bos.toByteArray())).build(); - } catch (IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - /** - * @see #marshallDescription(Map) - */ - private static Map unmarshallDescription(AttributeValue attributeValue) { - try (DataInputStream in = new DataInputStream( - new ByteBufferInputStream(attributeValue.b().asByteBuffer())) ) { - Map result = new HashMap<>(); - int version = in.readInt(); - if (version != CURRENT_VERSION) { - throw new IllegalArgumentException("Unsupported description version"); - } - - String key, value; - int keyLength, valueLength; - try { - while(in.available() > 0) { - keyLength = in.readInt(); - byte[] bytes = new byte[keyLength]; - if (in.read(bytes) != keyLength) { - throw new IllegalArgumentException("Malformed description"); - } - key = new String(bytes, UTF8); - valueLength = in.readInt(); - bytes = new byte[valueLength]; - if (in.read(bytes) != valueLength) { - throw new IllegalArgumentException("Malformed description"); - } - value = new String(bytes, UTF8); - result.put(key, value); - } - } catch (EOFException eof) { - throw new IllegalArgumentException("Malformed description", eof); - } - return result; - } catch (IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - /** - * @param encryptionContextOverrideOperator the nullable operator which will be used to override - * the EncryptionContext. - * @see EncryptionContextOperators - */ - void setEncryptionContextOverrideOperator( - Function encryptionContextOverrideOperator) { - this.encryptionContextOverrideOperator = encryptionContextOverrideOperator; - } - - /** - * @return the operator used to override the EncryptionContext - * @see #setEncryptionContextOverrideOperator(Function) - */ - private Function getEncryptionContextOverrideOperator() { - return encryptionContextOverrideOperator; - } - - private static byte[] toByteArray(ByteBuffer buffer) { - buffer = buffer.duplicate(); - // We can only return the array directly if: - // 1. The ByteBuffer exposes an array - // 2. The ByteBuffer starts at the beginning of the array - // 3. The ByteBuffer uses the entire array - if (buffer.hasArray() && buffer.arrayOffset() == 0) { - byte[] result = buffer.array(); - if (buffer.remaining() == result.length) { - return result; - } - } - - byte[] result = new byte[buffer.remaining()]; - buffer.get(result); - return result; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java deleted file mode 100644 index d2998057b0..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.security.GeneralSecurityException; -import java.security.Key; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -/** - * @author Greg Rubin - */ -// NOTE: This class must remain thread-safe. -class DynamoDbSigner { - private static final ConcurrentHashMap cache = - new ConcurrentHashMap(); - - protected static final Charset UTF8 = Charset.forName("UTF-8"); - private final SecureRandom rnd; - private final SecretKey hmacComparisonKey; - private final String signingAlgorithm; - - /** - * @param signingAlgorithm is the algorithm used for asymmetric signing (ex: SHA256withRSA). This - * is ignored for symmetric HMACs as that algorithm is fully specified by the key. - */ - static DynamoDbSigner getInstance(String signingAlgorithm, SecureRandom rnd) { - DynamoDbSigner result = cache.get(signingAlgorithm); - if (result == null) { - result = new DynamoDbSigner(signingAlgorithm, rnd); - cache.putIfAbsent(signingAlgorithm, result); - } - return result; - } - - /** - * @param signingAlgorithm is the algorithm used for asymmetric signing (ex: SHA256withRSA). This - * is ignored for symmetric HMACs as that algorithm is fully specified by the key. - */ - private DynamoDbSigner(String signingAlgorithm, SecureRandom rnd) { - if (rnd == null) { - rnd = Utils.getRng(); - } - this.rnd = rnd; - this.signingAlgorithm = signingAlgorithm; - // Shorter than the output of SHA256 to avoid weak keys. - // http://cs.nyu.edu/~dodis/ps/h-of-h.pdf - // http://link.springer.com/chapter/10.1007%2F978-3-642-32009-5_21 - byte[] tmpKey = new byte[31]; - rnd.nextBytes(tmpKey); - hmacComparisonKey = new SecretKeySpec(tmpKey, "HmacSHA256"); - } - - void verifySignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - Key verificationKey, - ByteBuffer signature) - throws GeneralSecurityException { - if (verificationKey instanceof DelegatedKey) { - DelegatedKey dKey = (DelegatedKey) verificationKey; - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - if (!dKey.verify(stringToSign, toByteArray(signature), dKey.getAlgorithm())) { - throw new SignatureException("Bad signature"); - } - } else if (verificationKey instanceof SecretKey) { - byte[] calculatedSig = - calculateSignature( - itemAttributes, attributeFlags, associatedData, (SecretKey) verificationKey); - if (!safeEquals(signature, calculatedSig)) { - throw new SignatureException("Bad signature"); - } - } else if (verificationKey instanceof PublicKey) { - PublicKey integrityKey = (PublicKey) verificationKey; - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - Signature sig = Signature.getInstance(getSigningAlgorithm()); - sig.initVerify(integrityKey); - sig.update(stringToSign); - if (!sig.verify(toByteArray(signature))) { - throw new SignatureException("Bad signature"); - } - } else { - throw new IllegalArgumentException("No integrity key provided"); - } - } - - static byte[] calculateStringToSign( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData) - throws NoSuchAlgorithmException { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - List attrNames = new ArrayList(itemAttributes.keySet()); - Collections.sort(attrNames); - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - if (associatedData != null) { - out.write(sha256.digest(associatedData)); - } else { - out.write(sha256.digest()); - } - sha256.reset(); - - for (String name : attrNames) { - Set set = attributeFlags.get(name); - if (set != null && set.contains(EncryptionFlags.SIGN)) { - AttributeValue tmp = itemAttributes.get(name); - out.write(sha256.digest(name.getBytes(UTF8))); - sha256.reset(); - if (set.contains(EncryptionFlags.ENCRYPT)) { - sha256.update("ENCRYPTED".getBytes(UTF8)); - } else { - sha256.update("PLAINTEXT".getBytes(UTF8)); - } - out.write(sha256.digest()); - - sha256.reset(); - - sha256.update(AttributeValueMarshaller.marshall(tmp)); - out.write(sha256.digest()); - sha256.reset(); - } - } - return out.toByteArray(); - } catch (IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - /** The itemAttributes have already been encrypted, if necessary, before the signing. */ - byte[] calculateSignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - Key key) - throws GeneralSecurityException { - if (key instanceof DelegatedKey) { - return calculateSignature(itemAttributes, attributeFlags, associatedData, (DelegatedKey) key); - } else if (key instanceof SecretKey) { - return calculateSignature(itemAttributes, attributeFlags, associatedData, (SecretKey) key); - } else if (key instanceof PrivateKey) { - return calculateSignature(itemAttributes, attributeFlags, associatedData, (PrivateKey) key); - } else { - throw new IllegalArgumentException("No integrity key provided"); - } - } - - byte[] calculateSignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - DelegatedKey key) - throws GeneralSecurityException { - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - return key.sign(stringToSign, key.getAlgorithm()); - } - - byte[] calculateSignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - SecretKey key) - throws GeneralSecurityException { - if (key instanceof DelegatedKey) { - return calculateSignature(itemAttributes, attributeFlags, associatedData, (DelegatedKey) key); - } - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - Mac hmac = Mac.getInstance(key.getAlgorithm()); - hmac.init(key); - hmac.update(stringToSign); - return hmac.doFinal(); - } - - byte[] calculateSignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - PrivateKey key) - throws GeneralSecurityException { - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - Signature sig = Signature.getInstance(signingAlgorithm); - sig.initSign(key, rnd); - sig.update(stringToSign); - return sig.sign(); - } - - String getSigningAlgorithm() { - return signingAlgorithm; - } - - /** Constant-time equality check. */ - private boolean safeEquals(ByteBuffer signature, byte[] calculatedSig) { - try { - signature.rewind(); - Mac hmac = Mac.getInstance(hmacComparisonKey.getAlgorithm()); - hmac.init(hmacComparisonKey); - hmac.update(signature); - byte[] signatureHash = hmac.doFinal(); - - hmac.reset(); - hmac.update(calculatedSig); - byte[] calculatedHash = hmac.doFinal(); - - return MessageDigest.isEqual(signatureHash, calculatedHash); - } catch (GeneralSecurityException ex) { - // We've hardcoded these algorithms, so the error should not be possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - private static byte[] toByteArray(ByteBuffer buffer) { - if (buffer.hasArray()) { - byte[] result = buffer.array(); - buffer.rewind(); - return result; - } else { - byte[] result = new byte[buffer.remaining()]; - buffer.get(result); - buffer.rewind(); - return result; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java deleted file mode 100644 index 9a78ad9b04..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -/** - * This class serves to provide additional useful data to - * {@link EncryptionMaterialsProvider}s so they can more intelligently select - * the proper {@link EncryptionMaterials} or {@link DecryptionMaterials} for - * use. Any of the methods are permitted to return null. - *

- * For the simplest cases, all a developer needs to provide in the context are: - *

    - *
  • TableName
  • - *
  • HashKeyName
  • - *
  • RangeKeyName (if present)
  • - *
- * - * This class is immutable. - * - * @author Greg Rubin - */ -public final class EncryptionContext { - private final String tableName; - private final Map attributeValues; - private final Object developerContext; - private final String hashKeyName; - private final String rangeKeyName; - private final Map materialDescription; - - /** - * Return a new builder that can be used to construct an {@link EncryptionContext} - * @return A newly initialized {@link EncryptionContext.Builder}. - */ - public static Builder builder() { - return new Builder(); - } - - private EncryptionContext(Builder builder) { - tableName = builder.tableName; - attributeValues = builder.attributeValues; - developerContext = builder.developerContext; - hashKeyName = builder.hashKeyName; - rangeKeyName = builder.rangeKeyName; - materialDescription = builder.materialDescription; - } - - /** - * Returns the name of the DynamoDB Table this record is associated with. - */ - public String getTableName() { - return tableName; - } - - /** - * Returns the DynamoDB record about to be encrypted/decrypted. - */ - public Map getAttributeValues() { - return attributeValues; - } - - /** - * This object has no meaning (and will not be set or examined) by any core libraries. - * It exists to allow custom object mappers and data access layers to pass - * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDbEncryptor}. - */ - public Object getDeveloperContext() { - return developerContext; - } - - /** - * Returns the name of the HashKey attribute for the record to be encrypted/decrypted. - */ - public String getHashKeyName() { - return hashKeyName; - } - - /** - * Returns the name of the RangeKey attribute for the record to be encrypted/decrypted. - */ - public String getRangeKeyName() { - return rangeKeyName; - } - - public Map getMaterialDescription() { - return materialDescription; - } - - /** - * Converts an existing {@link EncryptionContext} into a builder that can be used to mutate and make a new version. - * @return A new {@link EncryptionContext.Builder} with all the fields filled out to match the current object. - */ - public Builder toBuilder() { - return new Builder(this); - } - - /** - * Builder class for {@link EncryptionContext}. - * Mutable objects (other than developerContext) will undergo - * a defensive copy prior to being stored in the builder. - * - * This class is not thread-safe. - */ - public static final class Builder { - private String tableName = null; - private Map attributeValues = null; - private Object developerContext = null; - private String hashKeyName = null; - private String rangeKeyName = null; - private Map materialDescription = null; - - public Builder() { - } - - public Builder(EncryptionContext context) { - tableName = context.getTableName(); - attributeValues = context.getAttributeValues(); - hashKeyName = context.getHashKeyName(); - rangeKeyName = context.getRangeKeyName(); - developerContext = context.getDeveloperContext(); - materialDescription = context.getMaterialDescription(); - } - - public EncryptionContext build() { - return new EncryptionContext(this); - } - - public Builder tableName(String tableName) { - this.tableName = tableName; - return this; - } - - public Builder attributeValues(Map attributeValues) { - this.attributeValues = Collections.unmodifiableMap(new HashMap<>(attributeValues)); - return this; - } - - public Builder developerContext(Object developerContext) { - this.developerContext = developerContext; - return this; - } - - public Builder hashKeyName(String hashKeyName) { - this.hashKeyName = hashKeyName; - return this; - } - - public Builder rangeKeyName(String rangeKeyName) { - this.rangeKeyName = rangeKeyName; - return this; - } - - public Builder materialDescription(Map materialDescription) { - this.materialDescription = Collections.unmodifiableMap(new HashMap<>(materialDescription)); - return this; - } - } - - @Override - public String toString() { - return "EncryptionContext [tableName=" + tableName + ", attributeValues=" + attributeValues - + ", developerContext=" + developerContext - + ", hashKeyName=" + hashKeyName + ", rangeKeyName=" + rangeKeyName - + ", materialDescription=" + materialDescription + "]"; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java deleted file mode 100644 index 47329f7128..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -/** - * @author Greg Rubin - */ -public enum EncryptionFlags { - ENCRYPT, - SIGN -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java deleted file mode 100644 index f245d66e31..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java +++ /dev/null @@ -1,47 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions; - -/** - * Generic exception thrown for any problem the DynamoDB encryption client has performing tasks - */ -public class DynamoDbEncryptionException extends RuntimeException { - private static final long serialVersionUID = - 7565904179772520868L; - - /** - * Standard constructor - * @param cause exception cause - */ - public DynamoDbEncryptionException(Throwable cause) { - super(cause); - } - - /** - * Standard constructor - * @param message exception message - */ - public DynamoDbEncryptionException(String message) { - super(message); - } - - /** - * Standard constructor - * @param message exception message - * @param cause exception cause - */ - public DynamoDbEncryptionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java deleted file mode 100644 index 5dfbb19709..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.Key; -import java.security.KeyPair; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public abstract class AbstractRawMaterials implements DecryptionMaterials, EncryptionMaterials { - private Map description; - private final Key signingKey; - private final Key verificationKey; - - @SuppressWarnings("unchecked") - protected AbstractRawMaterials(KeyPair signingPair) { - this(signingPair, Collections.EMPTY_MAP); - } - - protected AbstractRawMaterials(KeyPair signingPair, Map description) { - this.signingKey = signingPair.getPrivate(); - this.verificationKey = signingPair.getPublic(); - setMaterialDescription(description); - } - - @SuppressWarnings("unchecked") - protected AbstractRawMaterials(SecretKey macKey) { - this(macKey, Collections.EMPTY_MAP); - } - - protected AbstractRawMaterials(SecretKey macKey, Map description) { - this.signingKey = macKey; - this.verificationKey = macKey; - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - } - - @Override - public Map getMaterialDescription() { - return new HashMap<>(description); - } - - public void setMaterialDescription(Map description) { - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - } - - @Override - public Key getSigningKey() { - return signingKey; - } - - @Override - public Key getVerificationKey() { - return verificationKey; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java deleted file mode 100644 index 003d0b60cc..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public class AsymmetricRawMaterials extends WrappedRawMaterials { - @SuppressWarnings("unchecked") - public AsymmetricRawMaterials(KeyPair encryptionKey, KeyPair signingPair) - throws GeneralSecurityException { - this(encryptionKey, signingPair, Collections.EMPTY_MAP); - } - - public AsymmetricRawMaterials(KeyPair encryptionKey, KeyPair signingPair, Map description) - throws GeneralSecurityException { - super(encryptionKey.getPublic(), encryptionKey.getPrivate(), signingPair, description); - } - - @SuppressWarnings("unchecked") - public AsymmetricRawMaterials(KeyPair encryptionKey, SecretKey macKey) - throws GeneralSecurityException { - this(encryptionKey, macKey, Collections.EMPTY_MAP); - } - - public AsymmetricRawMaterials(KeyPair encryptionKey, SecretKey macKey, Map description) - throws GeneralSecurityException { - super(encryptionKey.getPublic(), encryptionKey.getPrivate(), macKey, description); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java deleted file mode 100644 index 033d331f5b..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.util.Map; - -/** - * @author Greg Rubin - */ -public interface CryptographicMaterials { - Map getMaterialDescription(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java deleted file mode 100644 index 00f8548bc7..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.Key; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public interface DecryptionMaterials extends CryptographicMaterials { - SecretKey getDecryptionKey(); - Key getVerificationKey(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java deleted file mode 100644 index ecef9e9fc8..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.Key; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public interface EncryptionMaterials extends CryptographicMaterials { - SecretKey getEncryptionKey(); - Key getSigningKey(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java deleted file mode 100644 index b3daab44ba..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.KeyPair; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public class SymmetricRawMaterials extends AbstractRawMaterials { - private final SecretKey cryptoKey; - - @SuppressWarnings("unchecked") - public SymmetricRawMaterials(SecretKey encryptionKey, KeyPair signingPair) { - this(encryptionKey, signingPair, Collections.EMPTY_MAP); - } - - public SymmetricRawMaterials(SecretKey encryptionKey, KeyPair signingPair, Map description) { - super(signingPair, description); - this.cryptoKey = encryptionKey; - } - - @SuppressWarnings("unchecked") - public SymmetricRawMaterials(SecretKey encryptionKey, SecretKey macKey) { - this(encryptionKey, macKey, Collections.EMPTY_MAP); - } - - public SymmetricRawMaterials(SecretKey encryptionKey, SecretKey macKey, Map description) { - super(macKey, description); - this.cryptoKey = encryptionKey; - } - - @Override - public SecretKey getEncryptionKey() { - return cryptoKey; - } - - @Override - public SecretKey getDecryptionKey() { - return cryptoKey; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java deleted file mode 100644 index fd17521ca1..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DelegatedKey; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -/** - * Represents cryptographic materials used to manage unique record-level keys. - * This class specifically implements Envelope Encryption where a unique content - * key is randomly generated each time this class is constructed which is then - * encrypted with the Wrapping Key and then persisted in the Description. If a - * wrapped key is present in the Description, then that content key is unwrapped - * and used to decrypt the actual data in the record. - * - * Other possibly implementations might use a Key-Derivation Function to derive - * a unique key per record. - * - * @author Greg Rubin - */ -public class WrappedRawMaterials extends AbstractRawMaterials { - /** - * The key-name in the Description which contains the algorithm use to wrap - * content key. Example values are "AESWrap", or - * "RSA/ECB/OAEPWithSHA-256AndMGF1Padding". - */ - public static final String KEY_WRAPPING_ALGORITHM = "amzn-ddb-wrap-alg"; - /** - * The key-name in the Description which contains the algorithm used by the - * content key. Example values are "AES", or "Blowfish". - */ - public static final String CONTENT_KEY_ALGORITHM = "amzn-ddb-env-alg"; - /** - * The key-name in the Description which which contains the wrapped content - * key. - */ - public static final String ENVELOPE_KEY = "amzn-ddb-env-key"; - - private static final String DEFAULT_ALGORITHM = "AES/256"; - - protected final Key wrappingKey; - protected final Key unwrappingKey; - private final SecretKey envelopeKey; - - public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, KeyPair signingPair) - throws GeneralSecurityException { - this(wrappingKey, unwrappingKey, signingPair, Collections.emptyMap()); - } - - public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, KeyPair signingPair, - Map description) throws GeneralSecurityException { - super(signingPair, description); - this.wrappingKey = wrappingKey; - this.unwrappingKey = unwrappingKey; - this.envelopeKey = initEnvelopeKey(); - } - - public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, SecretKey macKey) - throws GeneralSecurityException { - this(wrappingKey, unwrappingKey, macKey, Collections.emptyMap()); - } - - public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, SecretKey macKey, - Map description) throws GeneralSecurityException { - super(macKey, description); - this.wrappingKey = wrappingKey; - this.unwrappingKey = unwrappingKey; - this.envelopeKey = initEnvelopeKey(); - } - - @Override - public SecretKey getDecryptionKey() { - return envelopeKey; - } - - @Override - public SecretKey getEncryptionKey() { - return envelopeKey; - } - - /** - * Called by the constructors. If there is already a key associated with - * this record (usually signified by a value stored in the description in - * the key {@link #ENVELOPE_KEY}) it extracts it and returns it. Otherwise - * it generates a new key, stores a wrapped version in the Description, and - * returns the key to the caller. - * - * @return the content key (which is returned by both - * {@link #getDecryptionKey()} and {@link #getEncryptionKey()}. - * @throws GeneralSecurityException if there is a problem - */ - protected SecretKey initEnvelopeKey() throws GeneralSecurityException { - Map description = getMaterialDescription(); - if (description.containsKey(ENVELOPE_KEY)) { - if (unwrappingKey == null) { - throw new IllegalStateException("No private decryption key provided."); - } - byte[] encryptedKey = Base64.decode(description.get(ENVELOPE_KEY)); - String wrappingAlgorithm = unwrappingKey.getAlgorithm(); - if (description.containsKey(KEY_WRAPPING_ALGORITHM)) { - wrappingAlgorithm = description.get(KEY_WRAPPING_ALGORITHM); - } - return unwrapKey(description, encryptedKey, wrappingAlgorithm); - } else { - SecretKey key = description.containsKey(CONTENT_KEY_ALGORITHM) ? - generateContentKey(description.get(CONTENT_KEY_ALGORITHM)) : - generateContentKey(DEFAULT_ALGORITHM); - - String wrappingAlg = description.containsKey(KEY_WRAPPING_ALGORITHM) ? - description.get(KEY_WRAPPING_ALGORITHM) : - getTransformation(wrappingKey.getAlgorithm()); - byte[] encryptedKey = wrapKey(key, wrappingAlg); - description.put(ENVELOPE_KEY, Base64.encodeToString(encryptedKey)); - description.put(CONTENT_KEY_ALGORITHM, key.getAlgorithm()); - description.put(KEY_WRAPPING_ALGORITHM, wrappingAlg); - setMaterialDescription(description); - return key; - } - } - - public byte[] wrapKey(SecretKey key, String wrappingAlg) throws NoSuchAlgorithmException, NoSuchPaddingException, - InvalidKeyException, IllegalBlockSizeException { - if (wrappingKey instanceof DelegatedKey) { - return ((DelegatedKey)wrappingKey).wrap(key, null, wrappingAlg); - } else { - Cipher cipher = Cipher.getInstance(wrappingAlg); - cipher.init(Cipher.WRAP_MODE, wrappingKey, Utils.getRng()); - return cipher.wrap(key); - } - } - - protected SecretKey unwrapKey( - Map description, byte[] encryptedKey, String wrappingAlgorithm) - throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { - if (unwrappingKey instanceof DelegatedKey) { - return (SecretKey) - ((DelegatedKey) unwrappingKey) - .unwrap( - encryptedKey, - description.get(CONTENT_KEY_ALGORITHM), - Cipher.SECRET_KEY, - null, - wrappingAlgorithm); - } else { - Cipher cipher = Cipher.getInstance(wrappingAlgorithm); - - // This can be of the form "AES/256" as well as "AES" e.g., - // but we want to set the SecretKey with just "AES" in either case - String[] algPieces = description.get(CONTENT_KEY_ALGORITHM).split("/", 2); - String contentKeyAlgorithm = algPieces[0]; - - cipher.init(Cipher.UNWRAP_MODE, unwrappingKey, Utils.getRng()); - return (SecretKey) cipher.unwrap(encryptedKey, contentKeyAlgorithm, Cipher.SECRET_KEY); - } - } - - protected SecretKey generateContentKey(final String algorithm) throws NoSuchAlgorithmException { - String[] pieces = algorithm.split("/", 2); - KeyGenerator kg = KeyGenerator.getInstance(pieces[0]); - int keyLen = 0; - if (pieces.length == 2) { - try { - keyLen = Integer.parseInt(pieces[1]); - } catch (NumberFormatException ignored) { - } - } - - if (keyLen > 0) { - kg.init(keyLen, Utils.getRng()); - } else { - kg.init(Utils.getRng()); - } - return kg.generateKey(); - } - - private static String getTransformation(final String algorithm) { - if (algorithm.equalsIgnoreCase("RSA")) { - return "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; - } else if (algorithm.equalsIgnoreCase("AES")) { - return "AESWrap"; - } else { - return algorithm; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java deleted file mode 100644 index b49e2b9a20..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import java.security.KeyPair; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.SecretKey; - -/** - * This is a thin wrapper around the {@link WrappedMaterialsProvider}, using - * the provided encryptionKey for wrapping and unwrapping the - * record key. Please see that class for detailed documentation. - * - * @author Greg Rubin - */ -public class AsymmetricStaticProvider extends WrappedMaterialsProvider { - public AsymmetricStaticProvider(KeyPair encryptionKey, KeyPair signingPair) { - this(encryptionKey, signingPair, Collections.emptyMap()); - } - - public AsymmetricStaticProvider(KeyPair encryptionKey, SecretKey macKey) { - this(encryptionKey, macKey, Collections.emptyMap()); - } - - public AsymmetricStaticProvider(KeyPair encryptionKey, KeyPair signingPair, Map description) { - super(encryptionKey.getPublic(), encryptionKey.getPrivate(), signingPair, description); - } - - public AsymmetricStaticProvider(KeyPair encryptionKey, SecretKey macKey, Map description) { - super(encryptionKey.getPublic(), encryptionKey.getPrivate(), macKey, description); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java deleted file mode 100644 index 653e754c26..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import io.netty.util.internal.ObjectUtil; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.TTLCache; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.TTLCache.EntryLoader; -import java.util.concurrent.TimeUnit; - -/** - * This meta-Provider encrypts data with the most recent version of keying materials from a {@link - * ProviderStore} and decrypts using whichever version is appropriate. It also caches the results - * from the {@link ProviderStore} to avoid excessive load on the backing systems. - */ -public class CachingMostRecentProvider implements EncryptionMaterialsProvider { - private static final long INITIAL_VERSION = 0; - private static final String PROVIDER_CACHE_KEY_DELIM = "#"; - private static final int DEFAULT_CACHE_MAX_SIZE = 1000; - - private final long ttlInNanos; - private final ProviderStore keystore; - protected final String defaultMaterialName; - private final TTLCache providerCache; - private final TTLCache versionCache; - - private final EntryLoader versionLoader = - new EntryLoader() { - @Override - public Long load(String entryKey) { - return keystore.getMaxVersion(entryKey); - } - }; - private final EntryLoader providerLoader = - new EntryLoader() { - @Override - public EncryptionMaterialsProvider load(String entryKey) { - final String[] parts = entryKey.split(PROVIDER_CACHE_KEY_DELIM, 2); - if (parts.length != 2) { - throw new IllegalStateException("Invalid cache key for provider cache: " + entryKey); - } - return keystore.getProvider(parts[0], Long.parseLong(parts[1])); - } - }; - - /** - * Creates a new {@link CachingMostRecentProvider}. - * - * @param keystore The key store that this provider will use to determine which material and which - * version of material to use - * @param materialName The name of the materials associated with this provider - * @param ttlInMillis The length of time in milliseconds to cache the most recent provider - */ - public CachingMostRecentProvider( - final ProviderStore keystore, final String materialName, final long ttlInMillis) { - this(keystore, materialName, ttlInMillis, DEFAULT_CACHE_MAX_SIZE); - } - - /** - * Creates a new {@link CachingMostRecentProvider}. - * - * @param keystore The key store that this provider will use to determine which material and which - * version of material to use - * @param materialName The name of the materials associated with this provider - * @param ttlInMillis The length of time in milliseconds to cache the most recent provider - * @param maxCacheSize The maximum size of the underlying caches this provider uses. Entries will - * be evicted from the cache once this size is exceeded. - */ - public CachingMostRecentProvider( - final ProviderStore keystore, - final String materialName, - final long ttlInMillis, - final int maxCacheSize) { - this.keystore = ObjectUtil.checkNotNull(keystore, "keystore must not be null"); - this.defaultMaterialName = materialName; - this.ttlInNanos = TimeUnit.MILLISECONDS.toNanos(ttlInMillis); - - this.providerCache = new TTLCache<>(maxCacheSize, ttlInMillis, providerLoader); - this.versionCache = new TTLCache<>(maxCacheSize, ttlInMillis, versionLoader); - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - final long version = - keystore.getVersionFromMaterialDescription(context.getMaterialDescription()); - final String materialName = getMaterialName(context); - final String cacheKey = buildCacheKey(materialName, version); - - EncryptionMaterialsProvider provider = providerCache.load(cacheKey); - return provider.getDecryptionMaterials(context); - } - - - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - final String materialName = getMaterialName(context); - final long currentVersion = versionCache.load(materialName); - - if (currentVersion < 0) { - // The material hasn't been created yet, so specify a loading function - // to create the first version of materials and update both caches. - // We want this to be done as part of the cache load to ensure that this logic - // only happens once in a multithreaded environment, - // in order to limit calls to the keystore's dependencies. - final String cacheKey = buildCacheKey(materialName, INITIAL_VERSION); - EncryptionMaterialsProvider newProvider = - providerCache.load( - cacheKey, - s -> { - // Create the new material in the keystore - final String[] parts = s.split(PROVIDER_CACHE_KEY_DELIM, 2); - if (parts.length != 2) { - throw new IllegalStateException("Invalid cache key for provider cache: " + s); - } - EncryptionMaterialsProvider provider = - keystore.getOrCreate(parts[0], Long.parseLong(parts[1])); - - // We now should have version 0 in our keystore. - // Update the version cache for this material as a side effect - versionCache.put(materialName, INITIAL_VERSION); - - // Return the new materials to be put into the cache - return provider; - }); - - return newProvider.getEncryptionMaterials(context); - } else { - final String cacheKey = buildCacheKey(materialName, currentVersion); - return providerCache.load(cacheKey).getEncryptionMaterials(context); - } - } - - @Override - public void refresh() { - versionCache.clear(); - providerCache.clear(); - } - - public String getMaterialName() { - return defaultMaterialName; - } - - public long getTtlInMills() { - return TimeUnit.NANOSECONDS.toMillis(ttlInNanos); - } - - /** - * The current version of the materials being used for encryption. Returns -1 if we do not - * currently have a current version. - */ - public long getCurrentVersion() { - return versionCache.load(getMaterialName()); - } - - /** - * The last time the current version was updated. Returns 0 if we do not currently have a current - * version. - */ - public long getLastUpdated() { - // We cache a version of -1 to mean that there is not a current version - if (versionCache.load(getMaterialName()) < 0) { - return 0; - } - // Otherwise, return the last update time of that entry - return TimeUnit.NANOSECONDS.toMillis(versionCache.getLastUpdated(getMaterialName())); - } - - protected String getMaterialName(final EncryptionContext context) { - return defaultMaterialName; - } - - private static String buildCacheKey(final String materialName, final long version) { - StringBuilder result = new StringBuilder(materialName); - result.append(PROVIDER_CACHE_KEY_DELIM); - result.append(version); - return result.toString(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java deleted file mode 100644 index 425a4119f2..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.CONTENT_KEY_ALGORITHM; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.ENVELOPE_KEY; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.KEY_WRAPPING_ALGORITHM; - -import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Hkdf; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.awssdk.services.kms.model.DecryptRequest; -import software.amazon.awssdk.services.kms.model.DecryptResponse; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; - -/** - * Generates a unique data key for each record in DynamoDB and protects that key - * using {@link KmsClient}. Currently, the HashKey, RangeKey, and TableName will be - * included in the KMS EncryptionContext for wrapping/unwrapping the key. This - * means that records cannot be copied/moved between tables without re-encryption. - * - * @see KMS Encryption Context - */ -public class DirectKmsMaterialsProvider implements EncryptionMaterialsProvider { - private static final String COVERED_ATTR_CTX_KEY = "aws-kms-ec-attr"; - private static final String SIGNING_KEY_ALGORITHM = "amzn-ddb-sig-alg"; - private static final String TABLE_NAME_EC_KEY = "*aws-kms-table*"; - - private static final String DEFAULT_ENC_ALG = "AES/256"; - private static final String DEFAULT_SIG_ALG = "HmacSHA256/256"; - private static final String KEY_COVERAGE = "*keys*"; - private static final String KDF_ALG = "HmacSHA256"; - private static final String KDF_SIG_INFO = "Signing"; - private static final String KDF_ENC_INFO = "Encryption"; - - private final KmsClient kms; - private final String encryptionKeyId; - private final Map description; - private final String dataKeyAlg; - private final int dataKeyLength; - private final String dataKeyDesc; - private final String sigKeyAlg; - private final int sigKeyLength; - private final String sigKeyDesc; - - public DirectKmsMaterialsProvider(KmsClient kms) { - this(kms, null); - } - - public DirectKmsMaterialsProvider(KmsClient kms, String encryptionKeyId, Map materialDescription) { - this.kms = kms; - this.encryptionKeyId = encryptionKeyId; - this.description = materialDescription != null ? - Collections.unmodifiableMap(new HashMap<>(materialDescription)) : - Collections.emptyMap(); - - dataKeyDesc = description.getOrDefault(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, DEFAULT_ENC_ALG); - - String[] parts = dataKeyDesc.split("/", 2); - this.dataKeyAlg = parts[0]; - this.dataKeyLength = parts.length == 2 ? Integer.parseInt(parts[1]) : 256; - - sigKeyDesc = description.getOrDefault(SIGNING_KEY_ALGORITHM, DEFAULT_SIG_ALG); - - parts = sigKeyDesc.split("/", 2); - this.sigKeyAlg = parts[0]; - this.sigKeyLength = parts.length == 2 ? Integer.parseInt(parts[1]) : 256; - } - - public DirectKmsMaterialsProvider(KmsClient kms, String encryptionKeyId) { - this(kms, encryptionKeyId, Collections.emptyMap()); - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - final Map materialDescription = context.getMaterialDescription(); - - final Map ec = new HashMap<>(); - final String providedEncAlg = materialDescription.get(CONTENT_KEY_ALGORITHM); - final String providedSigAlg = materialDescription.get(SIGNING_KEY_ALGORITHM); - - ec.put("*" + CONTENT_KEY_ALGORITHM + "*", providedEncAlg); - ec.put("*" + SIGNING_KEY_ALGORITHM + "*", providedSigAlg); - - populateKmsEcFromEc(context, ec); - - DecryptRequest.Builder request = DecryptRequest.builder(); - request.ciphertextBlob(SdkBytes.fromByteArray(Base64.decode(materialDescription.get(ENVELOPE_KEY)))); - request.encryptionContext(ec); - final DecryptResponse decryptResponse = decrypt(request.build(), context); - validateEncryptionKeyId(decryptResponse.keyId(), context); - - final Hkdf kdf; - try { - kdf = Hkdf.getInstance(KDF_ALG); - } catch (NoSuchAlgorithmException e) { - throw new DynamoDbEncryptionException(e); - } - kdf.init(decryptResponse.plaintext().asByteArray()); - - final String[] encAlgParts = providedEncAlg.split("/", 2); - int encLength = encAlgParts.length == 2 ? Integer.parseInt(encAlgParts[1]) : 256; - final String[] sigAlgParts = providedSigAlg.split("/", 2); - int sigLength = sigAlgParts.length == 2 ? Integer.parseInt(sigAlgParts[1]) : 256; - - final SecretKey encryptionKey = new SecretKeySpec(kdf.deriveKey(KDF_ENC_INFO, encLength / 8), encAlgParts[0]); - final SecretKey macKey = new SecretKeySpec(kdf.deriveKey(KDF_SIG_INFO, sigLength / 8), sigAlgParts[0]); - - return new SymmetricRawMaterials(encryptionKey, macKey, materialDescription); - } - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - final Map ec = new HashMap<>(); - ec.put("*" + CONTENT_KEY_ALGORITHM + "*", dataKeyDesc); - ec.put("*" + SIGNING_KEY_ALGORITHM + "*", sigKeyDesc); - populateKmsEcFromEc(context, ec); - - final String keyId = selectEncryptionKeyId(context); - if (keyId == null || keyId.isEmpty()) { - throw new DynamoDbEncryptionException("Encryption key id is empty."); - } - - final GenerateDataKeyRequest.Builder req = GenerateDataKeyRequest.builder(); - req.keyId(keyId); - // NumberOfBytes parameter is used because we're not using this key as an AES-256 key, - // we're using it as an HKDF-SHA256 key. - req.numberOfBytes(256 / 8); - req.encryptionContext(ec); - - final GenerateDataKeyResponse dataKeyResult = generateDataKey(req.build(), context); - - final Map materialDescription = new HashMap<>(description); - materialDescription.put(COVERED_ATTR_CTX_KEY, KEY_COVERAGE); - materialDescription.put(KEY_WRAPPING_ALGORITHM, "kms"); - materialDescription.put(CONTENT_KEY_ALGORITHM, dataKeyDesc); - materialDescription.put(SIGNING_KEY_ALGORITHM, sigKeyDesc); - materialDescription.put(ENVELOPE_KEY, - Base64.encodeToString(dataKeyResult.ciphertextBlob().asByteArray())); - - final Hkdf kdf; - try { - kdf = Hkdf.getInstance(KDF_ALG); - } catch (NoSuchAlgorithmException e) { - throw new DynamoDbEncryptionException(e); - } - - kdf.init(dataKeyResult.plaintext().asByteArray()); - - final SecretKey encryptionKey = new SecretKeySpec(kdf.deriveKey(KDF_ENC_INFO, dataKeyLength / 8), dataKeyAlg); - final SecretKey signatureKey = new SecretKeySpec(kdf.deriveKey(KDF_SIG_INFO, sigKeyLength / 8), sigKeyAlg); - return new SymmetricRawMaterials(encryptionKey, signatureKey, materialDescription); - } - - /** - * Get encryption key id that is used to create the {@link EncryptionMaterials}. - * - * @return encryption key id. - */ - protected String getEncryptionKeyId() { - return this.encryptionKeyId; - } - - /** - * Select encryption key id to be used to generate data key. The default implementation of this method returns - * {@link DirectKmsMaterialsProvider#encryptionKeyId}. - * - * @param context encryption context. - * @return the encryptionKeyId. - * @throws DynamoDbEncryptionException when we fails to select a valid encryption key id. - */ - protected String selectEncryptionKeyId(EncryptionContext context) throws DynamoDbEncryptionException { - return getEncryptionKeyId(); - } - - /** - * Validate the encryption key id. The default implementation of this method does not validate - * encryption key id. - * - * @param encryptionKeyId encryption key id from {@link DecryptResponse}. - * @param context encryption context. - * @throws DynamoDbEncryptionException when encryptionKeyId is invalid. - */ - protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext context) - throws DynamoDbEncryptionException { - // No action taken. - } - - /** - * Decrypts ciphertext. The default implementation calls KMS to decrypt the ciphertext using the parameters - * provided in the {@link DecryptRequest}. Subclass can override the default implementation to provide - * additional request parameters using attributes within the {@link EncryptionContext}. - * - * @param request request parameters to decrypt the given ciphertext. - * @param context additional useful data to decrypt the ciphertext. - * @return the decrypted plaintext for the given ciphertext. - */ - protected DecryptResponse decrypt(final DecryptRequest request, final EncryptionContext context) { - return kms.decrypt(request); - } - - /** - * Returns a data encryption key that you can use in your application to encrypt data locally. The default - * implementation calls KMS to generate the data key using the parameters provided in the - * {@link GenerateDataKeyRequest}. Subclass can override the default implementation to provide additional - * request parameters using attributes within the {@link EncryptionContext}. - * - * @param request request parameters to generate the data key. - * @param context additional useful data to generate the data key. - * @return the newly generated data key which includes both the plaintext and ciphertext. - */ - protected GenerateDataKeyResponse generateDataKey(final GenerateDataKeyRequest request, - final EncryptionContext context) { - return kms.generateDataKey(request); - } - - /** - * Extracts relevant information from {@code context} and uses it to populate fields in - * {@code kmsEc}. Currently, these fields are: - *
- *
{@code HashKeyName}
- *
{@code HashKeyValue}
- *
{@code RangeKeyName}
- *
{@code RangeKeyValue}
- *
{@link #TABLE_NAME_EC_KEY}
- *
{@code TableName}
- */ - private static void populateKmsEcFromEc(EncryptionContext context, Map kmsEc) { - final String hashKeyName = context.getHashKeyName(); - if (hashKeyName != null) { - final AttributeValue hashKey = context.getAttributeValues().get(hashKeyName); - if (hashKey.n() != null) { - kmsEc.put(hashKeyName, hashKey.n()); - } else if (hashKey.s() != null) { - kmsEc.put(hashKeyName, hashKey.s()); - } else if (hashKey.b() != null) { - kmsEc.put(hashKeyName, Base64.encodeToString(hashKey.b().asByteArray())); - } else { - throw new UnsupportedOperationException("DirectKmsMaterialsProvider only supports String, Number, and Binary HashKeys"); - } - } - final String rangeKeyName = context.getRangeKeyName(); - if (rangeKeyName != null) { - final AttributeValue rangeKey = context.getAttributeValues().get(rangeKeyName); - if (rangeKey.n() != null) { - kmsEc.put(rangeKeyName, rangeKey.n()); - } else if (rangeKey.s() != null) { - kmsEc.put(rangeKeyName, rangeKey.s()); - } else if (rangeKey.b() != null) { - kmsEc.put(rangeKeyName, Base64.encodeToString(rangeKey.b().asByteArray())); - } else { - throw new UnsupportedOperationException("DirectKmsMaterialsProvider only supports String, Number, and Binary RangeKeys"); - } - } - - final String tableName = context.getTableName(); - if (tableName != null) { - kmsEc.put(TABLE_NAME_EC_KEY, tableName); - } - } - - @Override - public void refresh() { - // No action needed - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java deleted file mode 100644 index b60fee3ee0..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; - -/** - * Interface for providing encryption materials. - * Implementations are free to use any strategy for providing encryption - * materials, such as simply providing static material that doesn't change, - * or more complicated implementations, such as integrating with existing - * key management systems. - * - * @author Greg Rubin - */ -public interface EncryptionMaterialsProvider { - - /** - * Retrieves encryption materials matching the specified description from some source. - * - * @param context - * Information to assist in selecting a the proper return value. The implementation - * is free to determine the minimum necessary for successful processing. - * - * @return - * The encryption materials that match the description, or null if no matching encryption materials found. - */ - DecryptionMaterials getDecryptionMaterials(EncryptionContext context); - - /** - * Returns EncryptionMaterials which the caller can use for encryption. - * Each implementation of EncryptionMaterialsProvider can choose its own - * strategy for loading encryption material. For example, an - * implementation might load encryption material from an existing key - * management system, or load new encryption material when keys are - * rotated. - * - * @param context - * Information to assist in selecting a the proper return value. The implementation - * is free to determine the minimum necessary for successful processing. - * - * @return EncryptionMaterials which the caller can use to encrypt or - * decrypt data. - */ - EncryptionMaterials getEncryptionMaterials(EncryptionContext context); - - /** - * Forces this encryption materials provider to refresh its encryption - * material. For many implementations of encryption materials provider, - * this may simply be a no-op, such as any encryption materials provider - * implementation that vends static/non-changing encryption material. - * For other implementations that vend different encryption material - * throughout their lifetime, this method should force the encryption - * materials provider to refresh its encryption material. - */ - void refresh(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java deleted file mode 100644 index 483b81b51a..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.KeyStore.Entry; -import java.security.KeyStore.PrivateKeyEntry; -import java.security.KeyStore.ProtectionParameter; -import java.security.KeyStore.SecretKeyEntry; -import java.security.KeyStore.TrustedCertificateEntry; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.UnrecoverableEntryException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.AsymmetricRawMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; - -/** - * @author Greg Rubin - */ -public class KeyStoreMaterialsProvider implements EncryptionMaterialsProvider { - private final Map description; - private final String encryptionAlias; - private final String signingAlias; - private final ProtectionParameter encryptionProtection; - private final ProtectionParameter signingProtection; - private final KeyStore keyStore; - private final AtomicReference currMaterials = - new AtomicReference<>(); - - public KeyStoreMaterialsProvider(KeyStore keyStore, String encryptionAlias, String signingAlias, Map description) - throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { - this(keyStore, encryptionAlias, signingAlias, null, null, description); - } - - public KeyStoreMaterialsProvider(KeyStore keyStore, String encryptionAlias, String signingAlias, - ProtectionParameter encryptionProtection, ProtectionParameter signingProtection, - Map description) - throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { - super(); - this.keyStore = keyStore; - this.encryptionAlias = encryptionAlias; - this.signingAlias = signingAlias; - this.encryptionProtection = encryptionProtection; - this.signingProtection = signingProtection; - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - - validateKeys(); - loadKeys(); - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - CurrentMaterials materials = currMaterials.get(); - if (context.getMaterialDescription().entrySet().containsAll(description.entrySet())) { - if (materials.encryptionEntry instanceof SecretKeyEntry) { - return materials.symRawMaterials; - } else { - try { - return makeAsymMaterials(materials, context.getMaterialDescription()); - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to decrypt envelope key", ex); - } - } - } else { - return null; - } - } - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - CurrentMaterials materials = currMaterials.get(); - if (materials.encryptionEntry instanceof SecretKeyEntry) { - return materials.symRawMaterials; - } else { - try { - return makeAsymMaterials(materials, description); - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to encrypt envelope key", ex); - } - } - } - - private AsymmetricRawMaterials makeAsymMaterials(CurrentMaterials materials, - Map description) throws GeneralSecurityException { - KeyPair encryptionPair = entry2Pair(materials.encryptionEntry); - if (materials.signingEntry instanceof SecretKeyEntry) { - return new AsymmetricRawMaterials(encryptionPair, - ((SecretKeyEntry) materials.signingEntry).getSecretKey(), description); - } else { - return new AsymmetricRawMaterials(encryptionPair, entry2Pair(materials.signingEntry), - description); - } - } - - private static KeyPair entry2Pair(Entry entry) { - PublicKey pub = null; - PrivateKey priv = null; - - if (entry instanceof PrivateKeyEntry) { - PrivateKeyEntry pk = (PrivateKeyEntry) entry; - if (pk.getCertificate() != null) { - pub = pk.getCertificate().getPublicKey(); - } - priv = pk.getPrivateKey(); - } else if (entry instanceof TrustedCertificateEntry) { - TrustedCertificateEntry tc = (TrustedCertificateEntry) entry; - pub = tc.getTrustedCertificate().getPublicKey(); - } else { - throw new IllegalArgumentException( - "Only entry types PrivateKeyEntry and TrustedCertificateEntry are supported."); - } - return new KeyPair(pub, priv); - } - - /** - * Reloads the keys from the underlying keystore by calling - * {@link KeyStore#getEntry(String, ProtectionParameter)} again for each of them. - */ - @Override - public void refresh() { - try { - loadKeys(); - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to load keys from keystore", ex); - } - } - - private void validateKeys() throws KeyStoreException { - if (!keyStore.containsAlias(encryptionAlias)) { - throw new IllegalArgumentException("Keystore does not contain alias: " - + encryptionAlias); - } - if (!keyStore.containsAlias(signingAlias)) { - throw new IllegalArgumentException("Keystore does not contain alias: " - + signingAlias); - } - } - - private void loadKeys() throws NoSuchAlgorithmException, UnrecoverableEntryException, - KeyStoreException { - Entry encryptionEntry = keyStore.getEntry(encryptionAlias, encryptionProtection); - Entry signingEntry = keyStore.getEntry(signingAlias, signingProtection); - CurrentMaterials newMaterials = new CurrentMaterials(encryptionEntry, signingEntry); - currMaterials.set(newMaterials); - } - - private class CurrentMaterials { - public final Entry encryptionEntry; - public final Entry signingEntry; - public final SymmetricRawMaterials symRawMaterials; - - public CurrentMaterials(Entry encryptionEntry, Entry signingEntry) { - super(); - this.encryptionEntry = encryptionEntry; - this.signingEntry = signingEntry; - - if (encryptionEntry instanceof SecretKeyEntry) { - if (signingEntry instanceof SecretKeyEntry) { - this.symRawMaterials = new SymmetricRawMaterials( - ((SecretKeyEntry) encryptionEntry).getSecretKey(), - ((SecretKeyEntry) signingEntry).getSecretKey(), - description); - } else { - this.symRawMaterials = new SymmetricRawMaterials( - ((SecretKeyEntry) encryptionEntry).getSecretKey(), - entry2Pair(signingEntry), - description); - } - } else { - this.symRawMaterials = null; - } - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java deleted file mode 100644 index 8a63a0328c..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import java.security.KeyPair; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.SecretKey; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.CryptographicMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; - -/** - * A provider which always returns the same provided symmetric - * encryption/decryption key and the same signing/verification key(s). - * - * @author Greg Rubin - */ -public class SymmetricStaticProvider implements EncryptionMaterialsProvider { - private final SymmetricRawMaterials materials; - - /** - * @param encryptionKey - * the value to be returned by - * {@link #getEncryptionMaterials(EncryptionContext)} and - * {@link #getDecryptionMaterials(EncryptionContext)} - * @param signingPair - * the keypair used to sign/verify the data stored in Dynamo. If - * only the public key is provided, then this provider may be - * used for decryption, but not encryption. - */ - public SymmetricStaticProvider(SecretKey encryptionKey, KeyPair signingPair) { - this(encryptionKey, signingPair, Collections.emptyMap()); - } - - /** - * @param encryptionKey - * the value to be returned by - * {@link #getEncryptionMaterials(EncryptionContext)} and - * {@link #getDecryptionMaterials(EncryptionContext)} - * @param signingPair - * the keypair used to sign/verify the data stored in Dynamo. If - * only the public key is provided, then this provider may be - * used for decryption, but not encryption. - * @param description - * the value to be returned by - * {@link CryptographicMaterials#getMaterialDescription()} for - * any {@link CryptographicMaterials} returned by this object. - */ - public SymmetricStaticProvider(SecretKey encryptionKey, - KeyPair signingPair, Map description) { - materials = new SymmetricRawMaterials(encryptionKey, signingPair, - description); - } - - /** - * @param encryptionKey - * the value to be returned by - * {@link #getEncryptionMaterials(EncryptionContext)} and - * {@link #getDecryptionMaterials(EncryptionContext)} - * @param macKey - * the key used to sign/verify the data stored in Dynamo. - */ - public SymmetricStaticProvider(SecretKey encryptionKey, SecretKey macKey) { - this(encryptionKey, macKey, Collections.emptyMap()); - } - - /** - * @param encryptionKey - * the value to be returned by - * {@link #getEncryptionMaterials(EncryptionContext)} and - * {@link #getDecryptionMaterials(EncryptionContext)} - * @param macKey - * the key used to sign/verify the data stored in Dynamo. - * @param description - * the value to be returned by - * {@link CryptographicMaterials#getMaterialDescription()} for - * any {@link CryptographicMaterials} returned by this object. - */ - public SymmetricStaticProvider(SecretKey encryptionKey, SecretKey macKey, Map description) { - materials = new SymmetricRawMaterials(encryptionKey, macKey, description); - } - - /** - * Returns the encryptionKey provided to the constructor if and only if - * materialDescription is a super-set (may be equal) to the - * description provided to the constructor. - */ - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - if (context.getMaterialDescription().entrySet().containsAll(materials.getMaterialDescription().entrySet())) { - return materials; - } - else { - return null; - } - } - - /** - * Returns the encryptionKey provided to the constructor. - */ - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - return materials; - } - - /** - * Does nothing. - */ - @Override - public void refresh() { - // Do Nothing - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java deleted file mode 100644 index 1c92fb3f4a..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import java.security.GeneralSecurityException; -import java.security.Key; -import java.security.KeyPair; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.SecretKey; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.CryptographicMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; - -/** - * This provider will use create a unique (random) symmetric key upon each call to - * {@link #getEncryptionMaterials(EncryptionContext)}. Practically, this means each record in DynamoDB will be - * encrypted under a unique record key. A wrapped/encrypted copy of this record key is stored in the - * MaterialsDescription field of that record and is unwrapped/decrypted upon reading that record. - * - * This is generally a more secure way of encrypting data than with the - * {@link SymmetricStaticProvider}. - * - * @see WrappedRawMaterials - * - * @author Greg Rubin - */ -public class WrappedMaterialsProvider implements EncryptionMaterialsProvider { - private final Key wrappingKey; - private final Key unwrappingKey; - private final KeyPair sigPair; - private final SecretKey macKey; - private final Map description; - - /** - * @param wrappingKey - * The key used to wrap/encrypt the symmetric record key. (May be the same as the - * unwrappingKey.) - * @param unwrappingKey - * The key used to unwrap/decrypt the symmetric record key. (May be the same as the - * wrappingKey.) If null, then this provider may only be used for - * decryption, but not encryption. - * @param signingPair - * the keypair used to sign/verify the data stored in Dynamo. If only the public key - * is provided, then this provider may only be used for decryption, but not - * encryption. - */ - public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, KeyPair signingPair) { - this(wrappingKey, unwrappingKey, signingPair, Collections.emptyMap()); - } - - /** - * @param wrappingKey - * The key used to wrap/encrypt the symmetric record key. (May be the same as the - * unwrappingKey.) - * @param unwrappingKey - * The key used to unwrap/decrypt the symmetric record key. (May be the same as the - * wrappingKey.) If null, then this provider may only be used for - * decryption, but not encryption. - * @param signingPair - * the keypair used to sign/verify the data stored in Dynamo. If only the public key - * is provided, then this provider may only be used for decryption, but not - * encryption. - * @param description - * description the value to be returned by - * {@link CryptographicMaterials#getMaterialDescription()} for any - * {@link CryptographicMaterials} returned by this object. - */ - public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, KeyPair signingPair, Map description) { - this.wrappingKey = wrappingKey; - this.unwrappingKey = unwrappingKey; - this.sigPair = signingPair; - this.macKey = null; - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - } - - /** - * @param wrappingKey - * The key used to wrap/encrypt the symmetric record key. (May be the same as the - * unwrappingKey.) - * @param unwrappingKey - * The key used to unwrap/decrypt the symmetric record key. (May be the same as the - * wrappingKey.) If null, then this provider may only be used for - * decryption, but not encryption. - * @param macKey - * the key used to sign/verify the data stored in Dynamo. - */ - public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, SecretKey macKey) { - this(wrappingKey, unwrappingKey, macKey, Collections.emptyMap()); - } - - /** - * @param wrappingKey - * The key used to wrap/encrypt the symmetric record key. (May be the same as the - * unwrappingKey.) - * @param unwrappingKey - * The key used to unwrap/decrypt the symmetric record key. (May be the same as the - * wrappingKey.) If null, then this provider may only be used for - * decryption, but not encryption. - * @param macKey - * the key used to sign/verify the data stored in Dynamo. - * @param description - * description the value to be returned by - * {@link CryptographicMaterials#getMaterialDescription()} for any - * {@link CryptographicMaterials} returned by this object. - */ - public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, SecretKey macKey, Map description) { - this.wrappingKey = wrappingKey; - this.unwrappingKey = unwrappingKey; - this.sigPair = null; - this.macKey = macKey; - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - try { - if (macKey != null) { - return new WrappedRawMaterials(wrappingKey, unwrappingKey, macKey, context.getMaterialDescription()); - } else { - return new WrappedRawMaterials(wrappingKey, unwrappingKey, sigPair, context.getMaterialDescription()); - } - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to decrypt envelope key", ex); - } - } - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - try { - if (macKey != null) { - return new WrappedRawMaterials(wrappingKey, unwrappingKey, macKey, description); - } else { - return new WrappedRawMaterials(wrappingKey, unwrappingKey, sigPair, description); - } - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to encrypt envelope key", ex); - } - } - - @Override - public void refresh() { - // Do nothing - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java deleted file mode 100644 index c0fbe5e06f..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; - -import java.security.GeneralSecurityException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.ComparisonOperator; -import software.amazon.awssdk.services.dynamodb.model.Condition; -import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; -import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; -import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse; -import software.amazon.awssdk.services.dynamodb.model.ExpectedAttributeValue; -import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; -import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; -import software.amazon.awssdk.services.dynamodb.model.KeyType; -import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; -import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; -import software.amazon.awssdk.services.dynamodb.model.QueryRequest; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - - -/** - * Provides a simple collection of EncryptionMaterialProviders backed by an encrypted DynamoDB - * table. This can be used to build key hierarchies or meta providers. - * - * Currently, this only supports AES-256 in AESWrap mode and HmacSHA256 for the providers persisted - * in the table. - * - * @author rubin - */ -public class MetaStore extends ProviderStore { - private static final String INTEGRITY_ALGORITHM_FIELD = "intAlg"; - private static final String INTEGRITY_KEY_FIELD = "int"; - private static final String ENCRYPTION_ALGORITHM_FIELD = "encAlg"; - private static final String ENCRYPTION_KEY_FIELD = "enc"; - private static final Pattern COMBINED_PATTERN = Pattern.compile("([^#]+)#(\\d*)"); - private static final String DEFAULT_INTEGRITY = "HmacSHA256"; - private static final String DEFAULT_ENCRYPTION = "AES"; - private static final String MATERIAL_TYPE_VERSION = "t"; - private static final String META_ID = "amzn-ddb-meta-id"; - - private static final String DEFAULT_HASH_KEY = "N"; - private static final String DEFAULT_RANGE_KEY = "V"; - - /** Default no-op implementation of {@link ExtraDataSupplier}. */ - private static final EmptyExtraDataSupplier EMPTY_EXTRA_DATA_SUPPLIER - = new EmptyExtraDataSupplier(); - - /** DDB fields that must be encrypted. */ - private static final Set ENCRYPTED_FIELDS; - static { - final Set tempEncryptedFields = new HashSet<>(); - tempEncryptedFields.add(MATERIAL_TYPE_VERSION); - tempEncryptedFields.add(ENCRYPTION_KEY_FIELD); - tempEncryptedFields.add(ENCRYPTION_ALGORITHM_FIELD); - tempEncryptedFields.add(INTEGRITY_KEY_FIELD); - tempEncryptedFields.add(INTEGRITY_ALGORITHM_FIELD); - ENCRYPTED_FIELDS = tempEncryptedFields; - } - - private final Map doesNotExist; - private final Set doNotEncrypt; -// private final DynamoDbEncryptionConfiguration encryptionConfiguration; - private final String tableName; - private final DynamoDbClient ddb; - private final DynamoDbEncryptor encryptor; - private final EncryptionContext ddbCtx; - private final ExtraDataSupplier extraDataSupplier; - - /** - * Provides extra data that should be persisted along with the standard material data. - */ - public interface ExtraDataSupplier { - - /** - * Gets the extra data attributes for the specified material name. - * - * @param materialName material name. - * @param version version number. - * @return plain text of the extra data. - */ - Map getAttributes(final String materialName, final long version); - - /** - * Gets the extra data field names that should be signed only but not encrypted. - * - * @return signed only fields. - */ - Set getSignedOnlyFieldNames(); - } - - /** - * Create a new MetaStore with specified table name. - * - * @param ddb Interface for accessing DynamoDB. - * @param tableName DynamoDB table name for this {@link MetaStore}. - * @param encryptor used to perform crypto operations on the record attributes. - */ - public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor) { - this(ddb, tableName, encryptor, EMPTY_EXTRA_DATA_SUPPLIER); - } - - /** - * Create a new MetaStore with specified table name and extra data supplier. - * - * @param ddb Interface for accessing DynamoDB. - * @param tableName DynamoDB table name for this {@link MetaStore}. - * @param encryptor used to perform crypto operations on the record attributes - * @param extraDataSupplier provides extra data that should be stored along with the material. - */ - public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { - this.ddb = checkNotNull(ddb, "ddb must not be null"); - this.tableName = checkNotNull(tableName, "tableName must not be null"); - this.encryptor = checkNotNull(encryptor, "encryptor must not be null"); - this.extraDataSupplier = checkNotNull(extraDataSupplier, "extraDataSupplier must not be null"); - this.ddbCtx = - new EncryptionContext.Builder() - .tableName(this.tableName) - .hashKeyName(DEFAULT_HASH_KEY) - .rangeKeyName(DEFAULT_RANGE_KEY) - .build(); - - final Map tmpExpected = new HashMap<>(); - tmpExpected.put(DEFAULT_HASH_KEY, ExpectedAttributeValue.builder().exists(false).build()); - tmpExpected.put(DEFAULT_RANGE_KEY, ExpectedAttributeValue.builder().exists(false).build()); - doesNotExist = Collections.unmodifiableMap(tmpExpected); - - this.doNotEncrypt = getSignedOnlyFields(extraDataSupplier); - } - - @Override - public EncryptionMaterialsProvider getProvider(final String materialName, final long version) { - final Map item = getMaterialItem(materialName, version); - return decryptProvider(item); - } - - @Override - public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) { - final Map plaintext = createMaterialItem(materialName, nextId); - final Map ciphertext = conditionalPut(getEncryptedText(plaintext)); - return decryptProvider(ciphertext); - } - - @Override - public long getMaxVersion(final String materialName) { - - final List> items = - ddb.query( - QueryRequest.builder() - .tableName(tableName) - .consistentRead(Boolean.TRUE) - .keyConditions( - Collections.singletonMap( - DEFAULT_HASH_KEY, - Condition.builder() - .comparisonOperator(ComparisonOperator.EQ) - .attributeValueList(AttributeValue.builder().s(materialName).build()) - .build())) - .limit(1) - .scanIndexForward(false) - .attributesToGet(DEFAULT_RANGE_KEY) - .build()) - .items(); - - if (items.isEmpty()) { - return -1L; - } else { - return Long.parseLong(items.get(0).get(DEFAULT_RANGE_KEY).n()); - } - } - - @Override - public long getVersionFromMaterialDescription(final Map description) { - final Matcher m = COMBINED_PATTERN.matcher(description.get(META_ID)); - if (m.matches()) { - return Long.parseLong(m.group(2)); - } else { - throw new IllegalArgumentException("No meta id found"); - } - } - - /** - * This API retrieves the intermediate keys from the source region and replicates it in the target region. - * - * @param materialName material name of the encryption material. - * @param version version of the encryption material. - * @param targetMetaStore target MetaStore where the encryption material to be stored. - */ - public void replicate(final String materialName, final long version, final MetaStore targetMetaStore) { - try { - final Map item = getMaterialItem(materialName, version); - - final Map plainText = getPlainText(item); - final Map encryptedText = targetMetaStore.getEncryptedText(plainText); - final PutItemRequest put = PutItemRequest.builder() - .tableName(targetMetaStore.tableName) - .item(encryptedText) - .expected(doesNotExist) - .build(); - targetMetaStore.ddb.putItem(put); - } catch (ConditionalCheckFailedException e) { - //Item already present. - } - } - - /** - * Creates a DynamoDB Table with the correct properties to be used with a ProviderStore. - * - * @param ddb interface for accessing DynamoDB - * @param tableName name of table that stores the meta data of the material. - * @param provisionedThroughput required provisioned throughput of the this table. - * @return result of create table request. - */ - public static CreateTableResponse createTable(final DynamoDbClient ddb, final String tableName, - final ProvisionedThroughput provisionedThroughput) { - return ddb.createTable( - CreateTableRequest.builder() - .tableName(tableName) - .attributeDefinitions(Arrays.asList( - AttributeDefinition.builder() - .attributeName(DEFAULT_HASH_KEY) - .attributeType(ScalarAttributeType.S) - .build(), - AttributeDefinition.builder() - .attributeName(DEFAULT_RANGE_KEY) - .attributeType(ScalarAttributeType.N).build())) - .keySchema(Arrays.asList( - KeySchemaElement.builder() - .attributeName(DEFAULT_HASH_KEY) - .keyType(KeyType.HASH) - .build(), - KeySchemaElement.builder() - .attributeName(DEFAULT_RANGE_KEY) - .keyType(KeyType.RANGE) - .build())) - .provisionedThroughput(provisionedThroughput).build()); - } - - private Map getMaterialItem(final String materialName, final long version) { - final Map ddbKey = new HashMap<>(); - ddbKey.put(DEFAULT_HASH_KEY, AttributeValue.builder().s(materialName).build()); - ddbKey.put(DEFAULT_RANGE_KEY, AttributeValue.builder().n(Long.toString(version)).build()); - final Map item = ddbGet(ddbKey); - if (item == null || item.isEmpty()) { - throw new IndexOutOfBoundsException("No material found: " + materialName + "#" + version); - } - return item; - } - - - /** - * Empty extra data supplier. This default class is intended to simplify the default - * implementation of {@link MetaStore}. - */ - private static class EmptyExtraDataSupplier implements ExtraDataSupplier { - @Override - public Map getAttributes(String materialName, long version) { - return Collections.emptyMap(); - } - - @Override - public Set getSignedOnlyFieldNames() { - return Collections.emptySet(); - } - } - - /** - * Get a set of fields that must be signed but not encrypted. - * - * @param extraDataSupplier extra data supplier that is used to return sign only field names. - * @return fields that must be signed. - */ - private static Set getSignedOnlyFields(final ExtraDataSupplier extraDataSupplier) { - final Set signedOnlyFields = extraDataSupplier.getSignedOnlyFieldNames(); - for (final String signedOnlyField : signedOnlyFields) { - if (ENCRYPTED_FIELDS.contains(signedOnlyField)) { - throw new IllegalArgumentException(signedOnlyField + " must be encrypted"); - } - } - - // fields that should not be encrypted - final Set doNotEncryptFields = new HashSet<>(); - doNotEncryptFields.add(DEFAULT_HASH_KEY); - doNotEncryptFields.add(DEFAULT_RANGE_KEY); - doNotEncryptFields.addAll(signedOnlyFields); - return Collections.unmodifiableSet(doNotEncryptFields); - } - - private Map conditionalPut(final Map item) { - try { - final PutItemRequest put = PutItemRequest.builder().tableName(tableName).item(item) - .expected(doesNotExist).build(); - ddb.putItem(put); - return item; - } catch (final ConditionalCheckFailedException ex) { - final Map ddbKey = new HashMap<>(); - ddbKey.put(DEFAULT_HASH_KEY, item.get(DEFAULT_HASH_KEY)); - ddbKey.put(DEFAULT_RANGE_KEY, item.get(DEFAULT_RANGE_KEY)); - return ddbGet(ddbKey); - } - } - - private Map ddbGet(final Map ddbKey) { - return ddb.getItem( - GetItemRequest.builder().tableName(tableName).consistentRead(true) - .key(ddbKey).build()).item(); - } - - /** - * Build an material item for a given material name and version with newly generated - * encryption and integrity keys. - * - * @param materialName material name. - * @param version version of the material. - * @return newly generated plaintext material item. - */ - private Map createMaterialItem(final String materialName, final long version) { - final SecretKeySpec encryptionKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_ENCRYPTION); - final SecretKeySpec integrityKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_INTEGRITY); - - final Map plaintext = new HashMap<>(); - plaintext.put(DEFAULT_HASH_KEY, AttributeValue.builder().s(materialName).build()); - plaintext.put(DEFAULT_RANGE_KEY, AttributeValue.builder().n(Long.toString(version)).build()); - plaintext.put(MATERIAL_TYPE_VERSION, AttributeValue.builder().s("0").build()); - plaintext.put(ENCRYPTION_KEY_FIELD, - AttributeValue.builder().b(SdkBytes.fromByteArray(encryptionKey.getEncoded())).build()); - plaintext.put(ENCRYPTION_ALGORITHM_FIELD, AttributeValue.builder().s(encryptionKey.getAlgorithm()).build()); - plaintext.put(INTEGRITY_KEY_FIELD, - AttributeValue.builder().b(SdkBytes.fromByteArray(integrityKey.getEncoded())).build()); - plaintext.put(INTEGRITY_ALGORITHM_FIELD, AttributeValue.builder().s(integrityKey.getAlgorithm()).build()); - plaintext.putAll(extraDataSupplier.getAttributes(materialName, version)); - - return plaintext; - } - - private EncryptionMaterialsProvider decryptProvider(final Map item) { - final Map plaintext = getPlainText(item); - - final String type = plaintext.get(MATERIAL_TYPE_VERSION).s(); - final SecretKey encryptionKey; - final SecretKey integrityKey; - // This switch statement is to make future extensibility easier and more obvious - switch (type) { - case "0": // Only currently supported type - encryptionKey = new SecretKeySpec(plaintext.get(ENCRYPTION_KEY_FIELD).b().asByteArray(), - plaintext.get(ENCRYPTION_ALGORITHM_FIELD).s()); - integrityKey = new SecretKeySpec(plaintext.get(INTEGRITY_KEY_FIELD).b().asByteArray(), plaintext - .get(INTEGRITY_ALGORITHM_FIELD).s()); - break; - default: - throw new IllegalStateException("Unsupported material type: " + type); - } - return new WrappedMaterialsProvider(encryptionKey, encryptionKey, integrityKey, - buildDescription(plaintext)); - } - - /** - * Decrypts attributes in the ciphertext item using {@link DynamoDbEncryptor}. except the - * attribute names specified in doNotEncrypt. - * - * @param ciphertext the ciphertext to be decrypted. - * @throws SdkClientException when failed to decrypt material item. - * @return decrypted item. - */ - private Map getPlainText(final Map ciphertext) { - try { - return encryptor.decryptAllFieldsExcept(ciphertext, ddbCtx, doNotEncrypt); - } catch (final GeneralSecurityException e) { - throw SdkClientException.create("Error retrieving PlainText", e); - } - } - - /** - * Encrypts attributes in the plaintext item using {@link DynamoDbEncryptor}. except the attribute - * names specified in doNotEncrypt. - * - * @throws SdkClientException when failed to encrypt material item. - * @param plaintext plaintext to be encrypted. - */ - private Map getEncryptedText(Map plaintext) { - try { - return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, doNotEncrypt); - } catch (final GeneralSecurityException e) { - throw SdkClientException.create("Error retrieving PlainText", e); - } - } - - private Map buildDescription(final Map plaintext) { - return Collections.singletonMap(META_ID, plaintext.get(DEFAULT_HASH_KEY).s() + "#" - + plaintext.get(DEFAULT_RANGE_KEY).n()); - } - - private static V checkNotNull(final V ref, final String errMsg) { - if (ref == null) { - throw new NullPointerException(errMsg); - } else { - return ref; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java deleted file mode 100644 index a29fe9b34d..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; - -import java.util.Map; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; - -/** - * Provides a standard way to retrieve and optionally create {@link EncryptionMaterialsProvider}s - * backed by some form of persistent storage. - * - * @author rubin - * - */ -public abstract class ProviderStore { - - /** - * Returns the most recent provider with the specified name. If there are no providers with this - * name, it will create one with version 0. - */ - public EncryptionMaterialsProvider getProvider(final String materialName) { - final long currVersion = getMaxVersion(materialName); - if (currVersion >= 0) { - return getProvider(materialName, currVersion); - } else { - return getOrCreate(materialName, 0); - } - } - - /** - * Returns the provider with the specified name and version. - * - * @throws IndexOutOfBoundsException - * if {@code version} is not a valid version - */ - public abstract EncryptionMaterialsProvider getProvider(final String materialName, final long version); - - /** - * Creates a new provider with a version one greater than the current max version. If multiple - * clients attempt to create a provider with this same version simultaneously, they will - * properly coordinate and the result will be that a single provider is created and that all - * ProviderStores return the same one. - */ - public EncryptionMaterialsProvider newProvider(final String materialName) { - final long nextId = getMaxVersion(materialName) + 1; - return getOrCreate(materialName, nextId); - } - - /** - * Returns the provider with the specified name and version and creates it if it doesn't exist. - * - * @throws UnsupportedOperationException - * if a new provider cannot be created - */ - public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) { - try { - return getProvider(materialName, nextId); - } catch (final IndexOutOfBoundsException ex) { - throw new UnsupportedOperationException("This ProviderStore does not support creation.", ex); - } - } - - /** - * Returns the maximum version number associated with {@code materialName}. If there are no - * versions, returns -1. - */ - public abstract long getMaxVersion(final String materialName); - - /** - * Extracts the material version from {@code description}. - */ - public abstract long getVersionFromMaterialDescription(final Map description); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java deleted file mode 100644 index d29bb818cb..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java +++ /dev/null @@ -1,81 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; - -import java.util.Map; -import java.util.function.UnaryOperator; - -/** - * Implementations of common operators for overriding the EncryptionContext - */ -public class EncryptionContextOperators { - - // Prevent instantiation - private EncryptionContextOperators() { - } - - /** - * An operator for overriding EncryptionContext's table name for a specific DynamoDbEncryptor. If any table names or - * the encryption context itself is null, then it returns the original EncryptionContext. - * - * @param originalTableName the name of the table that should be overridden in the Encryption Context - * @param newTableName the table name that should be used in the Encryption Context - * @return A UnaryOperator that produces a new EncryptionContext with the supplied table name - */ - public static UnaryOperator overrideEncryptionContextTableName( - String originalTableName, - String newTableName) { - return encryptionContext -> { - if (encryptionContext == null - || encryptionContext.getTableName() == null - || originalTableName == null - || newTableName == null) { - return encryptionContext; - } - if (originalTableName.equals(encryptionContext.getTableName())) { - return encryptionContext.toBuilder().tableName(newTableName).build(); - } else { - return encryptionContext; - } - }; - } - - /** - * An operator for mapping multiple table names in the Encryption Context to a new table name. If the table name for - * a given EncryptionContext is missing, then it returns the original EncryptionContext. Similarly, it returns the - * original EncryptionContext if the value it is overridden to is null, or if the original table name is null. - * - * @param tableNameOverrideMap a map specifying the names of tables that should be overridden, - * and the values to which they should be overridden. If the given table name - * corresponds to null, or isn't in the map, then the table name won't be overridden. - * @return A UnaryOperator that produces a new EncryptionContext with the supplied table name - */ - public static UnaryOperator overrideEncryptionContextTableNameUsingMap( - Map tableNameOverrideMap) { - return encryptionContext -> { - if (tableNameOverrideMap == null || encryptionContext == null || encryptionContext.getTableName() == null) { - return encryptionContext; - } - String newTableName = tableNameOverrideMap.get(encryptionContext.getTableName()); - if (newTableName != null) { - return encryptionContext.toBuilder().tableName(newTableName).build(); - } else { - return encryptionContext; - } - }; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java deleted file mode 100644 index e9348af05d..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.math.BigDecimal; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import software.amazon.awssdk.core.BytesWrapper; -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.core.util.DefaultSdkAutoConstructList; -import software.amazon.awssdk.core.util.DefaultSdkAutoConstructMap; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - - -/** - * @author Greg Rubin - */ -public class AttributeValueMarshaller { - private static final Charset UTF8 = Charset.forName("UTF-8"); - private static final int TRUE_FLAG = 1; - private static final int FALSE_FLAG = 0; - - private AttributeValueMarshaller() { - // Prevent instantiation - } - - /** - * Marshalls the data using a TLV (Tag-Length-Value) encoding. The tag may be 'b', 'n', 's', - * '?', '\0' to represent a ByteBuffer, Number, String, Boolean, or Null respectively. The tag - * may also be capitalized (for 'b', 'n', and 's',) to represent an array of that type. If an - * array is stored, then a four-byte big-endian integer is written representing the number of - * array elements. If a ByteBuffer is stored, the length of the buffer is stored as a four-byte - * big-endian integer and the buffer then copied directly. Both Numbers and Strings are treated - * identically and are stored as UTF8 encoded Unicode, proceeded by the length of the encoded - * string (in bytes) as a four-byte big-endian integer. Boolean is encoded as a single byte, 0 - * for false and 1 for true (and so has no Length parameter). The - * Null tag ('\0') takes neither a Length nor a Value parameter. - * - * The tags 'L' and 'M' are for the document types List and Map respectively. These are encoded - * recursively with the Length being the size of the collection. In the case of List, the value - * is a Length number of marshalled AttributeValues. If the case of Map, the value is a Length - * number of AttributeValue Pairs where the first must always have a String value. - * - * This implementation does not recognize loops. If an AttributeValue contains itself - * (even indirectly) this code will recurse infinitely. - * - * @param attributeValue an AttributeValue instance - * @return the serialized AttributeValue - * @see java.io.DataInput - */ - public static ByteBuffer marshall(final AttributeValue attributeValue) { - try (ByteArrayOutputStream resultBytes = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(resultBytes);) { - marshall(attributeValue, out); - out.close(); - resultBytes.close(); - return ByteBuffer.wrap(resultBytes.toByteArray()); - } catch (final IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - private static void marshall(final AttributeValue attributeValue, final DataOutputStream out) - throws IOException { - - if (attributeValue.b() != null) { - out.writeChar('b'); - writeBytes(attributeValue.b().asByteBuffer(), out); - } else if (hasAttributeValueSet(attributeValue.bs())) { - out.writeChar('B'); - writeBytesList(attributeValue.bs().stream() - .map(BytesWrapper::asByteBuffer).collect(Collectors.toList()), out); - } else if (attributeValue.n() != null) { - out.writeChar('n'); - writeString(trimZeros(attributeValue.n()), out); - } else if (hasAttributeValueSet(attributeValue.ns())) { - out.writeChar('N'); - - final List ns = new ArrayList<>(attributeValue.ns().size()); - for (final String n : attributeValue.ns()) { - ns.add(trimZeros(n)); - } - writeStringList(ns, out); - } else if (attributeValue.s() != null) { - out.writeChar('s'); - writeString(attributeValue.s(), out); - } else if (hasAttributeValueSet(attributeValue.ss())) { - out.writeChar('S'); - writeStringList(attributeValue.ss(), out); - } else if (attributeValue.bool() != null) { - out.writeChar('?'); - out.writeByte((attributeValue.bool() ? TRUE_FLAG : FALSE_FLAG)); - } else if (Boolean.TRUE.equals(attributeValue.nul())) { - out.writeChar('\0'); - } else if (hasAttributeValueSet(attributeValue.l())) { - final List l = attributeValue.l(); - out.writeChar('L'); - out.writeInt(l.size()); - for (final AttributeValue attr : l) { - if (attr == null) { - throw new NullPointerException( - "Encountered null list entry value while marshalling attribute value " - + attributeValue); - } - marshall(attr, out); - } - } else if (hasAttributeValueMap(attributeValue.m())) { - final Map m = attributeValue.m(); - final List mKeys = new ArrayList<>(m.keySet()); - Collections.sort(mKeys); - out.writeChar('M'); - out.writeInt(m.size()); - for (final String mKey : mKeys) { - marshall(AttributeValue.builder().s(mKey).build(), out); - - final AttributeValue mValue = m.get(mKey); - - if (mValue == null) { - throw new NullPointerException( - "Encountered null map value for key " - + mKey - + " while marshalling attribute value " - + attributeValue); - } - marshall(mValue, out); - } - } else { - throw new IllegalArgumentException("A seemingly empty AttributeValue is indicative of invalid input or potential errors"); - } - } - - /** - * @see #marshall(AttributeValue) - */ - public static AttributeValue unmarshall(final ByteBuffer plainText) { - try (final DataInputStream in = new DataInputStream( - new ByteBufferInputStream(plainText.asReadOnlyBuffer()))) { - return unmarshall(in); - } catch (IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - private static AttributeValue unmarshall(final DataInputStream in) throws IOException { - char type = in.readChar(); - AttributeValue.Builder result = AttributeValue.builder(); - switch (type) { - case '\0': - result.nul(Boolean.TRUE); - break; - case 'b': - result.b(SdkBytes.fromByteBuffer(readBytes(in))); - break; - case 'B': - result.bs(readBytesList(in).stream().map(SdkBytes::fromByteBuffer).collect(Collectors.toList())); - break; - case 'n': - result.n(readString(in)); - break; - case 'N': - result.ns(readStringList(in)); - break; - case 's': - result.s(readString(in)); - break; - case 'S': - result.ss(readStringList(in)); - break; - case '?': - final byte boolValue = in.readByte(); - - if (boolValue == TRUE_FLAG) { - result.bool(Boolean.TRUE); - } else if (boolValue == FALSE_FLAG) { - result.bool(Boolean.FALSE); - } else { - throw new IllegalArgumentException("Improperly formatted data"); - } - break; - case 'L': - final int lCount = in.readInt(); - final List l = new ArrayList<>(lCount); - for (int lIdx = 0; lIdx < lCount; lIdx++) { - l.add(unmarshall(in)); - } - result.l(l); - break; - case 'M': - final int mCount = in.readInt(); - final Map m = new HashMap<>(); - for (int mIdx = 0; mIdx < mCount; mIdx++) { - final AttributeValue key = unmarshall(in); - if (key.s() == null) { - throw new IllegalArgumentException("Improperly formatted data"); - } - AttributeValue value = unmarshall(in); - m.put(key.s(), value); - } - result.m(m); - break; - default: - throw new IllegalArgumentException("Unsupported data encoding"); - } - - return result.build(); - } - - private static String trimZeros(final String n) { - BigDecimal number = new BigDecimal(n); - if (number.compareTo(BigDecimal.ZERO) == 0) { - return "0"; - } - return number.stripTrailingZeros().toPlainString(); - } - - private static void writeStringList(List values, final DataOutputStream out) throws IOException { - final List sorted = new ArrayList<>(values); - Collections.sort(sorted); - out.writeInt(sorted.size()); - for (final String v : sorted) { - writeString(v, out); - } - } - - private static List readStringList(final DataInputStream in) throws IOException, - IllegalArgumentException { - final int nCount = in.readInt(); - List ns = new ArrayList<>(nCount); - for (int nIdx = 0; nIdx < nCount; nIdx++) { - ns.add(readString(in)); - } - return ns; - } - - private static void writeString(String value, final DataOutputStream out) throws IOException { - final byte[] bytes = value.getBytes(UTF8); - out.writeInt(bytes.length); - out.write(bytes); - } - - private static String readString(final DataInputStream in) throws IOException, - IllegalArgumentException { - byte[] bytes; - int length; - length = in.readInt(); - bytes = new byte[length]; - if(in.read(bytes) != length) { - throw new IllegalArgumentException("Improperly formatted data"); - } - return new String(bytes, UTF8); - } - - private static void writeBytesList(List values, final DataOutputStream out) throws IOException { - final List sorted = new ArrayList<>(values); - Collections.sort(sorted); - out.writeInt(sorted.size()); - for (final ByteBuffer v : sorted) { - writeBytes(v, out); - } - } - - private static List readBytesList(final DataInputStream in) throws IOException { - final int bCount = in.readInt(); - List bs = new ArrayList<>(bCount); - for (int bIdx = 0; bIdx < bCount; bIdx++) { - bs.add(readBytes(in)); - } - return bs; - } - - private static void writeBytes(ByteBuffer value, final DataOutputStream out) throws IOException { - value = value.asReadOnlyBuffer(); - value.rewind(); - out.writeInt(value.remaining()); - while (value.hasRemaining()) { - out.writeByte(value.get()); - } - } - - private static ByteBuffer readBytes(final DataInputStream in) throws IOException { - final int length = in.readInt(); - final byte[] buf = new byte[length]; - in.readFully(buf); - return ByteBuffer.wrap(buf); - } - - /** - * Determines if the value of a 'set' type AttributeValue (various S types) has been explicitly set or not. - * @param value the actual value portion of an AttributeValue of the appropriate type - * @return true if the value of this type field has been explicitly set, false if it has not - */ - private static boolean hasAttributeValueSet(Collection value) { - return value != null && value != DefaultSdkAutoConstructList.getInstance(); - } - - /** - * Determines if the value of a 'map' type AttributeValue (M type) has been explicitly set or not. - * @param value the actual value portion of a AttributeValue of the appropriate type - * @return true if the value of this type field has been explicitly set, false if it has not - */ - private static boolean hasAttributeValueMap(Map value) { - return value != null && value != DefaultSdkAutoConstructMap.getInstance(); - } - -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java deleted file mode 100644 index ee94a86a02..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java +++ /dev/null @@ -1,48 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static java.util.Base64.*; - -/** - * A class for decoding Base64 strings and encoding bytes as Base64 strings. - */ -public class Base64 { - private static final Decoder DECODER = getMimeDecoder(); - private static final Encoder ENCODER = getEncoder(); - - private Base64() { } - - /** - * Encode the bytes as a Base64 string. - *

- * See the Basic encoder in {@link java.util.Base64} - */ - public static String encodeToString(byte[] bytes) { - return ENCODER.encodeToString(bytes); - } - - /** - * Decode the Base64 string as bytes, ignoring illegal characters. - *

- * See the Mime Decoder in {@link java.util.Base64} - */ - public static byte[] decode(String str) { - if(str == null) { - return null; - } - return DECODER.decode(str); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java deleted file mode 100644 index ff70306841..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * @author Greg Rubin - */ -public class ByteBufferInputStream extends InputStream { - private final ByteBuffer buffer; - - public ByteBufferInputStream(ByteBuffer buffer) { - this.buffer = buffer; - } - - @Override - public int read() { - if (buffer.hasRemaining()) { - int tmp = buffer.get(); - if (tmp < 0) { - tmp += 256; - } - return tmp; - } else { - return -1; - } - } - - @Override - public int read(byte[] b, int off, int len) { - if (available() < len) { - len = available(); - } - buffer.get(b, off, len); - return len; - } - - @Override - public int available() { - return buffer.remaining(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java deleted file mode 100644 index 15422aaab7..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Provider; -import java.util.Arrays; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.SecretKeySpec; - -/** - * HMAC-based Key Derivation Function. - * - * @see RFC 5869 - */ -public final class Hkdf { - private static final byte[] EMPTY_ARRAY = new byte[0]; - private final String algorithm; - private final Provider provider; - - private SecretKey prk = null; - - /** - * Returns an Hkdf object using the specified algorithm. - * - * @param algorithm - * the standard name of the requested MAC algorithm. See the Mac - * section in the Java Cryptography Architecture Standard Algorithm Name - * Documentation for information about standard algorithm - * names. - * @return the new Hkdf object - * @throws NoSuchAlgorithmException - * if no Provider supports a MacSpi implementation for the - * specified algorithm. - */ - public static Hkdf getInstance(final String algorithm) - throws NoSuchAlgorithmException { - // Constructed specifically to sanity-test arguments. - Mac mac = Mac.getInstance(algorithm); - return new Hkdf(algorithm, mac.getProvider()); - } - - /** - * Returns an Hkdf object using the specified algorithm. - * - * @param algorithm - * the standard name of the requested MAC algorithm. See the Mac - * section in the Java Cryptography Architecture Standard Algorithm Name - * Documentation for information about standard algorithm - * names. - * @param provider - * the name of the provider - * @return the new Hkdf object - * @throws NoSuchAlgorithmException - * if a MacSpi implementation for the specified algorithm is not - * available from the specified provider. - * @throws NoSuchProviderException - * if the specified provider is not registered in the security - * provider list. - */ - public static Hkdf getInstance(final String algorithm, final String provider) - throws NoSuchAlgorithmException, NoSuchProviderException { - // Constructed specifically to sanity-test arguments. - Mac mac = Mac.getInstance(algorithm, provider); - return new Hkdf(algorithm, mac.getProvider()); - } - - /** - * Returns an Hkdf object using the specified algorithm. - * - * @param algorithm - * the standard name of the requested MAC algorithm. See the Mac - * section in the Java Cryptography Architecture Standard Algorithm Name - * Documentation for information about standard algorithm - * names. - * @param provider - * the provider - * @return the new Hkdf object - * @throws NoSuchAlgorithmException - * if a MacSpi implementation for the specified algorithm is not - * available from the specified provider. - */ - public static Hkdf getInstance(final String algorithm, - final Provider provider) throws NoSuchAlgorithmException { - // Constructed specifically to sanity-test arguments. - Mac mac = Mac.getInstance(algorithm, provider); - return new Hkdf(algorithm, mac.getProvider()); - } - - /** - * Initializes this Hkdf with input keying material. A default salt of - * HashLen zeros will be used (where HashLen is the length of the return - * value of the supplied algorithm). - * - * @param ikm - * the Input Keying Material - */ - public void init(final byte[] ikm) { - init(ikm, null); - } - - /** - * Initializes this Hkdf with input keying material and a salt. If - * salt is null or of length 0, then a default salt of - * HashLen zeros will be used (where HashLen is the length of the return - * value of the supplied algorithm). - * - * @param salt - * the salt used for key extraction (optional) - * @param ikm - * the Input Keying Material - */ - public void init(final byte[] ikm, final byte[] salt) { - byte[] realSalt = (salt == null) ? EMPTY_ARRAY : salt.clone(); - byte[] rawKeyMaterial = EMPTY_ARRAY; - try { - Mac extractionMac = Mac.getInstance(algorithm, provider); - if (realSalt.length == 0) { - realSalt = new byte[extractionMac.getMacLength()]; - Arrays.fill(realSalt, (byte) 0); - } - extractionMac.init(new SecretKeySpec(realSalt, algorithm)); - rawKeyMaterial = extractionMac.doFinal(ikm); - SecretKeySpec key = new SecretKeySpec(rawKeyMaterial, algorithm); - Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array - unsafeInitWithoutKeyExtraction(key); - } catch (GeneralSecurityException e) { - // We've already checked all of the parameters so no exceptions - // should be possible here. - throw new RuntimeException("Unexpected exception", e); - } finally { - Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array - } - } - - /** - * Initializes this Hkdf to use the provided key directly for creation of - * new keys. If rawKey is not securely generated and uniformly - * distributed over the total key-space, then this will result in an - * insecure key derivation function (KDF). DO NOT USE THIS UNLESS YOU - * ARE ABSOLUTELY POSITIVE THIS IS THE CORRECT THING TO DO. - * - * @param rawKey - * the pseudorandom key directly used to derive keys - * @throws InvalidKeyException - * if the algorithm for rawKey does not match the - * algorithm this Hkdf was created with - */ - public void unsafeInitWithoutKeyExtraction(final SecretKey rawKey) - throws InvalidKeyException { - if (!rawKey.getAlgorithm().equals(algorithm)) { - throw new InvalidKeyException( - "Algorithm for the provided key must match the algorithm for this Hkdf. Expected " + - algorithm + " but found " + rawKey.getAlgorithm()); - } - - this.prk = rawKey; - } - - private Hkdf(final String algorithm, final Provider provider) { - if (!algorithm.startsWith("Hmac")) { - throw new IllegalArgumentException("Invalid algorithm " + algorithm - + ". Hkdf may only be used with Hmac algorithms."); - } - this.algorithm = algorithm; - this.provider = provider; - } - - /** - * Returns a pseudorandom key of length bytes. - * - * @param info - * optional context and application specific information (can be - * a zero-length string). This will be treated as UTF-8. - * @param length - * the length of the output key in bytes - * @return a pseudorandom key of length bytes. - * @throws IllegalStateException - * if this object has not been initialized - */ - public byte[] deriveKey(final String info, final int length) throws IllegalStateException { - return deriveKey((info != null ? info.getBytes(StandardCharsets.UTF_8) : null), length); - } - - /** - * Returns a pseudorandom key of length bytes. - * - * @param info - * optional context and application specific information (can be - * a zero-length array). - * @param length - * the length of the output key in bytes - * @return a pseudorandom key of length bytes. - * @throws IllegalStateException - * if this object has not been initialized - */ - public byte[] deriveKey(final byte[] info, final int length) throws IllegalStateException { - byte[] result = new byte[length]; - try { - deriveKey(info, length, result, 0); - } catch (ShortBufferException ex) { - // This exception is impossible as we ensure the buffer is long - // enough - throw new RuntimeException(ex); - } - return result; - } - - /** - * Derives a pseudorandom key of length bytes and stores the - * result in output. - * - * @param info - * optional context and application specific information (can be - * a zero-length array). - * @param length - * the length of the output key in bytes - * @param output - * the buffer where the pseudorandom key will be stored - * @param offset - * the offset in output where the key will be stored - * @throws ShortBufferException - * if the given output buffer is too small to hold the result - * @throws IllegalStateException - * if this object has not been initialized - */ - public void deriveKey(final byte[] info, final int length, - final byte[] output, final int offset) throws ShortBufferException, - IllegalStateException { - assertInitialized(); - if (length < 0) { - throw new IllegalArgumentException("Length must be a non-negative value."); - } - if (output.length < offset + length) { - throw new ShortBufferException(); - } - Mac mac = createMac(); - - if (length > 255 * mac.getMacLength()) { - throw new IllegalArgumentException( - "Requested keys may not be longer than 255 times the underlying HMAC length."); - } - - byte[] t = EMPTY_ARRAY; - try { - int loc = 0; - byte i = 1; - while (loc < length) { - mac.update(t); - mac.update(info); - mac.update(i); - t = mac.doFinal(); - - for (int x = 0; x < t.length && loc < length; x++, loc++) { - output[loc] = t[x]; - } - - i++; - } - } finally { - Arrays.fill(t, (byte) 0); // Zeroize temporary array - } - } - - private Mac createMac() { - try { - Mac mac = Mac.getInstance(algorithm, provider); - mac.init(prk); - return mac; - } catch (NoSuchAlgorithmException ex) { - // We've already validated that this algorithm is correct. - throw new RuntimeException(ex); - } catch (InvalidKeyException ex) { - // We've already validated that this key is correct. - throw new RuntimeException(ex); - } - } - - /** - * Throws an IllegalStateException if this object has not been - * initialized. - * - * @throws IllegalStateException - * if this object has not been initialized - */ - private void assertInitialized() throws IllegalStateException { - if (prk == null) { - throw new IllegalStateException("Hkdf has not been initialized"); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java deleted file mode 100644 index e191a84215..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; - -import software.amazon.awssdk.annotations.ThreadSafe; - -/** - * A bounded cache that has a LRU eviction policy when the cache is full. - * - * @param - * value type - */ -@ThreadSafe -public final class LRUCache { - /** - * Used for the internal cache. - */ - private final Map map; - - /** - * Maximum size of the cache. - */ - private final int maxSize; - - /** - * @param maxSize - * the maximum number of entries of the cache - */ - public LRUCache(final int maxSize) { - if (maxSize < 1) { - throw new IllegalArgumentException("maxSize " + maxSize + " must be at least 1"); - } - this.maxSize = maxSize; - map = Collections.synchronizedMap(new LRUHashMap(maxSize)); - } - - /** - * Adds an entry to the cache, evicting the earliest entry if necessary. - */ - public T add(final String key, final T value) { - return map.put(key, value); - } - - /** Returns the value of the given key; or null of no such entry exists. */ - public T get(final String key) { - return map.get(key); - } - - /** - * Returns the current size of the cache. - */ - public int size() { - return map.size(); - } - - /** - * Returns the maximum size of the cache. - */ - public int getMaxSize() { - return maxSize; - } - - public void clear() { - map.clear(); - } - - public T remove(String key) { - return map.remove(key); - } - - @Override - public String toString() { - return map.toString(); - } - - @SuppressWarnings("serial") - private static class LRUHashMap extends LinkedHashMap { - private final int maxSize; - - private LRUHashMap(final int maxSize) { - super(10, 0.75F, true); - this.maxSize = maxSize; - } - - @Override - protected boolean removeEldestEntry(final Entry eldest) { - return size() > maxSize; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java deleted file mode 100644 index 3d776c0dc8..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java +++ /dev/null @@ -1,19 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -interface MsClock { - MsClock WALLCLOCK = System::nanoTime; - - public long timestampNano(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java deleted file mode 100644 index f529047c8a..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import io.netty.util.internal.ObjectUtil; -import software.amazon.awssdk.annotations.ThreadSafe; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; - -/** - * A cache, backed by an LRUCache, that uses a loader to calculate values on cache miss or expired - * TTL. - * - *

Note that this cache does not proactively evict expired entries, however will immediately - * evict entries discovered to be expired on load. - * - * @param value type - */ -@ThreadSafe -public final class TTLCache { - /** Used for the internal cache. */ - private final LRUCache> cache; - - /** Time to live for entries in the cache. */ - private final long ttlInNanos; - - /** Used for loading new values into the cache on cache miss or expiration. */ - private final EntryLoader defaultLoader; - - // Mockable time source, to allow us to test TTL behavior. - // package access for tests - MsClock clock = MsClock.WALLCLOCK; - - private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); - - /** - * @param maxSize the maximum number of entries of the cache - * @param ttlInMillis the time to live value for entries of the cache, in milliseconds - */ - public TTLCache(final int maxSize, final long ttlInMillis, final EntryLoader loader) { - if (maxSize < 1) { - throw new IllegalArgumentException("maxSize " + maxSize + " must be at least 1"); - } - if (ttlInMillis < 1) { - throw new IllegalArgumentException("ttlInMillis " + maxSize + " must be at least 1"); - } - this.ttlInNanos = TimeUnit.MILLISECONDS.toNanos(ttlInMillis); - this.cache = new LRUCache<>(maxSize); - this.defaultLoader = ObjectUtil.checkNotNull(loader, "loader must not be null"); - } - - /** - * Uses the default loader to calculate the value at key and insert it into the cache, if it - * doesn't already exist or is expired according to the TTL. - * - *

This immediately evicts entries past the TTL such that a load failure results in the removal - * of the entry. - * - *

Entries that are not expired according to the TTL are returned without recalculating the - * value. - * - *

Within a grace period past the TTL, the cache may either return the cached value without - * recalculating or use the loader to recalculate the value. This is implemented such that, in a - * multi-threaded environment, only one thread per cache key uses the loader to recalculate the - * value at one time. - * - * @param key The cache key to load the value at - * @return The value of the given value (already existing or re-calculated). - */ - public T load(final String key) { - return load(key, defaultLoader::load); - } - - /** - * Uses the inputted function to calculate the value at key and insert it into the cache, if it - * doesn't already exist or is expired according to the TTL. - * - *

This immediately evicts entries past the TTL such that a load failure results in the removal - * of the entry. - * - *

Entries that are not expired according to the TTL are returned without recalculating the - * value. - * - *

Within a grace period past the TTL, the cache may either return the cached value without - * recalculating or use the loader to recalculate the value. This is implemented such that, in a - * multi-threaded environment, only one thread per cache key uses the loader to recalculate the - * value at one time. - * - *

Returns the value of the given key (already existing or re-calculated). - * - * @param key The cache key to load the value at - * @param f The function to use to load the value, given key as input - * @return The value of the given value (already existing or re-calculated). - */ - public T load(final String key, Function f) { - final LockedState ls = cache.get(key); - - if (ls == null) { - // The entry doesn't exist yet, so load a new one. - return loadNewEntryIfAbsent(key, f); - } else if (clock.timestampNano() - ls.getState().lastUpdatedNano - > ttlInNanos + TTL_GRACE_IN_NANO) { - // The data has expired past the grace period. - // Evict the old entry and load a new entry. - cache.remove(key); - return loadNewEntryIfAbsent(key, f); - } else if (clock.timestampNano() - ls.getState().lastUpdatedNano <= ttlInNanos) { - // The data hasn't expired. Return as-is from the cache. - return ls.getState().data; - } else if (!ls.tryLock()) { - // We are in the TTL grace period. If we couldn't grab the lock, then some other - // thread is currently loading the new value. Because we are in the grace period, - // use the cached data instead of waiting for the lock. - return ls.getState().data; - } - - // We are in the grace period and have acquired a lock. - // Update the cache with the value determined by the loading function. - try { - T loadedData = f.apply(key); - ls.update(loadedData, clock.timestampNano()); - return ls.getState().data; - } finally { - ls.unlock(); - } - } - - // Synchronously calculate the value for a new entry in the cache if it doesn't already exist. - // Otherwise return the cached value. - // It is important that this is the only place where we use the loader for a new entry, - // given that we don't have the entry yet to lock on. - // This ensures that the loading function is only called once if multiple threads - // attempt to add a new entry for the same key at the same time. - private synchronized T loadNewEntryIfAbsent(final String key, Function f) { - // If the entry already exists in the cache, return it - final LockedState cachedState = cache.get(key); - if (cachedState != null) { - return cachedState.getState().data; - } - - // Otherwise, load the data and create a new entry - T loadedData = f.apply(key); - LockedState ls = new LockedState<>(loadedData, clock.timestampNano()); - cache.add(key, ls); - return loadedData; - } - - - /** - * Put a new entry in the cache. Returns the value previously at that key in the cache, or null if - * the entry previously didn't exist or is expired. - */ - public synchronized T put(final String key, final T value) { - LockedState ls = new LockedState<>(value, clock.timestampNano()); - LockedState oldLockedState = cache.add(key, ls); - if (oldLockedState == null - || clock.timestampNano() - oldLockedState.getState().lastUpdatedNano - > ttlInNanos + TTL_GRACE_IN_NANO) { - return null; - } - return oldLockedState.getState().data; - } - - /** - * Get when the entry at this key was last updated. Returns 0 if the entry doesn't exist at key. - */ - public long getLastUpdated(String key) { - LockedState ls = cache.get(key); - if (ls == null) { - return 0; - } - return ls.getState().lastUpdatedNano; - } - - /** Returns the current size of the cache. */ - public int size() { - return cache.size(); - } - - /** Returns the maximum size of the cache. */ - public int getMaxSize() { - return cache.getMaxSize(); - } - - /** Clears all entries from the cache. */ - public void clear() { - cache.clear(); - } - - @Override - public String toString() { - return cache.toString(); - } - - public interface EntryLoader { - T load(String entryKey); - } - - // An object which stores a state alongside a lock, - // and performs updates to that state atomically. - // The state may only be updated if the lock is acquired by the current thread. - private static class LockedState { - private final ReentrantLock lock = new ReentrantLock(true); - private final AtomicReference> state; - - public LockedState(T data, long createTimeNano) { - state = new AtomicReference<>(new State<>(data, createTimeNano)); - } - - public State getState() { - return state.get(); - } - - public void unlock() { - lock.unlock(); - } - - public boolean tryLock() { - return lock.tryLock(); - } - - public void update(T data, long createTimeNano) { - if (!lock.isHeldByCurrentThread()) { - throw new IllegalStateException("Lock not held by current thread"); - } - state.set(new State<>(data, createTimeNano)); - } - } - - // An object that holds some data and the time at which this object was created - private static class State { - public final T data; - public final long lastUpdatedNano; - - public State(T data, long lastUpdatedNano) { - this.data = data; - this.lastUpdatedNano = lastUpdatedNano; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java deleted file mode 100644 index 6d092cc06b..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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. - */ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.security.SecureRandom; - -public class Utils { - private static final ThreadLocal RND = ThreadLocal.withInitial(() -> { - final SecureRandom result = new SecureRandom(); - result.nextBoolean(); // Force seeding - return result; - }); - - private Utils() { - // Prevent instantiation - } - - public static SecureRandom getRng() { - return RND.get(); - } - - public static byte[] getRandom(int len) { - final byte[] result = new byte[len]; - getRng().nextBytes(result); - return result; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java deleted file mode 100644 index b9906bade0..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java +++ /dev/null @@ -1,932 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertTrue; - -import com.amazonaws.util.Base64; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.*; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.*; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.AsymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.CachingMostRecentProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.*; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.KeyData; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.Keys; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.Scenario; - -public class HolisticIT { - - private static final SecretKey aesKey = - new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "AES"); - private static final SecretKey hmacKey = - new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); - private static final String rsaEncPub = - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtiNSLSvT9cExXOcD0dGZ" - + "9DFEMHw8895gAZcCdSppDrxbD7XgZiQYTlgt058i5fS+l11guAUJtKt5sZ2u8Fx0" - + "K9pxMdlczGtvQJdx/LQETEnLnfzAijvHisJ8h6dQOVczM7t01KIkS24QZElyO+kY" - + "qMWLytUV4RSHnrnIuUtPHCe6LieDWT2+1UBguxgtFt1xdXlquACLVv/Em3wp40Xc" - + "bIwzhqLitb98rTY/wqSiGTz1uvvBX46n+f2j3geZKCEDGkWcXYw3dH4lRtDWTbqw" - + "eRcaNDT/MJswQlBk/Up9KCyN7gjX67gttiCO6jMoTNDejGeJhG4Dd2o0vmn8WJlr" - + "5wIDAQAB"; - private static final String rsaEncPriv = - "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2I1ItK9P1wTFc" - + "5wPR0Zn0MUQwfDzz3mABlwJ1KmkOvFsPteBmJBhOWC3TnyLl9L6XXWC4BQm0q3mx" - + "na7wXHQr2nEx2VzMa29Al3H8tARMScud/MCKO8eKwnyHp1A5VzMzu3TUoiRLbhBk" - + "SXI76RioxYvK1RXhFIeeuci5S08cJ7ouJ4NZPb7VQGC7GC0W3XF1eWq4AItW/8Sb" - + "fCnjRdxsjDOGouK1v3ytNj/CpKIZPPW6+8Ffjqf5/aPeB5koIQMaRZxdjDd0fiVG" - + "0NZNurB5Fxo0NP8wmzBCUGT9Sn0oLI3uCNfruC22II7qMyhM0N6MZ4mEbgN3ajS+" - + "afxYmWvnAgMBAAECggEBAIIU293zDWDZZ73oJ+w0fHXQsdjHAmlRitPX3CN99KZX" - + "k9m2ldudL9bUV3Zqk2wUzgIg6LDEuFfWmAVojsaP4VBopKtriEFfAYfqIbjPgLpT" - + "gh8FoyWW6D6MBJCFyGALjUAHQ7uRScathvt5ESMEqV3wKJTmdsfX97w/B8J+rLN3" - + "3fT3ZJUck5duZ8XKD+UtX1Y3UE1hTWo3Ae2MFND964XyUqy+HaYXjH0x6dhZzqyJ" - + "/OJ/MPGeMJgxp+nUbMWerwxrLQceNFVgnQgHj8e8k4fd04rkowkkPua912gNtmz7" - + "DuIEvcMnY64z585cn+cnXUPJwtu3JbAmn/AyLsV9FLECgYEA798Ut/r+vORB16JD" - + "KFu38pQCgIbdCPkXeI0DC6u1cW8JFhgRqi+AqSrEy5SzY3IY7NVMSRsBI9Y026Bl" - + "R9OQwTrOzLRAw26NPSDvbTkeYXlY9+hX7IovHjGkho/OxyTJ7bKRDYLoNCz56BC1" - + "khIWvECpcf/fZU0nqOFVFqF3H/UCgYEAwmJ4rjl5fksTNtNRL6ivkqkHIPKXzk5w" - + "C+L90HKNicic9bqyX8K4JRkGKSNYN3mkjrguAzUlEld390qNBw5Lu7PwATv0e2i+" - + "6hdwJsjTKNpj7Nh4Mieq6d7lWe1L8FLyHEhxgIeQ4BgqrVtPPOH8IBGpuzVZdWwI" - + "dgOvEvAi/usCgYBdfk3NB/+SEEW5jn0uldE0s4vmHKq6fJwxWIT/X4XxGJ4qBmec" - + "NbeoOAtMbkEdWbNtXBXHyMbA+RTRJctUG5ooNou0Le2wPr6+PMAVilXVGD8dIWpj" - + "v9htpFvENvkZlbU++IKhCY0ICR++3ARpUrOZ3Hou/NRN36y9nlZT48tSoQKBgES2" - + "Bi6fxmBsLUiN/f64xAc1lH2DA0I728N343xRYdK4hTMfYXoUHH+QjurvwXkqmI6S" - + "cEFWAdqv7IoPYjaCSSb6ffYRuWP+LK4WxuAO0QV53SSViDdCalntHmlhRhyXVVnG" - + "CckDIqT0JfHNev7savDzDWpNe2fUXlFJEBPDqrstAoGBAOpd5+QBHF/tP5oPILH4" - + "aD/zmqMH7VtB+b/fOPwtIM+B/WnU7hHLO5t2lJYu18Be3amPkfoQIB7bpkM3Cer2" - + "G7Jw+TcHrY+EtIziDB5vwau1fl4VcbA9SfWpBojJ5Ifo9ELVxGiK95WxeQNSmLUy" - + "7AJzhK1Gwey8a/v+xfqiu9sE"; - private static final PrivateKey rsaPriv; - private static final PublicKey rsaPub; - private static final KeyPair rsaPair; - private static final EncryptionMaterialsProvider symProv; - private static final EncryptionMaterialsProvider asymProv; - private static final EncryptionMaterialsProvider symWrappedProv; - private static final String HASH_KEY = "hashKey"; - private static final String RANGE_KEY = "rangeKey"; - private static final String RSA = "RSA"; - private static final String tableName = "TableName"; - final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); - final EnumSet encryptAndSign = - EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); - - private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); - private DynamoDbClient client; - private static KmsClient kmsClient = KmsClient.builder().build(); - - private static Map keyDataMap = new HashMap<>(); - - private static final Map ENCRYPTED_TEST_VALUE = new HashMap<>(); - private static final Map MIXED_TEST_VALUE = new HashMap<>(); - private static final Map SIGNED_TEST_VALUE = new HashMap<>(); - private static final Map UNTOUCHED_TEST_VALUE = new HashMap<>(); - - private static final Map ENCRYPTED_TEST_VALUE_2 = new HashMap<>(); - private static final Map MIXED_TEST_VALUE_2 = new HashMap<>(); - private static final Map SIGNED_TEST_VALUE_2 = new HashMap<>(); - private static final Map UNTOUCHED_TEST_VALUE_2 = new HashMap<>(); - - private static final String TEST_VECTOR_MANIFEST_DIR = "/vectors/encrypted_item/"; - private static final String SCENARIO_MANIFEST_PATH = TEST_VECTOR_MANIFEST_DIR + "scenarios.json"; - private static final String JAVA_DIR = "java"; - - static { - try { - KeyFactory rsaFact = KeyFactory.getInstance("RSA"); - rsaPub = rsaFact.generatePublic(new X509EncodedKeySpec(Base64.decode(rsaEncPub))); - rsaPriv = rsaFact.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(rsaEncPriv))); - rsaPair = new KeyPair(rsaPub, rsaPriv); - } catch (GeneralSecurityException ex) { - throw new RuntimeException(ex); - } - symProv = new SymmetricStaticProvider(aesKey, hmacKey); - asymProv = new AsymmetricStaticProvider(rsaPair, rsaPair); - symWrappedProv = new WrappedMaterialsProvider(aesKey, aesKey, hmacKey); - - ENCRYPTED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("5").build()); - ENCRYPTED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("7").build()); - ENCRYPTED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); - ENCRYPTED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); - ENCRYPTED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - ENCRYPTED_TEST_VALUE.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - ENCRYPTED_TEST_VALUE.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - ENCRYPTED_TEST_VALUE.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - - MIXED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("6").build()); - MIXED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("8").build()); - MIXED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); - MIXED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); - MIXED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - MIXED_TEST_VALUE.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - MIXED_TEST_VALUE.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - MIXED_TEST_VALUE.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - - SIGNED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("8").build()); - SIGNED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("10").build()); - SIGNED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); - SIGNED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); - SIGNED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - SIGNED_TEST_VALUE.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - SIGNED_TEST_VALUE.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - SIGNED_TEST_VALUE.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - - UNTOUCHED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("7").build()); - UNTOUCHED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("9").build()); - UNTOUCHED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); - UNTOUCHED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); - UNTOUCHED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - UNTOUCHED_TEST_VALUE.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - UNTOUCHED_TEST_VALUE.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - UNTOUCHED_TEST_VALUE.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - - // STORING DOUBLES - ENCRYPTED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("5").build()); - ENCRYPTED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("7").build()); - ENCRYPTED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); - ENCRYPTED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); - ENCRYPTED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - ENCRYPTED_TEST_VALUE_2.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - ENCRYPTED_TEST_VALUE_2.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - ENCRYPTED_TEST_VALUE_2.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - ENCRYPTED_TEST_VALUE_2.put( - "doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); - ENCRYPTED_TEST_VALUE_2.put( - "doubleSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) - .build()); - - MIXED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("6").build()); - MIXED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("8").build()); - MIXED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); - MIXED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); - MIXED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - MIXED_TEST_VALUE_2.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - MIXED_TEST_VALUE_2.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - MIXED_TEST_VALUE_2.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - MIXED_TEST_VALUE_2.put("doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); - MIXED_TEST_VALUE_2.put( - "doubleSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) - .build()); - - SIGNED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("8").build()); - SIGNED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("10").build()); - SIGNED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); - SIGNED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); - SIGNED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - SIGNED_TEST_VALUE_2.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - SIGNED_TEST_VALUE_2.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - SIGNED_TEST_VALUE_2.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - SIGNED_TEST_VALUE_2.put("doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); - SIGNED_TEST_VALUE_2.put( - "doubleSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) - .build()); - - UNTOUCHED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("7").build()); - UNTOUCHED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("9").build()); - UNTOUCHED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); - UNTOUCHED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); - UNTOUCHED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - UNTOUCHED_TEST_VALUE_2.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - UNTOUCHED_TEST_VALUE_2.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - UNTOUCHED_TEST_VALUE_2.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - UNTOUCHED_TEST_VALUE_2.put( - "doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); - UNTOUCHED_TEST_VALUE_2.put( - "doubleSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) - .build()); - } - - @DataProvider(name = "getEncryptTestVectors") - public static Object[][] getEncryptTestVectors() throws IOException { - ScenarioManifest scenarioManifest = - getManifestFromFile(SCENARIO_MANIFEST_PATH, new TypeReference() {}); - loadKeyData(scenarioManifest.keyDataPath); - - // Only use Java generated test vectors to dedupe the scenarios for encrypt, - // we only care that we are able to generate data using the different provider configurations - return scenarioManifest.scenarios.stream() - .filter(s -> s.ciphertextPath.contains(JAVA_DIR)) - .map(s -> new Object[] {s}) - .toArray(Object[][]::new); - } - - @DataProvider(name = "getDecryptTestVectors") - public static Object[][] getDecryptTestVectors() throws IOException { - ScenarioManifest scenarioManifest = - getManifestFromFile(SCENARIO_MANIFEST_PATH, new TypeReference() {}); - loadKeyData(scenarioManifest.keyDataPath); - - return scenarioManifest.scenarios.stream().map(s -> new Object[] {s}).toArray(Object[][]::new); - } - - @Test(dataProvider = "getDecryptTestVectors") - public void decryptTestVector(Scenario scenario) throws IOException, GeneralSecurityException { - localDynamoDb.start(); - client = localDynamoDb.createLimitedWrappedClient(); - - // load data into ciphertext tables - createCiphertextTables(client); - - // load data from vector file - putDataFromFile(client, scenario.ciphertextPath); - - // create and load metastore table if necessary - ProviderStore metastore = null; - if (scenario.metastore != null) { - MetaStore.createTable( - client, - scenario.metastore.tableName, - ProvisionedThroughput.builder().readCapacityUnits(100L).writeCapacityUnits(100L).build()); - putDataFromFile(client, scenario.metastore.path); - EncryptionMaterialsProvider metaProvider = - createProvider( - scenario.metastore.providerName, - scenario.materialName, - scenario.metastore.keys, - null); - metastore = - new MetaStore( - client, scenario.metastore.tableName, DynamoDbEncryptor.getInstance(metaProvider)); - } - - // Create the mapper with the provider under test - EncryptionMaterialsProvider provider = - createProvider(scenario.providerName, scenario.materialName, scenario.keys, metastore); - - // Verify successful decryption - switch (scenario.version) { - case "v0": - assertVersionCompatibility(provider, tableName); - break; - case "v1": - assertVersionCompatibility_2(provider, tableName); - break; - default: - throw new IllegalStateException( - "Version " + scenario.version + " not yet implemented in test vector runner"); - } - client.close(); - localDynamoDb.stop(); - } - - @Test(dataProvider = "getEncryptTestVectors") - public void encryptWithTestVector(Scenario scenario) throws IOException { - localDynamoDb.start(); - client = localDynamoDb.createLimitedWrappedClient(); - - // load data into ciphertext tables - createCiphertextTables(client); - - // create and load metastore table if necessary - ProviderStore metastore = null; - if (scenario.metastore != null) { - MetaStore.createTable( - client, - scenario.metastore.tableName, - ProvisionedThroughput.builder().readCapacityUnits(100L).writeCapacityUnits(100L).build()); - putDataFromFile(client, scenario.metastore.path); - EncryptionMaterialsProvider metaProvider = - createProvider( - scenario.metastore.providerName, - scenario.materialName, - scenario.metastore.keys, - null); - metastore = - new MetaStore( - client, scenario.metastore.tableName, DynamoDbEncryptor.getInstance(metaProvider)); - } - - // Encrypt data with the provider under test, only ensure that no exception is thrown - EncryptionMaterialsProvider provider = - createProvider(scenario.providerName, scenario.materialName, scenario.keys, metastore); - generateStandardData(provider); - client.close(); - localDynamoDb.stop(); - } - - private EncryptionMaterialsProvider createProvider( - String providerName, String materialName, Keys keys, ProviderStore metastore) { - switch (providerName) { - case ScenarioManifest.MOST_RECENT_PROVIDER_NAME: - return new CachingMostRecentProvider(metastore, materialName, 1000); - case ScenarioManifest.STATIC_PROVIDER_NAME: - KeyData decryptKeyData = keyDataMap.get(keys.decryptName); - KeyData verifyKeyData = keyDataMap.get(keys.verifyName); - SecretKey decryptKey = - new SecretKeySpec(Base64.decode(decryptKeyData.material), decryptKeyData.algorithm); - SecretKey verifyKey = - new SecretKeySpec(Base64.decode(verifyKeyData.material), verifyKeyData.algorithm); - return new SymmetricStaticProvider(decryptKey, verifyKey); - case ScenarioManifest.WRAPPED_PROVIDER_NAME: - decryptKeyData = keyDataMap.get(keys.decryptName); - verifyKeyData = keyDataMap.get(keys.verifyName); - - // This can be either the asymmetric provider, where we should test using it's explicit - // constructor, - // or a wrapped symmetric where we use the wrapped materials constructor. - if (decryptKeyData.keyType.equals(ScenarioManifest.SYMMETRIC_KEY_TYPE)) { - decryptKey = - new SecretKeySpec(Base64.decode(decryptKeyData.material), decryptKeyData.algorithm); - verifyKey = - new SecretKeySpec(Base64.decode(verifyKeyData.material), verifyKeyData.algorithm); - return new WrappedMaterialsProvider(decryptKey, decryptKey, verifyKey); - } else { - KeyData encryptKeyData = keyDataMap.get(keys.encryptName); - KeyData signKeyData = keyDataMap.get(keys.signName); - try { - // Hardcoded to use RSA for asymmetric keys. If we include vectors with a different - // asymmetric scheme this will need to be updated. - KeyFactory rsaFact = KeyFactory.getInstance(RSA); - - PublicKey encryptMaterial = - rsaFact.generatePublic( - new X509EncodedKeySpec(Base64.decode(encryptKeyData.material))); - PrivateKey decryptMaterial = - rsaFact.generatePrivate( - new PKCS8EncodedKeySpec(Base64.decode(decryptKeyData.material))); - KeyPair decryptPair = new KeyPair(encryptMaterial, decryptMaterial); - - PublicKey verifyMaterial = - rsaFact.generatePublic( - new X509EncodedKeySpec(Base64.decode(verifyKeyData.material))); - PrivateKey signingMaterial = - rsaFact.generatePrivate( - new PKCS8EncodedKeySpec(Base64.decode(signKeyData.material))); - KeyPair sigPair = new KeyPair(verifyMaterial, signingMaterial); - - return new AsymmetricStaticProvider(decryptPair, sigPair); - } catch (GeneralSecurityException ex) { - throw new RuntimeException(ex); - } - } - case ScenarioManifest.AWS_KMS_PROVIDER_NAME: - return new DirectKmsMaterialsProvider(kmsClient, keyDataMap.get(keys.decryptName).keyId); - default: - throw new IllegalStateException( - "Provider " + providerName + " not yet implemented in test vector runner"); - } - } - - // Create empty tables for the ciphertext. - // The underlying structure to these tables is hardcoded, - // and we run all test vectors assuming the ciphertext matches the key schema for these tables. - private void createCiphertextTables(DynamoDbClient localDynamoDb) { - // TableName Setup - ArrayList attrDef = new ArrayList<>(); - attrDef.add( - AttributeDefinition.builder() - .attributeName(HASH_KEY) - .attributeType(ScalarAttributeType.N) - .build()); - - attrDef.add( - AttributeDefinition.builder() - .attributeName(RANGE_KEY) - .attributeType(ScalarAttributeType.N) - .build()); - ArrayList keySchema = new ArrayList<>(); - keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); - keySchema.add( - KeySchemaElement.builder().attributeName(RANGE_KEY).keyType(KeyType.RANGE).build()); - - localDynamoDb.createTable( - CreateTableRequest.builder() - .tableName("TableName") - .attributeDefinitions(attrDef) - .keySchema(keySchema) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(100L) - .writeCapacityUnits(100L) - .build()) - .build()); - - // HashKeyOnly SetUp - attrDef = new ArrayList<>(); - attrDef.add( - AttributeDefinition.builder() - .attributeName(HASH_KEY) - .attributeType(ScalarAttributeType.S) - .build()); - - keySchema = new ArrayList<>(); - keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); - - localDynamoDb.createTable( - CreateTableRequest.builder() - .tableName("HashKeyOnly") - .attributeDefinitions(attrDef) - .keySchema(keySchema) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(100L) - .writeCapacityUnits(100L) - .build()) - .build()); - - // DeterministicTable SetUp - attrDef = new ArrayList<>(); - attrDef.add( - AttributeDefinition.builder() - .attributeName(HASH_KEY) - .attributeType(ScalarAttributeType.B) - .build()); - attrDef.add( - AttributeDefinition.builder() - .attributeName(RANGE_KEY) - .attributeType(ScalarAttributeType.N) - .build()); - - keySchema = new ArrayList<>(); - keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); - keySchema.add( - KeySchemaElement.builder().attributeName(RANGE_KEY).keyType(KeyType.RANGE).build()); - - localDynamoDb.createTable( - CreateTableRequest.builder() - .tableName("DeterministicTable") - .attributeDefinitions(attrDef) - .keySchema(keySchema) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(100L) - .writeCapacityUnits(100L) - .build()) - .build()); - } - - // Given a file in the test vector ciphertext format, put those entries into their tables. - // This assumes the expected tables have already been created. - private void putDataFromFile(DynamoDbClient localDynamoDb, String filename) throws IOException { - Map>> manifest = - getCiphertextManifestFromFile(filename); - for (String tableName : manifest.keySet()) { - for (Map attributes : manifest.get(tableName)) { - localDynamoDb.putItem( - PutItemRequest.builder().tableName(tableName).item(attributes).build()); - } - } - } - - private Map>> getCiphertextManifestFromFile( - String filename) throws IOException { - return getManifestFromFile( - TEST_VECTOR_MANIFEST_DIR + stripFilePath(filename), - new TypeReference>>>() {}); - } - - private static T getManifestFromFile(String filename, TypeReference typeRef) - throws IOException { - final URL url = HolisticIT.class.getResource(filename); - if (url == null) { - throw new IllegalStateException("Missing file " + filename + " in src/test/resources."); - } - final File manifestFile = new File(url.getPath()); - final ObjectMapper manifestMapper = new ObjectMapper(); - return (T) manifestMapper.readValue(manifestFile, typeRef); - } - - private static void loadKeyData(String filename) throws IOException { - keyDataMap = - getManifestFromFile( - TEST_VECTOR_MANIFEST_DIR + stripFilePath(filename), - new TypeReference>() {}); - } - - public void generateStandardData(EncryptionMaterialsProvider prov) { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - Map encryptedRecord; - Map> actions; - EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - Map hashKey1 = new HashMap<>(); - Map hashKey2 = new HashMap<>(); - Map hashKey3 = new HashMap<>(); - - hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); - hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); - hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); - - // encrypted record - actions = new HashMap<>(); - for (final String attr : ENCRYPTED_TEST_VALUE_2.keySet()) { - switch (attr) { - case "hashKey": - case "rangeKey": - case "version": - actions.put(attr, signOnly); - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - encryptedRecord = encryptor.encryptRecord(ENCRYPTED_TEST_VALUE_2, actions, encryptionContext); - putItems(encryptedRecord, tableName); - - // mixed test record - actions = new HashMap<>(); - for (final String attr : MIXED_TEST_VALUE_2.keySet()) { - switch (attr) { - case "rangeKey": - case "hashKey": - case "version": - case "stringValue": - case "doubleValue": - case "doubleSet": - actions.put(attr, signOnly); - break; - case "intValue": - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - encryptedRecord = encryptor.encryptRecord(MIXED_TEST_VALUE_2, actions, encryptionContext); - putItems(encryptedRecord, tableName); - - // sign only record - actions = new HashMap<>(); - for (final String attr : SIGNED_TEST_VALUE_2.keySet()) { - actions.put(attr, signOnly); - } - encryptedRecord = encryptor.encryptRecord(SIGNED_TEST_VALUE_2, actions, encryptionContext); - putItems(encryptedRecord, tableName); - - // untouched record - putItems(UNTOUCHED_TEST_VALUE_2, tableName); - } - - private void putItems(Map map, String tableName) { - PutItemRequest request = PutItemRequest.builder().item(map).tableName(tableName).build(); - client.putItem(request); - } - - private Map getItems(Map map, String tableName) { - GetItemRequest request = GetItemRequest.builder().key(map).tableName(tableName).build(); - return client.getItem(request).item(); - } - - private void assertVersionCompatibility(EncryptionMaterialsProvider provider, String tableName) - throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); - Map response; - Map decryptedRecord; - EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - - // Set up maps for table items - HashMap untouched = new HashMap<>(); - HashMap signed = new HashMap<>(); - HashMap mixed = new HashMap<>(); - HashMap encrypted = new HashMap<>(); - HashMap hashKey1 = new HashMap<>(); - HashMap hashKey2 = new HashMap<>(); - HashMap hashKey3 = new HashMap<>(); - untouched.put("hashKey", UNTOUCHED_TEST_VALUE.get("hashKey")); - untouched.put("rangeKey", UNTOUCHED_TEST_VALUE.get("rangeKey")); - - signed.put("hashKey", SIGNED_TEST_VALUE.get("hashKey")); - signed.put("rangeKey", SIGNED_TEST_VALUE.get("rangeKey")); - - mixed.put("hashKey", MIXED_TEST_VALUE.get("hashKey")); - mixed.put("rangeKey", MIXED_TEST_VALUE.get("rangeKey")); - - encrypted.put("hashKey", ENCRYPTED_TEST_VALUE.get("hashKey")); - encrypted.put("rangeKey", ENCRYPTED_TEST_VALUE.get("rangeKey")); - - hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); - hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); - hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); - - // check untouched attr - assertTrue( - new DdbRecordMatcher(UNTOUCHED_TEST_VALUE, false).matches(getItems(untouched, tableName))); - - // check signed attr - // Describe what actions need to be taken for each attribute - Map> actions = new HashMap<>(); - for (final String attr : SIGNED_TEST_VALUE.keySet()) { - actions.put(attr, signOnly); - } - response = getItems(signed, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(SIGNED_TEST_VALUE, false).matches(decryptedRecord)); - - // check mixed attr - actions = new HashMap<>(); - for (final String attr : MIXED_TEST_VALUE.keySet()) { - switch (attr) { - case "rangeKey": - case "hashKey": - case "version": - case "stringValue": - actions.put(attr, signOnly); - break; - case "intValue": - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - response = getItems(mixed, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(MIXED_TEST_VALUE, false).matches(decryptedRecord)); - - // check encrypted attr - actions = new HashMap<>(); - for (final String attr : ENCRYPTED_TEST_VALUE.keySet()) { - switch (attr) { - case "hashKey": - case "rangeKey": - case "version": - actions.put(attr, signOnly); - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - response = getItems(encrypted, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(ENCRYPTED_TEST_VALUE, false).matches(decryptedRecord)); - - assertEquals("Foo", getItems(hashKey1, "HashKeyOnly").get("hashKey").s()); - assertEquals("Bar", getItems(hashKey2, "HashKeyOnly").get("hashKey").s()); - assertEquals("Baz", getItems(hashKey3, "HashKeyOnly").get("hashKey").s()); - - Map key = new HashMap<>(); - for (int i = 1; i <= 3; ++i) { - key.put("hashKey", AttributeValue.builder().n("0").build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, "TableName"); - assertEquals(0, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - - key.put("hashKey", AttributeValue.builder().n("1").build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, "TableName"); - assertEquals(1, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - - key.put("hashKey", AttributeValue.builder().n(String.valueOf(4 + i)).build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, "TableName"); - assertEquals(4 + i, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - } - } - - private void assertVersionCompatibility_2(EncryptionMaterialsProvider provider, String tableName) - throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); - Map response; - Map decryptedRecord; - EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - - // Set up maps for table items - HashMap untouched = new HashMap<>(); - HashMap signed = new HashMap<>(); - HashMap mixed = new HashMap<>(); - HashMap encrypted = new HashMap<>(); - HashMap hashKey1 = new HashMap<>(); - HashMap hashKey2 = new HashMap<>(); - HashMap hashKey3 = new HashMap<>(); - - untouched.put("hashKey", UNTOUCHED_TEST_VALUE_2.get("hashKey")); - untouched.put("rangeKey", UNTOUCHED_TEST_VALUE_2.get("rangeKey")); - - signed.put("hashKey", SIGNED_TEST_VALUE_2.get("hashKey")); - signed.put("rangeKey", SIGNED_TEST_VALUE_2.get("rangeKey")); - - mixed.put("hashKey", MIXED_TEST_VALUE_2.get("hashKey")); - mixed.put("rangeKey", MIXED_TEST_VALUE_2.get("rangeKey")); - - encrypted.put("hashKey", ENCRYPTED_TEST_VALUE_2.get("hashKey")); - encrypted.put("rangeKey", ENCRYPTED_TEST_VALUE_2.get("rangeKey")); - - hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); - hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); - hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); - - // check untouched attr - assert new DdbRecordMatcher(UNTOUCHED_TEST_VALUE_2, false) - .matches(getItems(untouched, tableName)); - - // check signed attr - // Describe what actions need to be taken for each attribute - Map> actions = new HashMap<>(); - for (final String attr : SIGNED_TEST_VALUE_2.keySet()) { - actions.put(attr, signOnly); - } - response = getItems(signed, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(SIGNED_TEST_VALUE_2, false).matches(decryptedRecord)); - - // check mixed attr - actions = new HashMap<>(); - for (final String attr : MIXED_TEST_VALUE_2.keySet()) { - switch (attr) { - case "rangeKey": - case "hashKey": - case "version": - case "stringValue": - case "doubleValue": - case "doubleSet": - actions.put(attr, signOnly); - break; - case "intValue": - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - response = getItems(mixed, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(MIXED_TEST_VALUE_2, false).matches(decryptedRecord)); - - // check encrypted attr - actions = new HashMap<>(); - for (final String attr : ENCRYPTED_TEST_VALUE_2.keySet()) { - switch (attr) { - case "hashKey": - case "rangeKey": - case "version": - actions.put(attr, signOnly); - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - response = getItems(encrypted, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(ENCRYPTED_TEST_VALUE_2, false).matches(decryptedRecord)); - - // check HashKey Table - assertEquals("Foo", getItems(hashKey1, "HashKeyOnly").get("hashKey").s()); - assertEquals("Bar", getItems(hashKey2, "HashKeyOnly").get("hashKey").s()); - assertEquals("Baz", getItems(hashKey3, "HashKeyOnly").get("hashKey").s()); - - // Check Hash and Range Key Values - Map key = new HashMap<>(); - for (int i = 1; i <= 3; ++i) { - key.put("hashKey", AttributeValue.builder().n("0").build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, tableName); - assertEquals(0, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - - key.put("hashKey", AttributeValue.builder().n("1").build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, tableName); - assertEquals(1, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - - key.put("hashKey", AttributeValue.builder().n(String.valueOf(4 + i)).build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, tableName); - assertEquals(4 + i, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - } - } - - private static String stripFilePath(String path) { - return path.replaceFirst("file://", ""); - } - - @JsonDeserialize(using = AttributeValueDeserializer.class) - public abstract static class DeserializedAttributeValue implements AttributeValue.Builder {} -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java deleted file mode 100644 index fd3bf37ace..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.SignatureException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.crypto.spec.SecretKeySpec; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.TestDelegatedKey; - -public class DelegatedEncryptionTest { - private static SecretKeySpec rawEncryptionKey; - private static SecretKeySpec rawMacKey; - private static DelegatedKey encryptionKey; - private static DelegatedKey macKey; - - private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; - private Map attribs; - private EncryptionContext context; - - @BeforeClass - public static void setupClass() { - rawEncryptionKey = new SecretKeySpec(Utils.getRandom(32), "AES"); - encryptionKey = new TestDelegatedKey(rawEncryptionKey); - - rawMacKey = new SecretKeySpec(Utils.getRandom(32), "HmacSHA256"); - macKey = new TestDelegatedKey(rawMacKey); - } - - @BeforeMethod - public void setUp() { - prov = new SymmetricStaticProvider(encryptionKey, macKey, - Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - - attribs = new HashMap<>(); - attribs.put("intValue", AttributeValue.builder().n("123").build()); - attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - attribs.put("byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); - attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); - attribs.put("hashKey", AttributeValue.builder().n("5").build()); - attribs.put("rangeKey", AttributeValue.builder().n("7").build()); - attribs.put("version", AttributeValue.builder().n("0").build()); - - context = EncryptionContext.builder() - .tableName("TableName") - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - } - - @Test - public void testSetSignatureFieldName() { - assertNotNull(encryptor.getSignatureFieldName()); - encryptor.setSignatureFieldName("A different value"); - assertEquals("A different value", encryptor.getSignatureFieldName()); - } - - @Test - public void testSetMaterialDescriptionFieldName() { - assertNotNull(encryptor.getMaterialDescriptionFieldName()); - encryptor.setMaterialDescriptionFieldName("A different value"); - assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); - } - - @Test - public void fullEncryption() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has been encrypted (we'll assume the others are correct as well) - assertTrue(encryptedAttributes.containsKey("stringValue")); - assertNull(encryptedAttributes.get("stringValue").s()); - assertNotNull(encryptedAttributes.get("stringValue").b()); - } - - @Test(expectedExceptions = SignatureException.class) - public void fullEncryptionBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void badVersionNumber() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - SdkBytes materialDescription = - encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); - byte[] rawArray = materialDescription.asByteArray(); - assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. - rawArray[0] = 100; - encryptedAttributes.put( - encryptor.getMaterialDescriptionFieldName(), - AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test - public void signedOnly() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test - public void signedOnlyNullCryptoKey() throws GeneralSecurityException { - prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyNoSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.remove(encryptor.getSignatureFieldName()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test - public void RsaSignedOnly() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-" - ); - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-" - ); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.replace("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { - assertEquals(o1.b(), o2.b()); - assertSetsEqual(o1.bs(), o2.bs()); - assertEquals(o1.n(), o2.n()); - assertSetsEqual(o1.ns(), o2.ns()); - assertEquals(o1.s(), o2.s()); - assertSetsEqual(o1.ss(), o2.ss()); - } - - private void assertSetsEqual(Collection c1, Collection c2) { - assertFalse(c1 == null ^ c2 == null); - if (c1 != null) { - Set s1 = new HashSet<>(c1); - Set s2 = new HashSet<>(c2); - assertEquals(s1, s2); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java deleted file mode 100644 index ce22c396fa..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.SignatureException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.crypto.spec.SecretKeySpec; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.TestDelegatedKey; - -public class DelegatedEnvelopeEncryptionTest { - private static SecretKeySpec rawEncryptionKey; - private static SecretKeySpec rawMacKey; - private static DelegatedKey encryptionKey; - private static DelegatedKey macKey; - - private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; - private Map attribs; - private EncryptionContext context; - - @BeforeClass - public static void setupClass() { - rawEncryptionKey = new SecretKeySpec(Utils.getRandom(32), "AES"); - encryptionKey = new TestDelegatedKey(rawEncryptionKey); - - rawMacKey = new SecretKeySpec(Utils.getRandom(32), "HmacSHA256"); - macKey = new TestDelegatedKey(rawMacKey); - } - - @BeforeMethod - public void setUp() throws Exception { - prov = - new WrappedMaterialsProvider( - encryptionKey, encryptionKey, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - - attribs = new HashMap(); - attribs.put("intValue", AttributeValue.builder().n("123").build()); - attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - attribs.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5}))).build()); - attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); - attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); - attribs.put("hashKey", AttributeValue.builder().n("5").build()); - attribs.put("rangeKey", AttributeValue.builder().n("7").build()); - attribs.put("version", AttributeValue.builder().n("0").build()); - - context = - new EncryptionContext.Builder() - .tableName("TableName") - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - } - - @Test - public void testSetSignatureFieldName() { - assertNotNull(encryptor.getSignatureFieldName()); - encryptor.setSignatureFieldName("A different value"); - assertEquals("A different value", encryptor.getSignatureFieldName()); - } - - @Test - public void testSetMaterialDescriptionFieldName() { - assertNotNull(encryptor.getMaterialDescriptionFieldName()); - encryptor.setMaterialDescriptionFieldName("A different value"); - assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); - } - - @Test - public void fullEncryption() throws GeneralSecurityException{ - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has been encrypted (we'll assume the others are correct as well) - assertTrue(encryptedAttributes.containsKey("stringValue")); - assertNull(encryptedAttributes.get("stringValue").s()); - assertNotNull(encryptedAttributes.get("stringValue").b()); - } - - @Test(expectedExceptions = SignatureException.class) - public void fullEncryptionBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void badVersionNumber() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - SdkBytes materialDescription = - encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); - byte[] rawArray = materialDescription.asByteArray(); - assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. - rawArray[0] = 100; - encryptedAttributes.put( - encryptor.getMaterialDescriptionFieldName(), - AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test - public void signedOnlyNullCryptoKey() throws GeneralSecurityException { - prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyNoSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.remove(encryptor.getSignatureFieldName()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test - public void RsaSignedOnly() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-"); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-"); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.replace("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { - assertEquals(o1.b(), o2.b()); - assertSetsEqual(o1.bs(), o2.bs()); - assertEquals(o1.n(), o2.n()); - assertSetsEqual(o1.ns(), o2.ns()); - assertEquals(o1.s(), o2.s()); - assertSetsEqual(o1.ss(), o2.ss()); - } - - private void assertSetsEqual(Collection c1, Collection c2) { - assertFalse(c1 == null ^ c2 == null); - if (c1 != null) { - Set s1 = new HashSet<>(c1); - Set s2 = new HashSet<>(c2); - assertEquals(s1, s2); - } - } - -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java deleted file mode 100644 index 87fb8353bb..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java +++ /dev/null @@ -1,591 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import static java.util.stream.Collectors.toMap; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; -import static org.testng.collections.Sets.newHashSet; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableName; - -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.security.*; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jce.spec.ECParameterSpec; -import org.mockito.internal.util.collections.Sets; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; - -public class DynamoDbEncryptorTest { - private static SecretKey encryptionKey; - private static SecretKey macKey; - - private InstrumentedEncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; - private Map attribs; - private EncryptionContext context; - private static final String OVERRIDDEN_TABLE_NAME = "TheBestTableName"; - - @BeforeClass - public static void setUpClass() throws Exception { - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, Utils.getRng()); - encryptionKey = aesGen.generateKey(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - prov = new InstrumentedEncryptionMaterialsProvider( - new SymmetricStaticProvider(encryptionKey, macKey, - Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "enryptor-"); - - attribs = new HashMap<>(); - attribs.put("intValue", AttributeValue.builder().n("123").build()); - attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - attribs.put("byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); - attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); - attribs.put("hashKey", AttributeValue.builder().n("5").build()); - attribs.put("rangeKey", AttributeValue.builder().n("7").build()); - attribs.put("version", AttributeValue.builder().n("0").build()); - - // New(er) data types - attribs.put("booleanTrue", AttributeValue.builder().bool(true).build()); - attribs.put("booleanFalse", AttributeValue.builder().bool(false).build()); - attribs.put("nullValue", AttributeValue.builder().nul(true).build()); - Map tmpMap = new HashMap<>(attribs); - attribs.put("listValue", AttributeValue.builder().l( - AttributeValue.builder().s("I'm a string").build(), - AttributeValue.builder().n("42").build(), - AttributeValue.builder().s("Another string").build(), - AttributeValue.builder().ns("1", "4", "7").build(), - AttributeValue.builder().m(tmpMap).build(), - AttributeValue.builder().l( - AttributeValue.builder().n("123").build(), - AttributeValue.builder().ns("1", "200", "10", "15", "0").build(), - AttributeValue.builder().ss("Goodbye", "Cruel", "World", "!").build() - ).build()).build()); - tmpMap = new HashMap<>(); - tmpMap.put("another string", AttributeValue.builder().s("All around the cobbler's bench").build()); - tmpMap.put("next line", AttributeValue.builder().ss("the monkey", "chased", "the weasel").build()); - tmpMap.put("more lyrics", AttributeValue.builder().l( - AttributeValue.builder().s("the monkey").build(), - AttributeValue.builder().s("thought twas").build(), - AttributeValue.builder().s("all in fun").build() - ).build()); - tmpMap.put("weasel", AttributeValue.builder().m(Collections.singletonMap("pop", AttributeValue.builder().bool(true).build())).build()); - attribs.put("song", AttributeValue.builder().m(tmpMap).build()); - - context = EncryptionContext.builder() - .tableName("TableName") - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - } - - @Test - public void testSetSignatureFieldName() { - assertNotNull(encryptor.getSignatureFieldName()); - encryptor.setSignatureFieldName("A different value"); - assertEquals("A different value", encryptor.getSignatureFieldName()); - } - - @Test - public void testSetMaterialDescriptionFieldName() { - assertNotNull(encryptor.getMaterialDescriptionFieldName()); - encryptor.setMaterialDescriptionFieldName("A different value"); - assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); - } - - @Test - public void fullEncryption() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has been encrypted (we'll assume the others are correct as well) - assertTrue(encryptedAttributes.containsKey("stringValue")); - assertNull(encryptedAttributes.get("stringValue").s()); - assertNotNull(encryptedAttributes.get("stringValue").b()); - - // Make sure we're calling the proper getEncryptionMaterials method - assertEquals( - "Wrong getEncryptionMaterials() called", - 1, - prov.getCallCount("getEncryptionMaterials(EncryptionContext context)")); - } - - @Test - public void ensureEncryptedAttributesUnmodified() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - String encryptedString = encryptedAttributes.toString(); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - - assertEquals(encryptedString, encryptedAttributes.toString()); - } - - @Test(expectedExceptions = SignatureException.class) - public void fullEncryptionBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void badVersionNumber() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - SdkBytes materialDescription = - encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); - byte[] rawArray = materialDescription.asByteArray(); - assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. - rawArray[0] = 100; - encryptedAttributes.put( - encryptor.getMaterialDescriptionFieldName(), - AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test - public void signedOnly() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test - public void signedOnlyNullCryptoKey() throws GeneralSecurityException { - prov = - new InstrumentedEncryptionMaterialsProvider( - new SymmetricStaticProvider(null, macKey, Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyNoSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.remove(encryptor.getSignatureFieldName()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test - public void RsaSignedOnly() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-"); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-"); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - /** - * Tests that no exception is thrown when the encryption context override operator is null - * - * @throws GeneralSecurityException - */ - @Test - public void testNullEncryptionContextOperator() throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - encryptor.setEncryptionContextOverrideOperator(null); - encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); - } - - /** - * Tests decrypt and encrypt with an encryption context override operator - */ - @Test - public void testTableNameOverriddenEncryptionContextOperator() throws GeneralSecurityException { - // Ensure that the table name is different from what we override the table to. - assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - encryptor.setEncryptionContextOverrideOperator( - overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); - Map encryptedItems = - encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); - Map decryptedItems = - encryptor.decryptAllFieldsExcept(encryptedItems, context, Collections.emptyList()); - assertThat(decryptedItems, AttrMatcher.match(attribs)); - } - - - /** - * Tests encrypt with an encryption context override operator, and a second encryptor without an override - */ - @Test - public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor() - throws GeneralSecurityException { - // Ensure that the table name is different from what we override the table to. - assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); - encryptor.setEncryptionContextOverrideOperator( - overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); - Map encryptedItems = - encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); - - EncryptionContext expectedOverriddenContext = - new EncryptionContext.Builder(context).tableName("TheBestTableName").build(); - Map decryptedItems = - encryptorWithoutOverride.decryptAllFieldsExcept( - encryptedItems, expectedOverriddenContext, Collections.emptyList()); - assertThat(decryptedItems, AttrMatcher.match(attribs)); - } - - /** - * Tests encrypt with an encryption context override operator, and a second encryptor without an override - */ - @Test(expectedExceptions = SignatureException.class) - public void - testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptorButTheOriginalEncryptionContext() - throws GeneralSecurityException { - // Ensure that the table name is different from what we override the table to. - assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); - encryptor.setEncryptionContextOverrideOperator( - overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); - Map encryptedItems = - encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); - - // Use the original encryption context, and expect a signature failure - Map decryptedItems = - encryptorWithoutOverride.decryptAllFieldsExcept( - encryptedItems, context, Collections.emptyList()); - } - - @Test - public void EcdsaSignedOnly() throws GeneralSecurityException { - - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void EcdsaSignedOnlyBadSignature() throws GeneralSecurityException { - - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test - public void toByteArray() throws ReflectiveOperationException { - final byte[] expected = new byte[] {0, 1, 2, 3, 4, 5}; - assertToByteArray("Wrap", expected, ByteBuffer.wrap(expected)); - assertToByteArray("Wrap-RO", expected, ByteBuffer.wrap(expected).asReadOnlyBuffer()); - - assertToByteArray("Wrap-Truncated-Sliced", expected, ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 6).slice()); - assertToByteArray("Wrap-Offset-Sliced", expected, ByteBuffer.wrap(new byte[] {6, 0, 1, 2, 3, 4, 5, 6}, 1, 6).slice()); - assertToByteArray("Wrap-Truncated", expected, ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 6)); - assertToByteArray("Wrap-Offset", expected, ByteBuffer.wrap(new byte[] {6, 0, 1, 2, 3, 4, 5, 6}, 1, 6)); - - ByteBuffer buff = ByteBuffer.allocate(expected.length + 10); - buff.put(expected); - buff.flip(); - assertToByteArray("Normal", expected, buff); - - buff = ByteBuffer.allocateDirect(expected.length + 10); - buff.put(expected); - buff.flip(); - assertToByteArray("Direct", expected, buff); - } - - @Test - public void testDecryptWithPlaintextItem() throws GeneralSecurityException { - Map> attributeWithEmptyEncryptionFlags = - attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet())); - - Map decryptedAttributes = - encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - } - - /* - Test decrypt with a map that contains a new key (not included in attribs) with an encryption flag set that contains ENCRYPT and SIGN. - */ - @Test - public void testDecryptWithPlainTextItemAndAdditionNewAttributeHavingEncryptionFlag() - throws GeneralSecurityException { - Map> attributeWithEmptyEncryptionFlags = - attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet())); - attributeWithEmptyEncryptionFlags.put( - "newAttribute", Sets.newSet(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); - - Map decryptedAttributes = - encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - } - private void assertToByteArray( - final String msg, final byte[] expected, final ByteBuffer testValue) - throws ReflectiveOperationException { - Method m = DynamoDbEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); - m.setAccessible(true); - - int oldPosition = testValue.position(); - int oldLimit = testValue.limit(); - - assertThat(m.invoke(null, testValue), is(expected)); - assertEquals(msg + ":Position", oldPosition, testValue.position()); - assertEquals(msg + ":Limit", oldLimit, testValue.limit()); - } - - private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { - assertEquals(o1.b(), o2.b()); - assertSetsEqual(o1.bs(), o2.bs()); - assertEquals(o1.n(), o2.n()); - assertSetsEqual(o1.ns(), o2.ns()); - assertEquals(o1.s(), o2.s()); - assertSetsEqual(o1.ss(), o2.ss()); - } - - private void assertSetsEqual(Collection c1, Collection c2) { - assertFalse(c1 == null ^ c2 == null); - if (c1 != null) { - Set s1 = new HashSet<>(c1); - Set s2 = new HashSet<>(c2); - assertEquals(s1, s2); - } - } - - private EncryptionMaterialsProvider getMaterialProviderwithECDSA() - throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException { - Security.addProvider(new BouncyCastleProvider()); - ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp384r1"); - KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC"); - g.initialize(ecSpec, Utils.getRng()); - KeyPair keypair = g.generateKeyPair(); - Map description = new HashMap<>(); - description.put(DynamoDbEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); - return new SymmetricStaticProvider(null, keypair, description); - } - - private static final class InstrumentedEncryptionMaterialsProvider implements EncryptionMaterialsProvider { - private final EncryptionMaterialsProvider delegate; - private final ConcurrentHashMap calls = new ConcurrentHashMap<>(); - - InstrumentedEncryptionMaterialsProvider(EncryptionMaterialsProvider delegate) { - this.delegate = delegate; - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - incrementMethodCount("getDecryptionMaterials()"); - return delegate.getDecryptionMaterials(context); - } - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - incrementMethodCount("getEncryptionMaterials(EncryptionContext context)"); - return delegate.getEncryptionMaterials(context); - } - - @Override - public void refresh() { - incrementMethodCount("refresh()"); - delegate.refresh(); - } - - int getCallCount(String method) { - AtomicInteger count = calls.get(method); - if (count != null) { - return count.intValue(); - } else { - return 0; - } - } - - @SuppressWarnings("unused") - public void resetCallCounts() { - calls.clear(); - } - - private void incrementMethodCount(String method) { - AtomicInteger oldValue = calls.putIfAbsent(method, new AtomicInteger(1)); - if (oldValue != null) { - oldValue.incrementAndGet(); - } - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java deleted file mode 100644 index 8320e79526..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.Key; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.Security; -import java.security.SignatureException; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import javax.crypto.KeyGenerator; - -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jce.spec.ECParameterSpec; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -public class DynamoDbSignerTest { - // These use the Key type (rather than PublicKey, PrivateKey, and SecretKey) - // to test the routing logic within the signer. - private static Key pubKeyRsa; - private static Key privKeyRsa; - private static Key macKey; - private DynamoDbSigner signerRsa; - private DynamoDbSigner signerEcdsa; - private static Key pubKeyEcdsa; - private static Key privKeyEcdsa; - - @BeforeClass - public static void setUpClass() throws Exception { - - // RSA key generation - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - pubKeyRsa = sigPair.getPublic(); - privKeyRsa = sigPair.getPrivate(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - - Security.addProvider(new BouncyCastleProvider()); - ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp384r1"); - KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC"); - g.initialize(ecSpec, Utils.getRng()); - KeyPair keypair = g.generateKeyPair(); - pubKeyEcdsa = keypair.getPublic(); - privKeyEcdsa = keypair.getPrivate(); - } - - @BeforeMethod - public void setUp() { - signerRsa = DynamoDbSigner.getInstance("SHA256withRSA", Utils.getRng()); - signerEcdsa = DynamoDbSigner.getInstance("SHA384withECDSA", Utils.getRng()); - } - - @Test - public void mac() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", - AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void macLists() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().ss("Value1", "Value2", "Value3").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().ns("100", "200", "300").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", - AttributeValue.builder() - .bs( - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})), - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void macListsUnsorted() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().ss("Value3", "Value1", "Value2").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().ns("100", "300", "200").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", - AttributeValue.builder() - .bs( - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1})), - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - Map scrambledAttributes = new HashMap(); - scrambledAttributes.put("Key1", AttributeValue.builder().ss("Value1", "Value2", "Value3").build()); - scrambledAttributes.put("Key2", AttributeValue.builder().ns("100", "200", "300").build()); - scrambledAttributes.put( - "Key3", - AttributeValue.builder() - .bs( - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})), - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1}))) - .build()); - - signerRsa.verifySignature( - scrambledAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void macNoAdMatchesEmptyAd() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = signerRsa.calculateSignature(itemAttributes, attributeFlags, null, macKey); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void macWithIgnoredChange() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void macChangedValue() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void macChangedFlag() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void macChangedAssociatedData() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[] {3, 2, 1}, macKey); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[] {1, 2, 3}, macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void sig() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test - public void sigWithReadOnlySignature() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - signerRsa.verifySignature( - itemAttributes, - attributeFlags, - new byte[0], - pubKeyRsa, - ByteBuffer.wrap(signature).asReadOnlyBuffer()); - } - - @Test - public void sigNoAdMatchesEmptyAd() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, null, privKeyRsa); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test - public void sigWithIgnoredChange() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigChangedValue() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigChangedFlag() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigChangedAssociatedData() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - signerRsa.verifySignature( - itemAttributes, - attributeFlags, - new byte[] {1, 2, 3}, - pubKeyRsa, - ByteBuffer.wrap(signature)); - } - - @Test - public void sigEcdsa() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - signerEcdsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); - } - - @Test - public void sigEcdsaWithReadOnlySignature() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - signerEcdsa.verifySignature( - itemAttributes, - attributeFlags, - new byte[0], - pubKeyEcdsa, - ByteBuffer.wrap(signature).asReadOnlyBuffer()); - } - - @Test - public void sigEcdsaNoAdMatchesEmptyAd() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, null, privKeyEcdsa); - - signerEcdsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); - } - - @Test - public void sigEcdsaWithIgnoredChange() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); - signerEcdsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigEcdsaChangedValue() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); - signerEcdsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigEcdsaChangedAssociatedData() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - signerEcdsa.verifySignature( - itemAttributes, - attributeFlags, - new byte[] {1, 2, 3}, - pubKeyEcdsa, - ByteBuffer.wrap(signature)); - } -} \ No newline at end of file diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java deleted file mode 100644 index b9258c5f83..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; - -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -public class AsymmetricRawMaterialsTest { - private static SecureRandom rnd; - private static KeyPair encryptionPair; - private static SecretKey macKey; - private static KeyPair sigPair; - private Map description; - - @BeforeClass - public static void setUpClass() throws NoSuchAlgorithmException { - rnd = new SecureRandom(); - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, rnd); - encryptionPair = rsaGen.generateKeyPair(); - sigPair = rsaGen.generateKeyPair(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, rnd); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - } - - @Test - public void macNoDescription() throws GeneralSecurityException { - AsymmetricRawMaterials matEncryption = new AsymmetricRawMaterials(encryptionPair, macKey); - assertEquals(macKey, matEncryption.getSigningKey()); - assertEquals(macKey, matEncryption.getVerificationKey()); - assertFalse(matEncryption.getMaterialDescription().isEmpty()); - - SecretKey envelopeKey = matEncryption.getEncryptionKey(); - assertEquals(envelopeKey, matEncryption.getDecryptionKey()); - - AsymmetricRawMaterials matDecryption = - new AsymmetricRawMaterials(encryptionPair, macKey, matEncryption.getMaterialDescription()); - assertEquals(macKey, matDecryption.getSigningKey()); - assertEquals(macKey, matDecryption.getVerificationKey()); - assertEquals(envelopeKey, matDecryption.getEncryptionKey()); - assertEquals(envelopeKey, matDecryption.getDecryptionKey()); - } - - @Test - public void macWithDescription() throws GeneralSecurityException { - AsymmetricRawMaterials matEncryption = - new AsymmetricRawMaterials(encryptionPair, macKey, description); - assertEquals(macKey, matEncryption.getSigningKey()); - assertEquals(macKey, matEncryption.getVerificationKey()); - assertFalse(matEncryption.getMaterialDescription().isEmpty()); - assertEquals("test value", matEncryption.getMaterialDescription().get("TestKey")); - - SecretKey envelopeKey = matEncryption.getEncryptionKey(); - assertEquals(envelopeKey, matEncryption.getDecryptionKey()); - - AsymmetricRawMaterials matDecryption = - new AsymmetricRawMaterials(encryptionPair, macKey, matEncryption.getMaterialDescription()); - assertEquals(macKey, matDecryption.getSigningKey()); - assertEquals(macKey, matDecryption.getVerificationKey()); - assertEquals(envelopeKey, matDecryption.getEncryptionKey()); - assertEquals(envelopeKey, matDecryption.getDecryptionKey()); - assertEquals("test value", matDecryption.getMaterialDescription().get("TestKey")); - } - - @Test - public void sigNoDescription() throws GeneralSecurityException { - AsymmetricRawMaterials matEncryption = new AsymmetricRawMaterials(encryptionPair, sigPair); - assertEquals(sigPair.getPrivate(), matEncryption.getSigningKey()); - assertEquals(sigPair.getPublic(), matEncryption.getVerificationKey()); - assertFalse(matEncryption.getMaterialDescription().isEmpty()); - - SecretKey envelopeKey = matEncryption.getEncryptionKey(); - assertEquals(envelopeKey, matEncryption.getDecryptionKey()); - - AsymmetricRawMaterials matDecryption = - new AsymmetricRawMaterials(encryptionPair, sigPair, matEncryption.getMaterialDescription()); - assertEquals(sigPair.getPrivate(), matDecryption.getSigningKey()); - assertEquals(sigPair.getPublic(), matDecryption.getVerificationKey()); - assertEquals(envelopeKey, matDecryption.getEncryptionKey()); - assertEquals(envelopeKey, matDecryption.getDecryptionKey()); - } - - @Test - public void sigWithDescription() throws GeneralSecurityException { - AsymmetricRawMaterials matEncryption = - new AsymmetricRawMaterials(encryptionPair, sigPair, description); - assertEquals(sigPair.getPrivate(), matEncryption.getSigningKey()); - assertEquals(sigPair.getPublic(), matEncryption.getVerificationKey()); - assertFalse(matEncryption.getMaterialDescription().isEmpty()); - assertEquals("test value", matEncryption.getMaterialDescription().get("TestKey")); - - SecretKey envelopeKey = matEncryption.getEncryptionKey(); - assertEquals(envelopeKey, matEncryption.getDecryptionKey()); - - AsymmetricRawMaterials matDecryption = - new AsymmetricRawMaterials(encryptionPair, sigPair, matEncryption.getMaterialDescription()); - assertEquals(sigPair.getPrivate(), matDecryption.getSigningKey()); - assertEquals(sigPair.getPublic(), matDecryption.getVerificationKey()); - assertEquals(envelopeKey, matDecryption.getEncryptionKey()); - assertEquals(envelopeKey, matDecryption.getDecryptionKey()); - assertEquals("test value", matDecryption.getMaterialDescription().get("TestKey")); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java deleted file mode 100644 index a6987ce792..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertTrue; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -public class SymmetricRawMaterialsTest { - private static SecretKey encryptionKey; - private static SecretKey macKey; - private static KeyPair sigPair; - private static SecureRandom rnd; - private Map description; - - @BeforeClass - public static void setUpClass() throws NoSuchAlgorithmException { - rnd = new SecureRandom(); - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, rnd); - sigPair = rsaGen.generateKeyPair(); - - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, rnd); - encryptionKey = aesGen.generateKey(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, rnd); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - } - - @Test - public void macNoDescription() throws NoSuchAlgorithmException { - SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, macKey); - assertEquals(encryptionKey, mat.getEncryptionKey()); - assertEquals(encryptionKey, mat.getDecryptionKey()); - assertEquals(macKey, mat.getSigningKey()); - assertEquals(macKey, mat.getVerificationKey()); - assertTrue(mat.getMaterialDescription().isEmpty()); - } - - @Test - public void macWithDescription() throws NoSuchAlgorithmException { - SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, macKey, description); - assertEquals(encryptionKey, mat.getEncryptionKey()); - assertEquals(encryptionKey, mat.getDecryptionKey()); - assertEquals(macKey, mat.getSigningKey()); - assertEquals(macKey, mat.getVerificationKey()); - assertEquals(description, mat.getMaterialDescription()); - assertEquals("test value", mat.getMaterialDescription().get("TestKey")); - } - - @Test - public void sigNoDescription() throws NoSuchAlgorithmException { - SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, sigPair); - assertEquals(encryptionKey, mat.getEncryptionKey()); - assertEquals(encryptionKey, mat.getDecryptionKey()); - assertEquals(sigPair.getPrivate(), mat.getSigningKey()); - assertEquals(sigPair.getPublic(), mat.getVerificationKey()); - assertTrue(mat.getMaterialDescription().isEmpty()); - } - - @Test - public void sigWithDescription() throws NoSuchAlgorithmException { - SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, sigPair, description); - assertEquals(encryptionKey, mat.getEncryptionKey()); - assertEquals(encryptionKey, mat.getDecryptionKey()); - assertEquals(sigPair.getPrivate(), mat.getSigningKey()); - assertEquals(sigPair.getPublic(), mat.getVerificationKey()); - assertEquals(description, mat.getMaterialDescription()); - assertEquals("test value", mat.getMaterialDescription().get("TestKey")); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java deleted file mode 100644 index 8f71ac7b28..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; - -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -public class AsymmetricStaticProviderTest { - private static KeyPair encryptionPair; - private static SecretKey macKey; - private static KeyPair sigPair; - private Map description; - private EncryptionContext ctx; - - @BeforeClass - public static void setUpClass() throws Exception { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - sigPair = rsaGen.generateKeyPair(); - encryptionPair = rsaGen.generateKeyPair(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - description = Collections.unmodifiableMap(description); - ctx = new EncryptionContext.Builder().build(); - } - - @Test - public void constructWithMac() throws GeneralSecurityException { - AsymmetricStaticProvider prov = - new AsymmetricStaticProvider( - encryptionPair, macKey, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void constructWithSigPair() throws GeneralSecurityException { - AsymmetricStaticProvider prov = - new AsymmetricStaticProvider( - encryptionPair, sigPair, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void randomEnvelopeKeys() throws GeneralSecurityException { - AsymmetricStaticProvider prov = - new AsymmetricStaticProvider( - encryptionPair, macKey, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey2 = eMat2.getEncryptionKey(); - assertEquals(macKey, eMat.getSigningKey()); - - assertFalse("Envelope keys must be different", encryptionKey.equals(encryptionKey2)); - } - - @Test - public void testRefresh() { - // This does nothing, make sure we don't throw and exception. - AsymmetricStaticProvider prov = - new AsymmetricStaticProvider(encryptionPair, macKey, description); - prov.refresh(); - } - - private static EncryptionContext ctx(EncryptionMaterials mat) { - return new EncryptionContext.Builder() - .materialDescription(mat.getMaterialDescription()) - .build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java deleted file mode 100644 index f286648332..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java +++ /dev/null @@ -1,610 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; -import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; -import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; - -public class CachingMostRecentProviderTests { - private static final String TABLE_NAME = "keystoreTable"; - private static final String MATERIAL_NAME = "material"; - private static final String MATERIAL_PARAM = "materialName"; - private static final SecretKey AES_KEY = - new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "AES"); - private static final SecretKey HMAC_KEY = - new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); - private static final EncryptionMaterialsProvider BASE_PROVIDER = - new SymmetricStaticProvider(AES_KEY, HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); - - private DynamoDbClient client; - private Map methodCalls; - private ProvisionedThroughput throughput; - private ProviderStore store; - private EncryptionContext ctx; - - @BeforeMethod - public void setup() { - methodCalls = new HashMap(); - throughput = ProvisionedThroughput.builder().readCapacityUnits(1L).writeCapacityUnits(1L).build(); - - client = instrument(DynamoDBEmbedded.create().dynamoDbClient(), DynamoDbClient.class, methodCalls); - MetaStore.createTable(client, TABLE_NAME, throughput); - store = new MetaStore(client, TABLE_NAME, ENCRYPTOR); - ctx = new EncryptionContext.Builder().build(); - methodCalls.clear(); - } - - @Test - public void testConstructors() { - final CachingMostRecentProvider prov = - new CachingMostRecentProvider(store, MATERIAL_NAME, 100, 1000); - assertEquals(MATERIAL_NAME, prov.getMaterialName()); - assertEquals(100, prov.getTtlInMills()); - assertEquals(-1, prov.getCurrentVersion()); - assertEquals(0, prov.getLastUpdated()); - - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - assertEquals(MATERIAL_NAME, prov2.getMaterialName()); - assertEquals(500, prov2.getTtlInMills()); - assertEquals(-1, prov2.getCurrentVersion()); - assertEquals(0, prov2.getLastUpdated()); - } - - - @Test - public void testSmallMaxCacheSize() { - final Map attr1 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); - final EncryptionContext ctx1 = ctx(attr1); - final Map attr2 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); - final EncryptionContext ctx2 = ctx(attr2); - - final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 1); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the two materials are, in fact, different - assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); - - // Ensure the second set of materials are cached - final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - - // Ensure the first set of materials are no longer cached, due to being the LRU - final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); - } - - @Test - public void testSingleVersion() throws InterruptedException { - final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the cache is working - final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - // Let the TTL be exceeded - Thread.sleep(500); - final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); - assertEquals(2, methodCalls.size()); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); // To get provider - assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); - - assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); - assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey()); - // Check algorithms. Right now we only support AES and HmacSHA256 - assertEquals("AES", eMat1.getEncryptionKey().getAlgorithm()); - assertEquals("HmacSHA256", eMat1.getSigningKey().getAlgorithm()); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); - methodCalls.clear(); - assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); - assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); - final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); - assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); - assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); - final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); - assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); - assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - } - - @Test - public void testSingleVersionWithRefresh() throws InterruptedException { - final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the cache is working - final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - prov.refresh(); - final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); - prov.refresh(); - - assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); - assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey()); - - // Ensure that after cache refresh we only get one more hit as opposed to multiple - prov.getEncryptionMaterials(ctx); - Thread.sleep(700); - // Force refresh - prov.getEncryptionMaterials(ctx); - methodCalls.clear(); - // Check to ensure no more hits - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); - methodCalls.clear(); - assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); - assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); - final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); - assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); - assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); - final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); - assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); - assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - } - - @Test - public void testTwoVersions() throws InterruptedException { - final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Create the new material - store.newProvider(MATERIAL_NAME); - methodCalls.clear(); - - // Ensure the cache is working - final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - // Let the TTL be exceeded - Thread.sleep(500); - final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); - - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); // To retrieve current version - assertNull(methodCalls.get("putItem")); // No attempt to create a new item - assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); - - assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); - assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey())); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); - methodCalls.clear(); - assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); - assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); - final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); - assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); - assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); - final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); - assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); - assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); - // Get item will be hit once for the one old key - assertEquals(1, methodCalls.size()); - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - } - - @Test - public void testTwoVersionsWithRefresh() throws InterruptedException { - final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 100); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Create the new material - store.newProvider(MATERIAL_NAME); - methodCalls.clear(); - // Ensure the cache is working - final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - prov.refresh(); - final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); - - assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); - assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey())); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); - methodCalls.clear(); - assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); - assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); - final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); - assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); - assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); - final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); - assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); - assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); - // Get item will be hit once for the one old key - assertEquals(1, methodCalls.size()); - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - } - - @Test - public void testSingleVersionTwoMaterials() throws InterruptedException { - final Map attr1 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); - final EncryptionContext ctx1 = ctx(attr1); - final Map attr2 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); - final EncryptionContext ctx2 = ctx(attr2); - - final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the two materials are, in fact, different - assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); - - // Ensure the cache is working - final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); - final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); - - // Let the TTL be exceeded - Thread.sleep(500); - final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); - assertEquals(2, methodCalls.size()); - assertEquals(1, (int) methodCalls.get("query")); // To find current version - assertEquals(1, (int) methodCalls.get("getItem")); // To get the provider - assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); - methodCalls.clear(); - final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); - assertEquals(2, methodCalls.size()); - assertEquals(1, (int) methodCalls.get("query")); // To find current version - assertEquals(1, (int) methodCalls.get("getItem")); // To get the provider - assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); - - assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey()); - // Check algorithms. Right now we only support AES and HmacSHA256 - assertEquals("AES", eMat1_1.getEncryptionKey().getAlgorithm()); - assertEquals("AES", eMat1_2.getEncryptionKey().getAlgorithm()); - assertEquals("HmacSHA256", eMat1_1.getSigningKey().getAlgorithm()); - assertEquals("HmacSHA256", eMat1_2.getSigningKey().getAlgorithm()); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); - final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); - final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); - methodCalls.clear(); - assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); - assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); - assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); - assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); - final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); - final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); - assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); - assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); - assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); - assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); - final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); - final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); - assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); - assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); - assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); - assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - } - - @Test - public void testSingleVersionWithTwoMaterialsWithRefresh() throws InterruptedException { - final Map attr1 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); - final EncryptionContext ctx1 = ctx(attr1); - final Map attr2 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); - final EncryptionContext ctx2 = ctx(attr2); - - final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the two materials are, in fact, different - assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); - - // Ensure the cache is working - final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); - final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); - - prov.refresh(); - final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); - assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); - prov.refresh(); - - assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey()); - - // Ensure that after cache refresh we only get one more hit as opposed to multiple - prov.getEncryptionMaterials(ctx1); - prov.getEncryptionMaterials(ctx2); - Thread.sleep(700); - // Force refresh - prov.getEncryptionMaterials(ctx1); - prov.getEncryptionMaterials(ctx2); - methodCalls.clear(); - // Check to ensure no more hits - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); - final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); - final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); - methodCalls.clear(); - assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); - assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); - assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); - assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); - final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); - final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); - assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); - assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); - assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); - assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); - final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); - final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); - assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); - assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); - assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); - assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - } - - @Test - public void testTwoVersionsWithTwoMaterialsWithRefresh() throws InterruptedException { - final Map attr1 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); - final EncryptionContext ctx1 = ctx(attr1); - final Map attr2 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); - final EncryptionContext ctx2 = ctx(attr2); - - final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Create the new material - store.newProvider("material1"); - store.newProvider("material2"); - methodCalls.clear(); - // Ensure the cache is working - final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); - final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); - prov.refresh(); - final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); - final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); - assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); - assertEquals(1, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); - assertEquals(1, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); - - assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); - assertFalse(eMat1_1.getSigningKey().equals(eMat3_1.getSigningKey())); - assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); - assertFalse(eMat1_2.getSigningKey().equals(eMat3_2.getSigningKey())); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); - final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); - final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); - methodCalls.clear(); - assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); - assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); - assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); - assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); - final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); - final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); - assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); - assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); - assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); - assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); - final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); - final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); - assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); - assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); - assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); - assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); - // Get item will be hit twice, once for each old key - assertEquals(1, methodCalls.size()); - assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); - } - - private static EncryptionContext ctx(final Map attr) { - return new EncryptionContext.Builder().attributeValues(attr).build(); - } - - private static EncryptionContext ctx( - final EncryptionMaterials mat, Map attr) { - return new EncryptionContext.Builder() - .attributeValues(attr) - .materialDescription(mat.getMaterialDescription()) - .build(); - } - - private static EncryptionContext ctx(final EncryptionMaterials mat) { - return new EncryptionContext.Builder() - .materialDescription(mat.getMaterialDescription()) - .build(); - } - - private static class ExtendedProvider extends CachingMostRecentProvider { - public ExtendedProvider(ProviderStore keystore, long ttlInMillis, int maxCacheSize) { - super(keystore, null, ttlInMillis, maxCacheSize); - } - - @Override - public long getCurrentVersion() { - throw new UnsupportedOperationException(); - } - - @Override - protected String getMaterialName(final EncryptionContext context) { - return context.getAttributeValues().get(MATERIAL_PARAM).s(); - } - } - - @SuppressWarnings("unchecked") - private static T instrument( - final T obj, final Class clazz, final Map map) { - return (T) - Proxy.newProxyInstance( - clazz.getClassLoader(), - new Class[] {clazz}, - new InvocationHandler() { - private final Object lock = new Object(); - - @Override - public Object invoke(final Object proxy, final Method method, final Object[] args) - throws Throwable { - synchronized (lock) { - try { - final Integer oldCount = map.get(method.getName()); - if (oldCount != null) { - map.put(method.getName(), oldCount + 1); - } else { - map.put(method.getName(), 1); - } - return method.invoke(obj, args); - } catch (final InvocationTargetException ex) { - throw ex.getCause(); - } - } - } - }); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java deleted file mode 100644 index f5832a1e62..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.Key; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.core.exception.SdkException; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.awssdk.services.kms.model.DecryptRequest; -import software.amazon.awssdk.services.kms.model.DecryptResponse; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.FakeKMS; - -public class DirectKmsMaterialsProviderTest { - private FakeKMS kms; - private String keyId; - private Map description; - private EncryptionContext ctx; - - @BeforeMethod - public void setUp() { - description = new HashMap<>(); - description.put("TestKey", "test value"); - description = Collections.unmodifiableMap(description); - ctx = new EncryptionContext.Builder().build(); - kms = new FakeKMS(); - keyId = kms.createKey().keyMetadata().keyId(); - } - - @Test - public void simple() { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - - String expectedEncAlg = - encryptionKey.getAlgorithm() + "/" + (encryptionKey.getEncoded().length * 8); - String expectedSigAlg = signingKey.getAlgorithm() + "/" + (signingKey.getEncoded().length * 8); - - Map kmsCtx = kms.getSingleEc(); - assertEquals(expectedEncAlg, kmsCtx.get("*" + WrappedRawMaterials.CONTENT_KEY_ALGORITHM + "*")); - assertEquals(expectedSigAlg, kmsCtx.get("*amzn-ddb-sig-alg*")); - } - - @Test - public void simpleWithKmsEc() { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().s("HashKeyValue").build()); - attrVals.put("rk", AttributeValue.builder().s("RangeKeyValue").build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - Map kmsCtx = kms.getSingleEc(); - assertEquals("HashKeyValue", kmsCtx.get("hk")); - assertEquals("RangeKeyValue", kmsCtx.get("rk")); - assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - } - - @Test - public void simpleWithKmsEc2() throws GeneralSecurityException { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().n("10").build()); - attrVals.put("rk", AttributeValue.builder().n("20").build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - Map kmsCtx = kms.getSingleEc(); - assertEquals("10", kmsCtx.get("hk")); - assertEquals("20", kmsCtx.get("rk")); - assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - } - - @Test - public void simpleWithKmsEc3() throws GeneralSecurityException { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - Map attrVals = new HashMap<>(); - attrVals.put( - "hk", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap("Foo".getBytes(StandardCharsets.UTF_8)))) - .build()); - attrVals.put( - "rk", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap("Bar".getBytes(StandardCharsets.UTF_8)))) - .build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - assertNotNull(signingKey); - Map kmsCtx = kms.getSingleEc(); - assertEquals(Base64.encodeToString("Foo".getBytes(StandardCharsets.UTF_8)), kmsCtx.get("hk")); - assertEquals(Base64.encodeToString("Bar".getBytes(StandardCharsets.UTF_8)), kmsCtx.get("rk")); - assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - } - - @Test - public void randomEnvelopeKeys() throws GeneralSecurityException { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey2 = eMat2.getEncryptionKey(); - - assertFalse("Envelope keys must be different", encryptionKey.equals(encryptionKey2)); - } - - @Test - public void testRefresh() { - // This does nothing, make sure we don't throw and exception. - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - prov.refresh(); - } - - @Test - public void explicitContentKeyAlgorithm() throws GeneralSecurityException { - Map desc = new HashMap<>(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); - - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - } - - @Test - public void explicitContentKeyLength128() throws GeneralSecurityException { - Map desc = new HashMap<>(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(16, encryptionKey.getEncoded().length); // 128 Bits - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES/128", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", eMat.getEncryptionKey().getAlgorithm()); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - } - - @Test - public void explicitContentKeyLength256() throws GeneralSecurityException { - Map desc = new HashMap<>(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(32, encryptionKey.getEncoded().length); // 256 Bits - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES/256", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", eMat.getEncryptionKey().getAlgorithm()); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - } - - @Test - public void extendedWithDerivedEncryptionKeyId() { - ExtendedKmsMaterialsProvider prov = - new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); - String customKeyId = kms.createKey().keyMetadata().keyId(); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().n("10").build()); - attrVals.put("rk", AttributeValue.builder().n("20").build()); - attrVals.put("encryptionKeyId", AttributeValue.builder().s(customKeyId).build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - Map kmsCtx = kms.getSingleEc(); - assertEquals("10", kmsCtx.get("hk")); - assertEquals("20", kmsCtx.get("rk")); - assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - } - - @Test(expectedExceptions = SdkException.class) - public void encryptionKeyIdMismatch() throws SdkException { - DirectKmsMaterialsProvider directProvider = new DirectKmsMaterialsProvider(kms, keyId); - String customKeyId = kms.createKey().keyMetadata().keyId(); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().n("10").build()); - attrVals.put("rk", AttributeValue.builder().n("20").build()); - attrVals.put("encryptionKeyId", AttributeValue.builder().s(customKeyId).build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = directProvider.getEncryptionMaterials(ctx); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - ExtendedKmsMaterialsProvider extendedProvider = - new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); - - extendedProvider.getDecryptionMaterials(dCtx); - } - - @Test(expectedExceptions = SdkException.class) - public void missingEncryptionKeyId() throws SdkException { - ExtendedKmsMaterialsProvider prov = - new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().n("10").build()); - attrVals.put("rk", AttributeValue.builder().n("20").build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - prov.getEncryptionMaterials(ctx); - } - - @Test - public void generateDataKeyIsCalledWith256NumberOfBits() { - final AtomicBoolean gdkCalled = new AtomicBoolean(false); - KmsClient kmsSpy = - new FakeKMS() { - @Override - public GenerateDataKeyResponse generateDataKey(GenerateDataKeyRequest r) { - gdkCalled.set(true); - assertEquals((Integer) 32, r.numberOfBytes()); - assertNull(r.keySpec()); - return super.generateDataKey(r); - } - }; - assertFalse(gdkCalled.get()); - new DirectKmsMaterialsProvider(kmsSpy, keyId).getEncryptionMaterials(ctx); - assertTrue(gdkCalled.get()); - } - - private static class ExtendedKmsMaterialsProvider extends DirectKmsMaterialsProvider { - private final String encryptionKeyIdAttributeName; - - public ExtendedKmsMaterialsProvider( - KmsClient kms, String encryptionKeyId, String encryptionKeyIdAttributeName) { - super(kms, encryptionKeyId); - - this.encryptionKeyIdAttributeName = encryptionKeyIdAttributeName; - } - - @Override - protected String selectEncryptionKeyId(EncryptionContext context) - throws DynamoDbException { - if (!context.getAttributeValues().containsKey(encryptionKeyIdAttributeName)) { - throw DynamoDbException.create("encryption key attribute is not provided", new Exception()); - } - - return context.getAttributeValues().get(encryptionKeyIdAttributeName).s(); - } - - @Override - protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext context) - throws DynamoDbException { - if (!context.getAttributeValues().containsKey(encryptionKeyIdAttributeName)) { - throw DynamoDbException.create("encryption key attribute is not provided", new Exception()); - } - - String customEncryptionKeyId = - context.getAttributeValues().get(encryptionKeyIdAttributeName).s(); - if (!customEncryptionKeyId.equals(encryptionKeyId)) { - throw DynamoDbException.create("encryption key ids do not match.", new Exception()); - } - } - - @Override - protected DecryptResponse decrypt(DecryptRequest request, EncryptionContext context) { - return super.decrypt(request, context); - } - - @Override - protected GenerateDataKeyResponse generateDataKey( - GenerateDataKeyRequest request, EncryptionContext context) { - return super.generateDataKey(request, context); - } - } - - private static EncryptionContext ctx(EncryptionMaterials mat) { - return new EncryptionContext.Builder() - .materialDescription(mat.getMaterialDescription()) - .build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java deleted file mode 100644 index 406052452e..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.fail; - -import java.io.ByteArrayInputStream; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.KeyStore.PasswordProtection; -import java.security.KeyStore.PrivateKeyEntry; -import java.security.KeyStore.SecretKeyEntry; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -public class KeyStoreMaterialsProviderTest { - private static final String certPem = - "MIIDbTCCAlWgAwIBAgIJANdRvzVsW1CIMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV" + - "BAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMQwwCgYDVQQKDANBV1MxGzAZBgNV" + - "BAMMEktleVN0b3JlIFRlc3QgQ2VydDAeFw0xMzA1MDgyMzMyMjBaFw0xMzA2MDcy" + - "MzMyMjBaME0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMQwwCgYD" + - "VQQKDANBV1MxGzAZBgNVBAMMEktleVN0b3JlIFRlc3QgQ2VydDCCASIwDQYJKoZI" + - "hvcNAQEBBQADggEPADCCAQoCggEBAJ8+umOX8x/Ma4OZishtYpcA676bwK5KScf3" + - "w+YGM37L12KTdnOyieiGtRW8p0fS0YvnhmVTvaky09I33bH+qy9gliuNL2QkyMxp" + - "uu1IwkTKKuB67CaKT6osYJLFxV/OwHcaZnTszzDgbAVg/Z+8IZxhPgxMzMa+7nDn" + - "hEm9Jd+EONq3PnRagnFeLNbMIePprdJzXHyNNiZKRRGQ/Mo9rr7mqMLSKnFNsmzB" + - "OIfeZM8nXeg+cvlmtXl72obwnGGw2ksJfaxTPm4eEhzRoAgkbjPPLHbwiJlc+GwF" + - "i8kh0Y3vQTj/gOFE4nzipkm7ux1lsGHNRVpVDWpjNd8Fl9JFELkCAwEAAaNQME4w" + - "HQYDVR0OBBYEFM0oGUuFAWlLXZaMXoJgGZxWqfOxMB8GA1UdIwQYMBaAFM0oGUuF" + - "AWlLXZaMXoJgGZxWqfOxMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB" + - "AAXCsXeC8ZRxovP0Wc6C5qv3d7dtgJJVzHwoIRt2YR3yScBa1XI40GKT80jP3MYH" + - "8xMu3mBQtcYrgRKZBy4GpHAyxoFTnPcuzq5Fg7dw7fx4E4OKIbWOahdxwtbVxQfZ" + - "UHnGG88Z0bq2twj7dALGyJhUDdiccckJGmJPOFMzjqsvoAu0n/p7eS6y5WZ5ewqw" + - "p7VwYOP3N9wVV7Podmkh1os+eCcp9GoFf0MHBMFXi2Ps2azKx8wHRIA5D1MZv/Va" + - "4L4/oTBKCjORpFlP7EhMksHBYnjqXLDP6awPMAgQNYB5J9zX6GfJsAgly3t4Rjr5" + - "cLuNYBmRuByFGo+SOdrj6D8="; - private static final String keyPem = - "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCfPrpjl/MfzGuD" + - "mYrIbWKXAOu+m8CuSknH98PmBjN+y9dik3ZzsonohrUVvKdH0tGL54ZlU72pMtPS" + - "N92x/qsvYJYrjS9kJMjMabrtSMJEyirgeuwmik+qLGCSxcVfzsB3GmZ07M8w4GwF" + - "YP2fvCGcYT4MTMzGvu5w54RJvSXfhDjatz50WoJxXizWzCHj6a3Sc1x8jTYmSkUR" + - "kPzKPa6+5qjC0ipxTbJswTiH3mTPJ13oPnL5ZrV5e9qG8JxhsNpLCX2sUz5uHhIc" + - "0aAIJG4zzyx28IiZXPhsBYvJIdGN70E4/4DhROJ84qZJu7sdZbBhzUVaVQ1qYzXf" + - "BZfSRRC5AgMBAAECggEBAJMwx9eGe5LIwBfDtCPN93LbxwtHq7FtuQS8XrYexTpN" + - "76eN5c7LF+11lauh1HzuwAEw32iJHqVl9aQ5PxFm85O3ExbuSP+ngHJwx/bLacVr" + - "mHYlKGH3Net1WU5Qvz7vO7bbEBjDSj9DMJVIMSWUHv0MZO25jw2lLX/ufrgpvPf7" + - "KXSgXg/8uV7PbnTbBDNlg02u8eOc+IbH4O8XDKAhD+YQ8AE3pxtopJbb912U/cJs" + - "Y0hQ01zbkWYH7wL9BeQmR7+TEjjtr/IInNjnXmaOmSX867/rTSTuozaVrl1Ce7r8" + - "EmUDg9ZLZeKfoNYovMy08wnxWVX2J+WnNDjNiSOm+IECgYEA0v3jtGrOnKbd0d9E" + - "dbyIuhjgnwp+UsgALIiBeJYjhFS9NcWgs+02q/0ztqOK7g088KBBQOmiA+frLIVb" + - "uNCt/3jF6kJvHYkHMZ0eBEstxjVSM2UcxzJ6ceHZ68pmrru74382TewVosxccNy0" + - "glsUWNN0t5KQDcetaycRYg50MmcCgYEAwTb8klpNyQE8AWxVQlbOIEV24iarXxex" + - "7HynIg9lSeTzquZOXjp0m5omQ04psil2gZ08xjiudG+Dm7QKgYQcxQYUtZPQe15K" + - "m+2hQM0jA7tRfM1NAZHoTmUlYhzRNX6GWAqQXOgjOqBocT4ySBXRaSQq9zuZu36s" + - "fI17knap798CgYArDa2yOf0xEAfBdJqmn7MSrlLfgSenwrHuZGhu78wNi7EUUOBq" + - "9qOqUr+DrDmEO+VMgJbwJPxvaZqeehPuUX6/26gfFjFQSI7UO+hNHf4YLPc6D47g" + - "wtcjd9+c8q8jRqGfWWz+V4dOsf7G9PJMi0NKoNN3RgvpE+66J72vUZ26TwKBgEUq" + - "DdfGA7pEetp3kT2iHT9oHlpuRUJRFRv2s015/WQqVR+EOeF5Q2zADZpiTIK+XPGg" + - "+7Rpbem4UYBXPruGM1ZECv3E4AiJhGO0+Nhdln8reswWIc7CEEqf4nXwouNnW2gA" + - "wBTB9Hp0GW8QOKedR80/aTH/X9TCT7R2YRnY6JQ5AoGBAKjgPySgrNDhlJkW7jXR" + - "WiGpjGSAFPT9NMTvEHDo7oLTQ8AcYzcGQ7ISMRdVXR6GJOlFVsH4NLwuHGtcMTPK" + - "zoHbPHJyOn1SgC5tARD/1vm5CsG2hATRpWRQCTJFg5VRJ4R7Pz+HuxY4SoABcPQd" + - "K+MP8GlGqTldC6NaB1s7KuAX"; - - private static SecretKey encryptionKey; - private static SecretKey macKey; - private static KeyStore keyStore; - private static final String password = "Password"; - private static final PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray()); - - private Map description; - private EncryptionContext ctx; - private static PrivateKey privateKey; - private static Certificate certificate; - - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, Utils.getRng()); - encryptionKey = aesGen.generateKey(); - - keyStore = KeyStore.getInstance("jceks"); - keyStore.load(null, password.toCharArray()); - - KeyFactory kf = KeyFactory.getInstance("RSA"); - PKCS8EncodedKeySpec rsaSpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyPem)); - privateKey = kf.generatePrivate(rsaSpec); - CertificateFactory cf = CertificateFactory.getInstance("X509"); - certificate = cf.generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(certPem))); - - keyStore.setEntry("enc", new SecretKeyEntry(encryptionKey), passwordProtection); - keyStore.setEntry("sig", new SecretKeyEntry(macKey), passwordProtection); - keyStore.setEntry( - "enc-a", - new PrivateKeyEntry(privateKey, new Certificate[] {certificate}), - passwordProtection); - keyStore.setEntry( - "sig-a", - new PrivateKeyEntry(privateKey, new Certificate[] {certificate}), - passwordProtection); - keyStore.setCertificateEntry("trustedCert", certificate); - } - - @BeforeMethod - public void setUp() { - description = new HashMap<>(); - description.put("TestKey", "test value"); - description = Collections.unmodifiableMap(description); - ctx = EncryptionContext.builder().build(); - } - - - @Test - @SuppressWarnings("unchecked") - public void simpleSymMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, Collections.EMPTY_MAP); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); - } - - @Test - @SuppressWarnings("unchecked") - public void simpleSymSig() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig-a", passwordProtection, passwordProtection, Collections.EMPTY_MAP); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(privateKey, encryptionMaterials.getSigningKey()); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); - assertEquals(certificate.getPublicKey(), prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); - } - - @Test - public void equalSymDescMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); - } - - @Test - public void superSetSymDescMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - Map tmpDesc = - new HashMap<>(encryptionMaterials.getMaterialDescription()); - tmpDesc.put("randomValue", "random"); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(tmpDesc)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(tmpDesc)).getVerificationKey()); - } - - @Test - @SuppressWarnings("unchecked") - public void subSetSymDescMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - - assertNull(prov.getDecryptionMaterials(ctx(Collections.EMPTY_MAP))); - } - - - @Test - public void noMatchSymDescMac() throws Exception { - KeyStoreMaterialsProvider prov = new - KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - Map tmpDesc = new HashMap<>(); - tmpDesc.put("randomValue", "random"); - - assertNull(prov.getDecryptionMaterials(ctx(tmpDesc))); - } - - @Test - public void testRefresh() throws Exception { - // Mostly make sure we don't throw an exception - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - prov.refresh(); - } - - @Test - public void asymSimpleMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc-a", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void asymSimpleSig() throws Exception { - KeyStoreMaterialsProvider prov = new KeyStoreMaterialsProvider(keyStore, "enc-a", "sig-a", passwordProtection, passwordProtection, description); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(privateKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(certificate.getPublicKey(), dMat.getVerificationKey()); - } - - @Test - public void asymSigVerifyOnly() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc-a", "trustedCert", passwordProtection, null, description); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertNull(eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(certificate.getPublicKey(), dMat.getVerificationKey()); - } - - @Test - public void asymSigEncryptOnly() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "trustedCert", "sig-a", null, passwordProtection, description); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(privateKey, eMat.getSigningKey()); - - try { - prov.getDecryptionMaterials(ctx(eMat)); - fail("Expected exception"); - } catch (IllegalStateException ex) { - assertEquals("No private decryption key provided.", ex.getMessage()); - } - } - - private static EncryptionContext ctx(EncryptionMaterials mat) { - return ctx(mat.getMaterialDescription()); - } - - private static EncryptionContext ctx(Map desc) { - return EncryptionContext.builder() - .materialDescription(desc).build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java deleted file mode 100644 index 0485d4dff7..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -public class SymmetricStaticProviderTest { - private static SecretKey encryptionKey; - private static SecretKey macKey; - private static KeyPair sigPair; - private Map description; - private EncryptionContext ctx; - - @BeforeClass - public static void setUpClass() throws Exception { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - sigPair = rsaGen.generateKeyPair(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, Utils.getRng()); - encryptionKey = aesGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - description = Collections.unmodifiableMap(description); - ctx = new EncryptionContext.Builder().build(); - } - - @Test - public void simpleMac() { - SymmetricStaticProvider prov = - new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap()); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - - assertEquals( - encryptionKey, - prov.getDecryptionMaterials(ctx(Collections.emptyMap())) - .getDecryptionKey()); - assertEquals( - macKey, - prov.getDecryptionMaterials(ctx(Collections.emptyMap())) - .getVerificationKey()); - } - - @Test - public void simpleSig() { - SymmetricStaticProvider prov = - new SymmetricStaticProvider(encryptionKey, sigPair, Collections.emptyMap()); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(sigPair.getPrivate(), prov.getEncryptionMaterials(ctx).getSigningKey()); - - assertEquals( - encryptionKey, - prov.getDecryptionMaterials(ctx(Collections.emptyMap())) - .getDecryptionKey()); - assertEquals( - sigPair.getPublic(), - prov.getDecryptionMaterials(ctx(Collections.emptyMap())) - .getVerificationKey()); - } - - @Test - public void equalDescMac() { - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue( - prov.getEncryptionMaterials(ctx) - .getMaterialDescription() - .entrySet() - .containsAll(description.entrySet())); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(description)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(description)).getVerificationKey()); - } - - @Test - public void supersetDescMac() { - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue( - prov.getEncryptionMaterials(ctx) - .getMaterialDescription() - .entrySet() - .containsAll(description.entrySet())); - - Map superSet = new HashMap(description); - superSet.put("NewValue", "super!"); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(superSet)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(superSet)).getVerificationKey()); - } - - @Test - public void subsetDescMac() { - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue( - prov.getEncryptionMaterials(ctx) - .getMaterialDescription() - .entrySet() - .containsAll(description.entrySet())); - - assertNull(prov.getDecryptionMaterials(ctx(Collections.emptyMap()))); - } - - @Test - public void noMatchDescMac() { - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue( - prov.getEncryptionMaterials(ctx) - .getMaterialDescription() - .entrySet() - .containsAll(description.entrySet())); - - Map noMatch = new HashMap(); - noMatch.put("NewValue", "no match!"); - - assertNull(prov.getDecryptionMaterials(ctx(noMatch))); - } - - @Test - public void testRefresh() { - // This does nothing, make sure we don't throw and exception. - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - prov.refresh(); - } - - @SuppressWarnings("unused") - private static EncryptionContext ctx(EncryptionMaterials mat) { - return ctx(mat.getMaterialDescription()); - } - - private static EncryptionContext ctx(Map desc) { - return EncryptionContext.builder() - .materialDescription(desc).build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java deleted file mode 100644 index 5f82b47dd8..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java +++ /dev/null @@ -1,414 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -public class WrappedMaterialsProviderTest { - private static SecretKey symEncryptionKey; - private static SecretKey macKey; - private static KeyPair sigPair; - private static KeyPair encryptionPair; - private static SecureRandom rnd; - private Map description; - private EncryptionContext ctx; - - @BeforeClass - public static void setUpClass() throws NoSuchAlgorithmException { - rnd = new SecureRandom(); - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, rnd); - sigPair = rsaGen.generateKeyPair(); - encryptionPair = rsaGen.generateKeyPair(); - - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, rnd); - symEncryptionKey = aesGen.generateKey(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, rnd); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - ctx = new EncryptionContext.Builder().build(); - } - - @Test - public void simpleMac() { - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void simpleSigPair() { - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - symEncryptionKey, symEncryptionKey, sigPair, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void randomEnvelopeKeys() { - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey2 = eMat2.getEncryptionKey(); - assertEquals(macKey, eMat.getSigningKey()); - - assertFalse( - "Envelope keys must be different", contentEncryptionKey.equals(contentEncryptionKey2)); - } - - @Test - public void testRefresh() { - // This does nothing, make sure we don't throw an exception. - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); - prov.refresh(); - } - - @Test - public void wrapUnwrapAsymMatExplicitWrappingAlgorithmPkcs1() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM, "RSA/ECB/PKCS1Padding"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "RSA/ECB/PKCS1Padding", - eMat.getMaterialDescription().get(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapAsymMatExplicitWrappingAlgorithmPkcs2() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM, "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", - eMat.getMaterialDescription().get(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapAsymMatExplicitContentKeyAlgorithm() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), - encryptionPair.getPrivate(), - sigPair, - Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapAsymMatExplicitContentKeyLength128() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(16, contentEncryptionKey.getEncoded().length); // 128 Bits - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapAsymMatExplicitContentKeyLength256() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(32, contentEncryptionKey.getEncoded().length); // 256 Bits - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void unwrapAsymMatExplicitEncAlgAes128() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - // Get materials we can test unwrapping on - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - - // Ensure "AES/128" on the created materials creates the expected key - Map aes128Desc = eMat.getMaterialDescription(); - aes128Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - EncryptionContext aes128Ctx = - new EncryptionContext.Builder().materialDescription(aes128Desc).build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(aes128Ctx); - assertEquals( - "AES/128", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void unwrapAsymMatExplicitEncAlgAes256() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - // Get materials we can test unwrapping on - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - - // Ensure "AES/256" on the created materials creates the expected key - Map aes256Desc = eMat.getMaterialDescription(); - aes256Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - EncryptionContext aes256Ctx = - new EncryptionContext.Builder().materialDescription(aes256Desc).build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(aes256Ctx); - assertEquals( - "AES/256", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapSymMatExplicitContentKeyAlgorithm() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapSymMatExplicitContentKeyLength128() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(16, contentEncryptionKey.getEncoded().length); // 128 Bits - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapSymMatExplicitContentKeyLength256() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(32, contentEncryptionKey.getEncoded().length); // 256 Bits - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void unwrapSymMatExplicitEncAlgAes128() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - // Get materials we can test unwrapping on - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - - // Ensure "AES/128" on the created materials creates the expected key - Map aes128Desc = eMat.getMaterialDescription(); - aes128Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - EncryptionContext aes128Ctx = - new EncryptionContext.Builder().materialDescription(aes128Desc).build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(aes128Ctx); - assertEquals( - "AES/128", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void unwrapSymMatExplicitEncAlgAes256() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - - Map aes256Desc = eMat.getMaterialDescription(); - aes256Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - EncryptionContext aes256Ctx = - new EncryptionContext.Builder().materialDescription(aes256Desc).build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(aes256Ctx); - assertEquals( - "AES/256", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - private static EncryptionContext ctx(EncryptionMaterials mat) { - return new EncryptionContext.Builder() - .materialDescription(mat.getMaterialDescription()) - .build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java deleted file mode 100644 index 3449908a6d..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.fail; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttributeValueBuilder; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.LocalDynamoDb; - -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; - -public class MetaStoreTests { - private static final String SOURCE_TABLE_NAME = "keystoreTable"; - private static final String DESTINATION_TABLE_NAME = "keystoreDestinationTable"; - private static final String MATERIAL_NAME = "material"; - private static final SecretKey AES_KEY = new SecretKeySpec(new byte[] { 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, "AES"); - private static final SecretKey TARGET_AES_KEY = new SecretKeySpec(new byte[] { 0, - 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, "AES"); - private static final SecretKey HMAC_KEY = new SecretKeySpec(new byte[] { 0, - 1, 2, 3, 4, 5, 6, 7 }, "HmacSHA256"); - private static final SecretKey TARGET_HMAC_KEY = new SecretKeySpec(new byte[] { 0, - 2, 4, 6, 8, 10, 12, 14 }, "HmacSHA256"); - private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); - private static final EncryptionMaterialsProvider TARGET_BASE_PROVIDER = new SymmetricStaticProvider(TARGET_AES_KEY, TARGET_HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); - private static final DynamoDbEncryptor TARGET_ENCRYPTOR = DynamoDbEncryptor.getInstance(TARGET_BASE_PROVIDER); - - private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); - private final LocalDynamoDb targetLocalDynamoDb = new LocalDynamoDb(); - private DynamoDbClient client; - private DynamoDbClient targetClient; - private MetaStore store; - private MetaStore targetStore; - private EncryptionContext ctx; - - private static class TestExtraDataSupplier implements MetaStore.ExtraDataSupplier { - - private final Map attributeValueMap; - private final Set signedOnlyFieldNames; - - TestExtraDataSupplier(final Map attributeValueMap, - final Set signedOnlyFieldNames) { - this.attributeValueMap = attributeValueMap; - this.signedOnlyFieldNames = signedOnlyFieldNames; - } - - @Override - public Map getAttributes(String materialName, long version) { - return this.attributeValueMap; - } - - @Override - public Set getSignedOnlyFieldNames() { - return this.signedOnlyFieldNames; - } - } - - @BeforeMethod - public void setup() { - localDynamoDb.start(); - targetLocalDynamoDb.start(); - client = localDynamoDb.createClient(); - targetClient = targetLocalDynamoDb.createClient(); - - MetaStore.createTable(client, SOURCE_TABLE_NAME, ProvisionedThroughput.builder() - .readCapacityUnits(1L) - .writeCapacityUnits(1L) - .build()); - //Creating Targeted DynamoDB Object - MetaStore.createTable(targetClient, DESTINATION_TABLE_NAME, ProvisionedThroughput.builder() - .readCapacityUnits(1L) - .writeCapacityUnits(1L) - .build()); - store = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR); - targetStore = new MetaStore(targetClient, DESTINATION_TABLE_NAME, TARGET_ENCRYPTOR); - ctx = EncryptionContext.builder().build(); - } - - @AfterMethod - public void stopLocalDynamoDb() { - localDynamoDb.stop(); - targetLocalDynamoDb.stop(); - } - - @Test - public void testNoMaterials() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - } - - @Test - public void singleMaterial() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov = store.newProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void singleMaterialExplicitAccess() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = store.getProvider(MATERIAL_NAME); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void singleMaterialExplicitAccessWithVersion() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = store.getProvider(MATERIAL_NAME, 0); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void singleMaterialWithImplicitCreation() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov = store.getProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void twoDifferentMaterials() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = store.newProvider(MATERIAL_NAME); - assertEquals(1, store.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - try { - prov2.getDecryptionMaterials(ctx(eMat)); - fail("Missing expected exception"); - } catch (final DynamoDbEncryptionException ex) { - // Expected Exception - } - final EncryptionMaterials eMat2 = prov2.getEncryptionMaterials(ctx); - assertEquals(1, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - } - - @Test - public void getOrCreateCollision() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = store.getOrCreate(MATERIAL_NAME, 0); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = store.getOrCreate(MATERIAL_NAME, 0); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void getOrCreateWithContextSupplier() { - final Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("CustomKeyId", AttributeValueBuilder.ofS("testCustomKeyId")); - attributeValueMap.put("KeyToken", AttributeValueBuilder.ofS("testKeyToken")); - - final Set signedOnlyAttributes = new HashSet<>(); - signedOnlyAttributes.add("CustomKeyId"); - - final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( - attributeValueMap, signedOnlyAttributes); - - final MetaStore metaStore = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); - - assertEquals(-1, metaStore.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = metaStore.getOrCreate(MATERIAL_NAME, 0); - assertEquals(0, metaStore.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = metaStore.getOrCreate(MATERIAL_NAME, 0); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void replicateIntermediateKeysTest() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterialsProvider prov1 = store.getOrCreate(MATERIAL_NAME, 0); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - - store.replicate(MATERIAL_NAME, 0, targetStore); - assertEquals(0, targetStore.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final DecryptionMaterials dMat = targetStore.getProvider(MATERIAL_NAME, 0).getDecryptionMaterials(ctx(eMat)); - - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test(expectedExceptions = IndexOutOfBoundsException.class) - public void replicateIntermediateKeysWhenMaterialNotFoundTest() { - store.replicate(MATERIAL_NAME, 0, targetStore); - } - - @Test - public void newProviderCollision() throws InterruptedException { - final SlowNewProvider slowProv = new SlowNewProvider(); - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - assertEquals(-1, slowProv.slowStore.getMaxVersion(MATERIAL_NAME)); - - slowProv.start(); - Thread.sleep(100); - final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); - slowProv.join(); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - assertEquals(0, slowProv.slowStore.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = slowProv.result; - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test(expectedExceptions= IndexOutOfBoundsException.class) - public void invalidVersion() { - store.getProvider(MATERIAL_NAME, 1000); - } - - @Test(expectedExceptions= IllegalArgumentException.class) - public void invalidSignedOnlyField() { - final Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("enc", AttributeValueBuilder.ofS("testEncryptionKey")); - - final Set signedOnlyAttributes = new HashSet<>(); - signedOnlyAttributes.add("enc"); - - final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( - attributeValueMap, signedOnlyAttributes); - - new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); - } - - private static EncryptionContext ctx(final EncryptionMaterials mat) { - return EncryptionContext.builder() - .materialDescription(mat.getMaterialDescription()).build(); - } - - private class SlowNewProvider extends Thread { - public volatile EncryptionMaterialsProvider result; - public ProviderStore slowStore = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR) { - @Override - public EncryptionMaterialsProvider newProvider(final String materialName) { - final long nextId = getMaxVersion(materialName) + 1; - try { - Thread.sleep(1000); - } catch (final InterruptedException e) { - // Ignored - } - return getOrCreate(materialName, nextId); - } - }; - - @Override - public void run() { - result = slowStore.newProvider(MATERIAL_NAME); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java deleted file mode 100644 index 2ed128e9d3..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java +++ /dev/null @@ -1,164 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils; - -import static org.testng.AssertJUnit.assertEquals; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableName; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableNameUsingMap; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import org.testng.annotations.Test; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; - -public class EncryptionContextOperatorsTest { - - @Test - public void testCreateEncryptionContextTableNameOverride_expectedOverride() { - Function myNewTableName = overrideEncryptionContextTableName("OriginalTableName", "MyNewTableName"); - - EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName").build(); - - EncryptionContext newContext = myNewTableName.apply(context); - - assertEquals("OriginalTableName", context.getTableName()); - assertEquals("MyNewTableName", newContext.getTableName()); - } - - /** - * Some pretty clear repetition in null cases. May make sense to replace with data providers or parameterized - * classes for null cases - */ - @Test - public void testNullCasesCreateEncryptionContextTableNameOverride_nullOriginalTableName() { - assertEncryptionContextUnchanged(EncryptionContext.builder().tableName("example").build(), - null, - "MyNewTableName"); - } - - @Test - public void testCreateEncryptionContextTableNameOverride_differentOriginalTableName() { - assertEncryptionContextUnchanged(EncryptionContext.builder().tableName("example").build(), - "DifferentTableName", - "MyNewTableName"); - } - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverride_nullEncryptionContext() { - assertEncryptionContextUnchanged(null, - "DifferentTableName", - "MyNewTableName"); - } - - @Test - public void testCreateEncryptionContextTableNameOverrideMap_expectedOverride() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("OriginalTableName", "MyNewTableName"); - - - Function nameOverrideMap = - overrideEncryptionContextTableNameUsingMap(tableNameOverrides); - - EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName").build(); - - EncryptionContext newContext = nameOverrideMap.apply(context); - - assertEquals("OriginalTableName", context.getTableName()); - assertEquals("MyNewTableName", newContext.getTableName()); - } - - @Test - public void testCreateEncryptionContextTableNameOverrideMap_multipleOverrides() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("OriginalTableName1", "MyNewTableName1"); - tableNameOverrides.put("OriginalTableName2", "MyNewTableName2"); - - - Function overrideOperator = - overrideEncryptionContextTableNameUsingMap(tableNameOverrides); - - EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName1").build(); - - EncryptionContext newContext = overrideOperator.apply(context); - - assertEquals("OriginalTableName1", context.getTableName()); - assertEquals("MyNewTableName1", newContext.getTableName()); - - EncryptionContext context2 = EncryptionContext.builder().tableName("OriginalTableName2").build(); - - EncryptionContext newContext2 = overrideOperator.apply(context2); - - assertEquals("OriginalTableName2", context2.getTableName()); - assertEquals("MyNewTableName2", newContext2.getTableName()); - - } - - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullEncryptionContextTableName() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("DifferentTableName", "MyNewTableName"); - assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().build(), - tableNameOverrides); - } - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullEncryptionContext() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("DifferentTableName", "MyNewTableName"); - assertEncryptionContextUnchangedFromMap(null, - tableNameOverrides); - } - - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullOriginalTableName() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put(null, "MyNewTableName"); - assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("example").build(), - tableNameOverrides); - } - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullNewTableName() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("MyOriginalTableName", null); - assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("MyOriginalTableName").build(), - tableNameOverrides); - } - - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullMap() { - assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("MyOriginalTableName").build(), - null); - } - - - private void assertEncryptionContextUnchanged(EncryptionContext encryptionContext, String originalTableName, String newTableName) { - Function encryptionContextTableNameOverride = overrideEncryptionContextTableName(originalTableName, newTableName); - EncryptionContext newEncryptionContext = encryptionContextTableNameOverride.apply(encryptionContext); - assertEquals(encryptionContext, newEncryptionContext); - } - - - private void assertEncryptionContextUnchangedFromMap(EncryptionContext encryptionContext, Map overrideMap) { - Function encryptionContextTableNameOverrideFromMap = overrideEncryptionContextTableNameUsingMap(overrideMap); - EncryptionContext newEncryptionContext = encryptionContextTableNameOverrideFromMap.apply(encryptionContext); - assertEquals(encryptionContext, newEncryptionContext); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java deleted file mode 100644 index e098816275..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.fail; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller.marshall; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller.unmarshall; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static java.util.Collections.unmodifiableList; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttributeValueBuilder; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -public class AttributeValueMarshallerTest { - @Test(expectedExceptions = IllegalArgumentException.class) - public void testEmpty() { - AttributeValue av = AttributeValue.builder().build(); - marshall(av); - } - - @Test - public void testNumber() { - AttributeValue av = AttributeValue.builder().n("1337").build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testString() { - AttributeValue av = AttributeValue.builder().s("1337").build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testByteBuffer() { - AttributeValue av = AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - // We can't use straight .equals for comparison because Attribute Values represents Sets - // as Lists and so incorrectly does an ordered comparison - - @Test - public void testNumberS() { - AttributeValue av = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1337", "1", "5"))).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testNumberSOrdering() { - AttributeValue av1 = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1337", "1", "5"))).build(); - AttributeValue av2 = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1", "5", "1337"))).build(); - assertAttributesAreEqual(av1, av2); - ByteBuffer buff1 = marshall(av1); - ByteBuffer buff2 = marshall(av2); - assertEquals(buff1, buff2); - } - - @Test - public void testStringS() { - AttributeValue av = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Bob", "Ann", "5"))).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testStringSOrdering() { - AttributeValue av1 = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Bob", "Ann", "5"))).build(); - AttributeValue av2 = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Ann", "Bob", "5"))).build(); - assertAttributesAreEqual(av1, av2); - ByteBuffer buff1 = marshall(av1); - ByteBuffer buff2 = marshall(av2); - assertEquals(buff1, buff2); - } - - @Test - public void testByteBufferS() { - AttributeValue av = AttributeValue.builder().bs(unmodifiableList( - Arrays.asList(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5}), - SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7})))).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testByteBufferSOrdering() { - AttributeValue av1 = AttributeValue.builder().bs(unmodifiableList( - Arrays.asList(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5}), - SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7})))).build(); - AttributeValue av2 = AttributeValue.builder().bs(unmodifiableList( - Arrays.asList(SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7}), - SdkBytes.fromByteArray(new byte[]{0, 1, 2, 3, 4, 5})))).build(); - - assertAttributesAreEqual(av1, av2); - ByteBuffer buff1 = marshall(av1); - ByteBuffer buff2 = marshall(av2); - assertEquals(buff1, buff2); - } - - @Test - public void testBoolTrue() { - AttributeValue av = AttributeValue.builder().bool(Boolean.TRUE).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testBoolFalse() { - AttributeValue av = AttributeValue.builder().bool(Boolean.FALSE).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testNULL() { - AttributeValue av = AttributeValue.builder().nul(Boolean.TRUE).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test(expectedExceptions = NullPointerException.class) - public void testActualNULL() { - unmarshall(marshall(null)); - } - - @Test - public void testEmptyList() { - AttributeValue av = AttributeValue.builder().l(emptyList()).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testListOfString() { - AttributeValue av = - AttributeValue.builder().l(singletonList(AttributeValue.builder().s("StringValue").build())).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testList() { - AttributeValue av = AttributeValueBuilder.ofL( - AttributeValueBuilder.ofS("StringValue"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofBool(Boolean.TRUE)); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testListWithNull() { - final AttributeValue av = AttributeValueBuilder.ofL( - AttributeValueBuilder.ofS("StringValue"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofBool(Boolean.TRUE), - null); - - try { - marshall(av); - } catch (NullPointerException e) { - assertThat(e.getMessage(), - startsWith("Encountered null list entry value while marshalling attribute value")); - } - } - - @Test - public void testListDuplicates() { - AttributeValue av = AttributeValueBuilder.ofL( - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofN("1000")); - AttributeValue result = unmarshall(marshall(av)); - assertAttributesAreEqual(av, result); - assertEquals(4, result.l().size()); - } - - @Test - public void testComplexList() { - final List list1 = Arrays.asList( - AttributeValueBuilder.ofS("StringValue"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofBool(Boolean.TRUE)); - final List list22 = Arrays.asList( - AttributeValueBuilder.ofS("AWS"), - AttributeValueBuilder.ofN("-3700"), - AttributeValueBuilder.ofBool(Boolean.FALSE)); - final List list2 = Arrays.asList( - AttributeValueBuilder.ofL(list22), - AttributeValueBuilder.ofNull()); - AttributeValue av = AttributeValueBuilder.ofL( - AttributeValueBuilder.ofS("StringValue1"), - AttributeValueBuilder.ofL(list1), - AttributeValueBuilder.ofN("50"), - AttributeValueBuilder.ofL(list2)); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testEmptyMap() { - Map map = new HashMap<>(); - AttributeValue av = AttributeValueBuilder.ofM(map); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testSimpleMap() { - Map map = new HashMap<>(); - map.put("KeyValue", AttributeValueBuilder.ofS("ValueValue")); - AttributeValue av = AttributeValueBuilder.ofM(map); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testSimpleMapWithNull() { - final Map map = new HashMap<>(); - map.put("KeyValue", AttributeValueBuilder.ofS("ValueValue")); - map.put("NullKeyValue", null); - - final AttributeValue av = AttributeValueBuilder.ofM(map); - - try { - marshall(av); - fail("NullPointerException should have been thrown"); - } catch (NullPointerException e) { - assertThat(e.getMessage(), startsWith("Encountered null map value for key NullKeyValue while marshalling " - + "attribute value")); - } - } - - @Test - public void testMapOrdering() { - LinkedHashMap m1 = new LinkedHashMap<>(); - LinkedHashMap m2 = new LinkedHashMap<>(); - - m1.put("Value1", AttributeValueBuilder.ofN("1")); - m1.put("Value2", AttributeValueBuilder.ofBool(Boolean.TRUE)); - - m2.put("Value2", AttributeValueBuilder.ofBool(Boolean.TRUE)); - m2.put("Value1", AttributeValueBuilder.ofN("1")); - - AttributeValue av1 = AttributeValueBuilder.ofM(m1); - AttributeValue av2 = AttributeValueBuilder.ofM(m2); - - ByteBuffer buff1 = marshall(av1); - ByteBuffer buff2 = marshall(av2); - assertEquals(buff1, buff2); - assertAttributesAreEqual(av1, unmarshall(buff1)); - assertAttributesAreEqual(av1, unmarshall(buff2)); - assertAttributesAreEqual(av2, unmarshall(buff1)); - assertAttributesAreEqual(av2, unmarshall(buff2)); - } - - @Test - public void testComplexMap() { - AttributeValue av = buildComplexAttributeValue(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - // This test ensures that an AttributeValue marshalled by an older - // version of this library still unmarshalls correctly. It also - // ensures that old and new marshalling is identical. - @Test - public void testVersioningCompatibility() { - AttributeValue newObject = buildComplexAttributeValue(); - byte[] oldBytes = Base64.getDecoder().decode(COMPLEX_ATTRIBUTE_MARSHALLED); - byte[] newBytes = marshall(newObject).array(); - assertThat(oldBytes, is(newBytes)); - - AttributeValue oldObject = unmarshall(ByteBuffer.wrap(oldBytes)); - assertAttributesAreEqual(oldObject, newObject); - } - - private static final String COMPLEX_ATTRIBUTE_MARSHALLED = "AE0AAAADAHM" + - "AAAAJSW5uZXJMaXN0AEwAAAAGAHMAAAALQ29tcGxleExpc3QAbgAAAAE1AGIAA" + - "AAGAAECAwQFAEwAAAAFAD8BAAAAAABMAAAAAQA/AABNAAAAAwBzAAAABFBpbms" + - "AcwAAAAVGbG95ZABzAAAABFRlc3QAPwEAcwAAAAdWZXJzaW9uAG4AAAABMQAAA" + - "E0AAAADAHMAAAAETGlzdABMAAAABQBuAAAAATUAbgAAAAE0AG4AAAABMwBuAAA" + - "AATIAbgAAAAExAHMAAAADTWFwAE0AAAABAHMAAAAGTmVzdGVkAD8BAHMAAAAEV" + - "HJ1ZQA/AQBzAAAACVNpbmdsZU1hcABNAAAAAQBzAAAAA0ZPTwBzAAAAA0JBUgB" + - "zAAAACVN0cmluZ1NldABTAAAAAwAAAANiYXIAAAADYmF6AAAAA2Zvbw=="; - - private static AttributeValue buildComplexAttributeValue() { - Map floydMap = new HashMap<>(); - floydMap.put("Pink", AttributeValueBuilder.ofS("Floyd")); - floydMap.put("Version", AttributeValueBuilder.ofN("1")); - floydMap.put("Test", AttributeValueBuilder.ofBool(Boolean.TRUE)); - List floydList = Arrays.asList( - AttributeValueBuilder.ofBool(Boolean.TRUE), - AttributeValueBuilder.ofNull(), - AttributeValueBuilder.ofNull(), - AttributeValueBuilder.ofL(AttributeValueBuilder.ofBool(Boolean.FALSE)), - AttributeValueBuilder.ofM(floydMap) - ); - - List nestedList = Arrays.asList( - AttributeValueBuilder.ofN("5"), - AttributeValueBuilder.ofN("4"), - AttributeValueBuilder.ofN("3"), - AttributeValueBuilder.ofN("2"), - AttributeValueBuilder.ofN("1") - ); - Map nestedMap = new HashMap<>(); - nestedMap.put("True", AttributeValueBuilder.ofBool(Boolean.TRUE)); - nestedMap.put("List", AttributeValueBuilder.ofL(nestedList)); - nestedMap.put("Map", AttributeValueBuilder.ofM( - Collections.singletonMap("Nested", - AttributeValueBuilder.ofBool(Boolean.TRUE)))); - - List innerList = Arrays.asList( - AttributeValueBuilder.ofS("ComplexList"), - AttributeValueBuilder.ofN("5"), - AttributeValueBuilder.ofB(new byte[] {0, 1, 2, 3, 4, 5}), - AttributeValueBuilder.ofL(floydList), - AttributeValueBuilder.ofNull(), - AttributeValueBuilder.ofM(nestedMap) - ); - - Map result = new HashMap<>(); - result.put("SingleMap", AttributeValueBuilder.ofM( - Collections.singletonMap("FOO", AttributeValueBuilder.ofS("BAR")))); - result.put("InnerList", AttributeValueBuilder.ofL(innerList)); - result.put("StringSet", AttributeValueBuilder.ofSS("foo", "bar", "baz")); - return AttributeValue.builder().m(Collections.unmodifiableMap(result)).build(); - } - - private void assertAttributesAreEqual(AttributeValue o1, AttributeValue o2) { - assertEquals(o1.b(), o2.b()); - assertSetsEqual(o1.bs(), o2.bs()); - assertEquals(o1.n(), o2.n()); - assertSetsEqual(o1.ns(), o2.ns()); - assertEquals(o1.s(), o2.s()); - assertSetsEqual(o1.ss(), o2.ss()); - assertEquals(o1.bool(), o2.bool()); - assertEquals(o1.nul(), o2.nul()); - - if (o1.l() != null) { - assertNotNull(o2.l()); - final List l1 = o1.l(); - final List l2 = o2.l(); - assertEquals(l1.size(), l2.size()); - for (int x = 0; x < l1.size(); ++x) { - assertAttributesAreEqual(l1.get(x), l2.get(x)); - } - } - - if (o1.m() != null) { - assertNotNull(o2.m()); - final Map m1 = o1.m(); - final Map m2 = o2.m(); - assertEquals(m1.size(), m2.size()); - for (Map.Entry entry : m1.entrySet()) { - assertAttributesAreEqual(entry.getValue(), m2.get(entry.getKey())); - } - } - } - - private void assertSetsEqual(Collection c1, Collection c2) { - assertFalse(c1 == null ^ c2 == null); - if (c1 != null) { - Set s1 = new HashSet<>(c1); - Set s2 = new HashSet<>(c2); - assertEquals(s1, s2); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java deleted file mode 100644 index 4ec9c03ae4..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java +++ /dev/null @@ -1,93 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.quicktheories.QuickTheory.qt; -import static org.quicktheories.generators.Generate.byteArrays; -import static org.quicktheories.generators.Generate.bytes; -import static org.quicktheories.generators.SourceDSL.integers; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import org.apache.commons.lang3.StringUtils; -import org.testng.annotations.Test; - -public class Base64Tests { - @Test - public void testBase64EncodeEquivalence() { - qt().forAll( - byteArrays( - integers().between(0, 1000000), bytes(Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0))) - .check( - (a) -> { - // Base64 encode using both implementations and check for equality of output - // in case one version produces different output - String sdkV1Base64 = com.amazonaws.util.Base64.encodeAsString(a); - String encryptionClientBase64 = Base64.encodeToString(a); - return StringUtils.equals(sdkV1Base64, encryptionClientBase64); - }); - } - - @Test - public void testBase64DecodeEquivalence() { - qt().forAll( - byteArrays( - integers().between(0, 10000), bytes(Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0))) - .as((b) -> java.util.Base64.getMimeEncoder().encodeToString(b)) - .check( - (s) -> { - // Check for equality using the MimeEncoder, which inserts newlines - // The encryptionClient's decoder is expected to ignore them - byte[] sdkV1Bytes = com.amazonaws.util.Base64.decode(s); - byte[] encryptionClientBase64 = Base64.decode(s); - return Arrays.equals(sdkV1Bytes, encryptionClientBase64); - }); - } - - @Test - public void testNullDecodeBehavior() { - byte[] decoded = Base64.decode(null); - assertThat(decoded, equalTo(null)); - } - - @Test - public void testNullDecodeBehaviorSdk1() { - byte[] decoded = com.amazonaws.util.Base64.decode((String) null); - assertThat(decoded, equalTo(null)); - - byte[] decoded2 = com.amazonaws.util.Base64.decode((byte[]) null); - assertThat(decoded2, equalTo(null)); - } - - @Test - public void testBase64PaddingBehavior() { - String testInput = "another one bites the dust"; - String expectedEncoding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q="; - assertThat( - Base64.encodeToString(testInput.getBytes(StandardCharsets.UTF_8)), - equalTo(expectedEncoding)); - - String encodingWithoutPadding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q"; - assertThat(Base64.decode(encodingWithoutPadding), equalTo(testInput.getBytes())); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testBase64PaddingBehaviorSdk1() { - String testInput = "another one bites the dust"; - String encodingWithoutPadding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q"; - com.amazonaws.util.Base64.decode(encodingWithoutPadding); - } - - @Test - public void rfc4648TestVectors() { - assertThat(Base64.encodeToString("".getBytes(StandardCharsets.UTF_8)), equalTo("")); - assertThat(Base64.encodeToString("f".getBytes(StandardCharsets.UTF_8)), equalTo("Zg==")); - assertThat(Base64.encodeToString("fo".getBytes(StandardCharsets.UTF_8)), equalTo("Zm8=")); - assertThat(Base64.encodeToString("foo".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9v")); - assertThat(Base64.encodeToString("foob".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYg==")); - assertThat( - Base64.encodeToString("fooba".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYmE=")); - assertThat( - Base64.encodeToString("foobar".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYmFy")); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java deleted file mode 100644 index 71b90c195e..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import org.testng.annotations.Test; - -public class ByteBufferInputStreamTest { - - @Test - public void testRead() throws IOException { - ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); - for (int x = 0; x < 10; ++x) { - assertEquals(10 - x, bis.available()); - assertEquals(x, bis.read()); - } - assertEquals(0, bis.available()); - bis.close(); - } - - @Test - public void testReadByteArray() throws IOException { - ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); - assertEquals(10, bis.available()); - - byte[] buff = new byte[4]; - - int len = bis.read(buff); - assertEquals(4, len); - assertEquals(6, bis.available()); - assertThat(buff, is(new byte[] {0, 1, 2, 3})); - - len = bis.read(buff); - assertEquals(4, len); - assertEquals(2, bis.available()); - assertThat(buff, is(new byte[] {4, 5, 6, 7})); - - len = bis.read(buff); - assertEquals(2, len); - assertEquals(0, bis.available()); - assertThat(buff, is(new byte[] {8, 9, 6, 7})); - bis.close(); - } - - @Test - public void testSkip() throws IOException { - ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{(byte) 0xFA, 15, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); - assertEquals(13, bis.available()); - assertEquals(0xFA, bis.read()); - assertEquals(12, bis.available()); - bis.skip(2); - assertEquals(10, bis.available()); - for (int x = 0; x < 10; ++x) { - assertEquals(x, bis.read()); - } - assertEquals(0, bis.available()); - assertEquals(-1, bis.read()); - bis.close(); - } - - @Test - public void testMarkSupported() throws IOException { - try (ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.allocate(0))) { - assertFalse(bis.markSupported()); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java deleted file mode 100644 index 7fcb5b89ab..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java +++ /dev/null @@ -1,244 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import edu.umd.cs.mtc.MultithreadedTestCase; -import edu.umd.cs.mtc.TestFramework; -import java.util.concurrent.TimeUnit; -import org.testng.annotations.Test; - -/* Test specific thread interleavings with behaviors we care about in the - * TTLCache. - */ -public class ConcurrentTTLCacheTest { - - private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); - private static final long ttlInMillis = 1000; - - @Test - public void testGracePeriodCase() throws Throwable { - TestFramework.runOnce(new GracePeriodCase()); - } - - @Test - public void testExpiredCase() throws Throwable { - TestFramework.runOnce(new ExpiredCase()); - } - - @Test - public void testNewEntryCase() throws Throwable { - TestFramework.runOnce(new NewEntryCase()); - } - - @Test - public void testPutLoadCase() throws Throwable { - TestFramework.runOnce(new PutLoadCase()); - } - - // Ensure the loader is only called once if two threads attempt to load during the grace period - class GracePeriodCase extends MultithreadedTestCase { - TTLCache cache; - TTLCache.EntryLoader loader; - MsClock clock = mock(MsClock.class); - - @Override - public void initialize() { - loader = - spy( - new TTLCache.EntryLoader() { - @Override - public String load(String entryKey) { - // Wait until thread2 finishes to complete load - waitForTick(2); - return "loadedValue"; - } - }); - when(clock.timestampNano()).thenReturn((long) 0); - cache = new TTLCache<>(3, ttlInMillis, loader); - cache.clock = clock; - - // Put an initial value into the cache at time 0 - cache.put("k1", "v1"); - } - - // The thread that first calls load in the grace period and acquires the lock - public void thread1() { - when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); - String loadedValue = cache.load("k1"); - assertTick(2); - // Expect to get back the value calculated from load - assertEquals("loadedValue", loadedValue); - } - - // The thread that calls load in the grace period after the lock has been acquired - public void thread2() { - // Wait until the first thread acquires the lock and starts load - waitForTick(1); - when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); - String loadedValue = cache.load("k1"); - // Expect to get back the original value in the cache - assertEquals("v1", loadedValue); - } - - @Override - public void finish() { - // Ensure the loader was only called once - verify(loader, times(1)).load("k1"); - } - } - - // Ensure the loader is only called once if two threads attempt to load an expired entry. - class ExpiredCase extends MultithreadedTestCase { - TTLCache cache; - TTLCache.EntryLoader loader; - MsClock clock = mock(MsClock.class); - - @Override - public void initialize() { - loader = - spy( - new TTLCache.EntryLoader() { - @Override - public String load(String entryKey) { - // Wait until thread2 is waiting for the lock to complete load - waitForTick(2); - return "loadedValue"; - } - }); - when(clock.timestampNano()).thenReturn((long) 0); - cache = new TTLCache<>(3, ttlInMillis, loader); - cache.clock = clock; - - // Put an initial value into the cache at time 0 - cache.put("k1", "v1"); - } - - // The thread that first calls load after expiration - public void thread1() { - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - String loadedValue = cache.load("k1"); - assertTick(2); - // Expect to get back the value calculated from load - assertEquals("loadedValue", loadedValue); - } - - // The thread that calls load after expiration, - // after the first thread calls load, but before - // the new value is put into the cache. - public void thread2() { - // Wait until the first thread acquires the lock and starts load - waitForTick(1); - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - String loadedValue = cache.load("k1"); - // Expect to get back the newly loaded value - assertEquals("loadedValue", loadedValue); - // assert that this thread only finishes once the first thread's load does - assertTick(2); - } - - @Override - public void finish() { - // Ensure the loader was only called once - verify(loader, times(1)).load("k1"); - } - } - - // Ensure the loader is only called once if two threads attempt to load the same new entry. - class NewEntryCase extends MultithreadedTestCase { - TTLCache cache; - TTLCache.EntryLoader loader; - MsClock clock = mock(MsClock.class); - - @Override - public void initialize() { - loader = - spy( - new TTLCache.EntryLoader() { - @Override - public String load(String entryKey) { - // Wait until thread2 is blocked to complete load - waitForTick(2); - return "loadedValue"; - } - }); - when(clock.timestampNano()).thenReturn((long) 0); - cache = new TTLCache<>(3, ttlInMillis, loader); - cache.clock = clock; - } - - // The thread that first calls load - public void thread1() { - String loadedValue = cache.load("k1"); - assertTick(2); - // Expect to get back the value calculated from load - assertEquals("loadedValue", loadedValue); - } - - // The thread that calls load after the first thread calls load, - // but before the new value is put into the cache. - public void thread2() { - // Wait until the first thread acquires the lock and starts load - waitForTick(1); - String loadedValue = cache.load("k1"); - // Expect to get back the newly loaded value - assertEquals("loadedValue", loadedValue); - // assert that this thread only finishes once the first thread's load does - assertTick(2); - } - - @Override - public void finish() { - // Ensure the loader was only called once - verify(loader, times(1)).load("k1"); - } - } - - // Ensure the loader blocks put on load/put of the same new entry - class PutLoadCase extends MultithreadedTestCase { - TTLCache cache; - TTLCache.EntryLoader loader; - MsClock clock = mock(MsClock.class); - - @Override - public void initialize() { - loader = - spy( - new TTLCache.EntryLoader() { - @Override - public String load(String entryKey) { - // Wait until the put blocks to complete load - waitForTick(2); - return "loadedValue"; - } - }); - when(clock.timestampNano()).thenReturn((long) 0); - cache = new TTLCache<>(3, ttlInMillis, loader); - cache.clock = clock; - } - - // The thread that first calls load - public void thread1() { - String loadedValue = cache.load("k1"); - // Expect to get back the value calculated from load - assertEquals("loadedValue", loadedValue); - verify(loader, times(1)).load("k1"); - } - - // The thread that calls put during the first thread's load - public void thread2() { - // Wait until the first thread is loading - waitForTick(1); - String previousValue = cache.put("k1", "v1"); - // Expect to get back the value loaded into the cache by thread1 - assertEquals("loadedValue", previousValue); - // assert that this thread was blocked by the first thread - assertTick(2); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java deleted file mode 100644 index b9fdcb1d09..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.testng.AssertJUnit.assertArrayEquals; - -import org.testng.annotations.Test; - -public class HkdfTests { - private static final testCase[] testCases = - new testCase[] { - new testCase( - "HmacSHA256", - fromCHex( - "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" - + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), - fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), - fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), - fromHex( - "3CB25F25FAACD57A90434F64D0362F2A2D2D0A90CF1A5A4C5DB02D56ECC4C5BF34007208D5B887185865")), - new testCase( - "HmacSHA256", - fromCHex( - "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" - + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" - + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" - + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" - + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" - + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), - fromCHex( - "\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d" - + "\\x6e\\x6f\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b" - + "\\x7c\\x7d\\x7e\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" - + "\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" - + "\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5" - + "\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf"), - fromCHex( - "\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd" - + "\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb" - + "\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9" - + "\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7" - + "\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5" - + "\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff"), - fromHex( - "B11E398DC80327A1C8E7F78C596A4934" - + "4F012EDA2D4EFAD8A050CC4C19AFA97C" - + "59045A99CAC7827271CB41C65E590E09" - + "DA3275600C2F09B8367793A9ACA3DB71" - + "CC30C58179EC3E87C14C01D5C1F3434F" - + "1D87")), - new testCase( - "HmacSHA256", - fromCHex( - "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" - + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), - new byte[0], - new byte[0], - fromHex( - "8DA4E775A563C18F715F802A063C5A31" - + "B8A11F5C5EE1879EC3454E5F3C738D2D" - + "9D201395FAA4B61A96C8")), - new testCase( - "HmacSHA1", - fromCHex("\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), - fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), - fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), - fromHex( - "085A01EA1B10F36933068B56EFA5AD81" - + "A4F14B822F5B091568A9CDD4F155FDA2" - + "C22E422478D305F3F896")), - new testCase( - "HmacSHA1", - fromCHex( - "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" - + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" - + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" - + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" - + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" - + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), - fromCHex( - "\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6A\\x6B\\x6C\\x6D" - + "\\x6E\\x6F\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7A\\x7B" - + "\\x7C\\x7D\\x7E\\x7F\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" - + "\\x8A\\x8B\\x8C\\x8D\\x8E\\x8F\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" - + "\\x98\\x99\\x9A\\x9B\\x9C\\x9D\\x9E\\x9F\\xA0\\xA1\\xA2\\xA3\\xA4\\xA5" - + "\\xA6\\xA7\\xA8\\xA9\\xAA\\xAB\\xAC\\xAD\\xAE\\xAF"), - fromCHex( - "\\xB0\\xB1\\xB2\\xB3\\xB4\\xB5\\xB6\\xB7\\xB8\\xB9\\xBA\\xBB\\xBC\\xBD" - + "\\xBE\\xBF\\xC0\\xC1\\xC2\\xC3\\xC4\\xC5\\xC6\\xC7\\xC8\\xC9\\xCA\\xCB" - + "\\xCC\\xCD\\xCE\\xCF\\xD0\\xD1\\xD2\\xD3\\xD4\\xD5\\xD6\\xD7\\xD8\\xD9" - + "\\xDA\\xDB\\xDC\\xDD\\xDE\\xDF\\xE0\\xE1\\xE2\\xE3\\xE4\\xE5\\xE6\\xE7" - + "\\xE8\\xE9\\xEA\\xEB\\xEC\\xED\\xEE\\xEF\\xF0\\xF1\\xF2\\xF3\\xF4\\xF5" - + "\\xF6\\xF7\\xF8\\xF9\\xFA\\xFB\\xFC\\xFD\\xFE\\xFF"), - fromHex( - "0BD770A74D1160F7C9F12CD5912A06EB" - + "FF6ADCAE899D92191FE4305673BA2FFE" - + "8FA3F1A4E5AD79F3F334B3B202B2173C" - + "486EA37CE3D397ED034C7F9DFEB15C5E" - + "927336D0441F4C4300E2CFF0D0900B52D3B4")), - new testCase( - "HmacSHA1", - fromCHex( - "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" - + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), - new byte[0], - new byte[0], - fromHex("0AC1AF7002B3D761D1E55298DA9D0506" + "B9AE52057220A306E07B6B87E8DF21D0")), - new testCase( - "HmacSHA1", - fromCHex( - "\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c" - + "\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c"), - null, - new byte[0], - fromHex( - "2C91117204D745F3500D636A62F64F0A" - + "B3BAE548AA53D423B0D1F27EBBA6F5E5" - + "673A081D70CCE7ACFC48")) - }; - - @Test - public void rfc5869Tests() throws Exception { - for (int x = 0; x < testCases.length; x++) { - testCase trial = testCases[x]; - System.out.println("Test case A." + (x + 1)); - Hkdf kdf = Hkdf.getInstance(trial.algo); - kdf.init(trial.ikm, trial.salt); - byte[] result = kdf.deriveKey(trial.info, trial.expected.length); - assertArrayEquals("Trial A." + x, trial.expected, result); - } - } - - @Test - public void nullTests() throws Exception { - testCase trial = testCases[0]; - Hkdf kdf = Hkdf.getInstance(trial.algo); - kdf.init(trial.ikm, trial.salt); - // Just ensuring no exceptions are thrown - kdf.deriveKey((String) null, 16); - kdf.deriveKey((byte[]) null, 16); - } - - @Test - public void defaultSalt() throws Exception { - // Tests all the different ways to get the default salt - - testCase trial = testCases[0]; - Hkdf kdf1 = Hkdf.getInstance(trial.algo); - kdf1.init(trial.ikm, null); - Hkdf kdf2 = Hkdf.getInstance(trial.algo); - kdf2.init(trial.ikm, new byte[0]); - Hkdf kdf3 = Hkdf.getInstance(trial.algo); - kdf3.init(trial.ikm); - Hkdf kdf4 = Hkdf.getInstance(trial.algo); - kdf4.init(trial.ikm, new byte[32]); - - byte[] key1 = kdf1.deriveKey("Test", 16); - byte[] key2 = kdf2.deriveKey("Test", 16); - byte[] key3 = kdf3.deriveKey("Test", 16); - byte[] key4 = kdf4.deriveKey("Test", 16); - - assertArrayEquals(key1, key2); - assertArrayEquals(key1, key3); - assertArrayEquals(key1, key4); - } - - private static byte[] fromHex(String data) { - byte[] result = new byte[data.length() / 2]; - for (int x = 0; x < result.length; x++) { - result[x] = (byte) Integer.parseInt(data.substring(2 * x, 2 * x + 2), 16); - } - return result; - } - - private static byte[] fromCHex(String data) { - byte[] result = new byte[data.length() / 4]; - for (int x = 0; x < result.length; x++) { - result[x] = (byte) Integer.parseInt(data.substring(4 * x + 2, 4 * x + 4), 16); - } - return result; - } - - private static class testCase { - public final String algo; - public final byte[] ikm; - public final byte[] salt; - public final byte[] info; - public final byte[] expected; - - public testCase(String algo, byte[] ikm, byte[] salt, byte[] info, byte[] expected) { - super(); - this.algo = algo; - this.ikm = ikm; - this.salt = salt; - this.info = info; - this.expected = expected; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java deleted file mode 100644 index 8f56d35b96..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import org.testng.annotations.Test; - -public class LRUCacheTest { - @Test - public void test() { - final LRUCache cache = new LRUCache(3); - assertEquals(0, cache.size()); - assertEquals(3, cache.getMaxSize()); - cache.add("k1", "v1"); - assertTrue(cache.size() == 1); - cache.add("k1", "v11"); - assertTrue(cache.size() == 1); - cache.add("k2", "v2"); - assertTrue(cache.size() == 2); - cache.add("k3", "v3"); - assertTrue(cache.size() == 3); - assertEquals("v11", cache.get("k1")); - assertEquals("v2", cache.get("k2")); - assertEquals("v3", cache.get("k3")); - cache.add("k4", "v4"); - assertTrue(cache.size() == 3); - assertNull(cache.get("k1")); - assertEquals("v4", cache.get("k4")); - assertEquals("v2", cache.get("k2")); - assertEquals("v3", cache.get("k3")); - assertTrue(cache.size() == 3); - cache.add("k5", "v5"); - assertNull(cache.get("k4")); - assertEquals("v5", cache.get("k5")); - assertEquals("v2", cache.get("k2")); - assertEquals("v3", cache.get("k3")); - cache.clear(); - assertEquals(0, cache.size()); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testZeroSize() { - new LRUCache(0); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testIllegalArgument() { - new LRUCache(-1); - } - - @Test - public void testSingleEntry() { - final LRUCache cache = new LRUCache(1); - assertTrue(cache.size() == 0); - cache.add("k1", "v1"); - assertTrue(cache.size() == 1); - cache.add("k1", "v11"); - assertTrue(cache.size() == 1); - assertEquals("v11", cache.get("k1")); - - cache.add("k2", "v2"); - assertTrue(cache.size() == 1); - assertEquals("v2", cache.get("k2")); - assertNull(cache.get("k1")); - - cache.add("k3", "v3"); - assertTrue(cache.size() == 1); - assertEquals("v3", cache.get("k3")); - assertNull(cache.get("k2")); - } - -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java deleted file mode 100644 index 55e246f391..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import org.testng.annotations.Test; - -public class TTLCacheTest { - - private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testInvalidSize() { - final TTLCache cache = new TTLCache(0, 1000, mock(TTLCache.EntryLoader.class)); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testInvalidTTL() { - final TTLCache cache = new TTLCache(3, 0, mock(TTLCache.EntryLoader.class)); - } - - - @Test(expectedExceptions = NullPointerException.class) - public void testNullLoader() { - final TTLCache cache = new TTLCache(3, 1000, null); - } - - @Test - public void testConstructor() { - final TTLCache cache = - new TTLCache(1000, 1000, mock(TTLCache.EntryLoader.class)); - assertEquals(0, cache.size()); - assertEquals(1000, cache.getMaxSize()); - } - - @Test - public void testLoadPastMaxSize() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 1; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - cache.load("k1"); - verify(loader, times(1)).load("k1"); - assertTrue(cache.size() == 1); - - String result = cache.load("k2"); - verify(loader, times(1)).load("k2"); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - - // to verify result is in the cache, load one more time - // and expect the loader to not be called - String cachedValue = cache.load("k2"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, cachedValue); - } - - @Test - public void testLoadNoExistingEntry() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - String result = cache.load("k1"); - verify(loader, times(1)).load("k1"); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - - // to verify result is in the cache, load one more time - // and expect the loader to not be called - String cachedValue = cache.load("k1"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, cachedValue); - } - - @Test - public void testLoadNotExpired() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // when first creating the entry, time is 0 - when(clock.timestampNano()).thenReturn((long) 0); - cache.load("k1"); - assertTrue(cache.size() == 1); - verify(loader, times(1)).load("k1"); - - // on load, time is within TTL - when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis)); - String result = cache.load("k1"); - - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - } - - @Test - public void testLoadInGrace() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // when first creating the entry, time is zero - when(clock.timestampNano()).thenReturn((long) 0); - cache.load("k1"); - assertTrue(cache.size() == 1); - verify(loader, times(1)).load("k1"); - - // on load, time is past TTL but within the grace period - when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); - String result = cache.load("k1"); - - // Because this is tested in a single thread, - // this is expected to obtain the lock and load the new value - verify(loader, times(2)).load("k1"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - } - - @Test - public void testLoadExpired() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // when first creating the entry, time is zero - when(clock.timestampNano()).thenReturn((long) 0); - cache.load("k1"); - assertTrue(cache.size() == 1); - verify(loader, times(1)).load("k1"); - - // on load, time is past TTL and grace period - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - String result = cache.load("k1"); - - verify(loader, times(2)).load("k1"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - } - - @Test - public void testLoadExpiredEviction() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())) - .thenReturn(loadedValue) - .thenThrow(new IllegalStateException("This loader is mocked to throw a failure.")); - MsClock clock = mock(MsClock.class); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // when first creating the entry, time is zero - when(clock.timestampNano()).thenReturn((long) 0); - cache.load("k1"); - verify(loader, times(1)).load("k1"); - assertTrue(cache.size() == 1); - - // on load, time is past TTL and grace period - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - assertThrows(IllegalStateException.class, () -> cache.load("k1")); - - verify(loader, times(2)).load("k1"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 0); - } - - @Test - public void testLoadWithFunction() { - final String loadedValue = "loaded value"; - final String functionValue = "function value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - final Function function = spy(Function.class); - when(function.apply(any())).thenReturn(functionValue); - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())) - .thenReturn(loadedValue) - .thenThrow(new IllegalStateException("This loader is mocked to throw a failure.")); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - String result = cache.load("k1", function); - verify(function, times(1)).apply("k1"); - assertTrue(cache.size() == 1); - assertEquals(functionValue, result); - - // to verify result is in the cache, load one more time - // and expect the loader to not be called - String cachedValue = cache.load("k1"); - verifyNoMoreInteractions(function); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(functionValue, cachedValue); - } - - @Test - public void testClear() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - - assertTrue(cache.size() == 0); - cache.load("k1"); - cache.load("k2"); - assertTrue(cache.size() == 2); - - cache.clear(); - assertTrue(cache.size() == 0); - } - - @Test - public void testPut() { - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - String oldValue = cache.put("k1", "v1"); - assertNull(oldValue); - assertTrue(cache.size() == 1); - - String oldValue2 = cache.put("k1", "v11"); - assertEquals("v1", oldValue2); - assertTrue(cache.size() == 1); - } - - @Test - public void testExpiredPut() { - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // First put is at time 0 - String oldValue = cache.put("k1", "v1"); - assertNull(oldValue); - assertTrue(cache.size() == 1); - - // Second put is at time past TTL and grace period - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - String oldValue2 = cache.put("k1", "v11"); - assertNull(oldValue2); - assertTrue(cache.size() == 1); - } - - @Test - public void testPutPastMaxSize() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 1; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - cache.put("k1", "v1"); - assertTrue(cache.size() == 1); - - cache.put("k2", "v2"); - assertTrue(cache.size() == 1); - - // to verify put value is in the cache, load - // and expect the loader to not be called - String cachedValue = cache.load("k2"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(cachedValue, "v2"); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java deleted file mode 100644 index 122364e6db..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -public class AttrMatcher extends BaseMatcher> { - private final Map expected; - private final boolean invert; - - public static AttrMatcher invert(Map expected) { - return new AttrMatcher(expected, true); - } - - public static AttrMatcher match(Map expected) { - return new AttrMatcher(expected, false); - } - - public AttrMatcher(Map expected, boolean invert) { - this.expected = expected; - this.invert = invert; - } - - @Override - public boolean matches(Object item) { - @SuppressWarnings("unchecked") - Map actual = (Map)item; - if (!expected.keySet().equals(actual.keySet())) { - return invert; - } - for (String key: expected.keySet()) { - AttributeValue e = expected.get(key); - AttributeValue a = actual.get(key); - if (!attrEquals(a, e)) { - return invert; - } - } - return !invert; - } - - public static boolean attrEquals(AttributeValue e, AttributeValue a) { - if (!isEqual(e.b(), a.b()) || - !isEqual(e.bool(), a.bool()) || - !isSetEqual(e.bs(), a.bs()) || - !isEqual(e.n(), a.n()) || - !isSetEqual(e.ns(), a.ns()) || - !isEqual(e.nul(), a.nul()) || - !isEqual(e.s(), a.s()) || - !isSetEqual(e.ss(), a.ss())) { - return false; - } - // Recursive types need special handling - if (e.m() == null ^ a.m() == null) { - return false; - } else if (e.m() != null) { - if (!e.m().keySet().equals(a.m().keySet())) { - return false; - } - for (final String key : e.m().keySet()) { - if (!attrEquals(e.m().get(key), a.m().get(key))) { - return false; - } - } - } - if (e.l() == null ^ a.l() == null) { - return false; - } else if (e.l() != null) { - if (e.l().size() != a.l().size()) { - return false; - } - for (int x = 0; x < e.l().size(); x++) { - if (!attrEquals(e.l().get(x), a.l().get(x))) { - return false; - } - } - } - return true; - } - - @Override - public void describeTo(Description description) { } - - private static boolean isEqual(Object o1, Object o2) { - if(o1 == null ^ o2 == null) { - return false; - } - if (o1 == o2) - return true; - return o1.equals(o2); - } - - private static boolean isSetEqual(Collection c1, Collection c2) { - if(c1 == null ^ c2 == null) { - return false; - } - if (c1 != null) { - Set s1 = new HashSet(c1); - Set s2 = new HashSet(c2); - if(!s1.equals(s2)) { - return false; - } - } - return true; - } -} \ No newline at end of file diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java deleted file mode 100644 index 3ff7e4dff8..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java +++ /dev/null @@ -1,67 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.util.List; -import java.util.Map; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -/** - * Static helper methods to construct standard AttributeValues in a more compact way than specifying the full builder - * chain. - */ -public final class AttributeValueBuilder { - private AttributeValueBuilder() { - // Static helper class - } - - public static AttributeValue ofS(String value) { - return AttributeValue.builder().s(value).build(); - } - - public static AttributeValue ofN(String value) { - return AttributeValue.builder().n(value).build(); - } - - public static AttributeValue ofB(byte [] value) { - return AttributeValue.builder().b(SdkBytes.fromByteArray(value)).build(); - } - - public static AttributeValue ofBool(Boolean value) { - return AttributeValue.builder().bool(value).build(); - } - - public static AttributeValue ofNull() { - return AttributeValue.builder().nul(true).build(); - } - - public static AttributeValue ofL(List values) { - return AttributeValue.builder().l(values).build(); - } - - public static AttributeValue ofL(AttributeValue ...values) { - return AttributeValue.builder().l(values).build(); - } - - public static AttributeValue ofM(Map valueMap) { - return AttributeValue.builder().m(valueMap).build(); - } - - public static AttributeValue ofSS(String ...values) { - return AttributeValue.builder().ss(values).build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java deleted file mode 100644 index 42e479ba70..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java +++ /dev/null @@ -1,58 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -public class AttributeValueDeserializer extends JsonDeserializer { - @Override - public AttributeValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode attribute = jp.getCodec().readTree(jp); - - for (Iterator> iter = attribute.fields(); iter.hasNext(); ) { - Map.Entry rawAttribute = iter.next(); - // If there is more than one entry in this map, there is an error with our test data - if (iter.hasNext()) { - throw new IllegalStateException("Attribute value JSON has more than one value mapped."); - } - String typeString = rawAttribute.getKey(); - JsonNode value = rawAttribute.getValue(); - switch (typeString) { - case "S": - return AttributeValue.builder().s(value.asText()).build(); - case "B": - SdkBytes b = SdkBytes.fromByteArray(java.util.Base64.getDecoder().decode(value.asText())); - return AttributeValue.builder().b(b).build(); - case "N": - return AttributeValue.builder().n(value.asText()).build(); - case "SS": - final Set stringSet = - objectMapper.readValue( - objectMapper.treeAsTokens(value), new TypeReference>() {}); - return AttributeValue.builder().ss(stringSet).build(); - case "NS": - final Set numSet = - objectMapper.readValue( - objectMapper.treeAsTokens(value), new TypeReference>() {}); - return AttributeValue.builder().ns(numSet).build(); - default: - throw new IllegalStateException( - "DDB JSON type " - + typeString - + " not implemented for test attribute value deserialization."); - } - } - return null; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java deleted file mode 100644 index 0dbea38209..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java +++ /dev/null @@ -1,101 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; - -import java.math.BigDecimal; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -public class AttributeValueMatcher extends BaseMatcher { - private final AttributeValue expected; - private final boolean invert; - - public static AttributeValueMatcher invert(AttributeValue expected) { - return new AttributeValueMatcher(expected, true); - } - - public static AttributeValueMatcher match(AttributeValue expected) { - return new AttributeValueMatcher(expected, false); - } - - public AttributeValueMatcher(AttributeValue expected, boolean invert) { - this.expected = expected; - this.invert = invert; - } - - @Override - public boolean matches(Object item) { - AttributeValue other = (AttributeValue) item; - return invert ^ attrEquals(expected, other); - } - - @Override - public void describeTo(Description description) {} - - public static boolean attrEquals(AttributeValue e, AttributeValue a) { - if (!isEqual(e.b(), a.b()) - || !isNumberEqual(e.n(), a.n()) - || !isEqual(e.s(), a.s()) - || !isEqual(e.bs(), a.bs()) - || !isNumberEqual(e.ns(), a.ns()) - || !isEqual(e.ss(), a.ss())) { - return false; - } - return true; - } - - private static boolean isNumberEqual(String o1, String o2) { - if (o1 == null ^ o2 == null) { - return false; - } - if (o1 == o2) return true; - BigDecimal d1 = new BigDecimal(o1); - BigDecimal d2 = new BigDecimal(o2); - return d1.equals(d2); - } - - private static boolean isEqual(Object o1, Object o2) { - if (o1 == null ^ o2 == null) { - return false; - } - if (o1 == o2) return true; - return o1.equals(o2); - } - - private static boolean isNumberEqual(Collection c1, Collection c2) { - if (c1 == null ^ c2 == null) { - return false; - } - if (c1 != null) { - Set s1 = new HashSet(); - Set s2 = new HashSet(); - for (String s : c1) { - s1.add(new BigDecimal(s)); - } - for (String s : c2) { - s2.add(new BigDecimal(s)); - } - if (!s1.equals(s2)) { - return false; - } - } - return true; - } - - private static boolean isEqual(Collection c1, Collection c2) { - if (c1 == null ^ c2 == null) { - return false; - } - if (c1 != null) { - Set s1 = new HashSet(c1); - Set s2 = new HashSet(c2); - if (!s1.equals(s2)) { - return false; - } - } - return true; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java deleted file mode 100644 index 95abc5471d..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java +++ /dev/null @@ -1,48 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import com.amazonaws.util.Base64; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.nio.ByteBuffer; - -public class AttributeValueSerializer extends JsonSerializer { - @Override - public void serialize(AttributeValue.Builder value, JsonGenerator jgen, SerializerProvider provider) - throws IOException { - if (value != null) { - jgen.writeStartObject(); - if (value.build().s() != null) { - jgen.writeStringField("S", value.build().s()); - } else if (value.build().b() != null) { - ByteBuffer valueBytes = value.build().b().asByteBuffer(); - byte[] arr = new byte[valueBytes.remaining()]; - valueBytes.get(arr); - jgen.writeStringField("B", Base64.encodeAsString(arr)); - } else if (value.build().n() != null) { - jgen.writeStringField("N", value.build().n()); - } else if (value.build().ss() != null) { - jgen.writeFieldName("SS"); - jgen.writeStartArray(); - for (String s : value.build().ss()) { - jgen.writeString(s); - } - jgen.writeEndArray(); - } else if (value.build().ns() != null) { - jgen.writeFieldName("NS"); - jgen.writeStartArray(); - for (String num : value.build().ns()) { - jgen.writeString(num); - } - jgen.writeEndArray(); - } else { - throw new IllegalStateException( - "AttributeValue has no value or type not implemented for serialization."); - } - jgen.writeEndObject(); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java deleted file mode 100644 index 75cc574a1c..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java +++ /dev/null @@ -1,47 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import java.util.Map; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; - -public class DdbRecordMatcher extends BaseMatcher>{ - private final Map expected; - private final boolean invert; - - public static DdbRecordMatcher invert(Map expected) { - return new DdbRecordMatcher(expected, true); - } - - public static DdbRecordMatcher match(Map expected) { - return new DdbRecordMatcher(expected, false); - } - - public DdbRecordMatcher(Map expected, boolean invert) { - this.expected = expected; - this.invert = invert; - } - - @Override - public boolean matches(Object item) { - @SuppressWarnings("unchecked") - Map actual = (Map) item; - if (!expected.keySet().equals(actual.keySet())) { - return invert; - } - for (String key : expected.keySet()) { - if (key.equals("version")) continue; - AttributeValue e = expected.get(key); - AttributeValue a = actual.get(key); - if (!AttributeValueMatcher.attrEquals(a, e)) { - return invert; - } - } - return !invert; - } - - @Override - public void describeTo(Description description) { - - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java deleted file mode 100644 index d05aff4113..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.nio.ByteBuffer; -import java.security.SecureRandom; -import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.awssdk.services.kms.model.CreateKeyRequest; -import software.amazon.awssdk.services.kms.model.CreateKeyResponse; -import software.amazon.awssdk.services.kms.model.DecryptRequest; -import software.amazon.awssdk.services.kms.model.DecryptResponse; -import software.amazon.awssdk.services.kms.model.EncryptRequest; -import software.amazon.awssdk.services.kms.model.EncryptResponse; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyWithoutPlaintextRequest; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyWithoutPlaintextResponse; -import software.amazon.awssdk.services.kms.model.InvalidCiphertextException; -import software.amazon.awssdk.services.kms.model.KeyMetadata; -import software.amazon.awssdk.services.kms.model.KeyUsageType; - -public class FakeKMS implements KmsClient { - private static final SecureRandom rnd = new SecureRandom(); - private static final String ACCOUNT_ID = "01234567890"; - private final Map results_ = new HashMap<>(); - - @Override - public CreateKeyResponse createKey(CreateKeyRequest createKeyRequest) { - String keyId = UUID.randomUUID().toString(); - String arn = "arn:aws:testing:kms:" + ACCOUNT_ID + ":key/" + keyId; - return CreateKeyResponse.builder() - .keyMetadata(KeyMetadata.builder().awsAccountId(ACCOUNT_ID) - .creationDate(Instant.now()) - .description(createKeyRequest.description()) - .enabled(true) - .keyId(keyId) - .keyUsage(KeyUsageType.ENCRYPT_DECRYPT) - .arn(arn) - .build()) - .build(); - } - - @Override - public DecryptResponse decrypt(DecryptRequest decryptRequest) { - DecryptResponse result = results_.get(new DecryptMapKey(decryptRequest)); - if (result != null) { - return result; - } else { - throw InvalidCiphertextException.create("Invalid Ciphertext", new RuntimeException()); - } - } - - @Override - public EncryptResponse encrypt(EncryptRequest encryptRequest) { - final byte[] cipherText = new byte[512]; - rnd.nextBytes(cipherText); - DecryptResponse.Builder dec = DecryptResponse.builder(); - dec.keyId(encryptRequest.keyId()) - .plaintext(SdkBytes.fromByteBuffer(encryptRequest.plaintext().asByteBuffer().asReadOnlyBuffer())); - ByteBuffer ctBuff = ByteBuffer.wrap(cipherText); - - results_.put(new DecryptMapKey(ctBuff, encryptRequest.encryptionContext()), dec.build()); - - return EncryptResponse.builder() - .ciphertextBlob(SdkBytes.fromByteBuffer(ctBuff)) - .keyId(encryptRequest.keyId()) - .build(); - } - - @Override - public GenerateDataKeyResponse generateDataKey(GenerateDataKeyRequest generateDataKeyRequest) { - byte[] pt; - if (generateDataKeyRequest.keySpec() != null) { - if (generateDataKeyRequest.keySpec().toString().contains("256")) { - pt = new byte[32]; - } else if (generateDataKeyRequest.keySpec().toString().contains("128")) { - pt = new byte[16]; - } else { - throw new UnsupportedOperationException(); - } - } else { - pt = new byte[generateDataKeyRequest.numberOfBytes()]; - } - rnd.nextBytes(pt); - ByteBuffer ptBuff = ByteBuffer.wrap(pt); - EncryptResponse encryptresponse = encrypt(EncryptRequest.builder() - .keyId(generateDataKeyRequest.keyId()) - .plaintext(SdkBytes.fromByteBuffer(ptBuff)) - .encryptionContext(generateDataKeyRequest.encryptionContext()) - .build()); - return GenerateDataKeyResponse.builder().keyId(generateDataKeyRequest.keyId()) - .ciphertextBlob(encryptresponse.ciphertextBlob()) - .plaintext(SdkBytes.fromByteBuffer(ptBuff)) - .build(); - } - - @Override - public GenerateDataKeyWithoutPlaintextResponse generateDataKeyWithoutPlaintext( - GenerateDataKeyWithoutPlaintextRequest req) { - GenerateDataKeyResponse generateDataKey = generateDataKey(GenerateDataKeyRequest.builder() - .encryptionContext(req.encryptionContext()).numberOfBytes(req.numberOfBytes()).build()); - return GenerateDataKeyWithoutPlaintextResponse.builder().ciphertextBlob( - generateDataKey.ciphertextBlob()).keyId(req.keyId()).build(); - } - - public Map getSingleEc() { - if (results_.size() != 1) { - throw new IllegalStateException("Unexpected number of ciphertexts"); - } - for (final DecryptMapKey k : results_.keySet()) { - return k.ec; - } - throw new IllegalStateException("Unexpected number of ciphertexts"); - } - - @Override - public String serviceName() { - return KmsClient.SERVICE_NAME; - } - - @Override - public void close() { - // do nothing - } - - private static class DecryptMapKey { - private final ByteBuffer cipherText; - private final Map ec; - - public DecryptMapKey(DecryptRequest req) { - cipherText = req.ciphertextBlob().asByteBuffer(); - if (req.encryptionContext() != null) { - ec = Collections.unmodifiableMap(new HashMap<>(req.encryptionContext())); - } else { - ec = Collections.emptyMap(); - } - } - - public DecryptMapKey(ByteBuffer ctBuff, Map ec) { - cipherText = ctBuff.asReadOnlyBuffer(); - if (ec != null) { - this.ec = Collections.unmodifiableMap(new HashMap<>(ec)); - } else { - this.ec = Collections.emptyMap(); - } - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((cipherText == null) ? 0 : cipherText.hashCode()); - result = prime * result + ((ec == null) ? 0 : ec.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - DecryptMapKey other = (DecryptMapKey) obj; - if (cipherText == null) { - if (other.cipherText != null) - return false; - } else if (!cipherText.equals(other.cipherText)) - return false; - if (ec == null) { - if (other.ec != null) - return false; - } else if (!ec.equals(other.ec)) - return false; - return true; - } - - @Override - public String toString() { - return "DecryptMapKey [cipherText=" + cipherText + ", ec=" + ec + "]"; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java deleted file mode 100644 index fe293a0d02..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.URI; - -import com.amazonaws.services.dynamodbv2.local.main.ServerRunner; -import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer; - -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.*; - -/** - * Wrapper for a local DynamoDb server used in testing. Each instance of this class will find a new port to run on, - * so multiple instances can be safely run simultaneously. Each instance of this service uses memory as a storage medium - * and is thus completely ephemeral; no data will be persisted between stops and starts. - * - * LocalDynamoDb localDynamoDb = new LocalDynamoDb(); - * localDynamoDb.start(); // Start the service running locally on host - * DynamoDbClient dynamoDbClient = localDynamoDb.createClient(); - * ... // Do your testing with the client - * localDynamoDb.stop(); // Stop the service and free up resources - * - * If possible it's recommended to keep a single running instance for all your tests, as it can be slow to teardown - * and create new servers for every test, but there have been observed problems when dropping tables between tests for - * this scenario, so it's best to write your tests to be resilient to tables that already have data in them. - */ -public class LocalDynamoDb { - private static DynamoDBProxyServer server; - private static int port; - - /** - * Start the local DynamoDb service and run in background - */ - public void start() { - port = getFreePort(); - String portString = Integer.toString(port); - - try { - server = createServer(portString); - server.start(); - } catch (Exception e) { - throw propagate(e); - } - } - - /** - * Create a standard AWS v2 SDK client pointing to the local DynamoDb instance - * @return A DynamoDbClient pointing to the local DynamoDb instance - */ - public DynamoDbClient createClient() { - start(); - String endpoint = String.format("http://localhost:%d", port); - return DynamoDbClient.builder() - .endpointOverride(URI.create(endpoint)) - // The region is meaningless for local DynamoDb but required for client builder validation - .region(Region.US_EAST_1) - .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create("dummy-key", "dummy-secret"))) - .build(); - } - - /** - * If you require a client object that can be mocked or spied using standard mocking frameworks, then you must call - * this method to create the client instead. Only some methods are supported by this client, but it is easy to add - * new ones. - * @return A mockable/spyable DynamoDbClient pointing to the Local DynamoDB service. - */ - public DynamoDbClient createLimitedWrappedClient() { - return new WrappedDynamoDbClient(createClient()); - } - - /** - * Stops the local DynamoDb service and frees up resources it is using. - */ - public void stop() { - try { - server.stop(); - } catch (Exception e) { - throw propagate(e); - } - } - - private static DynamoDBProxyServer createServer(String portString) throws Exception { - return ServerRunner.createServerFromCommandLineArgs( - new String[]{ - "-inMemory", - "-port", portString - }); - } - - private static int getFreePort() { - try { - ServerSocket socket = new ServerSocket(0); - int port = socket.getLocalPort(); - socket.close(); - return port; - } catch (IOException ioe) { - throw propagate(ioe); - } - } - - private static RuntimeException propagate(Exception e) { - if (e instanceof RuntimeException) { - throw (RuntimeException)e; - } - throw new RuntimeException(e); - } - - /** - * This class can wrap any other implementation of a DynamoDbClient. The default implementation of the real - * DynamoDbClient is a final class, therefore it cannot be easily spied upon unless you first wrap it in a class - * like this. If there's a method you need it to support, just add it to the wrapper here. - */ - private static class WrappedDynamoDbClient implements DynamoDbClient { - private final DynamoDbClient wrappedClient; - - private WrappedDynamoDbClient(DynamoDbClient wrappedClient) { - this.wrappedClient = wrappedClient; - } - - @Override - public String serviceName() { - return wrappedClient.serviceName(); - } - - @Override - public void close() { - wrappedClient.close(); - } - - @Override - public PutItemResponse putItem(PutItemRequest putItemRequest) { - return wrappedClient.putItem(putItemRequest); - } - - @Override - public GetItemResponse getItem(GetItemRequest getItemRequest) { - return wrappedClient.getItem(getItemRequest); - } - - @Override - public QueryResponse query(QueryRequest queryRequest) { - return wrappedClient.query(queryRequest); - } - - @Override - public ListTablesResponse listTables(ListTablesRequest listTablesRequest) { return wrappedClient.listTables(listTablesRequest); } - - @Override - public ScanResponse scan(ScanRequest scanRequest) { return wrappedClient.scan(scanRequest); } - - @Override - public CreateTableResponse createTable(CreateTableRequest createTableRequest) { - return wrappedClient.createTable(createTableRequest); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java deleted file mode 100644 index 14c84d8605..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java +++ /dev/null @@ -1,77 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.List; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class ScenarioManifest { - - public static final String MOST_RECENT_PROVIDER_NAME = "most_recent"; - public static final String WRAPPED_PROVIDER_NAME = "wrapped"; - public static final String STATIC_PROVIDER_NAME = "static"; - public static final String AWS_KMS_PROVIDER_NAME = "awskms"; - public static final String SYMMETRIC_KEY_TYPE = "symmetric"; - - public List scenarios; - - @JsonProperty("keys") - public String keyDataPath; - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Scenario { - @JsonProperty("ciphertext") - public String ciphertextPath; - - @JsonProperty("provider") - public String providerName; - - public String version; - - @JsonProperty("material_name") - public String materialName; - - public Metastore metastore; - public Keys keys; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Metastore { - @JsonProperty("ciphertext") - public String path; - - @JsonProperty("table_name") - public String tableName; - - @JsonProperty("provider") - public String providerName; - - public Keys keys; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Keys { - @JsonProperty("encrypt") - public String encryptName; - - @JsonProperty("sign") - public String signName; - - @JsonProperty("decrypt") - public String decryptName; - - @JsonProperty("verify") - public String verifyName; - } - - public static class KeyData { - public String material; - public String algorithm; - public String encoding; - - @JsonProperty("type") - public String keyType; - - public String keyId; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java deleted file mode 100644 index c19c5565b3..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DelegatedKey; - -public class TestDelegatedKey implements DelegatedKey { - private static final long serialVersionUID = 1L; - - private final Key realKey; - - public TestDelegatedKey(Key key) { - this.realKey = key; - } - - @Override - public String getAlgorithm() { - return "DELEGATED:" + realKey.getAlgorithm(); - } - - @Override - public byte[] getEncoded() { - return realKey.getEncoded(); - } - - @Override - public String getFormat() { - return realKey.getFormat(); - } - - @Override - public byte[] encrypt(byte[] plainText, byte[] additionalAssociatedData, String algorithm) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, - NoSuchPaddingException { - Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); - cipher.init(Cipher.ENCRYPT_MODE, realKey); - byte[] iv = cipher.getIV(); - byte[] result = new byte[cipher.getOutputSize(plainText.length) + iv.length + 1]; - result[0] = (byte) iv.length; - System.arraycopy(iv, 0, result, 1, iv.length); - try { - cipher.doFinal(plainText, 0, plainText.length, result, iv.length + 1); - } catch (ShortBufferException e) { - throw new RuntimeException(e); - } - return result; - } - - @Override - public byte[] decrypt(byte[] cipherText, byte[] additionalAssociatedData, String algorithm) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, - NoSuchPaddingException, InvalidAlgorithmParameterException { - final byte ivLength = cipherText[0]; - IvParameterSpec iv = new IvParameterSpec(cipherText, 1, ivLength); - Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); - cipher.init(Cipher.DECRYPT_MODE, realKey, iv); - return cipher.doFinal(cipherText, ivLength + 1, cipherText.length - ivLength - 1); - } - - @Override - public byte[] wrap(Key key, byte[] additionalAssociatedData, String algorithm) throws InvalidKeyException, - NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException { - Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); - cipher.init(Cipher.WRAP_MODE, realKey); - return cipher.wrap(key); - } - - @Override - public Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType, - byte[] additionalAssociatedData, String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException, - InvalidKeyException { - Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); - cipher.init(Cipher.UNWRAP_MODE, realKey); - return cipher.unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType); - } - - @Override - public byte[] sign(byte[] dataToSign, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException { - Mac mac = Mac.getInstance(extractAlgorithm(algorithm)); - mac.init(realKey); - return mac.doFinal(dataToSign); - } - - @Override - public boolean verify(byte[] dataToSign, byte[] signature, String algorithm) { - try { - byte[] expected = sign(dataToSign, extractAlgorithm(algorithm)); - return MessageDigest.isEqual(expected, signature); - } catch (GeneralSecurityException ex) { - return false; - } - } - - private String extractAlgorithm(String alg) { - if (alg.startsWith(getAlgorithm())) { - return alg.substring(10); - } else { - return alg; - } - } -} From ea7b465e7e43457216689267281eaae26af9b195 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 14:20:24 -0800 Subject: [PATCH 05/33] m --- DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json diff --git a/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json b/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json deleted file mode 100644 index b041173a8c..0000000000 --- a/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"installationId":"64477908-a49f-45a3-b4b3-e44905cf8ec6","telemetryEnabled":"true"} \ No newline at end of file From 0290cffdd9e70f537ebaf29082c118eac0929f29 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 15:00:45 -0800 Subject: [PATCH 06/33] m --- .../dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java index fe293a0d02..561c9204cb 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java @@ -73,7 +73,7 @@ public DynamoDbClient createClient() { // The region is meaningless for local DynamoDb but required for client builder validation .region(Region.US_EAST_1) .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create("dummy-key", "dummy-secret"))) + AwsBasicCredentials.create("dummykey", "dummysecret"))) .build(); } From c3d2567d0ac89eb1b3b59495416e68c225e78ee8 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 15:03:56 -0800 Subject: [PATCH 07/33] m --- DynamoDbEncryption/runtimes/java/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DynamoDbEncryption/runtimes/java/build.gradle.kts b/DynamoDbEncryption/runtimes/java/build.gradle.kts index 77065d5ec1..0557befdbb 100644 --- a/DynamoDbEncryption/runtimes/java/build.gradle.kts +++ b/DynamoDbEncryption/runtimes/java/build.gradle.kts @@ -93,7 +93,7 @@ dependencies { testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.4") // For the DDB-EC with SDK v1 - implementation("com.amazonaws:aws-java-sdk-dynamodb:1.12.780") + compileOnly("com.amazonaws:aws-java-sdk-dynamodb:1.12.780") // For the DDB-EC with SDK V2 implementation("io.netty:netty-common:4.2.9.Final") From 53297ced8ea1ed9581b2abe0c9750c003f2bcc00 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Tue, 3 Feb 2026 13:14:54 -0800 Subject: [PATCH 08/33] adapter --- .../legacy/InternalLegacyOverride.java | 108 +++++++++++------- .../legacy/LegacyEncryptorAdapter.java | 18 +++ .../legacy/V1EncryptorAdapter.java | 50 ++++++++ .../legacy/V2EncryptorAdapter.java | 43 +++++++ ...bEncryptor.java => DynamoDBEncryptor.java} | 15 +-- .../encryption/EncryptionContext.java | 2 +- .../encryption/providers/store/MetaStore.java | 12 +- .../utils/EncryptionContextOperators.java | 2 +- .../HolisticIT.java | 12 +- .../encryption/DelegatedEncryptionTest.java | 10 +- .../DelegatedEnvelopeEncryptionTest.java | 10 +- .../encryption/DynamoDbEncryptorTest.java | 30 ++--- .../CachingMostRecentProviderTests.java | 4 +- .../providers/store/MetaStoreTests.java | 6 +- 14 files changed, 227 insertions(+), 95 deletions(-) create mode 100644 DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java rename DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/{DynamoDbEncryptor.java => DynamoDBEncryptor.java} (98%) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index b7ee420dcc..d399976fe1 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -34,33 +34,27 @@ public class InternalLegacyOverride extends _ExternBase_InternalLegacyOverride { - private DynamoDBEncryptor encryptor; - private Map> actions; - private EncryptionContext encryptionContext; - private LegacyPolicy _policy; - private DafnySequence materialDescriptionFieldName; - private DafnySequence signatureFieldName; + private final LegacyEncryptorAdapter _adapter; + private final LegacyPolicy _policy; + private final DafnySequence materialDescriptionFieldNameDafnyType; + private final DafnySequence signatureFieldNameDafnyType; private InternalLegacyOverride( - DynamoDBEncryptor encryptor, - Map> actions, - EncryptionContext encryptionContext, + LegacyEncryptorAdapter adapter, LegacyPolicy policy ) { - this.encryptor = encryptor; - this.actions = actions; - this.encryptionContext = encryptionContext; + this._adapter = adapter; this._policy = policy; // It is possible that these values // have been customized by the customer. - this.materialDescriptionFieldName = - software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - encryptor.getMaterialDescriptionFieldName() - ); - this.signatureFieldName = - software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - encryptor.getSignatureFieldName() - ); + this.materialDescriptionFieldNameDafnyType = + software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( + adapter.getMaterialDescriptionFieldName() + ); + this.signatureFieldNameDafnyType = + software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( + adapter.getSignatureFieldName() + ); } public static TypeDescriptor _typeDescriptor() { @@ -78,8 +72,8 @@ public boolean IsLegacyInput( //# attributes for the material description and the signature. return ( input.is_DecryptItemInput() && - input._encryptedItem.contains(materialDescriptionFieldName) && - input._encryptedItem.contains(signatureFieldName) + input._encryptedItem.contains(materialDescriptionFieldNameDafnyType) && + input._encryptedItem.contains(signatureFieldNameDafnyType) ); } @@ -109,19 +103,12 @@ > EncryptItem( .EncryptItemInput(input) .plaintextItem(); - final Map< - String, - com.amazonaws.services.dynamodbv2.model.AttributeValue - > encryptedItem = encryptor.encryptRecord( - V2MapToV1Map(plaintextItem), - actions, - encryptionContext - ); + Map encryptedItem = _adapter.encryptRecord(plaintextItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput .builder() - .encryptedItem(V1MapToV2Map(encryptedItem)) + .encryptedItem(encryptedItem) .build(); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.EncryptItemOutput dafnyOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.ToDafny.EncryptItemOutput( @@ -162,19 +149,12 @@ > DecryptItem( .DecryptItemInput(input) .encryptedItem(); - final Map< - String, - com.amazonaws.services.dynamodbv2.model.AttributeValue - > plaintextItem = encryptor.decryptRecord( - V2MapToV1Map(encryptedItem), - actions, - encryptionContext - ); + Map plaintextItem = _adapter.decryptRecord(encryptedItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput .builder() - .plaintextItem(V1MapToV2Map(plaintextItem)) + .plaintextItem(plaintextItem) .build(); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.DecryptItemOutput dafnyOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.ToDafny.DecryptItemOutput( @@ -224,11 +204,26 @@ public static Result, Error> Build( return CreateBuildFailure(maybeEncryptionContext.error()); } + final LegacyEncryptorAdapter adapter; + if (maybeEncryptor instanceof DynamoDBEncryptor) { + adapter = new V1EncryptorAdapter( + (DynamoDBEncryptor) maybeEncryptor, + maybeActions.value(), + maybeEncryptionContext.value() + ); + } else if (maybeEncryptor instanceof software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor) { + adapter = new V2EncryptorAdapter( + (software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor) maybeEncryptor, + convertActionsV1ToV2(maybeActions.value()), + convertEncryptionContextV1ToV2(maybeEncryptionContext.value()) + ); + } else { + return CreateBuildFailure(createError("Unsupported encryptor type")); + } + final InternalLegacyOverride internalLegacyOverride = new InternalLegacyOverride( - (DynamoDBEncryptor) maybeEncryptor, - maybeActions.value(), - maybeEncryptionContext.value(), + adapter, legacyOverride.dtor_policy() ); @@ -250,7 +245,32 @@ public static Error createError(String message) { public static boolean isDynamoDBEncryptor( software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor maybe ) { - return maybe instanceof DynamoDBEncryptor; + return maybe instanceof DynamoDBEncryptor || + maybe instanceof software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; + } + + // Convert SDK V1 EncryptionFlags to SDK V2 + private static Map> + convertActionsV1ToV2(Map> v1Actions) { + Map> v2Actions = new HashMap<>(); + for (Map.Entry> entry : v1Actions.entrySet()) { + Set v2Flags = new HashSet<>(); + for (EncryptionFlags v1Flag : entry.getValue()) { + v2Flags.add(software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags.valueOf(v1Flag.name())); + } + v2Actions.put(entry.getKey(), v2Flags); + } + return v2Actions; + } + + // Convert SDK V1 EncryptionContext to SDK V2 + private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext + convertEncryptionContextV1ToV2(EncryptionContext v1Context) { + return software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.builder() + .tableName(v1Context.getTableName()) + .hashKeyName(v1Context.getHashKeyName()) + .rangeKeyName(v1Context.getRangeKeyName()) + .build(); } public static String ToNativeString(DafnySequence s) { diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java new file mode 100644 index 0000000000..93a6cd6b5a --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java @@ -0,0 +1,18 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.security.GeneralSecurityException; +import java.util.Map; + +public interface LegacyEncryptorAdapter { + + Map + encryptRecord(Map item) throws GeneralSecurityException; + + Map + decryptRecord(Map item) throws GeneralSecurityException; + + String getMaterialDescriptionFieldName(); + String getSignatureFieldName(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java new file mode 100644 index 0000000000..1231caaed5 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java @@ -0,0 +1,50 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; + +import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor; +import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext; +import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionFlags; + +import java.security.GeneralSecurityException; +import java.util.Map; +import java.util.Set; + +import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V1MapToV2Map; +import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V2MapToV1Map; + +public class V1EncryptorAdapter implements LegacyEncryptorAdapter { + private final DynamoDBEncryptor encryptor; + private final Map> actions; + private final EncryptionContext encryptionContext; + + V1EncryptorAdapter(DynamoDBEncryptor encryptor, Map> actions,EncryptionContext encryptionContext) { + this.encryptor = encryptor; + this.actions = actions; + this.encryptionContext = encryptionContext; + } + + @Override + public Map + encryptRecord(Map item) throws GeneralSecurityException { + return V1MapToV2Map( + encryptor.encryptRecord(V2MapToV1Map(item), actions, encryptionContext) + ); + } + + @Override + public Map + decryptRecord(Map item) throws GeneralSecurityException { + return V1MapToV2Map( + encryptor.decryptRecord(V2MapToV1Map(item), actions, encryptionContext) + ); + } + + @Override + public String getMaterialDescriptionFieldName() { + return encryptor.getMaterialDescriptionFieldName(); + } + + @Override + public String getSignatureFieldName() { + return encryptor.getSignatureFieldName(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java new file mode 100644 index 0000000000..effa15e4d5 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java @@ -0,0 +1,43 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; + +import java.security.GeneralSecurityException; +import java.util.Map; +import java.util.Set; + +public class V2EncryptorAdapter implements LegacyEncryptorAdapter { + private final DynamoDBEncryptor encryptor; + private final Map> actions; + private final EncryptionContext encryptionContext; + + V2EncryptorAdapter(DynamoDBEncryptor encryptor, Map> actions, EncryptionContext encryptionContext) { + this.encryptor = encryptor; + this.actions = actions; + this.encryptionContext = encryptionContext; + } + + @Override + public Map + encryptRecord(Map item) throws GeneralSecurityException { + return encryptor.encryptRecord(item, actions, encryptionContext); + } + + @Override + public Map + decryptRecord(Map item) throws GeneralSecurityException { + return encryptor.decryptRecord(item, actions, encryptionContext); + } + + @Override + public String getMaterialDescriptionFieldName() { + return encryptor.getMaterialDescriptionFieldName(); + } + + @Override + public String getSignatureFieldName() { + return encryptor.getSignatureFieldName(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDBEncryptor.java similarity index 98% rename from DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java rename to DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDBEncryptor.java index 95e6ec73c7..b52494e76e 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDBEncryptor.java @@ -34,6 +34,7 @@ import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; @@ -48,7 +49,7 @@ * * @author Greg Rubin */ -public class DynamoDbEncryptor { +public class DynamoDBEncryptor implements ILegacyDynamoDbEncryptor { private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA"; private static final String DEFAULT_METADATA_FIELD = "*amzn-ddb-map-desc*"; private static final String DEFAULT_SIGNATURE_FIELD = "*amzn-ddb-map-sig*"; @@ -79,19 +80,19 @@ public class DynamoDbEncryptor { private Function encryptionContextOverrideOperator; - protected DynamoDbEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { + protected DynamoDBEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { this.encryptionMaterialsProvider = provider; this.descriptionBase = descriptionBase; symmetricEncryptionModeHeader = this.descriptionBase + "sym-mode"; signingAlgorithmHeader = this.descriptionBase + "signingAlg"; } - public static DynamoDbEncryptor getInstance( + public static DynamoDBEncryptor getInstance( EncryptionMaterialsProvider provider, String descriptionbase) { - return new DynamoDbEncryptor(provider, descriptionbase); + return new DynamoDBEncryptor(provider, descriptionbase); } - public static DynamoDbEncryptor getInstance(EncryptionMaterialsProvider provider) { + public static DynamoDBEncryptor getInstance(EncryptionMaterialsProvider provider) { return getInstance(provider, DEFAULT_DESCRIPTION_BASE); } @@ -454,7 +455,7 @@ private void actualEncryption(Map itemAttributes, * * @return the name of the DynamoDB field used to store the signature */ - String getSignatureFieldName() { + public String getSignatureFieldName() { return signatureFieldName; } @@ -474,7 +475,7 @@ void setSignatureFieldName(final String signatureFieldName) { * @return the name of the DynamoDB field used to store metadata used by the * DynamoDBEncryptedMapper */ - String getMaterialDescriptionFieldName() { + public String getMaterialDescriptionFieldName() { return materialDescriptionFieldName; } diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java index 9a78ad9b04..f0ae27e26c 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java @@ -83,7 +83,7 @@ public Map getAttributeValues() { /** * This object has no meaning (and will not be set or examined) by any core libraries. * It exists to allow custom object mappers and data access layers to pass - * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDbEncryptor}. + * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDBEncryptor}. */ public Object getDeveloperContext() { return developerContext; diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java index c0fbe5e06f..9744061094 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java @@ -44,7 +44,7 @@ import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; import software.amazon.awssdk.services.dynamodb.model.QueryRequest; import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; @@ -95,7 +95,7 @@ public class MetaStore extends ProviderStore { // private final DynamoDbEncryptionConfiguration encryptionConfiguration; private final String tableName; private final DynamoDbClient ddb; - private final DynamoDbEncryptor encryptor; + private final DynamoDBEncryptor encryptor; private final EncryptionContext ddbCtx; private final ExtraDataSupplier extraDataSupplier; @@ -129,7 +129,7 @@ public interface ExtraDataSupplier { * @param encryptor used to perform crypto operations on the record attributes. */ public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor) { + final DynamoDBEncryptor encryptor) { this(ddb, tableName, encryptor, EMPTY_EXTRA_DATA_SUPPLIER); } @@ -142,7 +142,7 @@ public MetaStore(final DynamoDbClient ddb, final String tableName, * @param extraDataSupplier provides extra data that should be stored along with the material. */ public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { + final DynamoDBEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { this.ddb = checkNotNull(ddb, "ddb must not be null"); this.tableName = checkNotNull(tableName, "tableName must not be null"); this.encryptor = checkNotNull(encryptor, "encryptor must not be null"); @@ -389,7 +389,7 @@ private EncryptionMaterialsProvider decryptProvider(final Map getPlainText(final Map encryptedRecord; Map> actions; EncryptionContext encryptionContext = @@ -690,7 +690,7 @@ private Map getItems(Map map, St private void assertVersionCompatibility(EncryptionMaterialsProvider provider, String tableName) throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(provider); Map response; Map decryptedRecord; EncryptionContext encryptionContext = @@ -805,7 +805,7 @@ private void assertVersionCompatibility(EncryptionMaterialsProvider provider, St private void assertVersionCompatibility_2(EncryptionMaterialsProvider provider, String tableName) throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(provider); Map response; Map decryptedRecord; EncryptionContext encryptionContext = diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java index fd3bf37ace..b2f76bd374 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java @@ -54,7 +54,7 @@ public class DelegatedEncryptionTest { private static DelegatedKey macKey; private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; + private DynamoDBEncryptor encryptor; private Map attribs; private EncryptionContext context; @@ -71,7 +71,7 @@ public static void setupClass() { public void setUp() { prov = new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); attribs = new HashMap<>(); attribs.put("intValue", AttributeValue.builder().n("123").build()); @@ -189,7 +189,7 @@ public void signedOnly() throws GeneralSecurityException { @Test public void signedOnlyNullCryptoKey() throws GeneralSecurityException { prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); @@ -234,7 +234,7 @@ public void RsaSignedOnly() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-" @@ -262,7 +262,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-" diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java index ce22c396fa..c052c6d1a8 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java @@ -55,7 +55,7 @@ public class DelegatedEnvelopeEncryptionTest { private static DelegatedKey macKey; private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; + private DynamoDBEncryptor encryptor; private Map attribs; private EncryptionContext context; @@ -73,7 +73,7 @@ public void setUp() throws Exception { prov = new WrappedMaterialsProvider( encryptionKey, encryptionKey, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); attribs = new HashMap(); attribs.put("intValue", AttributeValue.builder().n("123").build()); @@ -174,7 +174,7 @@ public void badVersionNumber() throws GeneralSecurityException { @Test public void signedOnlyNullCryptoKey() throws GeneralSecurityException { prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); @@ -218,7 +218,7 @@ public void RsaSignedOnly() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); @@ -246,7 +246,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java index 87fb8353bb..b332e9c2d6 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java @@ -64,7 +64,7 @@ public class DynamoDbEncryptorTest { private static SecretKey macKey; private InstrumentedEncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; + private DynamoDBEncryptor encryptor; private Map attribs; private EncryptionContext context; private static final String OVERRIDDEN_TABLE_NAME = "TheBestTableName"; @@ -85,7 +85,7 @@ public void setUp() { prov = new InstrumentedEncryptionMaterialsProvider( new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "enryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "enryptor-"); attribs = new HashMap<>(); attribs.put("intValue", AttributeValue.builder().n("123").build()); @@ -255,7 +255,7 @@ public void signedOnlyNullCryptoKey() throws GeneralSecurityException { prov = new InstrumentedEncryptionMaterialsProvider( new SymmetricStaticProvider(null, macKey, Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); @@ -299,7 +299,7 @@ public void RsaSignedOnly() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); @@ -327,7 +327,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); @@ -347,7 +347,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { */ @Test public void testNullEncryptionContextOperator() throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator(null); encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); } @@ -359,7 +359,7 @@ public void testNullEncryptionContextOperator() throws GeneralSecurityException public void testTableNameOverriddenEncryptionContextOperator() throws GeneralSecurityException { // Ensure that the table name is different from what we override the table to. assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator( overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); Map encryptedItems = @@ -378,8 +378,8 @@ public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor( throws GeneralSecurityException { // Ensure that the table name is different from what we override the table to. assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); + DynamoDBEncryptor encryptorWithoutOverride = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator( overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); Map encryptedItems = @@ -402,8 +402,8 @@ public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor( throws GeneralSecurityException { // Ensure that the table name is different from what we override the table to. assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); + DynamoDBEncryptor encryptorWithoutOverride = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator( overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); Map encryptedItems = @@ -418,7 +418,7 @@ public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor( @Test public void EcdsaSignedOnly() throws GeneralSecurityException { - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + encryptor = DynamoDBEncryptor.getInstance(getMaterialProviderwithECDSA()); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); @@ -440,7 +440,7 @@ public void EcdsaSignedOnly() throws GeneralSecurityException { @Test(expectedExceptions = SignatureException.class) public void EcdsaSignedOnlyBadSignature() throws GeneralSecurityException { - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + encryptor = DynamoDBEncryptor.getInstance(getMaterialProviderwithECDSA()); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); @@ -500,7 +500,7 @@ public void testDecryptWithPlainTextItemAndAdditionNewAttributeHavingEncryptionF private void assertToByteArray( final String msg, final byte[] expected, final ByteBuffer testValue) throws ReflectiveOperationException { - Method m = DynamoDbEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); + Method m = DynamoDBEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); m.setAccessible(true); int oldPosition = testValue.position(); @@ -537,7 +537,7 @@ private EncryptionMaterialsProvider getMaterialProviderwithECDSA() g.initialize(ecSpec, Utils.getRng()); KeyPair keypair = g.generateKeyPair(); Map description = new HashMap<>(); - description.put(DynamoDbEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); + description.put(DynamoDBEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); return new SymmetricStaticProvider(null, keypair, description); } diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java index f286648332..e3b4078bc8 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java @@ -8,7 +8,7 @@ import static org.testng.AssertJUnit.assertTrue; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; @@ -40,7 +40,7 @@ public class CachingMostRecentProviderTests { new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); + private static final DynamoDBEncryptor ENCRYPTOR = DynamoDBEncryptor.getInstance(BASE_PROVIDER); private DynamoDbClient client; private Map methodCalls; diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java index 3449908a6d..f98f807fda 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java @@ -28,7 +28,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; @@ -56,8 +56,8 @@ public class MetaStoreTests { 2, 4, 6, 8, 10, 12, 14 }, "HmacSHA256"); private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); private static final EncryptionMaterialsProvider TARGET_BASE_PROVIDER = new SymmetricStaticProvider(TARGET_AES_KEY, TARGET_HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); - private static final DynamoDbEncryptor TARGET_ENCRYPTOR = DynamoDbEncryptor.getInstance(TARGET_BASE_PROVIDER); + private static final DynamoDBEncryptor ENCRYPTOR = DynamoDBEncryptor.getInstance(BASE_PROVIDER); + private static final DynamoDBEncryptor TARGET_ENCRYPTOR = DynamoDBEncryptor.getInstance(TARGET_BASE_PROVIDER); private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); private final LocalDynamoDb targetLocalDynamoDb = new LocalDynamoDb(); From 817d8fae17b0c3fb46fb13175e7d373bcf618c2c Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 14:07:23 -0800 Subject: [PATCH 09/33] m --- DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json diff --git a/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json b/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json new file mode 100644 index 0000000000..b041173a8c --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json @@ -0,0 +1 @@ +{"installationId":"64477908-a49f-45a3-b4b3-e44905cf8ec6","telemetryEnabled":"true"} \ No newline at end of file From f520e9dba546ae68fc9c000b4ea45be071abaa2d Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 14:18:19 -0800 Subject: [PATCH 10/33] Revert "copy code from internal" This reverts commit ec8077cdc031bca3d22d6d785fd39e0d5713b214. --- .../encryption/DelegatedKey.java | 146 --- .../encryption/DynamoDbEncryptor.java | 595 ----------- .../encryption/DynamoDbSigner.java | 261 ----- .../encryption/EncryptionContext.java | 187 ---- .../encryption/EncryptionFlags.java | 23 - .../DynamoDbEncryptionException.java | 47 - .../materials/AbstractRawMaterials.java | 73 -- .../materials/AsymmetricRawMaterials.java | 49 - .../materials/CryptographicMaterials.java | 24 - .../materials/DecryptionMaterials.java | 27 - .../materials/EncryptionMaterials.java | 27 - .../materials/SymmetricRawMaterials.java | 58 -- .../materials/WrappedRawMaterials.java | 212 ---- .../providers/AsymmetricStaticProvider.java | 46 - .../providers/CachingMostRecentProvider.java | 183 ---- .../providers/DirectKmsMaterialsProvider.java | 296 ------ .../EncryptionMaterialsProvider.java | 71 -- .../providers/KeyStoreMaterialsProvider.java | 199 ---- .../providers/SymmetricStaticProvider.java | 130 --- .../providers/WrappedMaterialsProvider.java | 163 --- .../encryption/providers/store/MetaStore.java | 434 -------- .../providers/store/ProviderStore.java | 84 -- .../utils/EncryptionContextOperators.java | 81 -- .../internal/AttributeValueMarshaller.java | 331 ------- .../internal/Base64.java | 48 - .../internal/ByteBufferInputStream.java | 56 -- .../internal/Hkdf.java | 316 ------ .../internal/LRUCache.java | 107 -- .../internal/MsClock.java | 19 - .../internal/TTLCache.java | 242 ----- .../internal/Utils.java | 39 - .../HolisticIT.java | 932 ------------------ .../encryption/DelegatedEncryptionTest.java | 296 ------ .../DelegatedEnvelopeEncryptionTest.java | 280 ------ .../encryption/DynamoDbEncryptorTest.java | 591 ----------- .../encryption/DynamoDbSignerTest.java | 567 ----------- .../materials/AsymmetricRawMaterialsTest.java | 138 --- .../materials/SymmetricRawMaterialsTest.java | 104 -- .../AsymmetricStaticProviderTest.java | 130 --- .../CachingMostRecentProviderTests.java | 610 ------------ .../DirectKmsMaterialsProviderTest.java | 449 --------- .../KeyStoreMaterialsProviderTest.java | 315 ------ .../SymmetricStaticProviderTest.java | 182 ---- .../WrappedMaterialsProviderTest.java | 414 -------- .../providers/store/MetaStoreTests.java | 346 ------- .../utils/EncryptionContextOperatorsTest.java | 164 --- .../AttributeValueMarshallerTest.java | 393 -------- .../internal/Base64Tests.java | 93 -- .../internal/ByteBufferInputStreamTest.java | 86 -- .../internal/ConcurrentTTLCacheTest.java | 244 ----- .../internal/HkdfTests.java | 209 ---- .../internal/LRUCacheTest.java | 85 -- .../internal/TTLCacheTest.java | 372 ------- .../testing/AttrMatcher.java | 125 --- .../testing/AttributeValueBuilder.java | 67 -- .../testing/AttributeValueDeserializer.java | 58 -- .../testing/AttributeValueMatcher.java | 101 -- .../testing/AttributeValueSerializer.java | 48 - .../testing/DdbRecordMatcher.java | 47 - .../testing/FakeKMS.java | 201 ---- .../testing/LocalDynamoDb.java | 175 ---- .../testing/ScenarioManifest.java | 77 -- .../testing/TestDelegatedKey.java | 128 --- 63 files changed, 12601 deletions(-) delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java delete mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java deleted file mode 100644 index 52e02f2e8e..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -/** - * Identifies keys which should not be used directly with {@link Cipher} but - * instead contain their own cryptographic logic. This can be used to wrap more - * complex logic, HSM integration, or service-calls. - * - *

- * Most delegated keys will only support a subset of these operations. (For - * example, AES keys will generally not support {@link #sign(byte[], String)} or - * {@link #verify(byte[], byte[], String)} and HMAC keys will generally not - * support anything except sign and verify.) - * {@link UnsupportedOperationException} should be thrown in these cases. - * - * @author Greg Rubin - */ -public interface DelegatedKey extends SecretKey { - /** - * Encrypts the provided plaintext and returns a byte-array containing the ciphertext. - * - * @param plainText - * @param additionalAssociatedData - * Optional additional data which must then also be provided for successful - * decryption. Both null and arrays of length 0 are treated identically. - * Not all keys will support this parameter. - * @param algorithm - * the transformation to be used when encrypting the data - * @return ciphertext the ciphertext produced by this encryption operation - * @throws UnsupportedOperationException - * if encryption is not supported or if additionalAssociatedData is - * provided, but not supported. - */ - byte[] encrypt(byte[] plainText, byte[] additionalAssociatedData, String algorithm) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, - NoSuchPaddingException; - - /** - * Decrypts the provided ciphertext and returns a byte-array containing the - * plaintext. - * - * @param cipherText - * @param additionalAssociatedData - * Optional additional data which was provided during encryption. - * Both null and arrays of length 0 are treated - * identically. Not all keys will support this parameter. - * @param algorithm - * the transformation to be used when decrypting the data - * @return plaintext the result of decrypting the input ciphertext - * @throws UnsupportedOperationException - * if decryption is not supported or if - * additionalAssociatedData is provided, but not - * supported. - */ - byte[] decrypt(byte[] cipherText, byte[] additionalAssociatedData, String algorithm) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, - NoSuchPaddingException, InvalidAlgorithmParameterException; - - /** - * Wraps (encrypts) the provided key to make it safe for - * storage or transmission. - * - * @param key - * @param additionalAssociatedData - * Optional additional data which must then also be provided for - * successful unwrapping. Both null and arrays of - * length 0 are treated identically. Not all keys will support - * this parameter. - * @param algorithm - * the transformation to be used when wrapping the key - * @return the wrapped key - * @throws UnsupportedOperationException - * if wrapping is not supported or if - * additionalAssociatedData is provided, but not - * supported. - */ - byte[] wrap(Key key, byte[] additionalAssociatedData, String algorithm) throws InvalidKeyException, - NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException; - - /** - * Unwraps (decrypts) the provided wrappedKey to recover the - * original key. - * - * @param wrappedKey - * @param additionalAssociatedData - * Optional additional data which was provided during wrapping. - * Both null and arrays of length 0 are treated - * identically. Not all keys will support this parameter. - * @param algorithm - * the transformation to be used when unwrapping the key - * @return the unwrapped key - * @throws UnsupportedOperationException - * if wrapping is not supported or if - * additionalAssociatedData is provided, but not - * supported. - */ - Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType, - byte[] additionalAssociatedData, String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException, - InvalidKeyException; - - /** - * Calculates and returns a signature for dataToSign. - * - * @param dataToSign - * @param algorithm - * @return the signature - * @throws UnsupportedOperationException if signing is not supported - */ - byte[] sign(byte[] dataToSign, String algorithm) throws GeneralSecurityException; - - /** - * Checks the provided signature for correctness. - * - * @param dataToSign - * @param signature - * @param algorithm - * @return true if and only if the signature matches the dataToSign. - * @throws UnsupportedOperationException if signature validation is not supported - */ - boolean verify(byte[] dataToSign, byte[] signature, String algorithm); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java deleted file mode 100644 index 95e6ec73c7..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.security.GeneralSecurityException; -import java.security.PrivateKey; -import java.security.SignatureException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.ByteBufferInputStream; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -/** - * The low-level API for performing crypto operations on the record attributes. - * - * @author Greg Rubin - */ -public class DynamoDbEncryptor { - private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA"; - private static final String DEFAULT_METADATA_FIELD = "*amzn-ddb-map-desc*"; - private static final String DEFAULT_SIGNATURE_FIELD = "*amzn-ddb-map-sig*"; - private static final String DEFAULT_DESCRIPTION_BASE = "amzn-ddb-map-"; // Same as the Mapper - private static final Charset UTF8 = Charset.forName("UTF-8"); - private static final String SYMMETRIC_ENCRYPTION_MODE = "/CBC/PKCS5Padding"; - private static final ConcurrentHashMap BLOCK_SIZE_CACHE = new ConcurrentHashMap<>(); - private static final Function BLOCK_SIZE_CALCULATOR = (transformation) -> { - try { - final Cipher c = Cipher.getInstance(transformation); - return c.getBlockSize(); - } catch (final GeneralSecurityException ex) { - throw new IllegalArgumentException("Algorithm does not exist", ex); - } - }; - - private static final int CURRENT_VERSION = 0; - - private String signatureFieldName = DEFAULT_SIGNATURE_FIELD; - private String materialDescriptionFieldName = DEFAULT_METADATA_FIELD; - - private EncryptionMaterialsProvider encryptionMaterialsProvider; - private final String descriptionBase; - private final String symmetricEncryptionModeHeader; - private final String signingAlgorithmHeader; - - static final String DEFAULT_SIGNING_ALGORITHM_HEADER = DEFAULT_DESCRIPTION_BASE + "signingAlg"; - - private Function encryptionContextOverrideOperator; - - protected DynamoDbEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { - this.encryptionMaterialsProvider = provider; - this.descriptionBase = descriptionBase; - symmetricEncryptionModeHeader = this.descriptionBase + "sym-mode"; - signingAlgorithmHeader = this.descriptionBase + "signingAlg"; - } - - public static DynamoDbEncryptor getInstance( - EncryptionMaterialsProvider provider, String descriptionbase) { - return new DynamoDbEncryptor(provider, descriptionbase); - } - - public static DynamoDbEncryptor getInstance(EncryptionMaterialsProvider provider) { - return getInstance(provider, DEFAULT_DESCRIPTION_BASE); - } - - /** - * Returns a decrypted version of the provided DynamoDb record. The signature is verified across - * all provided fields. All fields (except those listed in doNotEncrypt are - * decrypted. - * - * @param itemAttributes the DynamoDbRecord - * @param context additional information used to successfully select the encryption materials and - * decrypt the data. This should include (at least) the tableName and the materialDescription. - * @param doNotDecrypt those fields which should not be encrypted - * @return a plaintext version of the DynamoDb record - * @throws SignatureException if the signature is invalid or cannot be verified - * @throws GeneralSecurityException - */ - public Map decryptAllFieldsExcept( - Map itemAttributes, EncryptionContext context, String... doNotDecrypt) - throws GeneralSecurityException { - return decryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotDecrypt)); - } - - /** @see #decryptAllFieldsExcept(Map, EncryptionContext, String...) */ - public Map decryptAllFieldsExcept( - Map itemAttributes, - EncryptionContext context, - Collection doNotDecrypt) - throws GeneralSecurityException { - Map> attributeFlags = - allDecryptionFlagsExcept(itemAttributes, doNotDecrypt); - return decryptRecord(itemAttributes, attributeFlags, context); - } - - /** - * Returns the decryption flags for all item attributes except for those explicitly specified to - * be excluded. - * - * @param doNotDecrypt fields to be excluded - */ - public Map> allDecryptionFlagsExcept( - Map itemAttributes, String... doNotDecrypt) { - return allDecryptionFlagsExcept(itemAttributes, Arrays.asList(doNotDecrypt)); - } - - /** - * Returns the decryption flags for all item attributes except for those explicitly specified to - * be excluded. - * - * @param doNotDecrypt fields to be excluded - */ - public Map> allDecryptionFlagsExcept( - Map itemAttributes, Collection doNotDecrypt) { - Map> attributeFlags = new HashMap>(); - - for (String fieldName : doNotDecrypt) { - attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN)); - } - - for (String fieldName : itemAttributes.keySet()) { - if (!attributeFlags.containsKey(fieldName) - && !fieldName.equals(getMaterialDescriptionFieldName()) - && !fieldName.equals(getSignatureFieldName())) { - attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); - } - } - return attributeFlags; - } - - /** - * Returns an encrypted version of the provided DynamoDb record. All fields are signed. All fields - * (except those listed in doNotEncrypt) are encrypted. - * - * @param itemAttributes a DynamoDb Record - * @param context additional information used to successfully select the encryption materials and - * encrypt the data. This should include (at least) the tableName. - * @param doNotEncrypt those fields which should not be encrypted - * @return a ciphertext version of the DynamoDb record - * @throws GeneralSecurityException - */ - public Map encryptAllFieldsExcept( - Map itemAttributes, EncryptionContext context, String... doNotEncrypt) - throws GeneralSecurityException { - - return encryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotEncrypt)); - } - - public Map encryptAllFieldsExcept( - Map itemAttributes, - EncryptionContext context, - Collection doNotEncrypt) - throws GeneralSecurityException { - Map> attributeFlags = - allEncryptionFlagsExcept(itemAttributes, doNotEncrypt); - return encryptRecord(itemAttributes, attributeFlags, context); - } - - /** - * Returns the encryption flags for all item attributes except for those explicitly specified to - * be excluded. - * - * @param doNotEncrypt fields to be excluded - */ - public Map> allEncryptionFlagsExcept( - Map itemAttributes, String... doNotEncrypt) { - return allEncryptionFlagsExcept(itemAttributes, Arrays.asList(doNotEncrypt)); - } - - /** - * Returns the encryption flags for all item attributes except for those explicitly specified to - * be excluded. - * - * @param doNotEncrypt fields to be excluded - */ - public Map> allEncryptionFlagsExcept( - Map itemAttributes, Collection doNotEncrypt) { - Map> attributeFlags = new HashMap>(); - for (String fieldName : doNotEncrypt) { - attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN)); - } - - for (String fieldName : itemAttributes.keySet()) { - if (!attributeFlags.containsKey(fieldName)) { - attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); - } - } - return attributeFlags; - } - - public Map decryptRecord( - Map itemAttributes, - Map> attributeFlags, - EncryptionContext context) - throws GeneralSecurityException { - if (!itemContainsFieldsToDecryptOrSign(itemAttributes.keySet(), attributeFlags)) { - return itemAttributes; - } - // Copy to avoid changing anyone elses objects - itemAttributes = new HashMap(itemAttributes); - - Map materialDescription = Collections.emptyMap(); - DecryptionMaterials materials; - SecretKey decryptionKey; - - DynamoDbSigner signer = DynamoDbSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng()); - - if (itemAttributes.containsKey(materialDescriptionFieldName)) { - materialDescription = unmarshallDescription(itemAttributes.get(materialDescriptionFieldName)); - } - // Copy the material description and attribute values into the context - context = - new EncryptionContext.Builder(context) - .materialDescription(materialDescription) - .attributeValues(itemAttributes) - .build(); - - Function encryptionContextOverrideOperator = - getEncryptionContextOverrideOperator(); - if (encryptionContextOverrideOperator != null) { - context = encryptionContextOverrideOperator.apply(context); - } - - materials = encryptionMaterialsProvider.getDecryptionMaterials(context); - decryptionKey = materials.getDecryptionKey(); - if (materialDescription.containsKey(signingAlgorithmHeader)) { - String signingAlg = materialDescription.get(signingAlgorithmHeader); - signer = DynamoDbSigner.getInstance(signingAlg, Utils.getRng()); - } - - ByteBuffer signature; - if (!itemAttributes.containsKey(signatureFieldName) - || itemAttributes.get(signatureFieldName).b() == null) { - signature = ByteBuffer.allocate(0); - } else { - signature = itemAttributes.get(signatureFieldName).b().asByteBuffer().asReadOnlyBuffer(); - } - itemAttributes.remove(signatureFieldName); - - String associatedData = "TABLE>" + context.getTableName() + " attributeNamesToCheck, Map> attributeFlags) { - return attributeNamesToCheck.stream() - .filter(attributeFlags::containsKey) - .anyMatch(attributeName -> !attributeFlags.get(attributeName).isEmpty()); - } - - public Map encryptRecord( - Map itemAttributes, - Map> attributeFlags, - EncryptionContext context) { - if (attributeFlags.isEmpty()) { - return itemAttributes; - } - // Copy to avoid changing anyone elses objects - itemAttributes = new HashMap<>(itemAttributes); - - // Copy the attribute values into the context - context = context.toBuilder() - .attributeValues(itemAttributes) - .build(); - - Function encryptionContextOverrideOperator = - getEncryptionContextOverrideOperator(); - if (encryptionContextOverrideOperator != null) { - context = encryptionContextOverrideOperator.apply(context); - } - - EncryptionMaterials materials = encryptionMaterialsProvider.getEncryptionMaterials(context); - // We need to copy this because we modify it to record other encryption details - Map materialDescription = new HashMap<>( - materials.getMaterialDescription()); - SecretKey encryptionKey = materials.getEncryptionKey(); - - try { - actualEncryption(itemAttributes, attributeFlags, materialDescription, encryptionKey); - - // The description must be stored after encryption because its data - // is necessary for proper decryption. - final String signingAlgo = materialDescription.get(signingAlgorithmHeader); - DynamoDbSigner signer; - if (signingAlgo != null) { - signer = DynamoDbSigner.getInstance(signingAlgo, Utils.getRng()); - } else { - signer = DynamoDbSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng()); - } - - if (materials.getSigningKey() instanceof PrivateKey) { - materialDescription.put(signingAlgorithmHeader, signer.getSigningAlgorithm()); - } - if (! materialDescription.isEmpty()) { - itemAttributes.put(materialDescriptionFieldName, marshallDescription(materialDescription)); - } - - String associatedData = "TABLE>" + context.getTableName() + " itemAttributes, - Map> attributeFlags, SecretKey encryptionKey, - Map materialDescription) throws GeneralSecurityException { - final String encryptionMode = encryptionKey != null ? encryptionKey.getAlgorithm() + - materialDescription.get(symmetricEncryptionModeHeader) : null; - Cipher cipher = null; - int blockSize = -1; - - for (Map.Entry entry: itemAttributes.entrySet()) { - Set flags = attributeFlags.get(entry.getKey()); - if (flags != null && flags.contains(EncryptionFlags.ENCRYPT)) { - if (!flags.contains(EncryptionFlags.SIGN)) { - throw new IllegalArgumentException("All encrypted fields must be signed. Bad field: " + entry.getKey()); - } - ByteBuffer plainText; - ByteBuffer cipherText = entry.getValue().b().asByteBuffer(); - cipherText.rewind(); - if (encryptionKey instanceof DelegatedKey) { - plainText = ByteBuffer.wrap(((DelegatedKey)encryptionKey).decrypt(toByteArray(cipherText), null, encryptionMode)); - } else { - if (cipher == null) { - blockSize = getBlockSize(encryptionMode); - cipher = Cipher.getInstance(encryptionMode); - } - byte[] iv = new byte[blockSize]; - cipherText.get(iv); - cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng()); - plainText = ByteBuffer.allocate(cipher.getOutputSize(cipherText.remaining())); - cipher.doFinal(cipherText, plainText); - plainText.rewind(); - } - entry.setValue(AttributeValueMarshaller.unmarshall(plainText)); - } - } - } - - private static int getBlockSize(final String encryptionMode) { - return BLOCK_SIZE_CACHE.computeIfAbsent(encryptionMode, BLOCK_SIZE_CALCULATOR); - } - - /** - * This method has the side effect of replacing the plaintext - * attribute-values of "itemAttributes" with ciphertext attribute-values - * (which are always in the form of ByteBuffer) as per the corresponding - * attribute flags. - */ - private void actualEncryption(Map itemAttributes, - Map> attributeFlags, - Map materialDescription, - SecretKey encryptionKey) throws GeneralSecurityException { - String encryptionMode = null; - if (encryptionKey != null) { - materialDescription.put(this.symmetricEncryptionModeHeader, - SYMMETRIC_ENCRYPTION_MODE); - encryptionMode = encryptionKey.getAlgorithm() + SYMMETRIC_ENCRYPTION_MODE; - } - Cipher cipher = null; - int blockSize = -1; - - for (Map.Entry entry: itemAttributes.entrySet()) { - Set flags = attributeFlags.get(entry.getKey()); - if (flags != null && flags.contains(EncryptionFlags.ENCRYPT)) { - if (!flags.contains(EncryptionFlags.SIGN)) { - throw new IllegalArgumentException("All encrypted fields must be signed. Bad field: " + entry.getKey()); - } - ByteBuffer plainText = AttributeValueMarshaller.marshall(entry.getValue()); - plainText.rewind(); - ByteBuffer cipherText; - if (encryptionKey instanceof DelegatedKey) { - DelegatedKey dk = (DelegatedKey) encryptionKey; - cipherText = ByteBuffer.wrap( - dk.encrypt(toByteArray(plainText), null, encryptionMode)); - } else { - if (cipher == null) { - blockSize = getBlockSize(encryptionMode); - cipher = Cipher.getInstance(encryptionMode); - } - // Encryption format: - // Note a unique iv is generated per attribute - cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, Utils.getRng()); - cipherText = ByteBuffer.allocate(blockSize + cipher.getOutputSize(plainText.remaining())); - cipherText.position(blockSize); - cipher.doFinal(plainText, cipherText); - cipherText.flip(); - final byte[] iv = cipher.getIV(); - if (iv.length != blockSize) { - throw new IllegalStateException(String.format("Generated IV length (%d) not equal to block size (%d)", - iv.length, blockSize)); - } - cipherText.put(iv); - cipherText.rewind(); - } - // Replace the plaintext attribute value with the encrypted content - entry.setValue(AttributeValue.builder().b(SdkBytes.fromByteBuffer(cipherText)).build()); - } - } - } - - /** - * Get the name of the DynamoDB field used to store the signature. - * Defaults to {@link #DEFAULT_SIGNATURE_FIELD}. - * - * @return the name of the DynamoDB field used to store the signature - */ - String getSignatureFieldName() { - return signatureFieldName; - } - - /** - * Set the name of the DynamoDB field used to store the signature. - * - * @param signatureFieldName - */ - void setSignatureFieldName(final String signatureFieldName) { - this.signatureFieldName = signatureFieldName; - } - - /** - * Get the name of the DynamoDB field used to store metadata used by the - * DynamoDBEncryptedMapper. Defaults to {@link #DEFAULT_METADATA_FIELD}. - * - * @return the name of the DynamoDB field used to store metadata used by the - * DynamoDBEncryptedMapper - */ - String getMaterialDescriptionFieldName() { - return materialDescriptionFieldName; - } - - /** - * Set the name of the DynamoDB field used to store metadata used by the - * DynamoDBEncryptedMapper - * - * @param materialDescriptionFieldName - */ - void setMaterialDescriptionFieldName(final String materialDescriptionFieldName) { - this.materialDescriptionFieldName = materialDescriptionFieldName; - } - - /** - * Marshalls the description into a ByteBuffer by outputting - * each key (modified UTF-8) followed by its value (also in modified UTF-8). - * - * @param description - * @return the description encoded as an AttributeValue with a ByteBuffer value - * @see java.io.DataOutput#writeUTF(String) - */ - private static AttributeValue marshallDescription(Map description) { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(bos); - out.writeInt(CURRENT_VERSION); - for (Map.Entry entry : description.entrySet()) { - byte[] bytes = entry.getKey().getBytes(UTF8); - out.writeInt(bytes.length); - out.write(bytes); - bytes = entry.getValue().getBytes(UTF8); - out.writeInt(bytes.length); - out.write(bytes); - } - out.close(); - return AttributeValue.builder().b(SdkBytes.fromByteArray(bos.toByteArray())).build(); - } catch (IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - /** - * @see #marshallDescription(Map) - */ - private static Map unmarshallDescription(AttributeValue attributeValue) { - try (DataInputStream in = new DataInputStream( - new ByteBufferInputStream(attributeValue.b().asByteBuffer())) ) { - Map result = new HashMap<>(); - int version = in.readInt(); - if (version != CURRENT_VERSION) { - throw new IllegalArgumentException("Unsupported description version"); - } - - String key, value; - int keyLength, valueLength; - try { - while(in.available() > 0) { - keyLength = in.readInt(); - byte[] bytes = new byte[keyLength]; - if (in.read(bytes) != keyLength) { - throw new IllegalArgumentException("Malformed description"); - } - key = new String(bytes, UTF8); - valueLength = in.readInt(); - bytes = new byte[valueLength]; - if (in.read(bytes) != valueLength) { - throw new IllegalArgumentException("Malformed description"); - } - value = new String(bytes, UTF8); - result.put(key, value); - } - } catch (EOFException eof) { - throw new IllegalArgumentException("Malformed description", eof); - } - return result; - } catch (IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - /** - * @param encryptionContextOverrideOperator the nullable operator which will be used to override - * the EncryptionContext. - * @see EncryptionContextOperators - */ - void setEncryptionContextOverrideOperator( - Function encryptionContextOverrideOperator) { - this.encryptionContextOverrideOperator = encryptionContextOverrideOperator; - } - - /** - * @return the operator used to override the EncryptionContext - * @see #setEncryptionContextOverrideOperator(Function) - */ - private Function getEncryptionContextOverrideOperator() { - return encryptionContextOverrideOperator; - } - - private static byte[] toByteArray(ByteBuffer buffer) { - buffer = buffer.duplicate(); - // We can only return the array directly if: - // 1. The ByteBuffer exposes an array - // 2. The ByteBuffer starts at the beginning of the array - // 3. The ByteBuffer uses the entire array - if (buffer.hasArray() && buffer.arrayOffset() == 0) { - byte[] result = buffer.array(); - if (buffer.remaining() == result.length) { - return result; - } - } - - byte[] result = new byte[buffer.remaining()]; - buffer.get(result); - return result; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java deleted file mode 100644 index d2998057b0..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.security.GeneralSecurityException; -import java.security.Key; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -/** - * @author Greg Rubin - */ -// NOTE: This class must remain thread-safe. -class DynamoDbSigner { - private static final ConcurrentHashMap cache = - new ConcurrentHashMap(); - - protected static final Charset UTF8 = Charset.forName("UTF-8"); - private final SecureRandom rnd; - private final SecretKey hmacComparisonKey; - private final String signingAlgorithm; - - /** - * @param signingAlgorithm is the algorithm used for asymmetric signing (ex: SHA256withRSA). This - * is ignored for symmetric HMACs as that algorithm is fully specified by the key. - */ - static DynamoDbSigner getInstance(String signingAlgorithm, SecureRandom rnd) { - DynamoDbSigner result = cache.get(signingAlgorithm); - if (result == null) { - result = new DynamoDbSigner(signingAlgorithm, rnd); - cache.putIfAbsent(signingAlgorithm, result); - } - return result; - } - - /** - * @param signingAlgorithm is the algorithm used for asymmetric signing (ex: SHA256withRSA). This - * is ignored for symmetric HMACs as that algorithm is fully specified by the key. - */ - private DynamoDbSigner(String signingAlgorithm, SecureRandom rnd) { - if (rnd == null) { - rnd = Utils.getRng(); - } - this.rnd = rnd; - this.signingAlgorithm = signingAlgorithm; - // Shorter than the output of SHA256 to avoid weak keys. - // http://cs.nyu.edu/~dodis/ps/h-of-h.pdf - // http://link.springer.com/chapter/10.1007%2F978-3-642-32009-5_21 - byte[] tmpKey = new byte[31]; - rnd.nextBytes(tmpKey); - hmacComparisonKey = new SecretKeySpec(tmpKey, "HmacSHA256"); - } - - void verifySignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - Key verificationKey, - ByteBuffer signature) - throws GeneralSecurityException { - if (verificationKey instanceof DelegatedKey) { - DelegatedKey dKey = (DelegatedKey) verificationKey; - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - if (!dKey.verify(stringToSign, toByteArray(signature), dKey.getAlgorithm())) { - throw new SignatureException("Bad signature"); - } - } else if (verificationKey instanceof SecretKey) { - byte[] calculatedSig = - calculateSignature( - itemAttributes, attributeFlags, associatedData, (SecretKey) verificationKey); - if (!safeEquals(signature, calculatedSig)) { - throw new SignatureException("Bad signature"); - } - } else if (verificationKey instanceof PublicKey) { - PublicKey integrityKey = (PublicKey) verificationKey; - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - Signature sig = Signature.getInstance(getSigningAlgorithm()); - sig.initVerify(integrityKey); - sig.update(stringToSign); - if (!sig.verify(toByteArray(signature))) { - throw new SignatureException("Bad signature"); - } - } else { - throw new IllegalArgumentException("No integrity key provided"); - } - } - - static byte[] calculateStringToSign( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData) - throws NoSuchAlgorithmException { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - List attrNames = new ArrayList(itemAttributes.keySet()); - Collections.sort(attrNames); - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - if (associatedData != null) { - out.write(sha256.digest(associatedData)); - } else { - out.write(sha256.digest()); - } - sha256.reset(); - - for (String name : attrNames) { - Set set = attributeFlags.get(name); - if (set != null && set.contains(EncryptionFlags.SIGN)) { - AttributeValue tmp = itemAttributes.get(name); - out.write(sha256.digest(name.getBytes(UTF8))); - sha256.reset(); - if (set.contains(EncryptionFlags.ENCRYPT)) { - sha256.update("ENCRYPTED".getBytes(UTF8)); - } else { - sha256.update("PLAINTEXT".getBytes(UTF8)); - } - out.write(sha256.digest()); - - sha256.reset(); - - sha256.update(AttributeValueMarshaller.marshall(tmp)); - out.write(sha256.digest()); - sha256.reset(); - } - } - return out.toByteArray(); - } catch (IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - /** The itemAttributes have already been encrypted, if necessary, before the signing. */ - byte[] calculateSignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - Key key) - throws GeneralSecurityException { - if (key instanceof DelegatedKey) { - return calculateSignature(itemAttributes, attributeFlags, associatedData, (DelegatedKey) key); - } else if (key instanceof SecretKey) { - return calculateSignature(itemAttributes, attributeFlags, associatedData, (SecretKey) key); - } else if (key instanceof PrivateKey) { - return calculateSignature(itemAttributes, attributeFlags, associatedData, (PrivateKey) key); - } else { - throw new IllegalArgumentException("No integrity key provided"); - } - } - - byte[] calculateSignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - DelegatedKey key) - throws GeneralSecurityException { - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - return key.sign(stringToSign, key.getAlgorithm()); - } - - byte[] calculateSignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - SecretKey key) - throws GeneralSecurityException { - if (key instanceof DelegatedKey) { - return calculateSignature(itemAttributes, attributeFlags, associatedData, (DelegatedKey) key); - } - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - Mac hmac = Mac.getInstance(key.getAlgorithm()); - hmac.init(key); - hmac.update(stringToSign); - return hmac.doFinal(); - } - - byte[] calculateSignature( - Map itemAttributes, - Map> attributeFlags, - byte[] associatedData, - PrivateKey key) - throws GeneralSecurityException { - byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); - Signature sig = Signature.getInstance(signingAlgorithm); - sig.initSign(key, rnd); - sig.update(stringToSign); - return sig.sign(); - } - - String getSigningAlgorithm() { - return signingAlgorithm; - } - - /** Constant-time equality check. */ - private boolean safeEquals(ByteBuffer signature, byte[] calculatedSig) { - try { - signature.rewind(); - Mac hmac = Mac.getInstance(hmacComparisonKey.getAlgorithm()); - hmac.init(hmacComparisonKey); - hmac.update(signature); - byte[] signatureHash = hmac.doFinal(); - - hmac.reset(); - hmac.update(calculatedSig); - byte[] calculatedHash = hmac.doFinal(); - - return MessageDigest.isEqual(signatureHash, calculatedHash); - } catch (GeneralSecurityException ex) { - // We've hardcoded these algorithms, so the error should not be possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - private static byte[] toByteArray(ByteBuffer buffer) { - if (buffer.hasArray()) { - byte[] result = buffer.array(); - buffer.rewind(); - return result; - } else { - byte[] result = new byte[buffer.remaining()]; - buffer.get(result); - buffer.rewind(); - return result; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java deleted file mode 100644 index 9a78ad9b04..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -/** - * This class serves to provide additional useful data to - * {@link EncryptionMaterialsProvider}s so they can more intelligently select - * the proper {@link EncryptionMaterials} or {@link DecryptionMaterials} for - * use. Any of the methods are permitted to return null. - *

- * For the simplest cases, all a developer needs to provide in the context are: - *

    - *
  • TableName
  • - *
  • HashKeyName
  • - *
  • RangeKeyName (if present)
  • - *
- * - * This class is immutable. - * - * @author Greg Rubin - */ -public final class EncryptionContext { - private final String tableName; - private final Map attributeValues; - private final Object developerContext; - private final String hashKeyName; - private final String rangeKeyName; - private final Map materialDescription; - - /** - * Return a new builder that can be used to construct an {@link EncryptionContext} - * @return A newly initialized {@link EncryptionContext.Builder}. - */ - public static Builder builder() { - return new Builder(); - } - - private EncryptionContext(Builder builder) { - tableName = builder.tableName; - attributeValues = builder.attributeValues; - developerContext = builder.developerContext; - hashKeyName = builder.hashKeyName; - rangeKeyName = builder.rangeKeyName; - materialDescription = builder.materialDescription; - } - - /** - * Returns the name of the DynamoDB Table this record is associated with. - */ - public String getTableName() { - return tableName; - } - - /** - * Returns the DynamoDB record about to be encrypted/decrypted. - */ - public Map getAttributeValues() { - return attributeValues; - } - - /** - * This object has no meaning (and will not be set or examined) by any core libraries. - * It exists to allow custom object mappers and data access layers to pass - * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDbEncryptor}. - */ - public Object getDeveloperContext() { - return developerContext; - } - - /** - * Returns the name of the HashKey attribute for the record to be encrypted/decrypted. - */ - public String getHashKeyName() { - return hashKeyName; - } - - /** - * Returns the name of the RangeKey attribute for the record to be encrypted/decrypted. - */ - public String getRangeKeyName() { - return rangeKeyName; - } - - public Map getMaterialDescription() { - return materialDescription; - } - - /** - * Converts an existing {@link EncryptionContext} into a builder that can be used to mutate and make a new version. - * @return A new {@link EncryptionContext.Builder} with all the fields filled out to match the current object. - */ - public Builder toBuilder() { - return new Builder(this); - } - - /** - * Builder class for {@link EncryptionContext}. - * Mutable objects (other than developerContext) will undergo - * a defensive copy prior to being stored in the builder. - * - * This class is not thread-safe. - */ - public static final class Builder { - private String tableName = null; - private Map attributeValues = null; - private Object developerContext = null; - private String hashKeyName = null; - private String rangeKeyName = null; - private Map materialDescription = null; - - public Builder() { - } - - public Builder(EncryptionContext context) { - tableName = context.getTableName(); - attributeValues = context.getAttributeValues(); - hashKeyName = context.getHashKeyName(); - rangeKeyName = context.getRangeKeyName(); - developerContext = context.getDeveloperContext(); - materialDescription = context.getMaterialDescription(); - } - - public EncryptionContext build() { - return new EncryptionContext(this); - } - - public Builder tableName(String tableName) { - this.tableName = tableName; - return this; - } - - public Builder attributeValues(Map attributeValues) { - this.attributeValues = Collections.unmodifiableMap(new HashMap<>(attributeValues)); - return this; - } - - public Builder developerContext(Object developerContext) { - this.developerContext = developerContext; - return this; - } - - public Builder hashKeyName(String hashKeyName) { - this.hashKeyName = hashKeyName; - return this; - } - - public Builder rangeKeyName(String rangeKeyName) { - this.rangeKeyName = rangeKeyName; - return this; - } - - public Builder materialDescription(Map materialDescription) { - this.materialDescription = Collections.unmodifiableMap(new HashMap<>(materialDescription)); - return this; - } - } - - @Override - public String toString() { - return "EncryptionContext [tableName=" + tableName + ", attributeValues=" + attributeValues - + ", developerContext=" + developerContext - + ", hashKeyName=" + hashKeyName + ", rangeKeyName=" + rangeKeyName - + ", materialDescription=" + materialDescription + "]"; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java deleted file mode 100644 index 47329f7128..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -/** - * @author Greg Rubin - */ -public enum EncryptionFlags { - ENCRYPT, - SIGN -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java deleted file mode 100644 index f245d66e31..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java +++ /dev/null @@ -1,47 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions; - -/** - * Generic exception thrown for any problem the DynamoDB encryption client has performing tasks - */ -public class DynamoDbEncryptionException extends RuntimeException { - private static final long serialVersionUID = - 7565904179772520868L; - - /** - * Standard constructor - * @param cause exception cause - */ - public DynamoDbEncryptionException(Throwable cause) { - super(cause); - } - - /** - * Standard constructor - * @param message exception message - */ - public DynamoDbEncryptionException(String message) { - super(message); - } - - /** - * Standard constructor - * @param message exception message - * @param cause exception cause - */ - public DynamoDbEncryptionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java deleted file mode 100644 index 5dfbb19709..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.Key; -import java.security.KeyPair; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public abstract class AbstractRawMaterials implements DecryptionMaterials, EncryptionMaterials { - private Map description; - private final Key signingKey; - private final Key verificationKey; - - @SuppressWarnings("unchecked") - protected AbstractRawMaterials(KeyPair signingPair) { - this(signingPair, Collections.EMPTY_MAP); - } - - protected AbstractRawMaterials(KeyPair signingPair, Map description) { - this.signingKey = signingPair.getPrivate(); - this.verificationKey = signingPair.getPublic(); - setMaterialDescription(description); - } - - @SuppressWarnings("unchecked") - protected AbstractRawMaterials(SecretKey macKey) { - this(macKey, Collections.EMPTY_MAP); - } - - protected AbstractRawMaterials(SecretKey macKey, Map description) { - this.signingKey = macKey; - this.verificationKey = macKey; - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - } - - @Override - public Map getMaterialDescription() { - return new HashMap<>(description); - } - - public void setMaterialDescription(Map description) { - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - } - - @Override - public Key getSigningKey() { - return signingKey; - } - - @Override - public Key getVerificationKey() { - return verificationKey; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java deleted file mode 100644 index 003d0b60cc..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public class AsymmetricRawMaterials extends WrappedRawMaterials { - @SuppressWarnings("unchecked") - public AsymmetricRawMaterials(KeyPair encryptionKey, KeyPair signingPair) - throws GeneralSecurityException { - this(encryptionKey, signingPair, Collections.EMPTY_MAP); - } - - public AsymmetricRawMaterials(KeyPair encryptionKey, KeyPair signingPair, Map description) - throws GeneralSecurityException { - super(encryptionKey.getPublic(), encryptionKey.getPrivate(), signingPair, description); - } - - @SuppressWarnings("unchecked") - public AsymmetricRawMaterials(KeyPair encryptionKey, SecretKey macKey) - throws GeneralSecurityException { - this(encryptionKey, macKey, Collections.EMPTY_MAP); - } - - public AsymmetricRawMaterials(KeyPair encryptionKey, SecretKey macKey, Map description) - throws GeneralSecurityException { - super(encryptionKey.getPublic(), encryptionKey.getPrivate(), macKey, description); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java deleted file mode 100644 index 033d331f5b..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.util.Map; - -/** - * @author Greg Rubin - */ -public interface CryptographicMaterials { - Map getMaterialDescription(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java deleted file mode 100644 index 00f8548bc7..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.Key; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public interface DecryptionMaterials extends CryptographicMaterials { - SecretKey getDecryptionKey(); - Key getVerificationKey(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java deleted file mode 100644 index ecef9e9fc8..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.Key; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public interface EncryptionMaterials extends CryptographicMaterials { - SecretKey getEncryptionKey(); - Key getSigningKey(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java deleted file mode 100644 index b3daab44ba..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.KeyPair; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.SecretKey; - -/** - * @author Greg Rubin - */ -public class SymmetricRawMaterials extends AbstractRawMaterials { - private final SecretKey cryptoKey; - - @SuppressWarnings("unchecked") - public SymmetricRawMaterials(SecretKey encryptionKey, KeyPair signingPair) { - this(encryptionKey, signingPair, Collections.EMPTY_MAP); - } - - public SymmetricRawMaterials(SecretKey encryptionKey, KeyPair signingPair, Map description) { - super(signingPair, description); - this.cryptoKey = encryptionKey; - } - - @SuppressWarnings("unchecked") - public SymmetricRawMaterials(SecretKey encryptionKey, SecretKey macKey) { - this(encryptionKey, macKey, Collections.EMPTY_MAP); - } - - public SymmetricRawMaterials(SecretKey encryptionKey, SecretKey macKey, Map description) { - super(macKey, description); - this.cryptoKey = encryptionKey; - } - - @Override - public SecretKey getEncryptionKey() { - return cryptoKey; - } - - @Override - public SecretKey getDecryptionKey() { - return cryptoKey; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java deleted file mode 100644 index fd17521ca1..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyPair; -import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DelegatedKey; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -/** - * Represents cryptographic materials used to manage unique record-level keys. - * This class specifically implements Envelope Encryption where a unique content - * key is randomly generated each time this class is constructed which is then - * encrypted with the Wrapping Key and then persisted in the Description. If a - * wrapped key is present in the Description, then that content key is unwrapped - * and used to decrypt the actual data in the record. - * - * Other possibly implementations might use a Key-Derivation Function to derive - * a unique key per record. - * - * @author Greg Rubin - */ -public class WrappedRawMaterials extends AbstractRawMaterials { - /** - * The key-name in the Description which contains the algorithm use to wrap - * content key. Example values are "AESWrap", or - * "RSA/ECB/OAEPWithSHA-256AndMGF1Padding". - */ - public static final String KEY_WRAPPING_ALGORITHM = "amzn-ddb-wrap-alg"; - /** - * The key-name in the Description which contains the algorithm used by the - * content key. Example values are "AES", or "Blowfish". - */ - public static final String CONTENT_KEY_ALGORITHM = "amzn-ddb-env-alg"; - /** - * The key-name in the Description which which contains the wrapped content - * key. - */ - public static final String ENVELOPE_KEY = "amzn-ddb-env-key"; - - private static final String DEFAULT_ALGORITHM = "AES/256"; - - protected final Key wrappingKey; - protected final Key unwrappingKey; - private final SecretKey envelopeKey; - - public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, KeyPair signingPair) - throws GeneralSecurityException { - this(wrappingKey, unwrappingKey, signingPair, Collections.emptyMap()); - } - - public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, KeyPair signingPair, - Map description) throws GeneralSecurityException { - super(signingPair, description); - this.wrappingKey = wrappingKey; - this.unwrappingKey = unwrappingKey; - this.envelopeKey = initEnvelopeKey(); - } - - public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, SecretKey macKey) - throws GeneralSecurityException { - this(wrappingKey, unwrappingKey, macKey, Collections.emptyMap()); - } - - public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, SecretKey macKey, - Map description) throws GeneralSecurityException { - super(macKey, description); - this.wrappingKey = wrappingKey; - this.unwrappingKey = unwrappingKey; - this.envelopeKey = initEnvelopeKey(); - } - - @Override - public SecretKey getDecryptionKey() { - return envelopeKey; - } - - @Override - public SecretKey getEncryptionKey() { - return envelopeKey; - } - - /** - * Called by the constructors. If there is already a key associated with - * this record (usually signified by a value stored in the description in - * the key {@link #ENVELOPE_KEY}) it extracts it and returns it. Otherwise - * it generates a new key, stores a wrapped version in the Description, and - * returns the key to the caller. - * - * @return the content key (which is returned by both - * {@link #getDecryptionKey()} and {@link #getEncryptionKey()}. - * @throws GeneralSecurityException if there is a problem - */ - protected SecretKey initEnvelopeKey() throws GeneralSecurityException { - Map description = getMaterialDescription(); - if (description.containsKey(ENVELOPE_KEY)) { - if (unwrappingKey == null) { - throw new IllegalStateException("No private decryption key provided."); - } - byte[] encryptedKey = Base64.decode(description.get(ENVELOPE_KEY)); - String wrappingAlgorithm = unwrappingKey.getAlgorithm(); - if (description.containsKey(KEY_WRAPPING_ALGORITHM)) { - wrappingAlgorithm = description.get(KEY_WRAPPING_ALGORITHM); - } - return unwrapKey(description, encryptedKey, wrappingAlgorithm); - } else { - SecretKey key = description.containsKey(CONTENT_KEY_ALGORITHM) ? - generateContentKey(description.get(CONTENT_KEY_ALGORITHM)) : - generateContentKey(DEFAULT_ALGORITHM); - - String wrappingAlg = description.containsKey(KEY_WRAPPING_ALGORITHM) ? - description.get(KEY_WRAPPING_ALGORITHM) : - getTransformation(wrappingKey.getAlgorithm()); - byte[] encryptedKey = wrapKey(key, wrappingAlg); - description.put(ENVELOPE_KEY, Base64.encodeToString(encryptedKey)); - description.put(CONTENT_KEY_ALGORITHM, key.getAlgorithm()); - description.put(KEY_WRAPPING_ALGORITHM, wrappingAlg); - setMaterialDescription(description); - return key; - } - } - - public byte[] wrapKey(SecretKey key, String wrappingAlg) throws NoSuchAlgorithmException, NoSuchPaddingException, - InvalidKeyException, IllegalBlockSizeException { - if (wrappingKey instanceof DelegatedKey) { - return ((DelegatedKey)wrappingKey).wrap(key, null, wrappingAlg); - } else { - Cipher cipher = Cipher.getInstance(wrappingAlg); - cipher.init(Cipher.WRAP_MODE, wrappingKey, Utils.getRng()); - return cipher.wrap(key); - } - } - - protected SecretKey unwrapKey( - Map description, byte[] encryptedKey, String wrappingAlgorithm) - throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { - if (unwrappingKey instanceof DelegatedKey) { - return (SecretKey) - ((DelegatedKey) unwrappingKey) - .unwrap( - encryptedKey, - description.get(CONTENT_KEY_ALGORITHM), - Cipher.SECRET_KEY, - null, - wrappingAlgorithm); - } else { - Cipher cipher = Cipher.getInstance(wrappingAlgorithm); - - // This can be of the form "AES/256" as well as "AES" e.g., - // but we want to set the SecretKey with just "AES" in either case - String[] algPieces = description.get(CONTENT_KEY_ALGORITHM).split("/", 2); - String contentKeyAlgorithm = algPieces[0]; - - cipher.init(Cipher.UNWRAP_MODE, unwrappingKey, Utils.getRng()); - return (SecretKey) cipher.unwrap(encryptedKey, contentKeyAlgorithm, Cipher.SECRET_KEY); - } - } - - protected SecretKey generateContentKey(final String algorithm) throws NoSuchAlgorithmException { - String[] pieces = algorithm.split("/", 2); - KeyGenerator kg = KeyGenerator.getInstance(pieces[0]); - int keyLen = 0; - if (pieces.length == 2) { - try { - keyLen = Integer.parseInt(pieces[1]); - } catch (NumberFormatException ignored) { - } - } - - if (keyLen > 0) { - kg.init(keyLen, Utils.getRng()); - } else { - kg.init(Utils.getRng()); - } - return kg.generateKey(); - } - - private static String getTransformation(final String algorithm) { - if (algorithm.equalsIgnoreCase("RSA")) { - return "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; - } else if (algorithm.equalsIgnoreCase("AES")) { - return "AESWrap"; - } else { - return algorithm; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java deleted file mode 100644 index b49e2b9a20..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import java.security.KeyPair; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.SecretKey; - -/** - * This is a thin wrapper around the {@link WrappedMaterialsProvider}, using - * the provided encryptionKey for wrapping and unwrapping the - * record key. Please see that class for detailed documentation. - * - * @author Greg Rubin - */ -public class AsymmetricStaticProvider extends WrappedMaterialsProvider { - public AsymmetricStaticProvider(KeyPair encryptionKey, KeyPair signingPair) { - this(encryptionKey, signingPair, Collections.emptyMap()); - } - - public AsymmetricStaticProvider(KeyPair encryptionKey, SecretKey macKey) { - this(encryptionKey, macKey, Collections.emptyMap()); - } - - public AsymmetricStaticProvider(KeyPair encryptionKey, KeyPair signingPair, Map description) { - super(encryptionKey.getPublic(), encryptionKey.getPrivate(), signingPair, description); - } - - public AsymmetricStaticProvider(KeyPair encryptionKey, SecretKey macKey, Map description) { - super(encryptionKey.getPublic(), encryptionKey.getPrivate(), macKey, description); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java deleted file mode 100644 index 653e754c26..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import io.netty.util.internal.ObjectUtil; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.TTLCache; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.TTLCache.EntryLoader; -import java.util.concurrent.TimeUnit; - -/** - * This meta-Provider encrypts data with the most recent version of keying materials from a {@link - * ProviderStore} and decrypts using whichever version is appropriate. It also caches the results - * from the {@link ProviderStore} to avoid excessive load on the backing systems. - */ -public class CachingMostRecentProvider implements EncryptionMaterialsProvider { - private static final long INITIAL_VERSION = 0; - private static final String PROVIDER_CACHE_KEY_DELIM = "#"; - private static final int DEFAULT_CACHE_MAX_SIZE = 1000; - - private final long ttlInNanos; - private final ProviderStore keystore; - protected final String defaultMaterialName; - private final TTLCache providerCache; - private final TTLCache versionCache; - - private final EntryLoader versionLoader = - new EntryLoader() { - @Override - public Long load(String entryKey) { - return keystore.getMaxVersion(entryKey); - } - }; - private final EntryLoader providerLoader = - new EntryLoader() { - @Override - public EncryptionMaterialsProvider load(String entryKey) { - final String[] parts = entryKey.split(PROVIDER_CACHE_KEY_DELIM, 2); - if (parts.length != 2) { - throw new IllegalStateException("Invalid cache key for provider cache: " + entryKey); - } - return keystore.getProvider(parts[0], Long.parseLong(parts[1])); - } - }; - - /** - * Creates a new {@link CachingMostRecentProvider}. - * - * @param keystore The key store that this provider will use to determine which material and which - * version of material to use - * @param materialName The name of the materials associated with this provider - * @param ttlInMillis The length of time in milliseconds to cache the most recent provider - */ - public CachingMostRecentProvider( - final ProviderStore keystore, final String materialName, final long ttlInMillis) { - this(keystore, materialName, ttlInMillis, DEFAULT_CACHE_MAX_SIZE); - } - - /** - * Creates a new {@link CachingMostRecentProvider}. - * - * @param keystore The key store that this provider will use to determine which material and which - * version of material to use - * @param materialName The name of the materials associated with this provider - * @param ttlInMillis The length of time in milliseconds to cache the most recent provider - * @param maxCacheSize The maximum size of the underlying caches this provider uses. Entries will - * be evicted from the cache once this size is exceeded. - */ - public CachingMostRecentProvider( - final ProviderStore keystore, - final String materialName, - final long ttlInMillis, - final int maxCacheSize) { - this.keystore = ObjectUtil.checkNotNull(keystore, "keystore must not be null"); - this.defaultMaterialName = materialName; - this.ttlInNanos = TimeUnit.MILLISECONDS.toNanos(ttlInMillis); - - this.providerCache = new TTLCache<>(maxCacheSize, ttlInMillis, providerLoader); - this.versionCache = new TTLCache<>(maxCacheSize, ttlInMillis, versionLoader); - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - final long version = - keystore.getVersionFromMaterialDescription(context.getMaterialDescription()); - final String materialName = getMaterialName(context); - final String cacheKey = buildCacheKey(materialName, version); - - EncryptionMaterialsProvider provider = providerCache.load(cacheKey); - return provider.getDecryptionMaterials(context); - } - - - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - final String materialName = getMaterialName(context); - final long currentVersion = versionCache.load(materialName); - - if (currentVersion < 0) { - // The material hasn't been created yet, so specify a loading function - // to create the first version of materials and update both caches. - // We want this to be done as part of the cache load to ensure that this logic - // only happens once in a multithreaded environment, - // in order to limit calls to the keystore's dependencies. - final String cacheKey = buildCacheKey(materialName, INITIAL_VERSION); - EncryptionMaterialsProvider newProvider = - providerCache.load( - cacheKey, - s -> { - // Create the new material in the keystore - final String[] parts = s.split(PROVIDER_CACHE_KEY_DELIM, 2); - if (parts.length != 2) { - throw new IllegalStateException("Invalid cache key for provider cache: " + s); - } - EncryptionMaterialsProvider provider = - keystore.getOrCreate(parts[0], Long.parseLong(parts[1])); - - // We now should have version 0 in our keystore. - // Update the version cache for this material as a side effect - versionCache.put(materialName, INITIAL_VERSION); - - // Return the new materials to be put into the cache - return provider; - }); - - return newProvider.getEncryptionMaterials(context); - } else { - final String cacheKey = buildCacheKey(materialName, currentVersion); - return providerCache.load(cacheKey).getEncryptionMaterials(context); - } - } - - @Override - public void refresh() { - versionCache.clear(); - providerCache.clear(); - } - - public String getMaterialName() { - return defaultMaterialName; - } - - public long getTtlInMills() { - return TimeUnit.NANOSECONDS.toMillis(ttlInNanos); - } - - /** - * The current version of the materials being used for encryption. Returns -1 if we do not - * currently have a current version. - */ - public long getCurrentVersion() { - return versionCache.load(getMaterialName()); - } - - /** - * The last time the current version was updated. Returns 0 if we do not currently have a current - * version. - */ - public long getLastUpdated() { - // We cache a version of -1 to mean that there is not a current version - if (versionCache.load(getMaterialName()) < 0) { - return 0; - } - // Otherwise, return the last update time of that entry - return TimeUnit.NANOSECONDS.toMillis(versionCache.getLastUpdated(getMaterialName())); - } - - protected String getMaterialName(final EncryptionContext context) { - return defaultMaterialName; - } - - private static String buildCacheKey(final String materialName, final long version) { - StringBuilder result = new StringBuilder(materialName); - result.append(PROVIDER_CACHE_KEY_DELIM); - result.append(version); - return result.toString(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java deleted file mode 100644 index 425a4119f2..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.CONTENT_KEY_ALGORITHM; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.ENVELOPE_KEY; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.KEY_WRAPPING_ALGORITHM; - -import java.security.NoSuchAlgorithmException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Hkdf; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.awssdk.services.kms.model.DecryptRequest; -import software.amazon.awssdk.services.kms.model.DecryptResponse; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; - -/** - * Generates a unique data key for each record in DynamoDB and protects that key - * using {@link KmsClient}. Currently, the HashKey, RangeKey, and TableName will be - * included in the KMS EncryptionContext for wrapping/unwrapping the key. This - * means that records cannot be copied/moved between tables without re-encryption. - * - * @see KMS Encryption Context - */ -public class DirectKmsMaterialsProvider implements EncryptionMaterialsProvider { - private static final String COVERED_ATTR_CTX_KEY = "aws-kms-ec-attr"; - private static final String SIGNING_KEY_ALGORITHM = "amzn-ddb-sig-alg"; - private static final String TABLE_NAME_EC_KEY = "*aws-kms-table*"; - - private static final String DEFAULT_ENC_ALG = "AES/256"; - private static final String DEFAULT_SIG_ALG = "HmacSHA256/256"; - private static final String KEY_COVERAGE = "*keys*"; - private static final String KDF_ALG = "HmacSHA256"; - private static final String KDF_SIG_INFO = "Signing"; - private static final String KDF_ENC_INFO = "Encryption"; - - private final KmsClient kms; - private final String encryptionKeyId; - private final Map description; - private final String dataKeyAlg; - private final int dataKeyLength; - private final String dataKeyDesc; - private final String sigKeyAlg; - private final int sigKeyLength; - private final String sigKeyDesc; - - public DirectKmsMaterialsProvider(KmsClient kms) { - this(kms, null); - } - - public DirectKmsMaterialsProvider(KmsClient kms, String encryptionKeyId, Map materialDescription) { - this.kms = kms; - this.encryptionKeyId = encryptionKeyId; - this.description = materialDescription != null ? - Collections.unmodifiableMap(new HashMap<>(materialDescription)) : - Collections.emptyMap(); - - dataKeyDesc = description.getOrDefault(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, DEFAULT_ENC_ALG); - - String[] parts = dataKeyDesc.split("/", 2); - this.dataKeyAlg = parts[0]; - this.dataKeyLength = parts.length == 2 ? Integer.parseInt(parts[1]) : 256; - - sigKeyDesc = description.getOrDefault(SIGNING_KEY_ALGORITHM, DEFAULT_SIG_ALG); - - parts = sigKeyDesc.split("/", 2); - this.sigKeyAlg = parts[0]; - this.sigKeyLength = parts.length == 2 ? Integer.parseInt(parts[1]) : 256; - } - - public DirectKmsMaterialsProvider(KmsClient kms, String encryptionKeyId) { - this(kms, encryptionKeyId, Collections.emptyMap()); - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - final Map materialDescription = context.getMaterialDescription(); - - final Map ec = new HashMap<>(); - final String providedEncAlg = materialDescription.get(CONTENT_KEY_ALGORITHM); - final String providedSigAlg = materialDescription.get(SIGNING_KEY_ALGORITHM); - - ec.put("*" + CONTENT_KEY_ALGORITHM + "*", providedEncAlg); - ec.put("*" + SIGNING_KEY_ALGORITHM + "*", providedSigAlg); - - populateKmsEcFromEc(context, ec); - - DecryptRequest.Builder request = DecryptRequest.builder(); - request.ciphertextBlob(SdkBytes.fromByteArray(Base64.decode(materialDescription.get(ENVELOPE_KEY)))); - request.encryptionContext(ec); - final DecryptResponse decryptResponse = decrypt(request.build(), context); - validateEncryptionKeyId(decryptResponse.keyId(), context); - - final Hkdf kdf; - try { - kdf = Hkdf.getInstance(KDF_ALG); - } catch (NoSuchAlgorithmException e) { - throw new DynamoDbEncryptionException(e); - } - kdf.init(decryptResponse.plaintext().asByteArray()); - - final String[] encAlgParts = providedEncAlg.split("/", 2); - int encLength = encAlgParts.length == 2 ? Integer.parseInt(encAlgParts[1]) : 256; - final String[] sigAlgParts = providedSigAlg.split("/", 2); - int sigLength = sigAlgParts.length == 2 ? Integer.parseInt(sigAlgParts[1]) : 256; - - final SecretKey encryptionKey = new SecretKeySpec(kdf.deriveKey(KDF_ENC_INFO, encLength / 8), encAlgParts[0]); - final SecretKey macKey = new SecretKeySpec(kdf.deriveKey(KDF_SIG_INFO, sigLength / 8), sigAlgParts[0]); - - return new SymmetricRawMaterials(encryptionKey, macKey, materialDescription); - } - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - final Map ec = new HashMap<>(); - ec.put("*" + CONTENT_KEY_ALGORITHM + "*", dataKeyDesc); - ec.put("*" + SIGNING_KEY_ALGORITHM + "*", sigKeyDesc); - populateKmsEcFromEc(context, ec); - - final String keyId = selectEncryptionKeyId(context); - if (keyId == null || keyId.isEmpty()) { - throw new DynamoDbEncryptionException("Encryption key id is empty."); - } - - final GenerateDataKeyRequest.Builder req = GenerateDataKeyRequest.builder(); - req.keyId(keyId); - // NumberOfBytes parameter is used because we're not using this key as an AES-256 key, - // we're using it as an HKDF-SHA256 key. - req.numberOfBytes(256 / 8); - req.encryptionContext(ec); - - final GenerateDataKeyResponse dataKeyResult = generateDataKey(req.build(), context); - - final Map materialDescription = new HashMap<>(description); - materialDescription.put(COVERED_ATTR_CTX_KEY, KEY_COVERAGE); - materialDescription.put(KEY_WRAPPING_ALGORITHM, "kms"); - materialDescription.put(CONTENT_KEY_ALGORITHM, dataKeyDesc); - materialDescription.put(SIGNING_KEY_ALGORITHM, sigKeyDesc); - materialDescription.put(ENVELOPE_KEY, - Base64.encodeToString(dataKeyResult.ciphertextBlob().asByteArray())); - - final Hkdf kdf; - try { - kdf = Hkdf.getInstance(KDF_ALG); - } catch (NoSuchAlgorithmException e) { - throw new DynamoDbEncryptionException(e); - } - - kdf.init(dataKeyResult.plaintext().asByteArray()); - - final SecretKey encryptionKey = new SecretKeySpec(kdf.deriveKey(KDF_ENC_INFO, dataKeyLength / 8), dataKeyAlg); - final SecretKey signatureKey = new SecretKeySpec(kdf.deriveKey(KDF_SIG_INFO, sigKeyLength / 8), sigKeyAlg); - return new SymmetricRawMaterials(encryptionKey, signatureKey, materialDescription); - } - - /** - * Get encryption key id that is used to create the {@link EncryptionMaterials}. - * - * @return encryption key id. - */ - protected String getEncryptionKeyId() { - return this.encryptionKeyId; - } - - /** - * Select encryption key id to be used to generate data key. The default implementation of this method returns - * {@link DirectKmsMaterialsProvider#encryptionKeyId}. - * - * @param context encryption context. - * @return the encryptionKeyId. - * @throws DynamoDbEncryptionException when we fails to select a valid encryption key id. - */ - protected String selectEncryptionKeyId(EncryptionContext context) throws DynamoDbEncryptionException { - return getEncryptionKeyId(); - } - - /** - * Validate the encryption key id. The default implementation of this method does not validate - * encryption key id. - * - * @param encryptionKeyId encryption key id from {@link DecryptResponse}. - * @param context encryption context. - * @throws DynamoDbEncryptionException when encryptionKeyId is invalid. - */ - protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext context) - throws DynamoDbEncryptionException { - // No action taken. - } - - /** - * Decrypts ciphertext. The default implementation calls KMS to decrypt the ciphertext using the parameters - * provided in the {@link DecryptRequest}. Subclass can override the default implementation to provide - * additional request parameters using attributes within the {@link EncryptionContext}. - * - * @param request request parameters to decrypt the given ciphertext. - * @param context additional useful data to decrypt the ciphertext. - * @return the decrypted plaintext for the given ciphertext. - */ - protected DecryptResponse decrypt(final DecryptRequest request, final EncryptionContext context) { - return kms.decrypt(request); - } - - /** - * Returns a data encryption key that you can use in your application to encrypt data locally. The default - * implementation calls KMS to generate the data key using the parameters provided in the - * {@link GenerateDataKeyRequest}. Subclass can override the default implementation to provide additional - * request parameters using attributes within the {@link EncryptionContext}. - * - * @param request request parameters to generate the data key. - * @param context additional useful data to generate the data key. - * @return the newly generated data key which includes both the plaintext and ciphertext. - */ - protected GenerateDataKeyResponse generateDataKey(final GenerateDataKeyRequest request, - final EncryptionContext context) { - return kms.generateDataKey(request); - } - - /** - * Extracts relevant information from {@code context} and uses it to populate fields in - * {@code kmsEc}. Currently, these fields are: - *
- *
{@code HashKeyName}
- *
{@code HashKeyValue}
- *
{@code RangeKeyName}
- *
{@code RangeKeyValue}
- *
{@link #TABLE_NAME_EC_KEY}
- *
{@code TableName}
- */ - private static void populateKmsEcFromEc(EncryptionContext context, Map kmsEc) { - final String hashKeyName = context.getHashKeyName(); - if (hashKeyName != null) { - final AttributeValue hashKey = context.getAttributeValues().get(hashKeyName); - if (hashKey.n() != null) { - kmsEc.put(hashKeyName, hashKey.n()); - } else if (hashKey.s() != null) { - kmsEc.put(hashKeyName, hashKey.s()); - } else if (hashKey.b() != null) { - kmsEc.put(hashKeyName, Base64.encodeToString(hashKey.b().asByteArray())); - } else { - throw new UnsupportedOperationException("DirectKmsMaterialsProvider only supports String, Number, and Binary HashKeys"); - } - } - final String rangeKeyName = context.getRangeKeyName(); - if (rangeKeyName != null) { - final AttributeValue rangeKey = context.getAttributeValues().get(rangeKeyName); - if (rangeKey.n() != null) { - kmsEc.put(rangeKeyName, rangeKey.n()); - } else if (rangeKey.s() != null) { - kmsEc.put(rangeKeyName, rangeKey.s()); - } else if (rangeKey.b() != null) { - kmsEc.put(rangeKeyName, Base64.encodeToString(rangeKey.b().asByteArray())); - } else { - throw new UnsupportedOperationException("DirectKmsMaterialsProvider only supports String, Number, and Binary RangeKeys"); - } - } - - final String tableName = context.getTableName(); - if (tableName != null) { - kmsEc.put(TABLE_NAME_EC_KEY, tableName); - } - } - - @Override - public void refresh() { - // No action needed - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java deleted file mode 100644 index b60fee3ee0..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; - -/** - * Interface for providing encryption materials. - * Implementations are free to use any strategy for providing encryption - * materials, such as simply providing static material that doesn't change, - * or more complicated implementations, such as integrating with existing - * key management systems. - * - * @author Greg Rubin - */ -public interface EncryptionMaterialsProvider { - - /** - * Retrieves encryption materials matching the specified description from some source. - * - * @param context - * Information to assist in selecting a the proper return value. The implementation - * is free to determine the minimum necessary for successful processing. - * - * @return - * The encryption materials that match the description, or null if no matching encryption materials found. - */ - DecryptionMaterials getDecryptionMaterials(EncryptionContext context); - - /** - * Returns EncryptionMaterials which the caller can use for encryption. - * Each implementation of EncryptionMaterialsProvider can choose its own - * strategy for loading encryption material. For example, an - * implementation might load encryption material from an existing key - * management system, or load new encryption material when keys are - * rotated. - * - * @param context - * Information to assist in selecting a the proper return value. The implementation - * is free to determine the minimum necessary for successful processing. - * - * @return EncryptionMaterials which the caller can use to encrypt or - * decrypt data. - */ - EncryptionMaterials getEncryptionMaterials(EncryptionContext context); - - /** - * Forces this encryption materials provider to refresh its encryption - * material. For many implementations of encryption materials provider, - * this may simply be a no-op, such as any encryption materials provider - * implementation that vends static/non-changing encryption material. - * For other implementations that vend different encryption material - * throughout their lifetime, this method should force the encryption - * materials provider to refresh its encryption material. - */ - void refresh(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java deleted file mode 100644 index 483b81b51a..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.KeyStore.Entry; -import java.security.KeyStore.PrivateKeyEntry; -import java.security.KeyStore.ProtectionParameter; -import java.security.KeyStore.SecretKeyEntry; -import java.security.KeyStore.TrustedCertificateEntry; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.UnrecoverableEntryException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.AsymmetricRawMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; - -/** - * @author Greg Rubin - */ -public class KeyStoreMaterialsProvider implements EncryptionMaterialsProvider { - private final Map description; - private final String encryptionAlias; - private final String signingAlias; - private final ProtectionParameter encryptionProtection; - private final ProtectionParameter signingProtection; - private final KeyStore keyStore; - private final AtomicReference currMaterials = - new AtomicReference<>(); - - public KeyStoreMaterialsProvider(KeyStore keyStore, String encryptionAlias, String signingAlias, Map description) - throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { - this(keyStore, encryptionAlias, signingAlias, null, null, description); - } - - public KeyStoreMaterialsProvider(KeyStore keyStore, String encryptionAlias, String signingAlias, - ProtectionParameter encryptionProtection, ProtectionParameter signingProtection, - Map description) - throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { - super(); - this.keyStore = keyStore; - this.encryptionAlias = encryptionAlias; - this.signingAlias = signingAlias; - this.encryptionProtection = encryptionProtection; - this.signingProtection = signingProtection; - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - - validateKeys(); - loadKeys(); - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - CurrentMaterials materials = currMaterials.get(); - if (context.getMaterialDescription().entrySet().containsAll(description.entrySet())) { - if (materials.encryptionEntry instanceof SecretKeyEntry) { - return materials.symRawMaterials; - } else { - try { - return makeAsymMaterials(materials, context.getMaterialDescription()); - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to decrypt envelope key", ex); - } - } - } else { - return null; - } - } - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - CurrentMaterials materials = currMaterials.get(); - if (materials.encryptionEntry instanceof SecretKeyEntry) { - return materials.symRawMaterials; - } else { - try { - return makeAsymMaterials(materials, description); - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to encrypt envelope key", ex); - } - } - } - - private AsymmetricRawMaterials makeAsymMaterials(CurrentMaterials materials, - Map description) throws GeneralSecurityException { - KeyPair encryptionPair = entry2Pair(materials.encryptionEntry); - if (materials.signingEntry instanceof SecretKeyEntry) { - return new AsymmetricRawMaterials(encryptionPair, - ((SecretKeyEntry) materials.signingEntry).getSecretKey(), description); - } else { - return new AsymmetricRawMaterials(encryptionPair, entry2Pair(materials.signingEntry), - description); - } - } - - private static KeyPair entry2Pair(Entry entry) { - PublicKey pub = null; - PrivateKey priv = null; - - if (entry instanceof PrivateKeyEntry) { - PrivateKeyEntry pk = (PrivateKeyEntry) entry; - if (pk.getCertificate() != null) { - pub = pk.getCertificate().getPublicKey(); - } - priv = pk.getPrivateKey(); - } else if (entry instanceof TrustedCertificateEntry) { - TrustedCertificateEntry tc = (TrustedCertificateEntry) entry; - pub = tc.getTrustedCertificate().getPublicKey(); - } else { - throw new IllegalArgumentException( - "Only entry types PrivateKeyEntry and TrustedCertificateEntry are supported."); - } - return new KeyPair(pub, priv); - } - - /** - * Reloads the keys from the underlying keystore by calling - * {@link KeyStore#getEntry(String, ProtectionParameter)} again for each of them. - */ - @Override - public void refresh() { - try { - loadKeys(); - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to load keys from keystore", ex); - } - } - - private void validateKeys() throws KeyStoreException { - if (!keyStore.containsAlias(encryptionAlias)) { - throw new IllegalArgumentException("Keystore does not contain alias: " - + encryptionAlias); - } - if (!keyStore.containsAlias(signingAlias)) { - throw new IllegalArgumentException("Keystore does not contain alias: " - + signingAlias); - } - } - - private void loadKeys() throws NoSuchAlgorithmException, UnrecoverableEntryException, - KeyStoreException { - Entry encryptionEntry = keyStore.getEntry(encryptionAlias, encryptionProtection); - Entry signingEntry = keyStore.getEntry(signingAlias, signingProtection); - CurrentMaterials newMaterials = new CurrentMaterials(encryptionEntry, signingEntry); - currMaterials.set(newMaterials); - } - - private class CurrentMaterials { - public final Entry encryptionEntry; - public final Entry signingEntry; - public final SymmetricRawMaterials symRawMaterials; - - public CurrentMaterials(Entry encryptionEntry, Entry signingEntry) { - super(); - this.encryptionEntry = encryptionEntry; - this.signingEntry = signingEntry; - - if (encryptionEntry instanceof SecretKeyEntry) { - if (signingEntry instanceof SecretKeyEntry) { - this.symRawMaterials = new SymmetricRawMaterials( - ((SecretKeyEntry) encryptionEntry).getSecretKey(), - ((SecretKeyEntry) signingEntry).getSecretKey(), - description); - } else { - this.symRawMaterials = new SymmetricRawMaterials( - ((SecretKeyEntry) encryptionEntry).getSecretKey(), - entry2Pair(signingEntry), - description); - } - } else { - this.symRawMaterials = null; - } - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java deleted file mode 100644 index 8a63a0328c..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import java.security.KeyPair; -import java.util.Collections; -import java.util.Map; - -import javax.crypto.SecretKey; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.CryptographicMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; - -/** - * A provider which always returns the same provided symmetric - * encryption/decryption key and the same signing/verification key(s). - * - * @author Greg Rubin - */ -public class SymmetricStaticProvider implements EncryptionMaterialsProvider { - private final SymmetricRawMaterials materials; - - /** - * @param encryptionKey - * the value to be returned by - * {@link #getEncryptionMaterials(EncryptionContext)} and - * {@link #getDecryptionMaterials(EncryptionContext)} - * @param signingPair - * the keypair used to sign/verify the data stored in Dynamo. If - * only the public key is provided, then this provider may be - * used for decryption, but not encryption. - */ - public SymmetricStaticProvider(SecretKey encryptionKey, KeyPair signingPair) { - this(encryptionKey, signingPair, Collections.emptyMap()); - } - - /** - * @param encryptionKey - * the value to be returned by - * {@link #getEncryptionMaterials(EncryptionContext)} and - * {@link #getDecryptionMaterials(EncryptionContext)} - * @param signingPair - * the keypair used to sign/verify the data stored in Dynamo. If - * only the public key is provided, then this provider may be - * used for decryption, but not encryption. - * @param description - * the value to be returned by - * {@link CryptographicMaterials#getMaterialDescription()} for - * any {@link CryptographicMaterials} returned by this object. - */ - public SymmetricStaticProvider(SecretKey encryptionKey, - KeyPair signingPair, Map description) { - materials = new SymmetricRawMaterials(encryptionKey, signingPair, - description); - } - - /** - * @param encryptionKey - * the value to be returned by - * {@link #getEncryptionMaterials(EncryptionContext)} and - * {@link #getDecryptionMaterials(EncryptionContext)} - * @param macKey - * the key used to sign/verify the data stored in Dynamo. - */ - public SymmetricStaticProvider(SecretKey encryptionKey, SecretKey macKey) { - this(encryptionKey, macKey, Collections.emptyMap()); - } - - /** - * @param encryptionKey - * the value to be returned by - * {@link #getEncryptionMaterials(EncryptionContext)} and - * {@link #getDecryptionMaterials(EncryptionContext)} - * @param macKey - * the key used to sign/verify the data stored in Dynamo. - * @param description - * the value to be returned by - * {@link CryptographicMaterials#getMaterialDescription()} for - * any {@link CryptographicMaterials} returned by this object. - */ - public SymmetricStaticProvider(SecretKey encryptionKey, SecretKey macKey, Map description) { - materials = new SymmetricRawMaterials(encryptionKey, macKey, description); - } - - /** - * Returns the encryptionKey provided to the constructor if and only if - * materialDescription is a super-set (may be equal) to the - * description provided to the constructor. - */ - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - if (context.getMaterialDescription().entrySet().containsAll(materials.getMaterialDescription().entrySet())) { - return materials; - } - else { - return null; - } - } - - /** - * Returns the encryptionKey provided to the constructor. - */ - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - return materials; - } - - /** - * Does nothing. - */ - @Override - public void refresh() { - // Do Nothing - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java deleted file mode 100644 index 1c92fb3f4a..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import java.security.GeneralSecurityException; -import java.security.Key; -import java.security.KeyPair; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.SecretKey; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.CryptographicMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; - -/** - * This provider will use create a unique (random) symmetric key upon each call to - * {@link #getEncryptionMaterials(EncryptionContext)}. Practically, this means each record in DynamoDB will be - * encrypted under a unique record key. A wrapped/encrypted copy of this record key is stored in the - * MaterialsDescription field of that record and is unwrapped/decrypted upon reading that record. - * - * This is generally a more secure way of encrypting data than with the - * {@link SymmetricStaticProvider}. - * - * @see WrappedRawMaterials - * - * @author Greg Rubin - */ -public class WrappedMaterialsProvider implements EncryptionMaterialsProvider { - private final Key wrappingKey; - private final Key unwrappingKey; - private final KeyPair sigPair; - private final SecretKey macKey; - private final Map description; - - /** - * @param wrappingKey - * The key used to wrap/encrypt the symmetric record key. (May be the same as the - * unwrappingKey.) - * @param unwrappingKey - * The key used to unwrap/decrypt the symmetric record key. (May be the same as the - * wrappingKey.) If null, then this provider may only be used for - * decryption, but not encryption. - * @param signingPair - * the keypair used to sign/verify the data stored in Dynamo. If only the public key - * is provided, then this provider may only be used for decryption, but not - * encryption. - */ - public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, KeyPair signingPair) { - this(wrappingKey, unwrappingKey, signingPair, Collections.emptyMap()); - } - - /** - * @param wrappingKey - * The key used to wrap/encrypt the symmetric record key. (May be the same as the - * unwrappingKey.) - * @param unwrappingKey - * The key used to unwrap/decrypt the symmetric record key. (May be the same as the - * wrappingKey.) If null, then this provider may only be used for - * decryption, but not encryption. - * @param signingPair - * the keypair used to sign/verify the data stored in Dynamo. If only the public key - * is provided, then this provider may only be used for decryption, but not - * encryption. - * @param description - * description the value to be returned by - * {@link CryptographicMaterials#getMaterialDescription()} for any - * {@link CryptographicMaterials} returned by this object. - */ - public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, KeyPair signingPair, Map description) { - this.wrappingKey = wrappingKey; - this.unwrappingKey = unwrappingKey; - this.sigPair = signingPair; - this.macKey = null; - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - } - - /** - * @param wrappingKey - * The key used to wrap/encrypt the symmetric record key. (May be the same as the - * unwrappingKey.) - * @param unwrappingKey - * The key used to unwrap/decrypt the symmetric record key. (May be the same as the - * wrappingKey.) If null, then this provider may only be used for - * decryption, but not encryption. - * @param macKey - * the key used to sign/verify the data stored in Dynamo. - */ - public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, SecretKey macKey) { - this(wrappingKey, unwrappingKey, macKey, Collections.emptyMap()); - } - - /** - * @param wrappingKey - * The key used to wrap/encrypt the symmetric record key. (May be the same as the - * unwrappingKey.) - * @param unwrappingKey - * The key used to unwrap/decrypt the symmetric record key. (May be the same as the - * wrappingKey.) If null, then this provider may only be used for - * decryption, but not encryption. - * @param macKey - * the key used to sign/verify the data stored in Dynamo. - * @param description - * description the value to be returned by - * {@link CryptographicMaterials#getMaterialDescription()} for any - * {@link CryptographicMaterials} returned by this object. - */ - public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, SecretKey macKey, Map description) { - this.wrappingKey = wrappingKey; - this.unwrappingKey = unwrappingKey; - this.sigPair = null; - this.macKey = macKey; - this.description = Collections.unmodifiableMap(new HashMap<>(description)); - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - try { - if (macKey != null) { - return new WrappedRawMaterials(wrappingKey, unwrappingKey, macKey, context.getMaterialDescription()); - } else { - return new WrappedRawMaterials(wrappingKey, unwrappingKey, sigPair, context.getMaterialDescription()); - } - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to decrypt envelope key", ex); - } - } - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - try { - if (macKey != null) { - return new WrappedRawMaterials(wrappingKey, unwrappingKey, macKey, description); - } else { - return new WrappedRawMaterials(wrappingKey, unwrappingKey, sigPair, description); - } - } catch (GeneralSecurityException ex) { - throw new DynamoDbEncryptionException("Unable to encrypt envelope key", ex); - } - } - - @Override - public void refresh() { - // Do nothing - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java deleted file mode 100644 index c0fbe5e06f..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; - -import java.security.GeneralSecurityException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.core.exception.SdkClientException; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.ComparisonOperator; -import software.amazon.awssdk.services.dynamodb.model.Condition; -import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; -import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; -import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse; -import software.amazon.awssdk.services.dynamodb.model.ExpectedAttributeValue; -import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; -import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; -import software.amazon.awssdk.services.dynamodb.model.KeyType; -import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; -import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; -import software.amazon.awssdk.services.dynamodb.model.QueryRequest; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - - -/** - * Provides a simple collection of EncryptionMaterialProviders backed by an encrypted DynamoDB - * table. This can be used to build key hierarchies or meta providers. - * - * Currently, this only supports AES-256 in AESWrap mode and HmacSHA256 for the providers persisted - * in the table. - * - * @author rubin - */ -public class MetaStore extends ProviderStore { - private static final String INTEGRITY_ALGORITHM_FIELD = "intAlg"; - private static final String INTEGRITY_KEY_FIELD = "int"; - private static final String ENCRYPTION_ALGORITHM_FIELD = "encAlg"; - private static final String ENCRYPTION_KEY_FIELD = "enc"; - private static final Pattern COMBINED_PATTERN = Pattern.compile("([^#]+)#(\\d*)"); - private static final String DEFAULT_INTEGRITY = "HmacSHA256"; - private static final String DEFAULT_ENCRYPTION = "AES"; - private static final String MATERIAL_TYPE_VERSION = "t"; - private static final String META_ID = "amzn-ddb-meta-id"; - - private static final String DEFAULT_HASH_KEY = "N"; - private static final String DEFAULT_RANGE_KEY = "V"; - - /** Default no-op implementation of {@link ExtraDataSupplier}. */ - private static final EmptyExtraDataSupplier EMPTY_EXTRA_DATA_SUPPLIER - = new EmptyExtraDataSupplier(); - - /** DDB fields that must be encrypted. */ - private static final Set ENCRYPTED_FIELDS; - static { - final Set tempEncryptedFields = new HashSet<>(); - tempEncryptedFields.add(MATERIAL_TYPE_VERSION); - tempEncryptedFields.add(ENCRYPTION_KEY_FIELD); - tempEncryptedFields.add(ENCRYPTION_ALGORITHM_FIELD); - tempEncryptedFields.add(INTEGRITY_KEY_FIELD); - tempEncryptedFields.add(INTEGRITY_ALGORITHM_FIELD); - ENCRYPTED_FIELDS = tempEncryptedFields; - } - - private final Map doesNotExist; - private final Set doNotEncrypt; -// private final DynamoDbEncryptionConfiguration encryptionConfiguration; - private final String tableName; - private final DynamoDbClient ddb; - private final DynamoDbEncryptor encryptor; - private final EncryptionContext ddbCtx; - private final ExtraDataSupplier extraDataSupplier; - - /** - * Provides extra data that should be persisted along with the standard material data. - */ - public interface ExtraDataSupplier { - - /** - * Gets the extra data attributes for the specified material name. - * - * @param materialName material name. - * @param version version number. - * @return plain text of the extra data. - */ - Map getAttributes(final String materialName, final long version); - - /** - * Gets the extra data field names that should be signed only but not encrypted. - * - * @return signed only fields. - */ - Set getSignedOnlyFieldNames(); - } - - /** - * Create a new MetaStore with specified table name. - * - * @param ddb Interface for accessing DynamoDB. - * @param tableName DynamoDB table name for this {@link MetaStore}. - * @param encryptor used to perform crypto operations on the record attributes. - */ - public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor) { - this(ddb, tableName, encryptor, EMPTY_EXTRA_DATA_SUPPLIER); - } - - /** - * Create a new MetaStore with specified table name and extra data supplier. - * - * @param ddb Interface for accessing DynamoDB. - * @param tableName DynamoDB table name for this {@link MetaStore}. - * @param encryptor used to perform crypto operations on the record attributes - * @param extraDataSupplier provides extra data that should be stored along with the material. - */ - public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { - this.ddb = checkNotNull(ddb, "ddb must not be null"); - this.tableName = checkNotNull(tableName, "tableName must not be null"); - this.encryptor = checkNotNull(encryptor, "encryptor must not be null"); - this.extraDataSupplier = checkNotNull(extraDataSupplier, "extraDataSupplier must not be null"); - this.ddbCtx = - new EncryptionContext.Builder() - .tableName(this.tableName) - .hashKeyName(DEFAULT_HASH_KEY) - .rangeKeyName(DEFAULT_RANGE_KEY) - .build(); - - final Map tmpExpected = new HashMap<>(); - tmpExpected.put(DEFAULT_HASH_KEY, ExpectedAttributeValue.builder().exists(false).build()); - tmpExpected.put(DEFAULT_RANGE_KEY, ExpectedAttributeValue.builder().exists(false).build()); - doesNotExist = Collections.unmodifiableMap(tmpExpected); - - this.doNotEncrypt = getSignedOnlyFields(extraDataSupplier); - } - - @Override - public EncryptionMaterialsProvider getProvider(final String materialName, final long version) { - final Map item = getMaterialItem(materialName, version); - return decryptProvider(item); - } - - @Override - public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) { - final Map plaintext = createMaterialItem(materialName, nextId); - final Map ciphertext = conditionalPut(getEncryptedText(plaintext)); - return decryptProvider(ciphertext); - } - - @Override - public long getMaxVersion(final String materialName) { - - final List> items = - ddb.query( - QueryRequest.builder() - .tableName(tableName) - .consistentRead(Boolean.TRUE) - .keyConditions( - Collections.singletonMap( - DEFAULT_HASH_KEY, - Condition.builder() - .comparisonOperator(ComparisonOperator.EQ) - .attributeValueList(AttributeValue.builder().s(materialName).build()) - .build())) - .limit(1) - .scanIndexForward(false) - .attributesToGet(DEFAULT_RANGE_KEY) - .build()) - .items(); - - if (items.isEmpty()) { - return -1L; - } else { - return Long.parseLong(items.get(0).get(DEFAULT_RANGE_KEY).n()); - } - } - - @Override - public long getVersionFromMaterialDescription(final Map description) { - final Matcher m = COMBINED_PATTERN.matcher(description.get(META_ID)); - if (m.matches()) { - return Long.parseLong(m.group(2)); - } else { - throw new IllegalArgumentException("No meta id found"); - } - } - - /** - * This API retrieves the intermediate keys from the source region and replicates it in the target region. - * - * @param materialName material name of the encryption material. - * @param version version of the encryption material. - * @param targetMetaStore target MetaStore where the encryption material to be stored. - */ - public void replicate(final String materialName, final long version, final MetaStore targetMetaStore) { - try { - final Map item = getMaterialItem(materialName, version); - - final Map plainText = getPlainText(item); - final Map encryptedText = targetMetaStore.getEncryptedText(plainText); - final PutItemRequest put = PutItemRequest.builder() - .tableName(targetMetaStore.tableName) - .item(encryptedText) - .expected(doesNotExist) - .build(); - targetMetaStore.ddb.putItem(put); - } catch (ConditionalCheckFailedException e) { - //Item already present. - } - } - - /** - * Creates a DynamoDB Table with the correct properties to be used with a ProviderStore. - * - * @param ddb interface for accessing DynamoDB - * @param tableName name of table that stores the meta data of the material. - * @param provisionedThroughput required provisioned throughput of the this table. - * @return result of create table request. - */ - public static CreateTableResponse createTable(final DynamoDbClient ddb, final String tableName, - final ProvisionedThroughput provisionedThroughput) { - return ddb.createTable( - CreateTableRequest.builder() - .tableName(tableName) - .attributeDefinitions(Arrays.asList( - AttributeDefinition.builder() - .attributeName(DEFAULT_HASH_KEY) - .attributeType(ScalarAttributeType.S) - .build(), - AttributeDefinition.builder() - .attributeName(DEFAULT_RANGE_KEY) - .attributeType(ScalarAttributeType.N).build())) - .keySchema(Arrays.asList( - KeySchemaElement.builder() - .attributeName(DEFAULT_HASH_KEY) - .keyType(KeyType.HASH) - .build(), - KeySchemaElement.builder() - .attributeName(DEFAULT_RANGE_KEY) - .keyType(KeyType.RANGE) - .build())) - .provisionedThroughput(provisionedThroughput).build()); - } - - private Map getMaterialItem(final String materialName, final long version) { - final Map ddbKey = new HashMap<>(); - ddbKey.put(DEFAULT_HASH_KEY, AttributeValue.builder().s(materialName).build()); - ddbKey.put(DEFAULT_RANGE_KEY, AttributeValue.builder().n(Long.toString(version)).build()); - final Map item = ddbGet(ddbKey); - if (item == null || item.isEmpty()) { - throw new IndexOutOfBoundsException("No material found: " + materialName + "#" + version); - } - return item; - } - - - /** - * Empty extra data supplier. This default class is intended to simplify the default - * implementation of {@link MetaStore}. - */ - private static class EmptyExtraDataSupplier implements ExtraDataSupplier { - @Override - public Map getAttributes(String materialName, long version) { - return Collections.emptyMap(); - } - - @Override - public Set getSignedOnlyFieldNames() { - return Collections.emptySet(); - } - } - - /** - * Get a set of fields that must be signed but not encrypted. - * - * @param extraDataSupplier extra data supplier that is used to return sign only field names. - * @return fields that must be signed. - */ - private static Set getSignedOnlyFields(final ExtraDataSupplier extraDataSupplier) { - final Set signedOnlyFields = extraDataSupplier.getSignedOnlyFieldNames(); - for (final String signedOnlyField : signedOnlyFields) { - if (ENCRYPTED_FIELDS.contains(signedOnlyField)) { - throw new IllegalArgumentException(signedOnlyField + " must be encrypted"); - } - } - - // fields that should not be encrypted - final Set doNotEncryptFields = new HashSet<>(); - doNotEncryptFields.add(DEFAULT_HASH_KEY); - doNotEncryptFields.add(DEFAULT_RANGE_KEY); - doNotEncryptFields.addAll(signedOnlyFields); - return Collections.unmodifiableSet(doNotEncryptFields); - } - - private Map conditionalPut(final Map item) { - try { - final PutItemRequest put = PutItemRequest.builder().tableName(tableName).item(item) - .expected(doesNotExist).build(); - ddb.putItem(put); - return item; - } catch (final ConditionalCheckFailedException ex) { - final Map ddbKey = new HashMap<>(); - ddbKey.put(DEFAULT_HASH_KEY, item.get(DEFAULT_HASH_KEY)); - ddbKey.put(DEFAULT_RANGE_KEY, item.get(DEFAULT_RANGE_KEY)); - return ddbGet(ddbKey); - } - } - - private Map ddbGet(final Map ddbKey) { - return ddb.getItem( - GetItemRequest.builder().tableName(tableName).consistentRead(true) - .key(ddbKey).build()).item(); - } - - /** - * Build an material item for a given material name and version with newly generated - * encryption and integrity keys. - * - * @param materialName material name. - * @param version version of the material. - * @return newly generated plaintext material item. - */ - private Map createMaterialItem(final String materialName, final long version) { - final SecretKeySpec encryptionKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_ENCRYPTION); - final SecretKeySpec integrityKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_INTEGRITY); - - final Map plaintext = new HashMap<>(); - plaintext.put(DEFAULT_HASH_KEY, AttributeValue.builder().s(materialName).build()); - plaintext.put(DEFAULT_RANGE_KEY, AttributeValue.builder().n(Long.toString(version)).build()); - plaintext.put(MATERIAL_TYPE_VERSION, AttributeValue.builder().s("0").build()); - plaintext.put(ENCRYPTION_KEY_FIELD, - AttributeValue.builder().b(SdkBytes.fromByteArray(encryptionKey.getEncoded())).build()); - plaintext.put(ENCRYPTION_ALGORITHM_FIELD, AttributeValue.builder().s(encryptionKey.getAlgorithm()).build()); - plaintext.put(INTEGRITY_KEY_FIELD, - AttributeValue.builder().b(SdkBytes.fromByteArray(integrityKey.getEncoded())).build()); - plaintext.put(INTEGRITY_ALGORITHM_FIELD, AttributeValue.builder().s(integrityKey.getAlgorithm()).build()); - plaintext.putAll(extraDataSupplier.getAttributes(materialName, version)); - - return plaintext; - } - - private EncryptionMaterialsProvider decryptProvider(final Map item) { - final Map plaintext = getPlainText(item); - - final String type = plaintext.get(MATERIAL_TYPE_VERSION).s(); - final SecretKey encryptionKey; - final SecretKey integrityKey; - // This switch statement is to make future extensibility easier and more obvious - switch (type) { - case "0": // Only currently supported type - encryptionKey = new SecretKeySpec(plaintext.get(ENCRYPTION_KEY_FIELD).b().asByteArray(), - plaintext.get(ENCRYPTION_ALGORITHM_FIELD).s()); - integrityKey = new SecretKeySpec(plaintext.get(INTEGRITY_KEY_FIELD).b().asByteArray(), plaintext - .get(INTEGRITY_ALGORITHM_FIELD).s()); - break; - default: - throw new IllegalStateException("Unsupported material type: " + type); - } - return new WrappedMaterialsProvider(encryptionKey, encryptionKey, integrityKey, - buildDescription(plaintext)); - } - - /** - * Decrypts attributes in the ciphertext item using {@link DynamoDbEncryptor}. except the - * attribute names specified in doNotEncrypt. - * - * @param ciphertext the ciphertext to be decrypted. - * @throws SdkClientException when failed to decrypt material item. - * @return decrypted item. - */ - private Map getPlainText(final Map ciphertext) { - try { - return encryptor.decryptAllFieldsExcept(ciphertext, ddbCtx, doNotEncrypt); - } catch (final GeneralSecurityException e) { - throw SdkClientException.create("Error retrieving PlainText", e); - } - } - - /** - * Encrypts attributes in the plaintext item using {@link DynamoDbEncryptor}. except the attribute - * names specified in doNotEncrypt. - * - * @throws SdkClientException when failed to encrypt material item. - * @param plaintext plaintext to be encrypted. - */ - private Map getEncryptedText(Map plaintext) { - try { - return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, doNotEncrypt); - } catch (final GeneralSecurityException e) { - throw SdkClientException.create("Error retrieving PlainText", e); - } - } - - private Map buildDescription(final Map plaintext) { - return Collections.singletonMap(META_ID, plaintext.get(DEFAULT_HASH_KEY).s() + "#" - + plaintext.get(DEFAULT_RANGE_KEY).n()); - } - - private static V checkNotNull(final V ref, final String errMsg) { - if (ref == null) { - throw new NullPointerException(errMsg); - } else { - return ref; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java deleted file mode 100644 index a29fe9b34d..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; - -import java.util.Map; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; - -/** - * Provides a standard way to retrieve and optionally create {@link EncryptionMaterialsProvider}s - * backed by some form of persistent storage. - * - * @author rubin - * - */ -public abstract class ProviderStore { - - /** - * Returns the most recent provider with the specified name. If there are no providers with this - * name, it will create one with version 0. - */ - public EncryptionMaterialsProvider getProvider(final String materialName) { - final long currVersion = getMaxVersion(materialName); - if (currVersion >= 0) { - return getProvider(materialName, currVersion); - } else { - return getOrCreate(materialName, 0); - } - } - - /** - * Returns the provider with the specified name and version. - * - * @throws IndexOutOfBoundsException - * if {@code version} is not a valid version - */ - public abstract EncryptionMaterialsProvider getProvider(final String materialName, final long version); - - /** - * Creates a new provider with a version one greater than the current max version. If multiple - * clients attempt to create a provider with this same version simultaneously, they will - * properly coordinate and the result will be that a single provider is created and that all - * ProviderStores return the same one. - */ - public EncryptionMaterialsProvider newProvider(final String materialName) { - final long nextId = getMaxVersion(materialName) + 1; - return getOrCreate(materialName, nextId); - } - - /** - * Returns the provider with the specified name and version and creates it if it doesn't exist. - * - * @throws UnsupportedOperationException - * if a new provider cannot be created - */ - public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) { - try { - return getProvider(materialName, nextId); - } catch (final IndexOutOfBoundsException ex) { - throw new UnsupportedOperationException("This ProviderStore does not support creation.", ex); - } - } - - /** - * Returns the maximum version number associated with {@code materialName}. If there are no - * versions, returns -1. - */ - public abstract long getMaxVersion(final String materialName); - - /** - * Extracts the material version from {@code description}. - */ - public abstract long getVersionFromMaterialDescription(final Map description); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java deleted file mode 100644 index d29bb818cb..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java +++ /dev/null @@ -1,81 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; - -import java.util.Map; -import java.util.function.UnaryOperator; - -/** - * Implementations of common operators for overriding the EncryptionContext - */ -public class EncryptionContextOperators { - - // Prevent instantiation - private EncryptionContextOperators() { - } - - /** - * An operator for overriding EncryptionContext's table name for a specific DynamoDbEncryptor. If any table names or - * the encryption context itself is null, then it returns the original EncryptionContext. - * - * @param originalTableName the name of the table that should be overridden in the Encryption Context - * @param newTableName the table name that should be used in the Encryption Context - * @return A UnaryOperator that produces a new EncryptionContext with the supplied table name - */ - public static UnaryOperator overrideEncryptionContextTableName( - String originalTableName, - String newTableName) { - return encryptionContext -> { - if (encryptionContext == null - || encryptionContext.getTableName() == null - || originalTableName == null - || newTableName == null) { - return encryptionContext; - } - if (originalTableName.equals(encryptionContext.getTableName())) { - return encryptionContext.toBuilder().tableName(newTableName).build(); - } else { - return encryptionContext; - } - }; - } - - /** - * An operator for mapping multiple table names in the Encryption Context to a new table name. If the table name for - * a given EncryptionContext is missing, then it returns the original EncryptionContext. Similarly, it returns the - * original EncryptionContext if the value it is overridden to is null, or if the original table name is null. - * - * @param tableNameOverrideMap a map specifying the names of tables that should be overridden, - * and the values to which they should be overridden. If the given table name - * corresponds to null, or isn't in the map, then the table name won't be overridden. - * @return A UnaryOperator that produces a new EncryptionContext with the supplied table name - */ - public static UnaryOperator overrideEncryptionContextTableNameUsingMap( - Map tableNameOverrideMap) { - return encryptionContext -> { - if (tableNameOverrideMap == null || encryptionContext == null || encryptionContext.getTableName() == null) { - return encryptionContext; - } - String newTableName = tableNameOverrideMap.get(encryptionContext.getTableName()); - if (newTableName != null) { - return encryptionContext.toBuilder().tableName(newTableName).build(); - } else { - return encryptionContext; - } - }; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java deleted file mode 100644 index e9348af05d..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.math.BigDecimal; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import software.amazon.awssdk.core.BytesWrapper; -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.core.util.DefaultSdkAutoConstructList; -import software.amazon.awssdk.core.util.DefaultSdkAutoConstructMap; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - - -/** - * @author Greg Rubin - */ -public class AttributeValueMarshaller { - private static final Charset UTF8 = Charset.forName("UTF-8"); - private static final int TRUE_FLAG = 1; - private static final int FALSE_FLAG = 0; - - private AttributeValueMarshaller() { - // Prevent instantiation - } - - /** - * Marshalls the data using a TLV (Tag-Length-Value) encoding. The tag may be 'b', 'n', 's', - * '?', '\0' to represent a ByteBuffer, Number, String, Boolean, or Null respectively. The tag - * may also be capitalized (for 'b', 'n', and 's',) to represent an array of that type. If an - * array is stored, then a four-byte big-endian integer is written representing the number of - * array elements. If a ByteBuffer is stored, the length of the buffer is stored as a four-byte - * big-endian integer and the buffer then copied directly. Both Numbers and Strings are treated - * identically and are stored as UTF8 encoded Unicode, proceeded by the length of the encoded - * string (in bytes) as a four-byte big-endian integer. Boolean is encoded as a single byte, 0 - * for false and 1 for true (and so has no Length parameter). The - * Null tag ('\0') takes neither a Length nor a Value parameter. - * - * The tags 'L' and 'M' are for the document types List and Map respectively. These are encoded - * recursively with the Length being the size of the collection. In the case of List, the value - * is a Length number of marshalled AttributeValues. If the case of Map, the value is a Length - * number of AttributeValue Pairs where the first must always have a String value. - * - * This implementation does not recognize loops. If an AttributeValue contains itself - * (even indirectly) this code will recurse infinitely. - * - * @param attributeValue an AttributeValue instance - * @return the serialized AttributeValue - * @see java.io.DataInput - */ - public static ByteBuffer marshall(final AttributeValue attributeValue) { - try (ByteArrayOutputStream resultBytes = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(resultBytes);) { - marshall(attributeValue, out); - out.close(); - resultBytes.close(); - return ByteBuffer.wrap(resultBytes.toByteArray()); - } catch (final IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - private static void marshall(final AttributeValue attributeValue, final DataOutputStream out) - throws IOException { - - if (attributeValue.b() != null) { - out.writeChar('b'); - writeBytes(attributeValue.b().asByteBuffer(), out); - } else if (hasAttributeValueSet(attributeValue.bs())) { - out.writeChar('B'); - writeBytesList(attributeValue.bs().stream() - .map(BytesWrapper::asByteBuffer).collect(Collectors.toList()), out); - } else if (attributeValue.n() != null) { - out.writeChar('n'); - writeString(trimZeros(attributeValue.n()), out); - } else if (hasAttributeValueSet(attributeValue.ns())) { - out.writeChar('N'); - - final List ns = new ArrayList<>(attributeValue.ns().size()); - for (final String n : attributeValue.ns()) { - ns.add(trimZeros(n)); - } - writeStringList(ns, out); - } else if (attributeValue.s() != null) { - out.writeChar('s'); - writeString(attributeValue.s(), out); - } else if (hasAttributeValueSet(attributeValue.ss())) { - out.writeChar('S'); - writeStringList(attributeValue.ss(), out); - } else if (attributeValue.bool() != null) { - out.writeChar('?'); - out.writeByte((attributeValue.bool() ? TRUE_FLAG : FALSE_FLAG)); - } else if (Boolean.TRUE.equals(attributeValue.nul())) { - out.writeChar('\0'); - } else if (hasAttributeValueSet(attributeValue.l())) { - final List l = attributeValue.l(); - out.writeChar('L'); - out.writeInt(l.size()); - for (final AttributeValue attr : l) { - if (attr == null) { - throw new NullPointerException( - "Encountered null list entry value while marshalling attribute value " - + attributeValue); - } - marshall(attr, out); - } - } else if (hasAttributeValueMap(attributeValue.m())) { - final Map m = attributeValue.m(); - final List mKeys = new ArrayList<>(m.keySet()); - Collections.sort(mKeys); - out.writeChar('M'); - out.writeInt(m.size()); - for (final String mKey : mKeys) { - marshall(AttributeValue.builder().s(mKey).build(), out); - - final AttributeValue mValue = m.get(mKey); - - if (mValue == null) { - throw new NullPointerException( - "Encountered null map value for key " - + mKey - + " while marshalling attribute value " - + attributeValue); - } - marshall(mValue, out); - } - } else { - throw new IllegalArgumentException("A seemingly empty AttributeValue is indicative of invalid input or potential errors"); - } - } - - /** - * @see #marshall(AttributeValue) - */ - public static AttributeValue unmarshall(final ByteBuffer plainText) { - try (final DataInputStream in = new DataInputStream( - new ByteBufferInputStream(plainText.asReadOnlyBuffer()))) { - return unmarshall(in); - } catch (IOException ex) { - // Due to the objects in use, an IOException is not possible. - throw new RuntimeException("Unexpected exception", ex); - } - } - - private static AttributeValue unmarshall(final DataInputStream in) throws IOException { - char type = in.readChar(); - AttributeValue.Builder result = AttributeValue.builder(); - switch (type) { - case '\0': - result.nul(Boolean.TRUE); - break; - case 'b': - result.b(SdkBytes.fromByteBuffer(readBytes(in))); - break; - case 'B': - result.bs(readBytesList(in).stream().map(SdkBytes::fromByteBuffer).collect(Collectors.toList())); - break; - case 'n': - result.n(readString(in)); - break; - case 'N': - result.ns(readStringList(in)); - break; - case 's': - result.s(readString(in)); - break; - case 'S': - result.ss(readStringList(in)); - break; - case '?': - final byte boolValue = in.readByte(); - - if (boolValue == TRUE_FLAG) { - result.bool(Boolean.TRUE); - } else if (boolValue == FALSE_FLAG) { - result.bool(Boolean.FALSE); - } else { - throw new IllegalArgumentException("Improperly formatted data"); - } - break; - case 'L': - final int lCount = in.readInt(); - final List l = new ArrayList<>(lCount); - for (int lIdx = 0; lIdx < lCount; lIdx++) { - l.add(unmarshall(in)); - } - result.l(l); - break; - case 'M': - final int mCount = in.readInt(); - final Map m = new HashMap<>(); - for (int mIdx = 0; mIdx < mCount; mIdx++) { - final AttributeValue key = unmarshall(in); - if (key.s() == null) { - throw new IllegalArgumentException("Improperly formatted data"); - } - AttributeValue value = unmarshall(in); - m.put(key.s(), value); - } - result.m(m); - break; - default: - throw new IllegalArgumentException("Unsupported data encoding"); - } - - return result.build(); - } - - private static String trimZeros(final String n) { - BigDecimal number = new BigDecimal(n); - if (number.compareTo(BigDecimal.ZERO) == 0) { - return "0"; - } - return number.stripTrailingZeros().toPlainString(); - } - - private static void writeStringList(List values, final DataOutputStream out) throws IOException { - final List sorted = new ArrayList<>(values); - Collections.sort(sorted); - out.writeInt(sorted.size()); - for (final String v : sorted) { - writeString(v, out); - } - } - - private static List readStringList(final DataInputStream in) throws IOException, - IllegalArgumentException { - final int nCount = in.readInt(); - List ns = new ArrayList<>(nCount); - for (int nIdx = 0; nIdx < nCount; nIdx++) { - ns.add(readString(in)); - } - return ns; - } - - private static void writeString(String value, final DataOutputStream out) throws IOException { - final byte[] bytes = value.getBytes(UTF8); - out.writeInt(bytes.length); - out.write(bytes); - } - - private static String readString(final DataInputStream in) throws IOException, - IllegalArgumentException { - byte[] bytes; - int length; - length = in.readInt(); - bytes = new byte[length]; - if(in.read(bytes) != length) { - throw new IllegalArgumentException("Improperly formatted data"); - } - return new String(bytes, UTF8); - } - - private static void writeBytesList(List values, final DataOutputStream out) throws IOException { - final List sorted = new ArrayList<>(values); - Collections.sort(sorted); - out.writeInt(sorted.size()); - for (final ByteBuffer v : sorted) { - writeBytes(v, out); - } - } - - private static List readBytesList(final DataInputStream in) throws IOException { - final int bCount = in.readInt(); - List bs = new ArrayList<>(bCount); - for (int bIdx = 0; bIdx < bCount; bIdx++) { - bs.add(readBytes(in)); - } - return bs; - } - - private static void writeBytes(ByteBuffer value, final DataOutputStream out) throws IOException { - value = value.asReadOnlyBuffer(); - value.rewind(); - out.writeInt(value.remaining()); - while (value.hasRemaining()) { - out.writeByte(value.get()); - } - } - - private static ByteBuffer readBytes(final DataInputStream in) throws IOException { - final int length = in.readInt(); - final byte[] buf = new byte[length]; - in.readFully(buf); - return ByteBuffer.wrap(buf); - } - - /** - * Determines if the value of a 'set' type AttributeValue (various S types) has been explicitly set or not. - * @param value the actual value portion of an AttributeValue of the appropriate type - * @return true if the value of this type field has been explicitly set, false if it has not - */ - private static boolean hasAttributeValueSet(Collection value) { - return value != null && value != DefaultSdkAutoConstructList.getInstance(); - } - - /** - * Determines if the value of a 'map' type AttributeValue (M type) has been explicitly set or not. - * @param value the actual value portion of a AttributeValue of the appropriate type - * @return true if the value of this type field has been explicitly set, false if it has not - */ - private static boolean hasAttributeValueMap(Map value) { - return value != null && value != DefaultSdkAutoConstructMap.getInstance(); - } - -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java deleted file mode 100644 index ee94a86a02..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java +++ /dev/null @@ -1,48 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static java.util.Base64.*; - -/** - * A class for decoding Base64 strings and encoding bytes as Base64 strings. - */ -public class Base64 { - private static final Decoder DECODER = getMimeDecoder(); - private static final Encoder ENCODER = getEncoder(); - - private Base64() { } - - /** - * Encode the bytes as a Base64 string. - *

- * See the Basic encoder in {@link java.util.Base64} - */ - public static String encodeToString(byte[] bytes) { - return ENCODER.encodeToString(bytes); - } - - /** - * Decode the Base64 string as bytes, ignoring illegal characters. - *

- * See the Mime Decoder in {@link java.util.Base64} - */ - public static byte[] decode(String str) { - if(str == null) { - return null; - } - return DECODER.decode(str); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java deleted file mode 100644 index ff70306841..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.io.InputStream; -import java.nio.ByteBuffer; - -/** - * @author Greg Rubin - */ -public class ByteBufferInputStream extends InputStream { - private final ByteBuffer buffer; - - public ByteBufferInputStream(ByteBuffer buffer) { - this.buffer = buffer; - } - - @Override - public int read() { - if (buffer.hasRemaining()) { - int tmp = buffer.get(); - if (tmp < 0) { - tmp += 256; - } - return tmp; - } else { - return -1; - } - } - - @Override - public int read(byte[] b, int off, int len) { - if (available() < len) { - len = available(); - } - buffer.get(b, off, len); - return len; - } - - @Override - public int available() { - return buffer.remaining(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java deleted file mode 100644 index 15422aaab7..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Provider; -import java.util.Arrays; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.SecretKeySpec; - -/** - * HMAC-based Key Derivation Function. - * - * @see RFC 5869 - */ -public final class Hkdf { - private static final byte[] EMPTY_ARRAY = new byte[0]; - private final String algorithm; - private final Provider provider; - - private SecretKey prk = null; - - /** - * Returns an Hkdf object using the specified algorithm. - * - * @param algorithm - * the standard name of the requested MAC algorithm. See the Mac - * section in the Java Cryptography Architecture Standard Algorithm Name - * Documentation for information about standard algorithm - * names. - * @return the new Hkdf object - * @throws NoSuchAlgorithmException - * if no Provider supports a MacSpi implementation for the - * specified algorithm. - */ - public static Hkdf getInstance(final String algorithm) - throws NoSuchAlgorithmException { - // Constructed specifically to sanity-test arguments. - Mac mac = Mac.getInstance(algorithm); - return new Hkdf(algorithm, mac.getProvider()); - } - - /** - * Returns an Hkdf object using the specified algorithm. - * - * @param algorithm - * the standard name of the requested MAC algorithm. See the Mac - * section in the Java Cryptography Architecture Standard Algorithm Name - * Documentation for information about standard algorithm - * names. - * @param provider - * the name of the provider - * @return the new Hkdf object - * @throws NoSuchAlgorithmException - * if a MacSpi implementation for the specified algorithm is not - * available from the specified provider. - * @throws NoSuchProviderException - * if the specified provider is not registered in the security - * provider list. - */ - public static Hkdf getInstance(final String algorithm, final String provider) - throws NoSuchAlgorithmException, NoSuchProviderException { - // Constructed specifically to sanity-test arguments. - Mac mac = Mac.getInstance(algorithm, provider); - return new Hkdf(algorithm, mac.getProvider()); - } - - /** - * Returns an Hkdf object using the specified algorithm. - * - * @param algorithm - * the standard name of the requested MAC algorithm. See the Mac - * section in the Java Cryptography Architecture Standard Algorithm Name - * Documentation for information about standard algorithm - * names. - * @param provider - * the provider - * @return the new Hkdf object - * @throws NoSuchAlgorithmException - * if a MacSpi implementation for the specified algorithm is not - * available from the specified provider. - */ - public static Hkdf getInstance(final String algorithm, - final Provider provider) throws NoSuchAlgorithmException { - // Constructed specifically to sanity-test arguments. - Mac mac = Mac.getInstance(algorithm, provider); - return new Hkdf(algorithm, mac.getProvider()); - } - - /** - * Initializes this Hkdf with input keying material. A default salt of - * HashLen zeros will be used (where HashLen is the length of the return - * value of the supplied algorithm). - * - * @param ikm - * the Input Keying Material - */ - public void init(final byte[] ikm) { - init(ikm, null); - } - - /** - * Initializes this Hkdf with input keying material and a salt. If - * salt is null or of length 0, then a default salt of - * HashLen zeros will be used (where HashLen is the length of the return - * value of the supplied algorithm). - * - * @param salt - * the salt used for key extraction (optional) - * @param ikm - * the Input Keying Material - */ - public void init(final byte[] ikm, final byte[] salt) { - byte[] realSalt = (salt == null) ? EMPTY_ARRAY : salt.clone(); - byte[] rawKeyMaterial = EMPTY_ARRAY; - try { - Mac extractionMac = Mac.getInstance(algorithm, provider); - if (realSalt.length == 0) { - realSalt = new byte[extractionMac.getMacLength()]; - Arrays.fill(realSalt, (byte) 0); - } - extractionMac.init(new SecretKeySpec(realSalt, algorithm)); - rawKeyMaterial = extractionMac.doFinal(ikm); - SecretKeySpec key = new SecretKeySpec(rawKeyMaterial, algorithm); - Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array - unsafeInitWithoutKeyExtraction(key); - } catch (GeneralSecurityException e) { - // We've already checked all of the parameters so no exceptions - // should be possible here. - throw new RuntimeException("Unexpected exception", e); - } finally { - Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array - } - } - - /** - * Initializes this Hkdf to use the provided key directly for creation of - * new keys. If rawKey is not securely generated and uniformly - * distributed over the total key-space, then this will result in an - * insecure key derivation function (KDF). DO NOT USE THIS UNLESS YOU - * ARE ABSOLUTELY POSITIVE THIS IS THE CORRECT THING TO DO. - * - * @param rawKey - * the pseudorandom key directly used to derive keys - * @throws InvalidKeyException - * if the algorithm for rawKey does not match the - * algorithm this Hkdf was created with - */ - public void unsafeInitWithoutKeyExtraction(final SecretKey rawKey) - throws InvalidKeyException { - if (!rawKey.getAlgorithm().equals(algorithm)) { - throw new InvalidKeyException( - "Algorithm for the provided key must match the algorithm for this Hkdf. Expected " + - algorithm + " but found " + rawKey.getAlgorithm()); - } - - this.prk = rawKey; - } - - private Hkdf(final String algorithm, final Provider provider) { - if (!algorithm.startsWith("Hmac")) { - throw new IllegalArgumentException("Invalid algorithm " + algorithm - + ". Hkdf may only be used with Hmac algorithms."); - } - this.algorithm = algorithm; - this.provider = provider; - } - - /** - * Returns a pseudorandom key of length bytes. - * - * @param info - * optional context and application specific information (can be - * a zero-length string). This will be treated as UTF-8. - * @param length - * the length of the output key in bytes - * @return a pseudorandom key of length bytes. - * @throws IllegalStateException - * if this object has not been initialized - */ - public byte[] deriveKey(final String info, final int length) throws IllegalStateException { - return deriveKey((info != null ? info.getBytes(StandardCharsets.UTF_8) : null), length); - } - - /** - * Returns a pseudorandom key of length bytes. - * - * @param info - * optional context and application specific information (can be - * a zero-length array). - * @param length - * the length of the output key in bytes - * @return a pseudorandom key of length bytes. - * @throws IllegalStateException - * if this object has not been initialized - */ - public byte[] deriveKey(final byte[] info, final int length) throws IllegalStateException { - byte[] result = new byte[length]; - try { - deriveKey(info, length, result, 0); - } catch (ShortBufferException ex) { - // This exception is impossible as we ensure the buffer is long - // enough - throw new RuntimeException(ex); - } - return result; - } - - /** - * Derives a pseudorandom key of length bytes and stores the - * result in output. - * - * @param info - * optional context and application specific information (can be - * a zero-length array). - * @param length - * the length of the output key in bytes - * @param output - * the buffer where the pseudorandom key will be stored - * @param offset - * the offset in output where the key will be stored - * @throws ShortBufferException - * if the given output buffer is too small to hold the result - * @throws IllegalStateException - * if this object has not been initialized - */ - public void deriveKey(final byte[] info, final int length, - final byte[] output, final int offset) throws ShortBufferException, - IllegalStateException { - assertInitialized(); - if (length < 0) { - throw new IllegalArgumentException("Length must be a non-negative value."); - } - if (output.length < offset + length) { - throw new ShortBufferException(); - } - Mac mac = createMac(); - - if (length > 255 * mac.getMacLength()) { - throw new IllegalArgumentException( - "Requested keys may not be longer than 255 times the underlying HMAC length."); - } - - byte[] t = EMPTY_ARRAY; - try { - int loc = 0; - byte i = 1; - while (loc < length) { - mac.update(t); - mac.update(info); - mac.update(i); - t = mac.doFinal(); - - for (int x = 0; x < t.length && loc < length; x++, loc++) { - output[loc] = t[x]; - } - - i++; - } - } finally { - Arrays.fill(t, (byte) 0); // Zeroize temporary array - } - } - - private Mac createMac() { - try { - Mac mac = Mac.getInstance(algorithm, provider); - mac.init(prk); - return mac; - } catch (NoSuchAlgorithmException ex) { - // We've already validated that this algorithm is correct. - throw new RuntimeException(ex); - } catch (InvalidKeyException ex) { - // We've already validated that this key is correct. - throw new RuntimeException(ex); - } - } - - /** - * Throws an IllegalStateException if this object has not been - * initialized. - * - * @throws IllegalStateException - * if this object has not been initialized - */ - private void assertInitialized() throws IllegalStateException { - if (prk == null) { - throw new IllegalStateException("Hkdf has not been initialized"); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java deleted file mode 100644 index e191a84215..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; - -import software.amazon.awssdk.annotations.ThreadSafe; - -/** - * A bounded cache that has a LRU eviction policy when the cache is full. - * - * @param - * value type - */ -@ThreadSafe -public final class LRUCache { - /** - * Used for the internal cache. - */ - private final Map map; - - /** - * Maximum size of the cache. - */ - private final int maxSize; - - /** - * @param maxSize - * the maximum number of entries of the cache - */ - public LRUCache(final int maxSize) { - if (maxSize < 1) { - throw new IllegalArgumentException("maxSize " + maxSize + " must be at least 1"); - } - this.maxSize = maxSize; - map = Collections.synchronizedMap(new LRUHashMap(maxSize)); - } - - /** - * Adds an entry to the cache, evicting the earliest entry if necessary. - */ - public T add(final String key, final T value) { - return map.put(key, value); - } - - /** Returns the value of the given key; or null of no such entry exists. */ - public T get(final String key) { - return map.get(key); - } - - /** - * Returns the current size of the cache. - */ - public int size() { - return map.size(); - } - - /** - * Returns the maximum size of the cache. - */ - public int getMaxSize() { - return maxSize; - } - - public void clear() { - map.clear(); - } - - public T remove(String key) { - return map.remove(key); - } - - @Override - public String toString() { - return map.toString(); - } - - @SuppressWarnings("serial") - private static class LRUHashMap extends LinkedHashMap { - private final int maxSize; - - private LRUHashMap(final int maxSize) { - super(10, 0.75F, true); - this.maxSize = maxSize; - } - - @Override - protected boolean removeEldestEntry(final Entry eldest) { - return size() > maxSize; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java deleted file mode 100644 index 3d776c0dc8..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java +++ /dev/null @@ -1,19 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -interface MsClock { - MsClock WALLCLOCK = System::nanoTime; - - public long timestampNano(); -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java deleted file mode 100644 index f529047c8a..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import io.netty.util.internal.ObjectUtil; -import software.amazon.awssdk.annotations.ThreadSafe; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; - -/** - * A cache, backed by an LRUCache, that uses a loader to calculate values on cache miss or expired - * TTL. - * - *

Note that this cache does not proactively evict expired entries, however will immediately - * evict entries discovered to be expired on load. - * - * @param value type - */ -@ThreadSafe -public final class TTLCache { - /** Used for the internal cache. */ - private final LRUCache> cache; - - /** Time to live for entries in the cache. */ - private final long ttlInNanos; - - /** Used for loading new values into the cache on cache miss or expiration. */ - private final EntryLoader defaultLoader; - - // Mockable time source, to allow us to test TTL behavior. - // package access for tests - MsClock clock = MsClock.WALLCLOCK; - - private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); - - /** - * @param maxSize the maximum number of entries of the cache - * @param ttlInMillis the time to live value for entries of the cache, in milliseconds - */ - public TTLCache(final int maxSize, final long ttlInMillis, final EntryLoader loader) { - if (maxSize < 1) { - throw new IllegalArgumentException("maxSize " + maxSize + " must be at least 1"); - } - if (ttlInMillis < 1) { - throw new IllegalArgumentException("ttlInMillis " + maxSize + " must be at least 1"); - } - this.ttlInNanos = TimeUnit.MILLISECONDS.toNanos(ttlInMillis); - this.cache = new LRUCache<>(maxSize); - this.defaultLoader = ObjectUtil.checkNotNull(loader, "loader must not be null"); - } - - /** - * Uses the default loader to calculate the value at key and insert it into the cache, if it - * doesn't already exist or is expired according to the TTL. - * - *

This immediately evicts entries past the TTL such that a load failure results in the removal - * of the entry. - * - *

Entries that are not expired according to the TTL are returned without recalculating the - * value. - * - *

Within a grace period past the TTL, the cache may either return the cached value without - * recalculating or use the loader to recalculate the value. This is implemented such that, in a - * multi-threaded environment, only one thread per cache key uses the loader to recalculate the - * value at one time. - * - * @param key The cache key to load the value at - * @return The value of the given value (already existing or re-calculated). - */ - public T load(final String key) { - return load(key, defaultLoader::load); - } - - /** - * Uses the inputted function to calculate the value at key and insert it into the cache, if it - * doesn't already exist or is expired according to the TTL. - * - *

This immediately evicts entries past the TTL such that a load failure results in the removal - * of the entry. - * - *

Entries that are not expired according to the TTL are returned without recalculating the - * value. - * - *

Within a grace period past the TTL, the cache may either return the cached value without - * recalculating or use the loader to recalculate the value. This is implemented such that, in a - * multi-threaded environment, only one thread per cache key uses the loader to recalculate the - * value at one time. - * - *

Returns the value of the given key (already existing or re-calculated). - * - * @param key The cache key to load the value at - * @param f The function to use to load the value, given key as input - * @return The value of the given value (already existing or re-calculated). - */ - public T load(final String key, Function f) { - final LockedState ls = cache.get(key); - - if (ls == null) { - // The entry doesn't exist yet, so load a new one. - return loadNewEntryIfAbsent(key, f); - } else if (clock.timestampNano() - ls.getState().lastUpdatedNano - > ttlInNanos + TTL_GRACE_IN_NANO) { - // The data has expired past the grace period. - // Evict the old entry and load a new entry. - cache.remove(key); - return loadNewEntryIfAbsent(key, f); - } else if (clock.timestampNano() - ls.getState().lastUpdatedNano <= ttlInNanos) { - // The data hasn't expired. Return as-is from the cache. - return ls.getState().data; - } else if (!ls.tryLock()) { - // We are in the TTL grace period. If we couldn't grab the lock, then some other - // thread is currently loading the new value. Because we are in the grace period, - // use the cached data instead of waiting for the lock. - return ls.getState().data; - } - - // We are in the grace period and have acquired a lock. - // Update the cache with the value determined by the loading function. - try { - T loadedData = f.apply(key); - ls.update(loadedData, clock.timestampNano()); - return ls.getState().data; - } finally { - ls.unlock(); - } - } - - // Synchronously calculate the value for a new entry in the cache if it doesn't already exist. - // Otherwise return the cached value. - // It is important that this is the only place where we use the loader for a new entry, - // given that we don't have the entry yet to lock on. - // This ensures that the loading function is only called once if multiple threads - // attempt to add a new entry for the same key at the same time. - private synchronized T loadNewEntryIfAbsent(final String key, Function f) { - // If the entry already exists in the cache, return it - final LockedState cachedState = cache.get(key); - if (cachedState != null) { - return cachedState.getState().data; - } - - // Otherwise, load the data and create a new entry - T loadedData = f.apply(key); - LockedState ls = new LockedState<>(loadedData, clock.timestampNano()); - cache.add(key, ls); - return loadedData; - } - - - /** - * Put a new entry in the cache. Returns the value previously at that key in the cache, or null if - * the entry previously didn't exist or is expired. - */ - public synchronized T put(final String key, final T value) { - LockedState ls = new LockedState<>(value, clock.timestampNano()); - LockedState oldLockedState = cache.add(key, ls); - if (oldLockedState == null - || clock.timestampNano() - oldLockedState.getState().lastUpdatedNano - > ttlInNanos + TTL_GRACE_IN_NANO) { - return null; - } - return oldLockedState.getState().data; - } - - /** - * Get when the entry at this key was last updated. Returns 0 if the entry doesn't exist at key. - */ - public long getLastUpdated(String key) { - LockedState ls = cache.get(key); - if (ls == null) { - return 0; - } - return ls.getState().lastUpdatedNano; - } - - /** Returns the current size of the cache. */ - public int size() { - return cache.size(); - } - - /** Returns the maximum size of the cache. */ - public int getMaxSize() { - return cache.getMaxSize(); - } - - /** Clears all entries from the cache. */ - public void clear() { - cache.clear(); - } - - @Override - public String toString() { - return cache.toString(); - } - - public interface EntryLoader { - T load(String entryKey); - } - - // An object which stores a state alongside a lock, - // and performs updates to that state atomically. - // The state may only be updated if the lock is acquired by the current thread. - private static class LockedState { - private final ReentrantLock lock = new ReentrantLock(true); - private final AtomicReference> state; - - public LockedState(T data, long createTimeNano) { - state = new AtomicReference<>(new State<>(data, createTimeNano)); - } - - public State getState() { - return state.get(); - } - - public void unlock() { - lock.unlock(); - } - - public boolean tryLock() { - return lock.tryLock(); - } - - public void update(T data, long createTimeNano) { - if (!lock.isHeldByCurrentThread()) { - throw new IllegalStateException("Lock not held by current thread"); - } - state.set(new State<>(data, createTimeNano)); - } - } - - // An object that holds some data and the time at which this object was created - private static class State { - public final T data; - public final long lastUpdatedNano; - - public State(T data, long lastUpdatedNano) { - this.data = data; - this.lastUpdatedNano = lastUpdatedNano; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java deleted file mode 100644 index 6d092cc06b..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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. - */ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import java.security.SecureRandom; - -public class Utils { - private static final ThreadLocal RND = ThreadLocal.withInitial(() -> { - final SecureRandom result = new SecureRandom(); - result.nextBoolean(); // Force seeding - return result; - }); - - private Utils() { - // Prevent instantiation - } - - public static SecureRandom getRng() { - return RND.get(); - } - - public static byte[] getRandom(int len) { - final byte[] result = new byte[len]; - getRng().nextBytes(result); - return result; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java deleted file mode 100644 index b9906bade0..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java +++ /dev/null @@ -1,932 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertTrue; - -import com.amazonaws.util.Base64; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.*; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.*; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.AsymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.CachingMostRecentProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.*; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.KeyData; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.Keys; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.Scenario; - -public class HolisticIT { - - private static final SecretKey aesKey = - new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "AES"); - private static final SecretKey hmacKey = - new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); - private static final String rsaEncPub = - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtiNSLSvT9cExXOcD0dGZ" - + "9DFEMHw8895gAZcCdSppDrxbD7XgZiQYTlgt058i5fS+l11guAUJtKt5sZ2u8Fx0" - + "K9pxMdlczGtvQJdx/LQETEnLnfzAijvHisJ8h6dQOVczM7t01KIkS24QZElyO+kY" - + "qMWLytUV4RSHnrnIuUtPHCe6LieDWT2+1UBguxgtFt1xdXlquACLVv/Em3wp40Xc" - + "bIwzhqLitb98rTY/wqSiGTz1uvvBX46n+f2j3geZKCEDGkWcXYw3dH4lRtDWTbqw" - + "eRcaNDT/MJswQlBk/Up9KCyN7gjX67gttiCO6jMoTNDejGeJhG4Dd2o0vmn8WJlr" - + "5wIDAQAB"; - private static final String rsaEncPriv = - "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2I1ItK9P1wTFc" - + "5wPR0Zn0MUQwfDzz3mABlwJ1KmkOvFsPteBmJBhOWC3TnyLl9L6XXWC4BQm0q3mx" - + "na7wXHQr2nEx2VzMa29Al3H8tARMScud/MCKO8eKwnyHp1A5VzMzu3TUoiRLbhBk" - + "SXI76RioxYvK1RXhFIeeuci5S08cJ7ouJ4NZPb7VQGC7GC0W3XF1eWq4AItW/8Sb" - + "fCnjRdxsjDOGouK1v3ytNj/CpKIZPPW6+8Ffjqf5/aPeB5koIQMaRZxdjDd0fiVG" - + "0NZNurB5Fxo0NP8wmzBCUGT9Sn0oLI3uCNfruC22II7qMyhM0N6MZ4mEbgN3ajS+" - + "afxYmWvnAgMBAAECggEBAIIU293zDWDZZ73oJ+w0fHXQsdjHAmlRitPX3CN99KZX" - + "k9m2ldudL9bUV3Zqk2wUzgIg6LDEuFfWmAVojsaP4VBopKtriEFfAYfqIbjPgLpT" - + "gh8FoyWW6D6MBJCFyGALjUAHQ7uRScathvt5ESMEqV3wKJTmdsfX97w/B8J+rLN3" - + "3fT3ZJUck5duZ8XKD+UtX1Y3UE1hTWo3Ae2MFND964XyUqy+HaYXjH0x6dhZzqyJ" - + "/OJ/MPGeMJgxp+nUbMWerwxrLQceNFVgnQgHj8e8k4fd04rkowkkPua912gNtmz7" - + "DuIEvcMnY64z585cn+cnXUPJwtu3JbAmn/AyLsV9FLECgYEA798Ut/r+vORB16JD" - + "KFu38pQCgIbdCPkXeI0DC6u1cW8JFhgRqi+AqSrEy5SzY3IY7NVMSRsBI9Y026Bl" - + "R9OQwTrOzLRAw26NPSDvbTkeYXlY9+hX7IovHjGkho/OxyTJ7bKRDYLoNCz56BC1" - + "khIWvECpcf/fZU0nqOFVFqF3H/UCgYEAwmJ4rjl5fksTNtNRL6ivkqkHIPKXzk5w" - + "C+L90HKNicic9bqyX8K4JRkGKSNYN3mkjrguAzUlEld390qNBw5Lu7PwATv0e2i+" - + "6hdwJsjTKNpj7Nh4Mieq6d7lWe1L8FLyHEhxgIeQ4BgqrVtPPOH8IBGpuzVZdWwI" - + "dgOvEvAi/usCgYBdfk3NB/+SEEW5jn0uldE0s4vmHKq6fJwxWIT/X4XxGJ4qBmec" - + "NbeoOAtMbkEdWbNtXBXHyMbA+RTRJctUG5ooNou0Le2wPr6+PMAVilXVGD8dIWpj" - + "v9htpFvENvkZlbU++IKhCY0ICR++3ARpUrOZ3Hou/NRN36y9nlZT48tSoQKBgES2" - + "Bi6fxmBsLUiN/f64xAc1lH2DA0I728N343xRYdK4hTMfYXoUHH+QjurvwXkqmI6S" - + "cEFWAdqv7IoPYjaCSSb6ffYRuWP+LK4WxuAO0QV53SSViDdCalntHmlhRhyXVVnG" - + "CckDIqT0JfHNev7savDzDWpNe2fUXlFJEBPDqrstAoGBAOpd5+QBHF/tP5oPILH4" - + "aD/zmqMH7VtB+b/fOPwtIM+B/WnU7hHLO5t2lJYu18Be3amPkfoQIB7bpkM3Cer2" - + "G7Jw+TcHrY+EtIziDB5vwau1fl4VcbA9SfWpBojJ5Ifo9ELVxGiK95WxeQNSmLUy" - + "7AJzhK1Gwey8a/v+xfqiu9sE"; - private static final PrivateKey rsaPriv; - private static final PublicKey rsaPub; - private static final KeyPair rsaPair; - private static final EncryptionMaterialsProvider symProv; - private static final EncryptionMaterialsProvider asymProv; - private static final EncryptionMaterialsProvider symWrappedProv; - private static final String HASH_KEY = "hashKey"; - private static final String RANGE_KEY = "rangeKey"; - private static final String RSA = "RSA"; - private static final String tableName = "TableName"; - final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); - final EnumSet encryptAndSign = - EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); - - private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); - private DynamoDbClient client; - private static KmsClient kmsClient = KmsClient.builder().build(); - - private static Map keyDataMap = new HashMap<>(); - - private static final Map ENCRYPTED_TEST_VALUE = new HashMap<>(); - private static final Map MIXED_TEST_VALUE = new HashMap<>(); - private static final Map SIGNED_TEST_VALUE = new HashMap<>(); - private static final Map UNTOUCHED_TEST_VALUE = new HashMap<>(); - - private static final Map ENCRYPTED_TEST_VALUE_2 = new HashMap<>(); - private static final Map MIXED_TEST_VALUE_2 = new HashMap<>(); - private static final Map SIGNED_TEST_VALUE_2 = new HashMap<>(); - private static final Map UNTOUCHED_TEST_VALUE_2 = new HashMap<>(); - - private static final String TEST_VECTOR_MANIFEST_DIR = "/vectors/encrypted_item/"; - private static final String SCENARIO_MANIFEST_PATH = TEST_VECTOR_MANIFEST_DIR + "scenarios.json"; - private static final String JAVA_DIR = "java"; - - static { - try { - KeyFactory rsaFact = KeyFactory.getInstance("RSA"); - rsaPub = rsaFact.generatePublic(new X509EncodedKeySpec(Base64.decode(rsaEncPub))); - rsaPriv = rsaFact.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(rsaEncPriv))); - rsaPair = new KeyPair(rsaPub, rsaPriv); - } catch (GeneralSecurityException ex) { - throw new RuntimeException(ex); - } - symProv = new SymmetricStaticProvider(aesKey, hmacKey); - asymProv = new AsymmetricStaticProvider(rsaPair, rsaPair); - symWrappedProv = new WrappedMaterialsProvider(aesKey, aesKey, hmacKey); - - ENCRYPTED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("5").build()); - ENCRYPTED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("7").build()); - ENCRYPTED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); - ENCRYPTED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); - ENCRYPTED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - ENCRYPTED_TEST_VALUE.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - ENCRYPTED_TEST_VALUE.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - ENCRYPTED_TEST_VALUE.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - - MIXED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("6").build()); - MIXED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("8").build()); - MIXED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); - MIXED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); - MIXED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - MIXED_TEST_VALUE.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - MIXED_TEST_VALUE.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - MIXED_TEST_VALUE.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - - SIGNED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("8").build()); - SIGNED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("10").build()); - SIGNED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); - SIGNED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); - SIGNED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - SIGNED_TEST_VALUE.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - SIGNED_TEST_VALUE.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - SIGNED_TEST_VALUE.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - - UNTOUCHED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("7").build()); - UNTOUCHED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("9").build()); - UNTOUCHED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); - UNTOUCHED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); - UNTOUCHED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - UNTOUCHED_TEST_VALUE.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - UNTOUCHED_TEST_VALUE.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - UNTOUCHED_TEST_VALUE.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - - // STORING DOUBLES - ENCRYPTED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("5").build()); - ENCRYPTED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("7").build()); - ENCRYPTED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); - ENCRYPTED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); - ENCRYPTED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - ENCRYPTED_TEST_VALUE_2.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - ENCRYPTED_TEST_VALUE_2.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - ENCRYPTED_TEST_VALUE_2.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - ENCRYPTED_TEST_VALUE_2.put( - "doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); - ENCRYPTED_TEST_VALUE_2.put( - "doubleSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) - .build()); - - MIXED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("6").build()); - MIXED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("8").build()); - MIXED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); - MIXED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); - MIXED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - MIXED_TEST_VALUE_2.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - MIXED_TEST_VALUE_2.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - MIXED_TEST_VALUE_2.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - MIXED_TEST_VALUE_2.put("doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); - MIXED_TEST_VALUE_2.put( - "doubleSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) - .build()); - - SIGNED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("8").build()); - SIGNED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("10").build()); - SIGNED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); - SIGNED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); - SIGNED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - SIGNED_TEST_VALUE_2.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - SIGNED_TEST_VALUE_2.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - SIGNED_TEST_VALUE_2.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - SIGNED_TEST_VALUE_2.put("doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); - SIGNED_TEST_VALUE_2.put( - "doubleSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) - .build()); - - UNTOUCHED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("7").build()); - UNTOUCHED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("9").build()); - UNTOUCHED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); - UNTOUCHED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); - UNTOUCHED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - UNTOUCHED_TEST_VALUE_2.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - UNTOUCHED_TEST_VALUE_2.put( - "stringSet", - AttributeValue.builder() - .ss(new HashSet(Arrays.asList("Goodbye", "Cruel", "World", "?"))) - .build()); - UNTOUCHED_TEST_VALUE_2.put( - "intSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("1", "200", "10", "15", "0"))) - .build()); - UNTOUCHED_TEST_VALUE_2.put( - "doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); - UNTOUCHED_TEST_VALUE_2.put( - "doubleSet", - AttributeValue.builder() - .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) - .build()); - } - - @DataProvider(name = "getEncryptTestVectors") - public static Object[][] getEncryptTestVectors() throws IOException { - ScenarioManifest scenarioManifest = - getManifestFromFile(SCENARIO_MANIFEST_PATH, new TypeReference() {}); - loadKeyData(scenarioManifest.keyDataPath); - - // Only use Java generated test vectors to dedupe the scenarios for encrypt, - // we only care that we are able to generate data using the different provider configurations - return scenarioManifest.scenarios.stream() - .filter(s -> s.ciphertextPath.contains(JAVA_DIR)) - .map(s -> new Object[] {s}) - .toArray(Object[][]::new); - } - - @DataProvider(name = "getDecryptTestVectors") - public static Object[][] getDecryptTestVectors() throws IOException { - ScenarioManifest scenarioManifest = - getManifestFromFile(SCENARIO_MANIFEST_PATH, new TypeReference() {}); - loadKeyData(scenarioManifest.keyDataPath); - - return scenarioManifest.scenarios.stream().map(s -> new Object[] {s}).toArray(Object[][]::new); - } - - @Test(dataProvider = "getDecryptTestVectors") - public void decryptTestVector(Scenario scenario) throws IOException, GeneralSecurityException { - localDynamoDb.start(); - client = localDynamoDb.createLimitedWrappedClient(); - - // load data into ciphertext tables - createCiphertextTables(client); - - // load data from vector file - putDataFromFile(client, scenario.ciphertextPath); - - // create and load metastore table if necessary - ProviderStore metastore = null; - if (scenario.metastore != null) { - MetaStore.createTable( - client, - scenario.metastore.tableName, - ProvisionedThroughput.builder().readCapacityUnits(100L).writeCapacityUnits(100L).build()); - putDataFromFile(client, scenario.metastore.path); - EncryptionMaterialsProvider metaProvider = - createProvider( - scenario.metastore.providerName, - scenario.materialName, - scenario.metastore.keys, - null); - metastore = - new MetaStore( - client, scenario.metastore.tableName, DynamoDbEncryptor.getInstance(metaProvider)); - } - - // Create the mapper with the provider under test - EncryptionMaterialsProvider provider = - createProvider(scenario.providerName, scenario.materialName, scenario.keys, metastore); - - // Verify successful decryption - switch (scenario.version) { - case "v0": - assertVersionCompatibility(provider, tableName); - break; - case "v1": - assertVersionCompatibility_2(provider, tableName); - break; - default: - throw new IllegalStateException( - "Version " + scenario.version + " not yet implemented in test vector runner"); - } - client.close(); - localDynamoDb.stop(); - } - - @Test(dataProvider = "getEncryptTestVectors") - public void encryptWithTestVector(Scenario scenario) throws IOException { - localDynamoDb.start(); - client = localDynamoDb.createLimitedWrappedClient(); - - // load data into ciphertext tables - createCiphertextTables(client); - - // create and load metastore table if necessary - ProviderStore metastore = null; - if (scenario.metastore != null) { - MetaStore.createTable( - client, - scenario.metastore.tableName, - ProvisionedThroughput.builder().readCapacityUnits(100L).writeCapacityUnits(100L).build()); - putDataFromFile(client, scenario.metastore.path); - EncryptionMaterialsProvider metaProvider = - createProvider( - scenario.metastore.providerName, - scenario.materialName, - scenario.metastore.keys, - null); - metastore = - new MetaStore( - client, scenario.metastore.tableName, DynamoDbEncryptor.getInstance(metaProvider)); - } - - // Encrypt data with the provider under test, only ensure that no exception is thrown - EncryptionMaterialsProvider provider = - createProvider(scenario.providerName, scenario.materialName, scenario.keys, metastore); - generateStandardData(provider); - client.close(); - localDynamoDb.stop(); - } - - private EncryptionMaterialsProvider createProvider( - String providerName, String materialName, Keys keys, ProviderStore metastore) { - switch (providerName) { - case ScenarioManifest.MOST_RECENT_PROVIDER_NAME: - return new CachingMostRecentProvider(metastore, materialName, 1000); - case ScenarioManifest.STATIC_PROVIDER_NAME: - KeyData decryptKeyData = keyDataMap.get(keys.decryptName); - KeyData verifyKeyData = keyDataMap.get(keys.verifyName); - SecretKey decryptKey = - new SecretKeySpec(Base64.decode(decryptKeyData.material), decryptKeyData.algorithm); - SecretKey verifyKey = - new SecretKeySpec(Base64.decode(verifyKeyData.material), verifyKeyData.algorithm); - return new SymmetricStaticProvider(decryptKey, verifyKey); - case ScenarioManifest.WRAPPED_PROVIDER_NAME: - decryptKeyData = keyDataMap.get(keys.decryptName); - verifyKeyData = keyDataMap.get(keys.verifyName); - - // This can be either the asymmetric provider, where we should test using it's explicit - // constructor, - // or a wrapped symmetric where we use the wrapped materials constructor. - if (decryptKeyData.keyType.equals(ScenarioManifest.SYMMETRIC_KEY_TYPE)) { - decryptKey = - new SecretKeySpec(Base64.decode(decryptKeyData.material), decryptKeyData.algorithm); - verifyKey = - new SecretKeySpec(Base64.decode(verifyKeyData.material), verifyKeyData.algorithm); - return new WrappedMaterialsProvider(decryptKey, decryptKey, verifyKey); - } else { - KeyData encryptKeyData = keyDataMap.get(keys.encryptName); - KeyData signKeyData = keyDataMap.get(keys.signName); - try { - // Hardcoded to use RSA for asymmetric keys. If we include vectors with a different - // asymmetric scheme this will need to be updated. - KeyFactory rsaFact = KeyFactory.getInstance(RSA); - - PublicKey encryptMaterial = - rsaFact.generatePublic( - new X509EncodedKeySpec(Base64.decode(encryptKeyData.material))); - PrivateKey decryptMaterial = - rsaFact.generatePrivate( - new PKCS8EncodedKeySpec(Base64.decode(decryptKeyData.material))); - KeyPair decryptPair = new KeyPair(encryptMaterial, decryptMaterial); - - PublicKey verifyMaterial = - rsaFact.generatePublic( - new X509EncodedKeySpec(Base64.decode(verifyKeyData.material))); - PrivateKey signingMaterial = - rsaFact.generatePrivate( - new PKCS8EncodedKeySpec(Base64.decode(signKeyData.material))); - KeyPair sigPair = new KeyPair(verifyMaterial, signingMaterial); - - return new AsymmetricStaticProvider(decryptPair, sigPair); - } catch (GeneralSecurityException ex) { - throw new RuntimeException(ex); - } - } - case ScenarioManifest.AWS_KMS_PROVIDER_NAME: - return new DirectKmsMaterialsProvider(kmsClient, keyDataMap.get(keys.decryptName).keyId); - default: - throw new IllegalStateException( - "Provider " + providerName + " not yet implemented in test vector runner"); - } - } - - // Create empty tables for the ciphertext. - // The underlying structure to these tables is hardcoded, - // and we run all test vectors assuming the ciphertext matches the key schema for these tables. - private void createCiphertextTables(DynamoDbClient localDynamoDb) { - // TableName Setup - ArrayList attrDef = new ArrayList<>(); - attrDef.add( - AttributeDefinition.builder() - .attributeName(HASH_KEY) - .attributeType(ScalarAttributeType.N) - .build()); - - attrDef.add( - AttributeDefinition.builder() - .attributeName(RANGE_KEY) - .attributeType(ScalarAttributeType.N) - .build()); - ArrayList keySchema = new ArrayList<>(); - keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); - keySchema.add( - KeySchemaElement.builder().attributeName(RANGE_KEY).keyType(KeyType.RANGE).build()); - - localDynamoDb.createTable( - CreateTableRequest.builder() - .tableName("TableName") - .attributeDefinitions(attrDef) - .keySchema(keySchema) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(100L) - .writeCapacityUnits(100L) - .build()) - .build()); - - // HashKeyOnly SetUp - attrDef = new ArrayList<>(); - attrDef.add( - AttributeDefinition.builder() - .attributeName(HASH_KEY) - .attributeType(ScalarAttributeType.S) - .build()); - - keySchema = new ArrayList<>(); - keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); - - localDynamoDb.createTable( - CreateTableRequest.builder() - .tableName("HashKeyOnly") - .attributeDefinitions(attrDef) - .keySchema(keySchema) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(100L) - .writeCapacityUnits(100L) - .build()) - .build()); - - // DeterministicTable SetUp - attrDef = new ArrayList<>(); - attrDef.add( - AttributeDefinition.builder() - .attributeName(HASH_KEY) - .attributeType(ScalarAttributeType.B) - .build()); - attrDef.add( - AttributeDefinition.builder() - .attributeName(RANGE_KEY) - .attributeType(ScalarAttributeType.N) - .build()); - - keySchema = new ArrayList<>(); - keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); - keySchema.add( - KeySchemaElement.builder().attributeName(RANGE_KEY).keyType(KeyType.RANGE).build()); - - localDynamoDb.createTable( - CreateTableRequest.builder() - .tableName("DeterministicTable") - .attributeDefinitions(attrDef) - .keySchema(keySchema) - .provisionedThroughput( - ProvisionedThroughput.builder() - .readCapacityUnits(100L) - .writeCapacityUnits(100L) - .build()) - .build()); - } - - // Given a file in the test vector ciphertext format, put those entries into their tables. - // This assumes the expected tables have already been created. - private void putDataFromFile(DynamoDbClient localDynamoDb, String filename) throws IOException { - Map>> manifest = - getCiphertextManifestFromFile(filename); - for (String tableName : manifest.keySet()) { - for (Map attributes : manifest.get(tableName)) { - localDynamoDb.putItem( - PutItemRequest.builder().tableName(tableName).item(attributes).build()); - } - } - } - - private Map>> getCiphertextManifestFromFile( - String filename) throws IOException { - return getManifestFromFile( - TEST_VECTOR_MANIFEST_DIR + stripFilePath(filename), - new TypeReference>>>() {}); - } - - private static T getManifestFromFile(String filename, TypeReference typeRef) - throws IOException { - final URL url = HolisticIT.class.getResource(filename); - if (url == null) { - throw new IllegalStateException("Missing file " + filename + " in src/test/resources."); - } - final File manifestFile = new File(url.getPath()); - final ObjectMapper manifestMapper = new ObjectMapper(); - return (T) manifestMapper.readValue(manifestFile, typeRef); - } - - private static void loadKeyData(String filename) throws IOException { - keyDataMap = - getManifestFromFile( - TEST_VECTOR_MANIFEST_DIR + stripFilePath(filename), - new TypeReference>() {}); - } - - public void generateStandardData(EncryptionMaterialsProvider prov) { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - Map encryptedRecord; - Map> actions; - EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - Map hashKey1 = new HashMap<>(); - Map hashKey2 = new HashMap<>(); - Map hashKey3 = new HashMap<>(); - - hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); - hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); - hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); - - // encrypted record - actions = new HashMap<>(); - for (final String attr : ENCRYPTED_TEST_VALUE_2.keySet()) { - switch (attr) { - case "hashKey": - case "rangeKey": - case "version": - actions.put(attr, signOnly); - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - encryptedRecord = encryptor.encryptRecord(ENCRYPTED_TEST_VALUE_2, actions, encryptionContext); - putItems(encryptedRecord, tableName); - - // mixed test record - actions = new HashMap<>(); - for (final String attr : MIXED_TEST_VALUE_2.keySet()) { - switch (attr) { - case "rangeKey": - case "hashKey": - case "version": - case "stringValue": - case "doubleValue": - case "doubleSet": - actions.put(attr, signOnly); - break; - case "intValue": - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - encryptedRecord = encryptor.encryptRecord(MIXED_TEST_VALUE_2, actions, encryptionContext); - putItems(encryptedRecord, tableName); - - // sign only record - actions = new HashMap<>(); - for (final String attr : SIGNED_TEST_VALUE_2.keySet()) { - actions.put(attr, signOnly); - } - encryptedRecord = encryptor.encryptRecord(SIGNED_TEST_VALUE_2, actions, encryptionContext); - putItems(encryptedRecord, tableName); - - // untouched record - putItems(UNTOUCHED_TEST_VALUE_2, tableName); - } - - private void putItems(Map map, String tableName) { - PutItemRequest request = PutItemRequest.builder().item(map).tableName(tableName).build(); - client.putItem(request); - } - - private Map getItems(Map map, String tableName) { - GetItemRequest request = GetItemRequest.builder().key(map).tableName(tableName).build(); - return client.getItem(request).item(); - } - - private void assertVersionCompatibility(EncryptionMaterialsProvider provider, String tableName) - throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); - Map response; - Map decryptedRecord; - EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - - // Set up maps for table items - HashMap untouched = new HashMap<>(); - HashMap signed = new HashMap<>(); - HashMap mixed = new HashMap<>(); - HashMap encrypted = new HashMap<>(); - HashMap hashKey1 = new HashMap<>(); - HashMap hashKey2 = new HashMap<>(); - HashMap hashKey3 = new HashMap<>(); - untouched.put("hashKey", UNTOUCHED_TEST_VALUE.get("hashKey")); - untouched.put("rangeKey", UNTOUCHED_TEST_VALUE.get("rangeKey")); - - signed.put("hashKey", SIGNED_TEST_VALUE.get("hashKey")); - signed.put("rangeKey", SIGNED_TEST_VALUE.get("rangeKey")); - - mixed.put("hashKey", MIXED_TEST_VALUE.get("hashKey")); - mixed.put("rangeKey", MIXED_TEST_VALUE.get("rangeKey")); - - encrypted.put("hashKey", ENCRYPTED_TEST_VALUE.get("hashKey")); - encrypted.put("rangeKey", ENCRYPTED_TEST_VALUE.get("rangeKey")); - - hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); - hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); - hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); - - // check untouched attr - assertTrue( - new DdbRecordMatcher(UNTOUCHED_TEST_VALUE, false).matches(getItems(untouched, tableName))); - - // check signed attr - // Describe what actions need to be taken for each attribute - Map> actions = new HashMap<>(); - for (final String attr : SIGNED_TEST_VALUE.keySet()) { - actions.put(attr, signOnly); - } - response = getItems(signed, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(SIGNED_TEST_VALUE, false).matches(decryptedRecord)); - - // check mixed attr - actions = new HashMap<>(); - for (final String attr : MIXED_TEST_VALUE.keySet()) { - switch (attr) { - case "rangeKey": - case "hashKey": - case "version": - case "stringValue": - actions.put(attr, signOnly); - break; - case "intValue": - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - response = getItems(mixed, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(MIXED_TEST_VALUE, false).matches(decryptedRecord)); - - // check encrypted attr - actions = new HashMap<>(); - for (final String attr : ENCRYPTED_TEST_VALUE.keySet()) { - switch (attr) { - case "hashKey": - case "rangeKey": - case "version": - actions.put(attr, signOnly); - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - response = getItems(encrypted, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(ENCRYPTED_TEST_VALUE, false).matches(decryptedRecord)); - - assertEquals("Foo", getItems(hashKey1, "HashKeyOnly").get("hashKey").s()); - assertEquals("Bar", getItems(hashKey2, "HashKeyOnly").get("hashKey").s()); - assertEquals("Baz", getItems(hashKey3, "HashKeyOnly").get("hashKey").s()); - - Map key = new HashMap<>(); - for (int i = 1; i <= 3; ++i) { - key.put("hashKey", AttributeValue.builder().n("0").build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, "TableName"); - assertEquals(0, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - - key.put("hashKey", AttributeValue.builder().n("1").build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, "TableName"); - assertEquals(1, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - - key.put("hashKey", AttributeValue.builder().n(String.valueOf(4 + i)).build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, "TableName"); - assertEquals(4 + i, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - } - } - - private void assertVersionCompatibility_2(EncryptionMaterialsProvider provider, String tableName) - throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); - Map response; - Map decryptedRecord; - EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - - // Set up maps for table items - HashMap untouched = new HashMap<>(); - HashMap signed = new HashMap<>(); - HashMap mixed = new HashMap<>(); - HashMap encrypted = new HashMap<>(); - HashMap hashKey1 = new HashMap<>(); - HashMap hashKey2 = new HashMap<>(); - HashMap hashKey3 = new HashMap<>(); - - untouched.put("hashKey", UNTOUCHED_TEST_VALUE_2.get("hashKey")); - untouched.put("rangeKey", UNTOUCHED_TEST_VALUE_2.get("rangeKey")); - - signed.put("hashKey", SIGNED_TEST_VALUE_2.get("hashKey")); - signed.put("rangeKey", SIGNED_TEST_VALUE_2.get("rangeKey")); - - mixed.put("hashKey", MIXED_TEST_VALUE_2.get("hashKey")); - mixed.put("rangeKey", MIXED_TEST_VALUE_2.get("rangeKey")); - - encrypted.put("hashKey", ENCRYPTED_TEST_VALUE_2.get("hashKey")); - encrypted.put("rangeKey", ENCRYPTED_TEST_VALUE_2.get("rangeKey")); - - hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); - hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); - hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); - - // check untouched attr - assert new DdbRecordMatcher(UNTOUCHED_TEST_VALUE_2, false) - .matches(getItems(untouched, tableName)); - - // check signed attr - // Describe what actions need to be taken for each attribute - Map> actions = new HashMap<>(); - for (final String attr : SIGNED_TEST_VALUE_2.keySet()) { - actions.put(attr, signOnly); - } - response = getItems(signed, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(SIGNED_TEST_VALUE_2, false).matches(decryptedRecord)); - - // check mixed attr - actions = new HashMap<>(); - for (final String attr : MIXED_TEST_VALUE_2.keySet()) { - switch (attr) { - case "rangeKey": - case "hashKey": - case "version": - case "stringValue": - case "doubleValue": - case "doubleSet": - actions.put(attr, signOnly); - break; - case "intValue": - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - response = getItems(mixed, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(MIXED_TEST_VALUE_2, false).matches(decryptedRecord)); - - // check encrypted attr - actions = new HashMap<>(); - for (final String attr : ENCRYPTED_TEST_VALUE_2.keySet()) { - switch (attr) { - case "hashKey": - case "rangeKey": - case "version": - actions.put(attr, signOnly); - break; - default: - actions.put(attr, encryptAndSign); - break; - } - } - response = getItems(encrypted, tableName); - decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); - assertTrue(new DdbRecordMatcher(ENCRYPTED_TEST_VALUE_2, false).matches(decryptedRecord)); - - // check HashKey Table - assertEquals("Foo", getItems(hashKey1, "HashKeyOnly").get("hashKey").s()); - assertEquals("Bar", getItems(hashKey2, "HashKeyOnly").get("hashKey").s()); - assertEquals("Baz", getItems(hashKey3, "HashKeyOnly").get("hashKey").s()); - - // Check Hash and Range Key Values - Map key = new HashMap<>(); - for (int i = 1; i <= 3; ++i) { - key.put("hashKey", AttributeValue.builder().n("0").build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, tableName); - assertEquals(0, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - - key.put("hashKey", AttributeValue.builder().n("1").build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, tableName); - assertEquals(1, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - - key.put("hashKey", AttributeValue.builder().n(String.valueOf(4 + i)).build()); - key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); - response = getItems(key, tableName); - assertEquals(4 + i, Integer.parseInt(response.get("hashKey").n())); - assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); - } - } - - private static String stripFilePath(String path) { - return path.replaceFirst("file://", ""); - } - - @JsonDeserialize(using = AttributeValueDeserializer.class) - public abstract static class DeserializedAttributeValue implements AttributeValue.Builder {} -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java deleted file mode 100644 index fd3bf37ace..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.SignatureException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.crypto.spec.SecretKeySpec; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.TestDelegatedKey; - -public class DelegatedEncryptionTest { - private static SecretKeySpec rawEncryptionKey; - private static SecretKeySpec rawMacKey; - private static DelegatedKey encryptionKey; - private static DelegatedKey macKey; - - private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; - private Map attribs; - private EncryptionContext context; - - @BeforeClass - public static void setupClass() { - rawEncryptionKey = new SecretKeySpec(Utils.getRandom(32), "AES"); - encryptionKey = new TestDelegatedKey(rawEncryptionKey); - - rawMacKey = new SecretKeySpec(Utils.getRandom(32), "HmacSHA256"); - macKey = new TestDelegatedKey(rawMacKey); - } - - @BeforeMethod - public void setUp() { - prov = new SymmetricStaticProvider(encryptionKey, macKey, - Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - - attribs = new HashMap<>(); - attribs.put("intValue", AttributeValue.builder().n("123").build()); - attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - attribs.put("byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); - attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); - attribs.put("hashKey", AttributeValue.builder().n("5").build()); - attribs.put("rangeKey", AttributeValue.builder().n("7").build()); - attribs.put("version", AttributeValue.builder().n("0").build()); - - context = EncryptionContext.builder() - .tableName("TableName") - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - } - - @Test - public void testSetSignatureFieldName() { - assertNotNull(encryptor.getSignatureFieldName()); - encryptor.setSignatureFieldName("A different value"); - assertEquals("A different value", encryptor.getSignatureFieldName()); - } - - @Test - public void testSetMaterialDescriptionFieldName() { - assertNotNull(encryptor.getMaterialDescriptionFieldName()); - encryptor.setMaterialDescriptionFieldName("A different value"); - assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); - } - - @Test - public void fullEncryption() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has been encrypted (we'll assume the others are correct as well) - assertTrue(encryptedAttributes.containsKey("stringValue")); - assertNull(encryptedAttributes.get("stringValue").s()); - assertNotNull(encryptedAttributes.get("stringValue").b()); - } - - @Test(expectedExceptions = SignatureException.class) - public void fullEncryptionBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void badVersionNumber() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - SdkBytes materialDescription = - encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); - byte[] rawArray = materialDescription.asByteArray(); - assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. - rawArray[0] = 100; - encryptedAttributes.put( - encryptor.getMaterialDescriptionFieldName(), - AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test - public void signedOnly() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test - public void signedOnlyNullCryptoKey() throws GeneralSecurityException { - prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyNoSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.remove(encryptor.getSignatureFieldName()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test - public void RsaSignedOnly() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-" - ); - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-" - ); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.replace("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { - assertEquals(o1.b(), o2.b()); - assertSetsEqual(o1.bs(), o2.bs()); - assertEquals(o1.n(), o2.n()); - assertSetsEqual(o1.ns(), o2.ns()); - assertEquals(o1.s(), o2.s()); - assertSetsEqual(o1.ss(), o2.ss()); - } - - private void assertSetsEqual(Collection c1, Collection c2) { - assertFalse(c1 == null ^ c2 == null); - if (c1 != null) { - Set s1 = new HashSet<>(c1); - Set s2 = new HashSet<>(c2); - assertEquals(s1, s2); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java deleted file mode 100644 index ce22c396fa..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.SignatureException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.crypto.spec.SecretKeySpec; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.TestDelegatedKey; - -public class DelegatedEnvelopeEncryptionTest { - private static SecretKeySpec rawEncryptionKey; - private static SecretKeySpec rawMacKey; - private static DelegatedKey encryptionKey; - private static DelegatedKey macKey; - - private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; - private Map attribs; - private EncryptionContext context; - - @BeforeClass - public static void setupClass() { - rawEncryptionKey = new SecretKeySpec(Utils.getRandom(32), "AES"); - encryptionKey = new TestDelegatedKey(rawEncryptionKey); - - rawMacKey = new SecretKeySpec(Utils.getRandom(32), "HmacSHA256"); - macKey = new TestDelegatedKey(rawMacKey); - } - - @BeforeMethod - public void setUp() throws Exception { - prov = - new WrappedMaterialsProvider( - encryptionKey, encryptionKey, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - - attribs = new HashMap(); - attribs.put("intValue", AttributeValue.builder().n("123").build()); - attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - attribs.put( - "byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5}))).build()); - attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); - attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); - attribs.put("hashKey", AttributeValue.builder().n("5").build()); - attribs.put("rangeKey", AttributeValue.builder().n("7").build()); - attribs.put("version", AttributeValue.builder().n("0").build()); - - context = - new EncryptionContext.Builder() - .tableName("TableName") - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - } - - @Test - public void testSetSignatureFieldName() { - assertNotNull(encryptor.getSignatureFieldName()); - encryptor.setSignatureFieldName("A different value"); - assertEquals("A different value", encryptor.getSignatureFieldName()); - } - - @Test - public void testSetMaterialDescriptionFieldName() { - assertNotNull(encryptor.getMaterialDescriptionFieldName()); - encryptor.setMaterialDescriptionFieldName("A different value"); - assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); - } - - @Test - public void fullEncryption() throws GeneralSecurityException{ - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has been encrypted (we'll assume the others are correct as well) - assertTrue(encryptedAttributes.containsKey("stringValue")); - assertNull(encryptedAttributes.get("stringValue").s()); - assertNotNull(encryptedAttributes.get("stringValue").b()); - } - - @Test(expectedExceptions = SignatureException.class) - public void fullEncryptionBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void badVersionNumber() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - SdkBytes materialDescription = - encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); - byte[] rawArray = materialDescription.asByteArray(); - assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. - rawArray[0] = 100; - encryptedAttributes.put( - encryptor.getMaterialDescriptionFieldName(), - AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test - public void signedOnlyNullCryptoKey() throws GeneralSecurityException { - prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyNoSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.remove(encryptor.getSignatureFieldName()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test - public void RsaSignedOnly() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-"); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-"); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.replace("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { - assertEquals(o1.b(), o2.b()); - assertSetsEqual(o1.bs(), o2.bs()); - assertEquals(o1.n(), o2.n()); - assertSetsEqual(o1.ns(), o2.ns()); - assertEquals(o1.s(), o2.s()); - assertSetsEqual(o1.ss(), o2.ss()); - } - - private void assertSetsEqual(Collection c1, Collection c2) { - assertFalse(c1 == null ^ c2 == null); - if (c1 != null) { - Set s1 = new HashSet<>(c1); - Set s2 = new HashSet<>(c2); - assertEquals(s1, s2); - } - } - -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java deleted file mode 100644 index 87fb8353bb..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java +++ /dev/null @@ -1,591 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import static java.util.stream.Collectors.toMap; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; -import static org.testng.collections.Sets.newHashSet; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableName; - -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.security.*; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jce.spec.ECParameterSpec; -import org.mockito.internal.util.collections.Sets; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; - -public class DynamoDbEncryptorTest { - private static SecretKey encryptionKey; - private static SecretKey macKey; - - private InstrumentedEncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; - private Map attribs; - private EncryptionContext context; - private static final String OVERRIDDEN_TABLE_NAME = "TheBestTableName"; - - @BeforeClass - public static void setUpClass() throws Exception { - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, Utils.getRng()); - encryptionKey = aesGen.generateKey(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - prov = new InstrumentedEncryptionMaterialsProvider( - new SymmetricStaticProvider(encryptionKey, macKey, - Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "enryptor-"); - - attribs = new HashMap<>(); - attribs.put("intValue", AttributeValue.builder().n("123").build()); - attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); - attribs.put("byteArrayValue", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); - attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); - attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); - attribs.put("hashKey", AttributeValue.builder().n("5").build()); - attribs.put("rangeKey", AttributeValue.builder().n("7").build()); - attribs.put("version", AttributeValue.builder().n("0").build()); - - // New(er) data types - attribs.put("booleanTrue", AttributeValue.builder().bool(true).build()); - attribs.put("booleanFalse", AttributeValue.builder().bool(false).build()); - attribs.put("nullValue", AttributeValue.builder().nul(true).build()); - Map tmpMap = new HashMap<>(attribs); - attribs.put("listValue", AttributeValue.builder().l( - AttributeValue.builder().s("I'm a string").build(), - AttributeValue.builder().n("42").build(), - AttributeValue.builder().s("Another string").build(), - AttributeValue.builder().ns("1", "4", "7").build(), - AttributeValue.builder().m(tmpMap).build(), - AttributeValue.builder().l( - AttributeValue.builder().n("123").build(), - AttributeValue.builder().ns("1", "200", "10", "15", "0").build(), - AttributeValue.builder().ss("Goodbye", "Cruel", "World", "!").build() - ).build()).build()); - tmpMap = new HashMap<>(); - tmpMap.put("another string", AttributeValue.builder().s("All around the cobbler's bench").build()); - tmpMap.put("next line", AttributeValue.builder().ss("the monkey", "chased", "the weasel").build()); - tmpMap.put("more lyrics", AttributeValue.builder().l( - AttributeValue.builder().s("the monkey").build(), - AttributeValue.builder().s("thought twas").build(), - AttributeValue.builder().s("all in fun").build() - ).build()); - tmpMap.put("weasel", AttributeValue.builder().m(Collections.singletonMap("pop", AttributeValue.builder().bool(true).build())).build()); - attribs.put("song", AttributeValue.builder().m(tmpMap).build()); - - context = EncryptionContext.builder() - .tableName("TableName") - .hashKeyName("hashKey") - .rangeKeyName("rangeKey") - .build(); - } - - @Test - public void testSetSignatureFieldName() { - assertNotNull(encryptor.getSignatureFieldName()); - encryptor.setSignatureFieldName("A different value"); - assertEquals("A different value", encryptor.getSignatureFieldName()); - } - - @Test - public void testSetMaterialDescriptionFieldName() { - assertNotNull(encryptor.getMaterialDescriptionFieldName()); - encryptor.setMaterialDescriptionFieldName("A different value"); - assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); - } - - @Test - public void fullEncryption() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has been encrypted (we'll assume the others are correct as well) - assertTrue(encryptedAttributes.containsKey("stringValue")); - assertNull(encryptedAttributes.get("stringValue").s()); - assertNotNull(encryptedAttributes.get("stringValue").b()); - - // Make sure we're calling the proper getEncryptionMaterials method - assertEquals( - "Wrong getEncryptionMaterials() called", - 1, - prov.getCallCount("getEncryptionMaterials(EncryptionContext context)")); - } - - @Test - public void ensureEncryptedAttributesUnmodified() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - String encryptedString = encryptedAttributes.toString(); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - - assertEquals(encryptedString, encryptedAttributes.toString()); - } - - @Test(expectedExceptions = SignatureException.class) - public void fullEncryptionBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void badVersionNumber() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept( - Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); - SdkBytes materialDescription = - encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); - byte[] rawArray = materialDescription.asByteArray(); - assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. - rawArray[0] = 100; - encryptedAttributes.put( - encryptor.getMaterialDescriptionFieldName(), - AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); - encryptor.decryptAllFieldsExcept( - Collections.unmodifiableMap(encryptedAttributes), - context, - "hashKey", - "rangeKey", - "version"); - } - - @Test - public void signedOnly() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test - public void signedOnlyNullCryptoKey() throws GeneralSecurityException { - prov = - new InstrumentedEncryptionMaterialsProvider( - new SymmetricStaticProvider(null, macKey, Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyBadSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test(expectedExceptions = SignatureException.class) - public void signedOnlyNoSignature() throws GeneralSecurityException { - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.remove(encryptor.getSignatureFieldName()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test - public void RsaSignedOnly() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-"); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - encryptor = - DynamoDbEncryptor.getInstance( - new SymmetricStaticProvider( - encryptionKey, sigPair, Collections.emptyMap()), - "encryptor-"); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - /** - * Tests that no exception is thrown when the encryption context override operator is null - * - * @throws GeneralSecurityException - */ - @Test - public void testNullEncryptionContextOperator() throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - encryptor.setEncryptionContextOverrideOperator(null); - encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); - } - - /** - * Tests decrypt and encrypt with an encryption context override operator - */ - @Test - public void testTableNameOverriddenEncryptionContextOperator() throws GeneralSecurityException { - // Ensure that the table name is different from what we override the table to. - assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - encryptor.setEncryptionContextOverrideOperator( - overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); - Map encryptedItems = - encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); - Map decryptedItems = - encryptor.decryptAllFieldsExcept(encryptedItems, context, Collections.emptyList()); - assertThat(decryptedItems, AttrMatcher.match(attribs)); - } - - - /** - * Tests encrypt with an encryption context override operator, and a second encryptor without an override - */ - @Test - public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor() - throws GeneralSecurityException { - // Ensure that the table name is different from what we override the table to. - assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); - encryptor.setEncryptionContextOverrideOperator( - overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); - Map encryptedItems = - encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); - - EncryptionContext expectedOverriddenContext = - new EncryptionContext.Builder(context).tableName("TheBestTableName").build(); - Map decryptedItems = - encryptorWithoutOverride.decryptAllFieldsExcept( - encryptedItems, expectedOverriddenContext, Collections.emptyList()); - assertThat(decryptedItems, AttrMatcher.match(attribs)); - } - - /** - * Tests encrypt with an encryption context override operator, and a second encryptor without an override - */ - @Test(expectedExceptions = SignatureException.class) - public void - testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptorButTheOriginalEncryptionContext() - throws GeneralSecurityException { - // Ensure that the table name is different from what we override the table to. - assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); - encryptor.setEncryptionContextOverrideOperator( - overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); - Map encryptedItems = - encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); - - // Use the original encryption context, and expect a signature failure - Map decryptedItems = - encryptorWithoutOverride.decryptAllFieldsExcept( - encryptedItems, context, Collections.emptyList()); - } - - @Test - public void EcdsaSignedOnly() throws GeneralSecurityException { - - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - Map decryptedAttributes = - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - - // Make sure keys and version are not encrypted - assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); - assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); - assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); - - // Make sure String has not been encrypted (we'll assume the others are correct as well) - assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); - } - - @Test(expectedExceptions = SignatureException.class) - public void EcdsaSignedOnlyBadSignature() throws GeneralSecurityException { - - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); - - Map encryptedAttributes = - encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); - assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); - encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); - encryptor.decryptAllFieldsExcept( - encryptedAttributes, context, attribs.keySet().toArray(new String[0])); - } - - @Test - public void toByteArray() throws ReflectiveOperationException { - final byte[] expected = new byte[] {0, 1, 2, 3, 4, 5}; - assertToByteArray("Wrap", expected, ByteBuffer.wrap(expected)); - assertToByteArray("Wrap-RO", expected, ByteBuffer.wrap(expected).asReadOnlyBuffer()); - - assertToByteArray("Wrap-Truncated-Sliced", expected, ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 6).slice()); - assertToByteArray("Wrap-Offset-Sliced", expected, ByteBuffer.wrap(new byte[] {6, 0, 1, 2, 3, 4, 5, 6}, 1, 6).slice()); - assertToByteArray("Wrap-Truncated", expected, ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 6)); - assertToByteArray("Wrap-Offset", expected, ByteBuffer.wrap(new byte[] {6, 0, 1, 2, 3, 4, 5, 6}, 1, 6)); - - ByteBuffer buff = ByteBuffer.allocate(expected.length + 10); - buff.put(expected); - buff.flip(); - assertToByteArray("Normal", expected, buff); - - buff = ByteBuffer.allocateDirect(expected.length + 10); - buff.put(expected); - buff.flip(); - assertToByteArray("Direct", expected, buff); - } - - @Test - public void testDecryptWithPlaintextItem() throws GeneralSecurityException { - Map> attributeWithEmptyEncryptionFlags = - attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet())); - - Map decryptedAttributes = - encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - } - - /* - Test decrypt with a map that contains a new key (not included in attribs) with an encryption flag set that contains ENCRYPT and SIGN. - */ - @Test - public void testDecryptWithPlainTextItemAndAdditionNewAttributeHavingEncryptionFlag() - throws GeneralSecurityException { - Map> attributeWithEmptyEncryptionFlags = - attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet())); - attributeWithEmptyEncryptionFlags.put( - "newAttribute", Sets.newSet(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); - - Map decryptedAttributes = - encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context); - assertThat(decryptedAttributes, AttrMatcher.match(attribs)); - } - private void assertToByteArray( - final String msg, final byte[] expected, final ByteBuffer testValue) - throws ReflectiveOperationException { - Method m = DynamoDbEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); - m.setAccessible(true); - - int oldPosition = testValue.position(); - int oldLimit = testValue.limit(); - - assertThat(m.invoke(null, testValue), is(expected)); - assertEquals(msg + ":Position", oldPosition, testValue.position()); - assertEquals(msg + ":Limit", oldLimit, testValue.limit()); - } - - private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { - assertEquals(o1.b(), o2.b()); - assertSetsEqual(o1.bs(), o2.bs()); - assertEquals(o1.n(), o2.n()); - assertSetsEqual(o1.ns(), o2.ns()); - assertEquals(o1.s(), o2.s()); - assertSetsEqual(o1.ss(), o2.ss()); - } - - private void assertSetsEqual(Collection c1, Collection c2) { - assertFalse(c1 == null ^ c2 == null); - if (c1 != null) { - Set s1 = new HashSet<>(c1); - Set s2 = new HashSet<>(c2); - assertEquals(s1, s2); - } - } - - private EncryptionMaterialsProvider getMaterialProviderwithECDSA() - throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException { - Security.addProvider(new BouncyCastleProvider()); - ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp384r1"); - KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC"); - g.initialize(ecSpec, Utils.getRng()); - KeyPair keypair = g.generateKeyPair(); - Map description = new HashMap<>(); - description.put(DynamoDbEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); - return new SymmetricStaticProvider(null, keypair, description); - } - - private static final class InstrumentedEncryptionMaterialsProvider implements EncryptionMaterialsProvider { - private final EncryptionMaterialsProvider delegate; - private final ConcurrentHashMap calls = new ConcurrentHashMap<>(); - - InstrumentedEncryptionMaterialsProvider(EncryptionMaterialsProvider delegate) { - this.delegate = delegate; - } - - @Override - public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { - incrementMethodCount("getDecryptionMaterials()"); - return delegate.getDecryptionMaterials(context); - } - - @Override - public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { - incrementMethodCount("getEncryptionMaterials(EncryptionContext context)"); - return delegate.getEncryptionMaterials(context); - } - - @Override - public void refresh() { - incrementMethodCount("refresh()"); - delegate.refresh(); - } - - int getCallCount(String method) { - AtomicInteger count = calls.get(method); - if (count != null) { - return count.intValue(); - } else { - return 0; - } - } - - @SuppressWarnings("unused") - public void resetCallCounts() { - calls.clear(); - } - - private void incrementMethodCount(String method) { - AtomicInteger oldValue = calls.putIfAbsent(method, new AtomicInteger(1)); - if (oldValue != null) { - oldValue.incrementAndGet(); - } - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java deleted file mode 100644 index 8320e79526..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; - -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.Key; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.Security; -import java.security.SignatureException; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import javax.crypto.KeyGenerator; - -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jce.spec.ECParameterSpec; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -public class DynamoDbSignerTest { - // These use the Key type (rather than PublicKey, PrivateKey, and SecretKey) - // to test the routing logic within the signer. - private static Key pubKeyRsa; - private static Key privKeyRsa; - private static Key macKey; - private DynamoDbSigner signerRsa; - private DynamoDbSigner signerEcdsa; - private static Key pubKeyEcdsa; - private static Key privKeyEcdsa; - - @BeforeClass - public static void setUpClass() throws Exception { - - // RSA key generation - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - KeyPair sigPair = rsaGen.generateKeyPair(); - pubKeyRsa = sigPair.getPublic(); - privKeyRsa = sigPair.getPrivate(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - - Security.addProvider(new BouncyCastleProvider()); - ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp384r1"); - KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC"); - g.initialize(ecSpec, Utils.getRng()); - KeyPair keypair = g.generateKeyPair(); - pubKeyEcdsa = keypair.getPublic(); - privKeyEcdsa = keypair.getPrivate(); - } - - @BeforeMethod - public void setUp() { - signerRsa = DynamoDbSigner.getInstance("SHA256withRSA", Utils.getRng()); - signerEcdsa = DynamoDbSigner.getInstance("SHA384withECDSA", Utils.getRng()); - } - - @Test - public void mac() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", - AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void macLists() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().ss("Value1", "Value2", "Value3").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().ns("100", "200", "300").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", - AttributeValue.builder() - .bs( - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})), - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void macListsUnsorted() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().ss("Value3", "Value1", "Value2").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().ns("100", "300", "200").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", - AttributeValue.builder() - .bs( - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1})), - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - Map scrambledAttributes = new HashMap(); - scrambledAttributes.put("Key1", AttributeValue.builder().ss("Value1", "Value2", "Value3").build()); - scrambledAttributes.put("Key2", AttributeValue.builder().ns("100", "200", "300").build()); - scrambledAttributes.put( - "Key3", - AttributeValue.builder() - .bs( - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})), - SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1}))) - .build()); - - signerRsa.verifySignature( - scrambledAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void macNoAdMatchesEmptyAd() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = signerRsa.calculateSignature(itemAttributes, attributeFlags, null, macKey); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void macWithIgnoredChange() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void macChangedValue() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void macChangedFlag() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); - - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void macChangedAssociatedData() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[] {3, 2, 1}, macKey); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[] {1, 2, 3}, macKey, ByteBuffer.wrap(signature)); - } - - @Test - public void sig() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test - public void sigWithReadOnlySignature() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - signerRsa.verifySignature( - itemAttributes, - attributeFlags, - new byte[0], - pubKeyRsa, - ByteBuffer.wrap(signature).asReadOnlyBuffer()); - } - - @Test - public void sigNoAdMatchesEmptyAd() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, null, privKeyRsa); - - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test - public void sigWithIgnoredChange() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigChangedValue() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigChangedFlag() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - signerRsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigChangedAssociatedData() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); - byte[] signature = - signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); - - signerRsa.verifySignature( - itemAttributes, - attributeFlags, - new byte[] {1, 2, 3}, - pubKeyRsa, - ByteBuffer.wrap(signature)); - } - - @Test - public void sigEcdsa() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - signerEcdsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); - } - - @Test - public void sigEcdsaWithReadOnlySignature() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - signerEcdsa.verifySignature( - itemAttributes, - attributeFlags, - new byte[0], - pubKeyEcdsa, - ByteBuffer.wrap(signature).asReadOnlyBuffer()); - } - - @Test - public void sigEcdsaNoAdMatchesEmptyAd() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, null, privKeyEcdsa); - - signerEcdsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); - } - - @Test - public void sigEcdsaWithIgnoredChange() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); - signerEcdsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigEcdsaChangedValue() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); - signerEcdsa.verifySignature( - itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); - } - - @Test(expectedExceptions = SignatureException.class) - public void sigEcdsaChangedAssociatedData() throws GeneralSecurityException { - Map itemAttributes = new HashMap(); - Map> attributeFlags = new HashMap>(); - - itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); - attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); - attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); - itemAttributes.put( - "Key3", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) - .build()); - attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); - byte[] signature = - signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); - - signerEcdsa.verifySignature( - itemAttributes, - attributeFlags, - new byte[] {1, 2, 3}, - pubKeyEcdsa, - ByteBuffer.wrap(signature)); - } -} \ No newline at end of file diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java deleted file mode 100644 index b9258c5f83..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; - -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -public class AsymmetricRawMaterialsTest { - private static SecureRandom rnd; - private static KeyPair encryptionPair; - private static SecretKey macKey; - private static KeyPair sigPair; - private Map description; - - @BeforeClass - public static void setUpClass() throws NoSuchAlgorithmException { - rnd = new SecureRandom(); - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, rnd); - encryptionPair = rsaGen.generateKeyPair(); - sigPair = rsaGen.generateKeyPair(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, rnd); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - } - - @Test - public void macNoDescription() throws GeneralSecurityException { - AsymmetricRawMaterials matEncryption = new AsymmetricRawMaterials(encryptionPair, macKey); - assertEquals(macKey, matEncryption.getSigningKey()); - assertEquals(macKey, matEncryption.getVerificationKey()); - assertFalse(matEncryption.getMaterialDescription().isEmpty()); - - SecretKey envelopeKey = matEncryption.getEncryptionKey(); - assertEquals(envelopeKey, matEncryption.getDecryptionKey()); - - AsymmetricRawMaterials matDecryption = - new AsymmetricRawMaterials(encryptionPair, macKey, matEncryption.getMaterialDescription()); - assertEquals(macKey, matDecryption.getSigningKey()); - assertEquals(macKey, matDecryption.getVerificationKey()); - assertEquals(envelopeKey, matDecryption.getEncryptionKey()); - assertEquals(envelopeKey, matDecryption.getDecryptionKey()); - } - - @Test - public void macWithDescription() throws GeneralSecurityException { - AsymmetricRawMaterials matEncryption = - new AsymmetricRawMaterials(encryptionPair, macKey, description); - assertEquals(macKey, matEncryption.getSigningKey()); - assertEquals(macKey, matEncryption.getVerificationKey()); - assertFalse(matEncryption.getMaterialDescription().isEmpty()); - assertEquals("test value", matEncryption.getMaterialDescription().get("TestKey")); - - SecretKey envelopeKey = matEncryption.getEncryptionKey(); - assertEquals(envelopeKey, matEncryption.getDecryptionKey()); - - AsymmetricRawMaterials matDecryption = - new AsymmetricRawMaterials(encryptionPair, macKey, matEncryption.getMaterialDescription()); - assertEquals(macKey, matDecryption.getSigningKey()); - assertEquals(macKey, matDecryption.getVerificationKey()); - assertEquals(envelopeKey, matDecryption.getEncryptionKey()); - assertEquals(envelopeKey, matDecryption.getDecryptionKey()); - assertEquals("test value", matDecryption.getMaterialDescription().get("TestKey")); - } - - @Test - public void sigNoDescription() throws GeneralSecurityException { - AsymmetricRawMaterials matEncryption = new AsymmetricRawMaterials(encryptionPair, sigPair); - assertEquals(sigPair.getPrivate(), matEncryption.getSigningKey()); - assertEquals(sigPair.getPublic(), matEncryption.getVerificationKey()); - assertFalse(matEncryption.getMaterialDescription().isEmpty()); - - SecretKey envelopeKey = matEncryption.getEncryptionKey(); - assertEquals(envelopeKey, matEncryption.getDecryptionKey()); - - AsymmetricRawMaterials matDecryption = - new AsymmetricRawMaterials(encryptionPair, sigPair, matEncryption.getMaterialDescription()); - assertEquals(sigPair.getPrivate(), matDecryption.getSigningKey()); - assertEquals(sigPair.getPublic(), matDecryption.getVerificationKey()); - assertEquals(envelopeKey, matDecryption.getEncryptionKey()); - assertEquals(envelopeKey, matDecryption.getDecryptionKey()); - } - - @Test - public void sigWithDescription() throws GeneralSecurityException { - AsymmetricRawMaterials matEncryption = - new AsymmetricRawMaterials(encryptionPair, sigPair, description); - assertEquals(sigPair.getPrivate(), matEncryption.getSigningKey()); - assertEquals(sigPair.getPublic(), matEncryption.getVerificationKey()); - assertFalse(matEncryption.getMaterialDescription().isEmpty()); - assertEquals("test value", matEncryption.getMaterialDescription().get("TestKey")); - - SecretKey envelopeKey = matEncryption.getEncryptionKey(); - assertEquals(envelopeKey, matEncryption.getDecryptionKey()); - - AsymmetricRawMaterials matDecryption = - new AsymmetricRawMaterials(encryptionPair, sigPair, matEncryption.getMaterialDescription()); - assertEquals(sigPair.getPrivate(), matDecryption.getSigningKey()); - assertEquals(sigPair.getPublic(), matDecryption.getVerificationKey()); - assertEquals(envelopeKey, matDecryption.getEncryptionKey()); - assertEquals(envelopeKey, matDecryption.getDecryptionKey()); - assertEquals("test value", matDecryption.getMaterialDescription().get("TestKey")); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java deleted file mode 100644 index a6987ce792..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertTrue; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -public class SymmetricRawMaterialsTest { - private static SecretKey encryptionKey; - private static SecretKey macKey; - private static KeyPair sigPair; - private static SecureRandom rnd; - private Map description; - - @BeforeClass - public static void setUpClass() throws NoSuchAlgorithmException { - rnd = new SecureRandom(); - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, rnd); - sigPair = rsaGen.generateKeyPair(); - - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, rnd); - encryptionKey = aesGen.generateKey(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, rnd); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - } - - @Test - public void macNoDescription() throws NoSuchAlgorithmException { - SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, macKey); - assertEquals(encryptionKey, mat.getEncryptionKey()); - assertEquals(encryptionKey, mat.getDecryptionKey()); - assertEquals(macKey, mat.getSigningKey()); - assertEquals(macKey, mat.getVerificationKey()); - assertTrue(mat.getMaterialDescription().isEmpty()); - } - - @Test - public void macWithDescription() throws NoSuchAlgorithmException { - SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, macKey, description); - assertEquals(encryptionKey, mat.getEncryptionKey()); - assertEquals(encryptionKey, mat.getDecryptionKey()); - assertEquals(macKey, mat.getSigningKey()); - assertEquals(macKey, mat.getVerificationKey()); - assertEquals(description, mat.getMaterialDescription()); - assertEquals("test value", mat.getMaterialDescription().get("TestKey")); - } - - @Test - public void sigNoDescription() throws NoSuchAlgorithmException { - SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, sigPair); - assertEquals(encryptionKey, mat.getEncryptionKey()); - assertEquals(encryptionKey, mat.getDecryptionKey()); - assertEquals(sigPair.getPrivate(), mat.getSigningKey()); - assertEquals(sigPair.getPublic(), mat.getVerificationKey()); - assertTrue(mat.getMaterialDescription().isEmpty()); - } - - @Test - public void sigWithDescription() throws NoSuchAlgorithmException { - SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, sigPair, description); - assertEquals(encryptionKey, mat.getEncryptionKey()); - assertEquals(encryptionKey, mat.getDecryptionKey()); - assertEquals(sigPair.getPrivate(), mat.getSigningKey()); - assertEquals(sigPair.getPublic(), mat.getVerificationKey()); - assertEquals(description, mat.getMaterialDescription()); - assertEquals("test value", mat.getMaterialDescription().get("TestKey")); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java deleted file mode 100644 index 8f71ac7b28..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; - -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -public class AsymmetricStaticProviderTest { - private static KeyPair encryptionPair; - private static SecretKey macKey; - private static KeyPair sigPair; - private Map description; - private EncryptionContext ctx; - - @BeforeClass - public static void setUpClass() throws Exception { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - sigPair = rsaGen.generateKeyPair(); - encryptionPair = rsaGen.generateKeyPair(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - description = Collections.unmodifiableMap(description); - ctx = new EncryptionContext.Builder().build(); - } - - @Test - public void constructWithMac() throws GeneralSecurityException { - AsymmetricStaticProvider prov = - new AsymmetricStaticProvider( - encryptionPair, macKey, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void constructWithSigPair() throws GeneralSecurityException { - AsymmetricStaticProvider prov = - new AsymmetricStaticProvider( - encryptionPair, sigPair, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void randomEnvelopeKeys() throws GeneralSecurityException { - AsymmetricStaticProvider prov = - new AsymmetricStaticProvider( - encryptionPair, macKey, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey2 = eMat2.getEncryptionKey(); - assertEquals(macKey, eMat.getSigningKey()); - - assertFalse("Envelope keys must be different", encryptionKey.equals(encryptionKey2)); - } - - @Test - public void testRefresh() { - // This does nothing, make sure we don't throw and exception. - AsymmetricStaticProvider prov = - new AsymmetricStaticProvider(encryptionPair, macKey, description); - prov.refresh(); - } - - private static EncryptionContext ctx(EncryptionMaterials mat) { - return new EncryptionContext.Builder() - .materialDescription(mat.getMaterialDescription()) - .build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java deleted file mode 100644 index f286648332..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java +++ /dev/null @@ -1,610 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; -import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; -import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; - -public class CachingMostRecentProviderTests { - private static final String TABLE_NAME = "keystoreTable"; - private static final String MATERIAL_NAME = "material"; - private static final String MATERIAL_PARAM = "materialName"; - private static final SecretKey AES_KEY = - new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "AES"); - private static final SecretKey HMAC_KEY = - new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); - private static final EncryptionMaterialsProvider BASE_PROVIDER = - new SymmetricStaticProvider(AES_KEY, HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); - - private DynamoDbClient client; - private Map methodCalls; - private ProvisionedThroughput throughput; - private ProviderStore store; - private EncryptionContext ctx; - - @BeforeMethod - public void setup() { - methodCalls = new HashMap(); - throughput = ProvisionedThroughput.builder().readCapacityUnits(1L).writeCapacityUnits(1L).build(); - - client = instrument(DynamoDBEmbedded.create().dynamoDbClient(), DynamoDbClient.class, methodCalls); - MetaStore.createTable(client, TABLE_NAME, throughput); - store = new MetaStore(client, TABLE_NAME, ENCRYPTOR); - ctx = new EncryptionContext.Builder().build(); - methodCalls.clear(); - } - - @Test - public void testConstructors() { - final CachingMostRecentProvider prov = - new CachingMostRecentProvider(store, MATERIAL_NAME, 100, 1000); - assertEquals(MATERIAL_NAME, prov.getMaterialName()); - assertEquals(100, prov.getTtlInMills()); - assertEquals(-1, prov.getCurrentVersion()); - assertEquals(0, prov.getLastUpdated()); - - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - assertEquals(MATERIAL_NAME, prov2.getMaterialName()); - assertEquals(500, prov2.getTtlInMills()); - assertEquals(-1, prov2.getCurrentVersion()); - assertEquals(0, prov2.getLastUpdated()); - } - - - @Test - public void testSmallMaxCacheSize() { - final Map attr1 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); - final EncryptionContext ctx1 = ctx(attr1); - final Map attr2 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); - final EncryptionContext ctx2 = ctx(attr2); - - final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 1); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the two materials are, in fact, different - assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); - - // Ensure the second set of materials are cached - final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - - // Ensure the first set of materials are no longer cached, due to being the LRU - final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); - } - - @Test - public void testSingleVersion() throws InterruptedException { - final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the cache is working - final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - // Let the TTL be exceeded - Thread.sleep(500); - final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); - assertEquals(2, methodCalls.size()); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); // To get provider - assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); - - assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); - assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey()); - // Check algorithms. Right now we only support AES and HmacSHA256 - assertEquals("AES", eMat1.getEncryptionKey().getAlgorithm()); - assertEquals("HmacSHA256", eMat1.getSigningKey().getAlgorithm()); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); - methodCalls.clear(); - assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); - assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); - final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); - assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); - assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); - final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); - assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); - assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - } - - @Test - public void testSingleVersionWithRefresh() throws InterruptedException { - final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the cache is working - final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - prov.refresh(); - final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); - prov.refresh(); - - assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); - assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey()); - - // Ensure that after cache refresh we only get one more hit as opposed to multiple - prov.getEncryptionMaterials(ctx); - Thread.sleep(700); - // Force refresh - prov.getEncryptionMaterials(ctx); - methodCalls.clear(); - // Check to ensure no more hits - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); - methodCalls.clear(); - assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); - assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); - final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); - assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); - assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); - final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); - assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); - assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - } - - @Test - public void testTwoVersions() throws InterruptedException { - final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Create the new material - store.newProvider(MATERIAL_NAME); - methodCalls.clear(); - - // Ensure the cache is working - final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - // Let the TTL be exceeded - Thread.sleep(500); - final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); - - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); // To retrieve current version - assertNull(methodCalls.get("putItem")); // No attempt to create a new item - assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); - - assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); - assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey())); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); - methodCalls.clear(); - assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); - assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); - final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); - assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); - assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); - final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); - assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); - assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); - // Get item will be hit once for the one old key - assertEquals(1, methodCalls.size()); - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - } - - @Test - public void testTwoVersionsWithRefresh() throws InterruptedException { - final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 100); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Create the new material - store.newProvider(MATERIAL_NAME); - methodCalls.clear(); - // Ensure the cache is working - final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - prov.refresh(); - final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); - - assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); - assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey())); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = - new CachingMostRecentProvider(store, MATERIAL_NAME, 500); - final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); - methodCalls.clear(); - assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); - assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); - final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); - assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); - assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); - final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); - assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); - assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); - // Get item will be hit once for the one old key - assertEquals(1, methodCalls.size()); - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - } - - @Test - public void testSingleVersionTwoMaterials() throws InterruptedException { - final Map attr1 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); - final EncryptionContext ctx1 = ctx(attr1); - final Map attr2 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); - final EncryptionContext ctx2 = ctx(attr2); - - final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the two materials are, in fact, different - assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); - - // Ensure the cache is working - final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); - final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); - - // Let the TTL be exceeded - Thread.sleep(500); - final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); - assertEquals(2, methodCalls.size()); - assertEquals(1, (int) methodCalls.get("query")); // To find current version - assertEquals(1, (int) methodCalls.get("getItem")); // To get the provider - assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); - methodCalls.clear(); - final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); - assertEquals(2, methodCalls.size()); - assertEquals(1, (int) methodCalls.get("query")); // To find current version - assertEquals(1, (int) methodCalls.get("getItem")); // To get the provider - assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); - - assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey()); - // Check algorithms. Right now we only support AES and HmacSHA256 - assertEquals("AES", eMat1_1.getEncryptionKey().getAlgorithm()); - assertEquals("AES", eMat1_2.getEncryptionKey().getAlgorithm()); - assertEquals("HmacSHA256", eMat1_1.getSigningKey().getAlgorithm()); - assertEquals("HmacSHA256", eMat1_2.getSigningKey().getAlgorithm()); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); - final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); - final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); - methodCalls.clear(); - assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); - assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); - assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); - assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); - final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); - final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); - assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); - assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); - assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); - assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); - final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); - final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); - assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); - assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); - assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); - assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - } - - @Test - public void testSingleVersionWithTwoMaterialsWithRefresh() throws InterruptedException { - final Map attr1 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); - final EncryptionContext ctx1 = ctx(attr1); - final Map attr2 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); - final EncryptionContext ctx2 = ctx(attr2); - - final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Ensure the two materials are, in fact, different - assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); - - // Ensure the cache is working - final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); - final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); - - prov.refresh(); - final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); - assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); - final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); - assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); - prov.refresh(); - - assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey()); - - // Ensure that after cache refresh we only get one more hit as opposed to multiple - prov.getEncryptionMaterials(ctx1); - prov.getEncryptionMaterials(ctx2); - Thread.sleep(700); - // Force refresh - prov.getEncryptionMaterials(ctx1); - prov.getEncryptionMaterials(ctx2); - methodCalls.clear(); - // Check to ensure no more hits - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); - - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); - final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); - final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); - methodCalls.clear(); - assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); - assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); - assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); - assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); - final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); - final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); - assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); - assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); - assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); - assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); - final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); - final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); - assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); - assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); - assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); - assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - } - - @Test - public void testTwoVersionsWithTwoMaterialsWithRefresh() throws InterruptedException { - final Map attr1 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); - final EncryptionContext ctx1 = ctx(attr1); - final Map attr2 = - Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); - final EncryptionContext ctx2 = ctx(attr2); - - final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); - assertNull(methodCalls.get("putItem")); - final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); - // It's a new provider, so we see a single putItem - assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); - methodCalls.clear(); - // Create the new material - store.newProvider("material1"); - store.newProvider("material2"); - methodCalls.clear(); - // Ensure the cache is working - final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); - final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); - assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); - assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); - prov.refresh(); - final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); - final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); - assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version - assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); - assertEquals(1, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); - assertEquals(1, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); - - assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); - assertFalse(eMat1_1.getSigningKey().equals(eMat3_1.getSigningKey())); - assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); - assertFalse(eMat1_2.getSigningKey().equals(eMat3_2.getSigningKey())); - - // Ensure we can decrypt all of them without hitting ddb more than the minimum - final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); - final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); - final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); - methodCalls.clear(); - assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); - assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); - assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); - assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); - final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); - final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); - assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); - assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); - assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); - assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); - final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); - final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); - assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); - assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); - assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); - assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); - // Get item will be hit twice, once for each old key - assertEquals(1, methodCalls.size()); - assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); - } - - private static EncryptionContext ctx(final Map attr) { - return new EncryptionContext.Builder().attributeValues(attr).build(); - } - - private static EncryptionContext ctx( - final EncryptionMaterials mat, Map attr) { - return new EncryptionContext.Builder() - .attributeValues(attr) - .materialDescription(mat.getMaterialDescription()) - .build(); - } - - private static EncryptionContext ctx(final EncryptionMaterials mat) { - return new EncryptionContext.Builder() - .materialDescription(mat.getMaterialDescription()) - .build(); - } - - private static class ExtendedProvider extends CachingMostRecentProvider { - public ExtendedProvider(ProviderStore keystore, long ttlInMillis, int maxCacheSize) { - super(keystore, null, ttlInMillis, maxCacheSize); - } - - @Override - public long getCurrentVersion() { - throw new UnsupportedOperationException(); - } - - @Override - protected String getMaterialName(final EncryptionContext context) { - return context.getAttributeValues().get(MATERIAL_PARAM).s(); - } - } - - @SuppressWarnings("unchecked") - private static T instrument( - final T obj, final Class clazz, final Map map) { - return (T) - Proxy.newProxyInstance( - clazz.getClassLoader(), - new Class[] {clazz}, - new InvocationHandler() { - private final Object lock = new Object(); - - @Override - public Object invoke(final Object proxy, final Method method, final Object[] args) - throws Throwable { - synchronized (lock) { - try { - final Integer oldCount = map.get(method.getName()); - if (oldCount != null) { - map.put(method.getName(), oldCount + 1); - } else { - map.put(method.getName(), 1); - } - return method.invoke(obj, args); - } catch (final InvocationTargetException ex) { - throw ex.getCause(); - } - } - } - }); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java deleted file mode 100644 index f5832a1e62..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.Key; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.core.exception.SdkException; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.awssdk.services.kms.model.DecryptRequest; -import software.amazon.awssdk.services.kms.model.DecryptResponse; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.FakeKMS; - -public class DirectKmsMaterialsProviderTest { - private FakeKMS kms; - private String keyId; - private Map description; - private EncryptionContext ctx; - - @BeforeMethod - public void setUp() { - description = new HashMap<>(); - description.put("TestKey", "test value"); - description = Collections.unmodifiableMap(description); - ctx = new EncryptionContext.Builder().build(); - kms = new FakeKMS(); - keyId = kms.createKey().keyMetadata().keyId(); - } - - @Test - public void simple() { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - - String expectedEncAlg = - encryptionKey.getAlgorithm() + "/" + (encryptionKey.getEncoded().length * 8); - String expectedSigAlg = signingKey.getAlgorithm() + "/" + (signingKey.getEncoded().length * 8); - - Map kmsCtx = kms.getSingleEc(); - assertEquals(expectedEncAlg, kmsCtx.get("*" + WrappedRawMaterials.CONTENT_KEY_ALGORITHM + "*")); - assertEquals(expectedSigAlg, kmsCtx.get("*amzn-ddb-sig-alg*")); - } - - @Test - public void simpleWithKmsEc() { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().s("HashKeyValue").build()); - attrVals.put("rk", AttributeValue.builder().s("RangeKeyValue").build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - Map kmsCtx = kms.getSingleEc(); - assertEquals("HashKeyValue", kmsCtx.get("hk")); - assertEquals("RangeKeyValue", kmsCtx.get("rk")); - assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - } - - @Test - public void simpleWithKmsEc2() throws GeneralSecurityException { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().n("10").build()); - attrVals.put("rk", AttributeValue.builder().n("20").build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - Map kmsCtx = kms.getSingleEc(); - assertEquals("10", kmsCtx.get("hk")); - assertEquals("20", kmsCtx.get("rk")); - assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - } - - @Test - public void simpleWithKmsEc3() throws GeneralSecurityException { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - Map attrVals = new HashMap<>(); - attrVals.put( - "hk", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap("Foo".getBytes(StandardCharsets.UTF_8)))) - .build()); - attrVals.put( - "rk", AttributeValue.builder() - .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap("Bar".getBytes(StandardCharsets.UTF_8)))) - .build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - assertNotNull(signingKey); - Map kmsCtx = kms.getSingleEc(); - assertEquals(Base64.encodeToString("Foo".getBytes(StandardCharsets.UTF_8)), kmsCtx.get("hk")); - assertEquals(Base64.encodeToString("Bar".getBytes(StandardCharsets.UTF_8)), kmsCtx.get("rk")); - assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - } - - @Test - public void randomEnvelopeKeys() throws GeneralSecurityException { - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey2 = eMat2.getEncryptionKey(); - - assertFalse("Envelope keys must be different", encryptionKey.equals(encryptionKey2)); - } - - @Test - public void testRefresh() { - // This does nothing, make sure we don't throw and exception. - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); - prov.refresh(); - } - - @Test - public void explicitContentKeyAlgorithm() throws GeneralSecurityException { - Map desc = new HashMap<>(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); - - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - } - - @Test - public void explicitContentKeyLength128() throws GeneralSecurityException { - Map desc = new HashMap<>(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(16, encryptionKey.getEncoded().length); // 128 Bits - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES/128", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", eMat.getEncryptionKey().getAlgorithm()); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - } - - @Test - public void explicitContentKeyLength256() throws GeneralSecurityException { - Map desc = new HashMap<>(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(32, encryptionKey.getEncoded().length); // 256 Bits - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES/256", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", eMat.getEncryptionKey().getAlgorithm()); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - } - - @Test - public void extendedWithDerivedEncryptionKeyId() { - ExtendedKmsMaterialsProvider prov = - new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); - String customKeyId = kms.createKey().keyMetadata().keyId(); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().n("10").build()); - attrVals.put("rk", AttributeValue.builder().n("20").build()); - attrVals.put("encryptionKeyId", AttributeValue.builder().s(customKeyId).build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - Key signingKey = eMat.getSigningKey(); - assertNotNull(signingKey); - Map kmsCtx = kms.getSingleEc(); - assertEquals("10", kmsCtx.get("hk")); - assertEquals("20", kmsCtx.get("rk")); - assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(signingKey, dMat.getVerificationKey()); - } - - @Test(expectedExceptions = SdkException.class) - public void encryptionKeyIdMismatch() throws SdkException { - DirectKmsMaterialsProvider directProvider = new DirectKmsMaterialsProvider(kms, keyId); - String customKeyId = kms.createKey().keyMetadata().keyId(); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().n("10").build()); - attrVals.put("rk", AttributeValue.builder().n("20").build()); - attrVals.put("encryptionKeyId", AttributeValue.builder().s(customKeyId).build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - EncryptionMaterials eMat = directProvider.getEncryptionMaterials(ctx); - - EncryptionContext dCtx = - new EncryptionContext.Builder(ctx(eMat)) - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - - ExtendedKmsMaterialsProvider extendedProvider = - new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); - - extendedProvider.getDecryptionMaterials(dCtx); - } - - @Test(expectedExceptions = SdkException.class) - public void missingEncryptionKeyId() throws SdkException { - ExtendedKmsMaterialsProvider prov = - new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); - - Map attrVals = new HashMap<>(); - attrVals.put("hk", AttributeValue.builder().n("10").build()); - attrVals.put("rk", AttributeValue.builder().n("20").build()); - - ctx = - new EncryptionContext.Builder() - .hashKeyName("hk") - .rangeKeyName("rk") - .tableName("KmsTableName") - .attributeValues(attrVals) - .build(); - prov.getEncryptionMaterials(ctx); - } - - @Test - public void generateDataKeyIsCalledWith256NumberOfBits() { - final AtomicBoolean gdkCalled = new AtomicBoolean(false); - KmsClient kmsSpy = - new FakeKMS() { - @Override - public GenerateDataKeyResponse generateDataKey(GenerateDataKeyRequest r) { - gdkCalled.set(true); - assertEquals((Integer) 32, r.numberOfBytes()); - assertNull(r.keySpec()); - return super.generateDataKey(r); - } - }; - assertFalse(gdkCalled.get()); - new DirectKmsMaterialsProvider(kmsSpy, keyId).getEncryptionMaterials(ctx); - assertTrue(gdkCalled.get()); - } - - private static class ExtendedKmsMaterialsProvider extends DirectKmsMaterialsProvider { - private final String encryptionKeyIdAttributeName; - - public ExtendedKmsMaterialsProvider( - KmsClient kms, String encryptionKeyId, String encryptionKeyIdAttributeName) { - super(kms, encryptionKeyId); - - this.encryptionKeyIdAttributeName = encryptionKeyIdAttributeName; - } - - @Override - protected String selectEncryptionKeyId(EncryptionContext context) - throws DynamoDbException { - if (!context.getAttributeValues().containsKey(encryptionKeyIdAttributeName)) { - throw DynamoDbException.create("encryption key attribute is not provided", new Exception()); - } - - return context.getAttributeValues().get(encryptionKeyIdAttributeName).s(); - } - - @Override - protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext context) - throws DynamoDbException { - if (!context.getAttributeValues().containsKey(encryptionKeyIdAttributeName)) { - throw DynamoDbException.create("encryption key attribute is not provided", new Exception()); - } - - String customEncryptionKeyId = - context.getAttributeValues().get(encryptionKeyIdAttributeName).s(); - if (!customEncryptionKeyId.equals(encryptionKeyId)) { - throw DynamoDbException.create("encryption key ids do not match.", new Exception()); - } - } - - @Override - protected DecryptResponse decrypt(DecryptRequest request, EncryptionContext context) { - return super.decrypt(request, context); - } - - @Override - protected GenerateDataKeyResponse generateDataKey( - GenerateDataKeyRequest request, EncryptionContext context) { - return super.generateDataKey(request, context); - } - } - - private static EncryptionContext ctx(EncryptionMaterials mat) { - return new EncryptionContext.Builder() - .materialDescription(mat.getMaterialDescription()) - .build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java deleted file mode 100644 index 406052452e..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.fail; - -import java.io.ByteArrayInputStream; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.KeyStore.PasswordProtection; -import java.security.KeyStore.PrivateKeyEntry; -import java.security.KeyStore.SecretKeyEntry; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -public class KeyStoreMaterialsProviderTest { - private static final String certPem = - "MIIDbTCCAlWgAwIBAgIJANdRvzVsW1CIMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV" + - "BAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMQwwCgYDVQQKDANBV1MxGzAZBgNV" + - "BAMMEktleVN0b3JlIFRlc3QgQ2VydDAeFw0xMzA1MDgyMzMyMjBaFw0xMzA2MDcy" + - "MzMyMjBaME0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMQwwCgYD" + - "VQQKDANBV1MxGzAZBgNVBAMMEktleVN0b3JlIFRlc3QgQ2VydDCCASIwDQYJKoZI" + - "hvcNAQEBBQADggEPADCCAQoCggEBAJ8+umOX8x/Ma4OZishtYpcA676bwK5KScf3" + - "w+YGM37L12KTdnOyieiGtRW8p0fS0YvnhmVTvaky09I33bH+qy9gliuNL2QkyMxp" + - "uu1IwkTKKuB67CaKT6osYJLFxV/OwHcaZnTszzDgbAVg/Z+8IZxhPgxMzMa+7nDn" + - "hEm9Jd+EONq3PnRagnFeLNbMIePprdJzXHyNNiZKRRGQ/Mo9rr7mqMLSKnFNsmzB" + - "OIfeZM8nXeg+cvlmtXl72obwnGGw2ksJfaxTPm4eEhzRoAgkbjPPLHbwiJlc+GwF" + - "i8kh0Y3vQTj/gOFE4nzipkm7ux1lsGHNRVpVDWpjNd8Fl9JFELkCAwEAAaNQME4w" + - "HQYDVR0OBBYEFM0oGUuFAWlLXZaMXoJgGZxWqfOxMB8GA1UdIwQYMBaAFM0oGUuF" + - "AWlLXZaMXoJgGZxWqfOxMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB" + - "AAXCsXeC8ZRxovP0Wc6C5qv3d7dtgJJVzHwoIRt2YR3yScBa1XI40GKT80jP3MYH" + - "8xMu3mBQtcYrgRKZBy4GpHAyxoFTnPcuzq5Fg7dw7fx4E4OKIbWOahdxwtbVxQfZ" + - "UHnGG88Z0bq2twj7dALGyJhUDdiccckJGmJPOFMzjqsvoAu0n/p7eS6y5WZ5ewqw" + - "p7VwYOP3N9wVV7Podmkh1os+eCcp9GoFf0MHBMFXi2Ps2azKx8wHRIA5D1MZv/Va" + - "4L4/oTBKCjORpFlP7EhMksHBYnjqXLDP6awPMAgQNYB5J9zX6GfJsAgly3t4Rjr5" + - "cLuNYBmRuByFGo+SOdrj6D8="; - private static final String keyPem = - "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCfPrpjl/MfzGuD" + - "mYrIbWKXAOu+m8CuSknH98PmBjN+y9dik3ZzsonohrUVvKdH0tGL54ZlU72pMtPS" + - "N92x/qsvYJYrjS9kJMjMabrtSMJEyirgeuwmik+qLGCSxcVfzsB3GmZ07M8w4GwF" + - "YP2fvCGcYT4MTMzGvu5w54RJvSXfhDjatz50WoJxXizWzCHj6a3Sc1x8jTYmSkUR" + - "kPzKPa6+5qjC0ipxTbJswTiH3mTPJ13oPnL5ZrV5e9qG8JxhsNpLCX2sUz5uHhIc" + - "0aAIJG4zzyx28IiZXPhsBYvJIdGN70E4/4DhROJ84qZJu7sdZbBhzUVaVQ1qYzXf" + - "BZfSRRC5AgMBAAECggEBAJMwx9eGe5LIwBfDtCPN93LbxwtHq7FtuQS8XrYexTpN" + - "76eN5c7LF+11lauh1HzuwAEw32iJHqVl9aQ5PxFm85O3ExbuSP+ngHJwx/bLacVr" + - "mHYlKGH3Net1WU5Qvz7vO7bbEBjDSj9DMJVIMSWUHv0MZO25jw2lLX/ufrgpvPf7" + - "KXSgXg/8uV7PbnTbBDNlg02u8eOc+IbH4O8XDKAhD+YQ8AE3pxtopJbb912U/cJs" + - "Y0hQ01zbkWYH7wL9BeQmR7+TEjjtr/IInNjnXmaOmSX867/rTSTuozaVrl1Ce7r8" + - "EmUDg9ZLZeKfoNYovMy08wnxWVX2J+WnNDjNiSOm+IECgYEA0v3jtGrOnKbd0d9E" + - "dbyIuhjgnwp+UsgALIiBeJYjhFS9NcWgs+02q/0ztqOK7g088KBBQOmiA+frLIVb" + - "uNCt/3jF6kJvHYkHMZ0eBEstxjVSM2UcxzJ6ceHZ68pmrru74382TewVosxccNy0" + - "glsUWNN0t5KQDcetaycRYg50MmcCgYEAwTb8klpNyQE8AWxVQlbOIEV24iarXxex" + - "7HynIg9lSeTzquZOXjp0m5omQ04psil2gZ08xjiudG+Dm7QKgYQcxQYUtZPQe15K" + - "m+2hQM0jA7tRfM1NAZHoTmUlYhzRNX6GWAqQXOgjOqBocT4ySBXRaSQq9zuZu36s" + - "fI17knap798CgYArDa2yOf0xEAfBdJqmn7MSrlLfgSenwrHuZGhu78wNi7EUUOBq" + - "9qOqUr+DrDmEO+VMgJbwJPxvaZqeehPuUX6/26gfFjFQSI7UO+hNHf4YLPc6D47g" + - "wtcjd9+c8q8jRqGfWWz+V4dOsf7G9PJMi0NKoNN3RgvpE+66J72vUZ26TwKBgEUq" + - "DdfGA7pEetp3kT2iHT9oHlpuRUJRFRv2s015/WQqVR+EOeF5Q2zADZpiTIK+XPGg" + - "+7Rpbem4UYBXPruGM1ZECv3E4AiJhGO0+Nhdln8reswWIc7CEEqf4nXwouNnW2gA" + - "wBTB9Hp0GW8QOKedR80/aTH/X9TCT7R2YRnY6JQ5AoGBAKjgPySgrNDhlJkW7jXR" + - "WiGpjGSAFPT9NMTvEHDo7oLTQ8AcYzcGQ7ISMRdVXR6GJOlFVsH4NLwuHGtcMTPK" + - "zoHbPHJyOn1SgC5tARD/1vm5CsG2hATRpWRQCTJFg5VRJ4R7Pz+HuxY4SoABcPQd" + - "K+MP8GlGqTldC6NaB1s7KuAX"; - - private static SecretKey encryptionKey; - private static SecretKey macKey; - private static KeyStore keyStore; - private static final String password = "Password"; - private static final PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray()); - - private Map description; - private EncryptionContext ctx; - private static PrivateKey privateKey; - private static Certificate certificate; - - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, Utils.getRng()); - encryptionKey = aesGen.generateKey(); - - keyStore = KeyStore.getInstance("jceks"); - keyStore.load(null, password.toCharArray()); - - KeyFactory kf = KeyFactory.getInstance("RSA"); - PKCS8EncodedKeySpec rsaSpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyPem)); - privateKey = kf.generatePrivate(rsaSpec); - CertificateFactory cf = CertificateFactory.getInstance("X509"); - certificate = cf.generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(certPem))); - - keyStore.setEntry("enc", new SecretKeyEntry(encryptionKey), passwordProtection); - keyStore.setEntry("sig", new SecretKeyEntry(macKey), passwordProtection); - keyStore.setEntry( - "enc-a", - new PrivateKeyEntry(privateKey, new Certificate[] {certificate}), - passwordProtection); - keyStore.setEntry( - "sig-a", - new PrivateKeyEntry(privateKey, new Certificate[] {certificate}), - passwordProtection); - keyStore.setCertificateEntry("trustedCert", certificate); - } - - @BeforeMethod - public void setUp() { - description = new HashMap<>(); - description.put("TestKey", "test value"); - description = Collections.unmodifiableMap(description); - ctx = EncryptionContext.builder().build(); - } - - - @Test - @SuppressWarnings("unchecked") - public void simpleSymMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, Collections.EMPTY_MAP); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); - } - - @Test - @SuppressWarnings("unchecked") - public void simpleSymSig() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig-a", passwordProtection, passwordProtection, Collections.EMPTY_MAP); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(privateKey, encryptionMaterials.getSigningKey()); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); - assertEquals(certificate.getPublicKey(), prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); - } - - @Test - public void equalSymDescMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); - } - - @Test - public void superSetSymDescMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - Map tmpDesc = - new HashMap<>(encryptionMaterials.getMaterialDescription()); - tmpDesc.put("randomValue", "random"); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(tmpDesc)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(tmpDesc)).getVerificationKey()); - } - - @Test - @SuppressWarnings("unchecked") - public void subSetSymDescMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - - assertNull(prov.getDecryptionMaterials(ctx(Collections.EMPTY_MAP))); - } - - - @Test - public void noMatchSymDescMac() throws Exception { - KeyStoreMaterialsProvider prov = new - KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); - assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); - assertEquals(macKey, encryptionMaterials.getSigningKey()); - Map tmpDesc = new HashMap<>(); - tmpDesc.put("randomValue", "random"); - - assertNull(prov.getDecryptionMaterials(ctx(tmpDesc))); - } - - @Test - public void testRefresh() throws Exception { - // Mostly make sure we don't throw an exception - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc", "sig", passwordProtection, passwordProtection, description); - prov.refresh(); - } - - @Test - public void asymSimpleMac() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc-a", "sig", passwordProtection, passwordProtection, description); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void asymSimpleSig() throws Exception { - KeyStoreMaterialsProvider prov = new KeyStoreMaterialsProvider(keyStore, "enc-a", "sig-a", passwordProtection, passwordProtection, description); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(privateKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(certificate.getPublicKey(), dMat.getVerificationKey()); - } - - @Test - public void asymSigVerifyOnly() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "enc-a", "trustedCert", passwordProtection, null, description); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertNull(eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(certificate.getPublicKey(), dMat.getVerificationKey()); - } - - @Test - public void asymSigEncryptOnly() throws Exception { - KeyStoreMaterialsProvider prov = - new KeyStoreMaterialsProvider( - keyStore, "trustedCert", "sig-a", null, passwordProtection, description); - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - assertEquals(privateKey, eMat.getSigningKey()); - - try { - prov.getDecryptionMaterials(ctx(eMat)); - fail("Expected exception"); - } catch (IllegalStateException ex) { - assertEquals("No private decryption key provided.", ex.getMessage()); - } - } - - private static EncryptionContext ctx(EncryptionMaterials mat) { - return ctx(mat.getMaterialDescription()); - } - - private static EncryptionContext ctx(Map desc) { - return EncryptionContext.builder() - .materialDescription(desc).build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java deleted file mode 100644 index 0485d4dff7..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; - -public class SymmetricStaticProviderTest { - private static SecretKey encryptionKey; - private static SecretKey macKey; - private static KeyPair sigPair; - private Map description; - private EncryptionContext ctx; - - @BeforeClass - public static void setUpClass() throws Exception { - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, Utils.getRng()); - sigPair = rsaGen.generateKeyPair(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, Utils.getRng()); - macKey = macGen.generateKey(); - - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, Utils.getRng()); - encryptionKey = aesGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - description = Collections.unmodifiableMap(description); - ctx = new EncryptionContext.Builder().build(); - } - - @Test - public void simpleMac() { - SymmetricStaticProvider prov = - new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap()); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - - assertEquals( - encryptionKey, - prov.getDecryptionMaterials(ctx(Collections.emptyMap())) - .getDecryptionKey()); - assertEquals( - macKey, - prov.getDecryptionMaterials(ctx(Collections.emptyMap())) - .getVerificationKey()); - } - - @Test - public void simpleSig() { - SymmetricStaticProvider prov = - new SymmetricStaticProvider(encryptionKey, sigPair, Collections.emptyMap()); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(sigPair.getPrivate(), prov.getEncryptionMaterials(ctx).getSigningKey()); - - assertEquals( - encryptionKey, - prov.getDecryptionMaterials(ctx(Collections.emptyMap())) - .getDecryptionKey()); - assertEquals( - sigPair.getPublic(), - prov.getDecryptionMaterials(ctx(Collections.emptyMap())) - .getVerificationKey()); - } - - @Test - public void equalDescMac() { - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue( - prov.getEncryptionMaterials(ctx) - .getMaterialDescription() - .entrySet() - .containsAll(description.entrySet())); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(description)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(description)).getVerificationKey()); - } - - @Test - public void supersetDescMac() { - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue( - prov.getEncryptionMaterials(ctx) - .getMaterialDescription() - .entrySet() - .containsAll(description.entrySet())); - - Map superSet = new HashMap(description); - superSet.put("NewValue", "super!"); - - assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(superSet)).getDecryptionKey()); - assertEquals(macKey, prov.getDecryptionMaterials(ctx(superSet)).getVerificationKey()); - } - - @Test - public void subsetDescMac() { - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue( - prov.getEncryptionMaterials(ctx) - .getMaterialDescription() - .entrySet() - .containsAll(description.entrySet())); - - assertNull(prov.getDecryptionMaterials(ctx(Collections.emptyMap()))); - } - - @Test - public void noMatchDescMac() { - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); - assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); - assertTrue( - prov.getEncryptionMaterials(ctx) - .getMaterialDescription() - .entrySet() - .containsAll(description.entrySet())); - - Map noMatch = new HashMap(); - noMatch.put("NewValue", "no match!"); - - assertNull(prov.getDecryptionMaterials(ctx(noMatch))); - } - - @Test - public void testRefresh() { - // This does nothing, make sure we don't throw and exception. - SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); - prov.refresh(); - } - - @SuppressWarnings("unused") - private static EncryptionContext ctx(EncryptionMaterials mat) { - return ctx(mat.getMaterialDescription()); - } - - private static EncryptionContext ctx(Map desc) { - return EncryptionContext.builder() - .materialDescription(desc).build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java deleted file mode 100644 index 5f82b47dd8..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java +++ /dev/null @@ -1,414 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -public class WrappedMaterialsProviderTest { - private static SecretKey symEncryptionKey; - private static SecretKey macKey; - private static KeyPair sigPair; - private static KeyPair encryptionPair; - private static SecureRandom rnd; - private Map description; - private EncryptionContext ctx; - - @BeforeClass - public static void setUpClass() throws NoSuchAlgorithmException { - rnd = new SecureRandom(); - KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); - rsaGen.initialize(2048, rnd); - sigPair = rsaGen.generateKeyPair(); - encryptionPair = rsaGen.generateKeyPair(); - - KeyGenerator aesGen = KeyGenerator.getInstance("AES"); - aesGen.init(128, rnd); - symEncryptionKey = aesGen.generateKey(); - - KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); - macGen.init(256, rnd); - macKey = macGen.generateKey(); - } - - @BeforeMethod - public void setUp() { - description = new HashMap(); - description.put("TestKey", "test value"); - ctx = new EncryptionContext.Builder().build(); - } - - @Test - public void simpleMac() { - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void simpleSigPair() { - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - symEncryptionKey, symEncryptionKey, sigPair, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void randomEnvelopeKeys() { - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(macKey, eMat.getSigningKey()); - - EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey2 = eMat2.getEncryptionKey(); - assertEquals(macKey, eMat.getSigningKey()); - - assertFalse( - "Envelope keys must be different", contentEncryptionKey.equals(contentEncryptionKey2)); - } - - @Test - public void testRefresh() { - // This does nothing, make sure we don't throw an exception. - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); - prov.refresh(); - } - - @Test - public void wrapUnwrapAsymMatExplicitWrappingAlgorithmPkcs1() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM, "RSA/ECB/PKCS1Padding"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "RSA/ECB/PKCS1Padding", - eMat.getMaterialDescription().get(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapAsymMatExplicitWrappingAlgorithmPkcs2() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM, "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", - eMat.getMaterialDescription().get(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapAsymMatExplicitContentKeyAlgorithm() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), - encryptionPair.getPrivate(), - sigPair, - Collections.emptyMap()); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapAsymMatExplicitContentKeyLength128() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(16, contentEncryptionKey.getEncoded().length); // 128 Bits - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapAsymMatExplicitContentKeyLength256() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(32, contentEncryptionKey.getEncoded().length); // 256 Bits - assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void unwrapAsymMatExplicitEncAlgAes128() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - // Get materials we can test unwrapping on - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - - // Ensure "AES/128" on the created materials creates the expected key - Map aes128Desc = eMat.getMaterialDescription(); - aes128Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - EncryptionContext aes128Ctx = - new EncryptionContext.Builder().materialDescription(aes128Desc).build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(aes128Ctx); - assertEquals( - "AES/128", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void unwrapAsymMatExplicitEncAlgAes256() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider( - encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); - - // Get materials we can test unwrapping on - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - - // Ensure "AES/256" on the created materials creates the expected key - Map aes256Desc = eMat.getMaterialDescription(); - aes256Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - EncryptionContext aes256Ctx = - new EncryptionContext.Builder().materialDescription(aes256Desc).build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(aes256Ctx); - assertEquals( - "AES/256", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapSymMatExplicitContentKeyAlgorithm() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapSymMatExplicitContentKeyLength128() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(16, contentEncryptionKey.getEncoded().length); // 128 Bits - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void wrapUnwrapSymMatExplicitContentKeyLength256() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - SecretKey contentEncryptionKey = eMat.getEncryptionKey(); - assertNotNull(contentEncryptionKey); - assertEquals("AES", contentEncryptionKey.getAlgorithm()); - assertEquals( - "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(32, contentEncryptionKey.getEncoded().length); // 256 Bits - assertEquals(macKey, eMat.getSigningKey()); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals( - "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void unwrapSymMatExplicitEncAlgAes128() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - // Get materials we can test unwrapping on - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - - // Ensure "AES/128" on the created materials creates the expected key - Map aes128Desc = eMat.getMaterialDescription(); - aes128Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); - EncryptionContext aes128Ctx = - new EncryptionContext.Builder().materialDescription(aes128Desc).build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(aes128Ctx); - assertEquals( - "AES/128", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - @Test - public void unwrapSymMatExplicitEncAlgAes256() { - Map desc = new HashMap(); - desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - - WrappedMaterialsProvider prov = - new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); - - EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - - Map aes256Desc = eMat.getMaterialDescription(); - aes256Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); - EncryptionContext aes256Ctx = - new EncryptionContext.Builder().materialDescription(aes256Desc).build(); - - DecryptionMaterials dMat = prov.getDecryptionMaterials(aes256Ctx); - assertEquals( - "AES/256", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); - assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(macKey, dMat.getVerificationKey()); - } - - private static EncryptionContext ctx(EncryptionMaterials mat) { - return new EncryptionContext.Builder() - .materialDescription(mat.getMaterialDescription()) - .build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java deleted file mode 100644 index 3449908a6d..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.fail; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttributeValueBuilder; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.LocalDynamoDb; - -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; - -public class MetaStoreTests { - private static final String SOURCE_TABLE_NAME = "keystoreTable"; - private static final String DESTINATION_TABLE_NAME = "keystoreDestinationTable"; - private static final String MATERIAL_NAME = "material"; - private static final SecretKey AES_KEY = new SecretKeySpec(new byte[] { 0, - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, "AES"); - private static final SecretKey TARGET_AES_KEY = new SecretKeySpec(new byte[] { 0, - 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, "AES"); - private static final SecretKey HMAC_KEY = new SecretKeySpec(new byte[] { 0, - 1, 2, 3, 4, 5, 6, 7 }, "HmacSHA256"); - private static final SecretKey TARGET_HMAC_KEY = new SecretKeySpec(new byte[] { 0, - 2, 4, 6, 8, 10, 12, 14 }, "HmacSHA256"); - private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); - private static final EncryptionMaterialsProvider TARGET_BASE_PROVIDER = new SymmetricStaticProvider(TARGET_AES_KEY, TARGET_HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); - private static final DynamoDbEncryptor TARGET_ENCRYPTOR = DynamoDbEncryptor.getInstance(TARGET_BASE_PROVIDER); - - private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); - private final LocalDynamoDb targetLocalDynamoDb = new LocalDynamoDb(); - private DynamoDbClient client; - private DynamoDbClient targetClient; - private MetaStore store; - private MetaStore targetStore; - private EncryptionContext ctx; - - private static class TestExtraDataSupplier implements MetaStore.ExtraDataSupplier { - - private final Map attributeValueMap; - private final Set signedOnlyFieldNames; - - TestExtraDataSupplier(final Map attributeValueMap, - final Set signedOnlyFieldNames) { - this.attributeValueMap = attributeValueMap; - this.signedOnlyFieldNames = signedOnlyFieldNames; - } - - @Override - public Map getAttributes(String materialName, long version) { - return this.attributeValueMap; - } - - @Override - public Set getSignedOnlyFieldNames() { - return this.signedOnlyFieldNames; - } - } - - @BeforeMethod - public void setup() { - localDynamoDb.start(); - targetLocalDynamoDb.start(); - client = localDynamoDb.createClient(); - targetClient = targetLocalDynamoDb.createClient(); - - MetaStore.createTable(client, SOURCE_TABLE_NAME, ProvisionedThroughput.builder() - .readCapacityUnits(1L) - .writeCapacityUnits(1L) - .build()); - //Creating Targeted DynamoDB Object - MetaStore.createTable(targetClient, DESTINATION_TABLE_NAME, ProvisionedThroughput.builder() - .readCapacityUnits(1L) - .writeCapacityUnits(1L) - .build()); - store = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR); - targetStore = new MetaStore(targetClient, DESTINATION_TABLE_NAME, TARGET_ENCRYPTOR); - ctx = EncryptionContext.builder().build(); - } - - @AfterMethod - public void stopLocalDynamoDb() { - localDynamoDb.stop(); - targetLocalDynamoDb.stop(); - } - - @Test - public void testNoMaterials() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - } - - @Test - public void singleMaterial() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov = store.newProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void singleMaterialExplicitAccess() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = store.getProvider(MATERIAL_NAME); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void singleMaterialExplicitAccessWithVersion() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = store.getProvider(MATERIAL_NAME, 0); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void singleMaterialWithImplicitCreation() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov = store.getProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void twoDifferentMaterials() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = store.newProvider(MATERIAL_NAME); - assertEquals(1, store.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - try { - prov2.getDecryptionMaterials(ctx(eMat)); - fail("Missing expected exception"); - } catch (final DynamoDbEncryptionException ex) { - // Expected Exception - } - final EncryptionMaterials eMat2 = prov2.getEncryptionMaterials(ctx); - assertEquals(1, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); - } - - @Test - public void getOrCreateCollision() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = store.getOrCreate(MATERIAL_NAME, 0); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = store.getOrCreate(MATERIAL_NAME, 0); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void getOrCreateWithContextSupplier() { - final Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("CustomKeyId", AttributeValueBuilder.ofS("testCustomKeyId")); - attributeValueMap.put("KeyToken", AttributeValueBuilder.ofS("testKeyToken")); - - final Set signedOnlyAttributes = new HashSet<>(); - signedOnlyAttributes.add("CustomKeyId"); - - final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( - attributeValueMap, signedOnlyAttributes); - - final MetaStore metaStore = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); - - assertEquals(-1, metaStore.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov1 = metaStore.getOrCreate(MATERIAL_NAME, 0); - assertEquals(0, metaStore.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = metaStore.getOrCreate(MATERIAL_NAME, 0); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test - public void replicateIntermediateKeysTest() { - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterialsProvider prov1 = store.getOrCreate(MATERIAL_NAME, 0); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - - store.replicate(MATERIAL_NAME, 0, targetStore); - assertEquals(0, targetStore.getMaxVersion(MATERIAL_NAME)); - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final DecryptionMaterials dMat = targetStore.getProvider(MATERIAL_NAME, 0).getDecryptionMaterials(ctx(eMat)); - - assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test(expectedExceptions = IndexOutOfBoundsException.class) - public void replicateIntermediateKeysWhenMaterialNotFoundTest() { - store.replicate(MATERIAL_NAME, 0, targetStore); - } - - @Test - public void newProviderCollision() throws InterruptedException { - final SlowNewProvider slowProv = new SlowNewProvider(); - assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); - assertEquals(-1, slowProv.slowStore.getMaxVersion(MATERIAL_NAME)); - - slowProv.start(); - Thread.sleep(100); - final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); - slowProv.join(); - assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); - assertEquals(0, slowProv.slowStore.getMaxVersion(MATERIAL_NAME)); - final EncryptionMaterialsProvider prov2 = slowProv.result; - - final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); - final SecretKey encryptionKey = eMat.getEncryptionKey(); - assertNotNull(encryptionKey); - - final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); - assertEquals(encryptionKey, dMat.getDecryptionKey()); - assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); - } - - @Test(expectedExceptions= IndexOutOfBoundsException.class) - public void invalidVersion() { - store.getProvider(MATERIAL_NAME, 1000); - } - - @Test(expectedExceptions= IllegalArgumentException.class) - public void invalidSignedOnlyField() { - final Map attributeValueMap = new HashMap<>(); - attributeValueMap.put("enc", AttributeValueBuilder.ofS("testEncryptionKey")); - - final Set signedOnlyAttributes = new HashSet<>(); - signedOnlyAttributes.add("enc"); - - final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( - attributeValueMap, signedOnlyAttributes); - - new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); - } - - private static EncryptionContext ctx(final EncryptionMaterials mat) { - return EncryptionContext.builder() - .materialDescription(mat.getMaterialDescription()).build(); - } - - private class SlowNewProvider extends Thread { - public volatile EncryptionMaterialsProvider result; - public ProviderStore slowStore = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR) { - @Override - public EncryptionMaterialsProvider newProvider(final String materialName) { - final long nextId = getMaxVersion(materialName) + 1; - try { - Thread.sleep(1000); - } catch (final InterruptedException e) { - // Ignored - } - return getOrCreate(materialName, nextId); - } - }; - - @Override - public void run() { - result = slowStore.newProvider(MATERIAL_NAME); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java deleted file mode 100644 index 2ed128e9d3..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java +++ /dev/null @@ -1,164 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils; - -import static org.testng.AssertJUnit.assertEquals; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableName; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableNameUsingMap; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import org.testng.annotations.Test; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; - -public class EncryptionContextOperatorsTest { - - @Test - public void testCreateEncryptionContextTableNameOverride_expectedOverride() { - Function myNewTableName = overrideEncryptionContextTableName("OriginalTableName", "MyNewTableName"); - - EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName").build(); - - EncryptionContext newContext = myNewTableName.apply(context); - - assertEquals("OriginalTableName", context.getTableName()); - assertEquals("MyNewTableName", newContext.getTableName()); - } - - /** - * Some pretty clear repetition in null cases. May make sense to replace with data providers or parameterized - * classes for null cases - */ - @Test - public void testNullCasesCreateEncryptionContextTableNameOverride_nullOriginalTableName() { - assertEncryptionContextUnchanged(EncryptionContext.builder().tableName("example").build(), - null, - "MyNewTableName"); - } - - @Test - public void testCreateEncryptionContextTableNameOverride_differentOriginalTableName() { - assertEncryptionContextUnchanged(EncryptionContext.builder().tableName("example").build(), - "DifferentTableName", - "MyNewTableName"); - } - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverride_nullEncryptionContext() { - assertEncryptionContextUnchanged(null, - "DifferentTableName", - "MyNewTableName"); - } - - @Test - public void testCreateEncryptionContextTableNameOverrideMap_expectedOverride() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("OriginalTableName", "MyNewTableName"); - - - Function nameOverrideMap = - overrideEncryptionContextTableNameUsingMap(tableNameOverrides); - - EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName").build(); - - EncryptionContext newContext = nameOverrideMap.apply(context); - - assertEquals("OriginalTableName", context.getTableName()); - assertEquals("MyNewTableName", newContext.getTableName()); - } - - @Test - public void testCreateEncryptionContextTableNameOverrideMap_multipleOverrides() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("OriginalTableName1", "MyNewTableName1"); - tableNameOverrides.put("OriginalTableName2", "MyNewTableName2"); - - - Function overrideOperator = - overrideEncryptionContextTableNameUsingMap(tableNameOverrides); - - EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName1").build(); - - EncryptionContext newContext = overrideOperator.apply(context); - - assertEquals("OriginalTableName1", context.getTableName()); - assertEquals("MyNewTableName1", newContext.getTableName()); - - EncryptionContext context2 = EncryptionContext.builder().tableName("OriginalTableName2").build(); - - EncryptionContext newContext2 = overrideOperator.apply(context2); - - assertEquals("OriginalTableName2", context2.getTableName()); - assertEquals("MyNewTableName2", newContext2.getTableName()); - - } - - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullEncryptionContextTableName() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("DifferentTableName", "MyNewTableName"); - assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().build(), - tableNameOverrides); - } - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullEncryptionContext() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("DifferentTableName", "MyNewTableName"); - assertEncryptionContextUnchangedFromMap(null, - tableNameOverrides); - } - - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullOriginalTableName() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put(null, "MyNewTableName"); - assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("example").build(), - tableNameOverrides); - } - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullNewTableName() { - Map tableNameOverrides = new HashMap<>(); - tableNameOverrides.put("MyOriginalTableName", null); - assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("MyOriginalTableName").build(), - tableNameOverrides); - } - - - @Test - public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullMap() { - assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("MyOriginalTableName").build(), - null); - } - - - private void assertEncryptionContextUnchanged(EncryptionContext encryptionContext, String originalTableName, String newTableName) { - Function encryptionContextTableNameOverride = overrideEncryptionContextTableName(originalTableName, newTableName); - EncryptionContext newEncryptionContext = encryptionContextTableNameOverride.apply(encryptionContext); - assertEquals(encryptionContext, newEncryptionContext); - } - - - private void assertEncryptionContextUnchangedFromMap(EncryptionContext encryptionContext, Map overrideMap) { - Function encryptionContextTableNameOverrideFromMap = overrideEncryptionContextTableNameUsingMap(overrideMap); - EncryptionContext newEncryptionContext = encryptionContextTableNameOverrideFromMap.apply(encryptionContext); - assertEquals(encryptionContext, newEncryptionContext); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java deleted file mode 100644 index e098816275..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; -import static org.testng.AssertJUnit.assertNotNull; -import static org.testng.AssertJUnit.fail; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller.marshall; -import static software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller.unmarshall; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static java.util.Collections.unmodifiableList; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.testng.annotations.Test; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttributeValueBuilder; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -public class AttributeValueMarshallerTest { - @Test(expectedExceptions = IllegalArgumentException.class) - public void testEmpty() { - AttributeValue av = AttributeValue.builder().build(); - marshall(av); - } - - @Test - public void testNumber() { - AttributeValue av = AttributeValue.builder().n("1337").build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testString() { - AttributeValue av = AttributeValue.builder().s("1337").build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testByteBuffer() { - AttributeValue av = AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - // We can't use straight .equals for comparison because Attribute Values represents Sets - // as Lists and so incorrectly does an ordered comparison - - @Test - public void testNumberS() { - AttributeValue av = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1337", "1", "5"))).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testNumberSOrdering() { - AttributeValue av1 = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1337", "1", "5"))).build(); - AttributeValue av2 = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1", "5", "1337"))).build(); - assertAttributesAreEqual(av1, av2); - ByteBuffer buff1 = marshall(av1); - ByteBuffer buff2 = marshall(av2); - assertEquals(buff1, buff2); - } - - @Test - public void testStringS() { - AttributeValue av = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Bob", "Ann", "5"))).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testStringSOrdering() { - AttributeValue av1 = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Bob", "Ann", "5"))).build(); - AttributeValue av2 = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Ann", "Bob", "5"))).build(); - assertAttributesAreEqual(av1, av2); - ByteBuffer buff1 = marshall(av1); - ByteBuffer buff2 = marshall(av2); - assertEquals(buff1, buff2); - } - - @Test - public void testByteBufferS() { - AttributeValue av = AttributeValue.builder().bs(unmodifiableList( - Arrays.asList(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5}), - SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7})))).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testByteBufferSOrdering() { - AttributeValue av1 = AttributeValue.builder().bs(unmodifiableList( - Arrays.asList(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5}), - SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7})))).build(); - AttributeValue av2 = AttributeValue.builder().bs(unmodifiableList( - Arrays.asList(SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7}), - SdkBytes.fromByteArray(new byte[]{0, 1, 2, 3, 4, 5})))).build(); - - assertAttributesAreEqual(av1, av2); - ByteBuffer buff1 = marshall(av1); - ByteBuffer buff2 = marshall(av2); - assertEquals(buff1, buff2); - } - - @Test - public void testBoolTrue() { - AttributeValue av = AttributeValue.builder().bool(Boolean.TRUE).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testBoolFalse() { - AttributeValue av = AttributeValue.builder().bool(Boolean.FALSE).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testNULL() { - AttributeValue av = AttributeValue.builder().nul(Boolean.TRUE).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test(expectedExceptions = NullPointerException.class) - public void testActualNULL() { - unmarshall(marshall(null)); - } - - @Test - public void testEmptyList() { - AttributeValue av = AttributeValue.builder().l(emptyList()).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testListOfString() { - AttributeValue av = - AttributeValue.builder().l(singletonList(AttributeValue.builder().s("StringValue").build())).build(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testList() { - AttributeValue av = AttributeValueBuilder.ofL( - AttributeValueBuilder.ofS("StringValue"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofBool(Boolean.TRUE)); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testListWithNull() { - final AttributeValue av = AttributeValueBuilder.ofL( - AttributeValueBuilder.ofS("StringValue"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofBool(Boolean.TRUE), - null); - - try { - marshall(av); - } catch (NullPointerException e) { - assertThat(e.getMessage(), - startsWith("Encountered null list entry value while marshalling attribute value")); - } - } - - @Test - public void testListDuplicates() { - AttributeValue av = AttributeValueBuilder.ofL( - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofN("1000")); - AttributeValue result = unmarshall(marshall(av)); - assertAttributesAreEqual(av, result); - assertEquals(4, result.l().size()); - } - - @Test - public void testComplexList() { - final List list1 = Arrays.asList( - AttributeValueBuilder.ofS("StringValue"), - AttributeValueBuilder.ofN("1000"), - AttributeValueBuilder.ofBool(Boolean.TRUE)); - final List list22 = Arrays.asList( - AttributeValueBuilder.ofS("AWS"), - AttributeValueBuilder.ofN("-3700"), - AttributeValueBuilder.ofBool(Boolean.FALSE)); - final List list2 = Arrays.asList( - AttributeValueBuilder.ofL(list22), - AttributeValueBuilder.ofNull()); - AttributeValue av = AttributeValueBuilder.ofL( - AttributeValueBuilder.ofS("StringValue1"), - AttributeValueBuilder.ofL(list1), - AttributeValueBuilder.ofN("50"), - AttributeValueBuilder.ofL(list2)); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testEmptyMap() { - Map map = new HashMap<>(); - AttributeValue av = AttributeValueBuilder.ofM(map); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testSimpleMap() { - Map map = new HashMap<>(); - map.put("KeyValue", AttributeValueBuilder.ofS("ValueValue")); - AttributeValue av = AttributeValueBuilder.ofM(map); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - @Test - public void testSimpleMapWithNull() { - final Map map = new HashMap<>(); - map.put("KeyValue", AttributeValueBuilder.ofS("ValueValue")); - map.put("NullKeyValue", null); - - final AttributeValue av = AttributeValueBuilder.ofM(map); - - try { - marshall(av); - fail("NullPointerException should have been thrown"); - } catch (NullPointerException e) { - assertThat(e.getMessage(), startsWith("Encountered null map value for key NullKeyValue while marshalling " - + "attribute value")); - } - } - - @Test - public void testMapOrdering() { - LinkedHashMap m1 = new LinkedHashMap<>(); - LinkedHashMap m2 = new LinkedHashMap<>(); - - m1.put("Value1", AttributeValueBuilder.ofN("1")); - m1.put("Value2", AttributeValueBuilder.ofBool(Boolean.TRUE)); - - m2.put("Value2", AttributeValueBuilder.ofBool(Boolean.TRUE)); - m2.put("Value1", AttributeValueBuilder.ofN("1")); - - AttributeValue av1 = AttributeValueBuilder.ofM(m1); - AttributeValue av2 = AttributeValueBuilder.ofM(m2); - - ByteBuffer buff1 = marshall(av1); - ByteBuffer buff2 = marshall(av2); - assertEquals(buff1, buff2); - assertAttributesAreEqual(av1, unmarshall(buff1)); - assertAttributesAreEqual(av1, unmarshall(buff2)); - assertAttributesAreEqual(av2, unmarshall(buff1)); - assertAttributesAreEqual(av2, unmarshall(buff2)); - } - - @Test - public void testComplexMap() { - AttributeValue av = buildComplexAttributeValue(); - assertAttributesAreEqual(av, unmarshall(marshall(av))); - } - - // This test ensures that an AttributeValue marshalled by an older - // version of this library still unmarshalls correctly. It also - // ensures that old and new marshalling is identical. - @Test - public void testVersioningCompatibility() { - AttributeValue newObject = buildComplexAttributeValue(); - byte[] oldBytes = Base64.getDecoder().decode(COMPLEX_ATTRIBUTE_MARSHALLED); - byte[] newBytes = marshall(newObject).array(); - assertThat(oldBytes, is(newBytes)); - - AttributeValue oldObject = unmarshall(ByteBuffer.wrap(oldBytes)); - assertAttributesAreEqual(oldObject, newObject); - } - - private static final String COMPLEX_ATTRIBUTE_MARSHALLED = "AE0AAAADAHM" + - "AAAAJSW5uZXJMaXN0AEwAAAAGAHMAAAALQ29tcGxleExpc3QAbgAAAAE1AGIAA" + - "AAGAAECAwQFAEwAAAAFAD8BAAAAAABMAAAAAQA/AABNAAAAAwBzAAAABFBpbms" + - "AcwAAAAVGbG95ZABzAAAABFRlc3QAPwEAcwAAAAdWZXJzaW9uAG4AAAABMQAAA" + - "E0AAAADAHMAAAAETGlzdABMAAAABQBuAAAAATUAbgAAAAE0AG4AAAABMwBuAAA" + - "AATIAbgAAAAExAHMAAAADTWFwAE0AAAABAHMAAAAGTmVzdGVkAD8BAHMAAAAEV" + - "HJ1ZQA/AQBzAAAACVNpbmdsZU1hcABNAAAAAQBzAAAAA0ZPTwBzAAAAA0JBUgB" + - "zAAAACVN0cmluZ1NldABTAAAAAwAAAANiYXIAAAADYmF6AAAAA2Zvbw=="; - - private static AttributeValue buildComplexAttributeValue() { - Map floydMap = new HashMap<>(); - floydMap.put("Pink", AttributeValueBuilder.ofS("Floyd")); - floydMap.put("Version", AttributeValueBuilder.ofN("1")); - floydMap.put("Test", AttributeValueBuilder.ofBool(Boolean.TRUE)); - List floydList = Arrays.asList( - AttributeValueBuilder.ofBool(Boolean.TRUE), - AttributeValueBuilder.ofNull(), - AttributeValueBuilder.ofNull(), - AttributeValueBuilder.ofL(AttributeValueBuilder.ofBool(Boolean.FALSE)), - AttributeValueBuilder.ofM(floydMap) - ); - - List nestedList = Arrays.asList( - AttributeValueBuilder.ofN("5"), - AttributeValueBuilder.ofN("4"), - AttributeValueBuilder.ofN("3"), - AttributeValueBuilder.ofN("2"), - AttributeValueBuilder.ofN("1") - ); - Map nestedMap = new HashMap<>(); - nestedMap.put("True", AttributeValueBuilder.ofBool(Boolean.TRUE)); - nestedMap.put("List", AttributeValueBuilder.ofL(nestedList)); - nestedMap.put("Map", AttributeValueBuilder.ofM( - Collections.singletonMap("Nested", - AttributeValueBuilder.ofBool(Boolean.TRUE)))); - - List innerList = Arrays.asList( - AttributeValueBuilder.ofS("ComplexList"), - AttributeValueBuilder.ofN("5"), - AttributeValueBuilder.ofB(new byte[] {0, 1, 2, 3, 4, 5}), - AttributeValueBuilder.ofL(floydList), - AttributeValueBuilder.ofNull(), - AttributeValueBuilder.ofM(nestedMap) - ); - - Map result = new HashMap<>(); - result.put("SingleMap", AttributeValueBuilder.ofM( - Collections.singletonMap("FOO", AttributeValueBuilder.ofS("BAR")))); - result.put("InnerList", AttributeValueBuilder.ofL(innerList)); - result.put("StringSet", AttributeValueBuilder.ofSS("foo", "bar", "baz")); - return AttributeValue.builder().m(Collections.unmodifiableMap(result)).build(); - } - - private void assertAttributesAreEqual(AttributeValue o1, AttributeValue o2) { - assertEquals(o1.b(), o2.b()); - assertSetsEqual(o1.bs(), o2.bs()); - assertEquals(o1.n(), o2.n()); - assertSetsEqual(o1.ns(), o2.ns()); - assertEquals(o1.s(), o2.s()); - assertSetsEqual(o1.ss(), o2.ss()); - assertEquals(o1.bool(), o2.bool()); - assertEquals(o1.nul(), o2.nul()); - - if (o1.l() != null) { - assertNotNull(o2.l()); - final List l1 = o1.l(); - final List l2 = o2.l(); - assertEquals(l1.size(), l2.size()); - for (int x = 0; x < l1.size(); ++x) { - assertAttributesAreEqual(l1.get(x), l2.get(x)); - } - } - - if (o1.m() != null) { - assertNotNull(o2.m()); - final Map m1 = o1.m(); - final Map m2 = o2.m(); - assertEquals(m1.size(), m2.size()); - for (Map.Entry entry : m1.entrySet()) { - assertAttributesAreEqual(entry.getValue(), m2.get(entry.getKey())); - } - } - } - - private void assertSetsEqual(Collection c1, Collection c2) { - assertFalse(c1 == null ^ c2 == null); - if (c1 != null) { - Set s1 = new HashSet<>(c1); - Set s2 = new HashSet<>(c2); - assertEquals(s1, s2); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java deleted file mode 100644 index 4ec9c03ae4..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java +++ /dev/null @@ -1,93 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.quicktheories.QuickTheory.qt; -import static org.quicktheories.generators.Generate.byteArrays; -import static org.quicktheories.generators.Generate.bytes; -import static org.quicktheories.generators.SourceDSL.integers; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import org.apache.commons.lang3.StringUtils; -import org.testng.annotations.Test; - -public class Base64Tests { - @Test - public void testBase64EncodeEquivalence() { - qt().forAll( - byteArrays( - integers().between(0, 1000000), bytes(Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0))) - .check( - (a) -> { - // Base64 encode using both implementations and check for equality of output - // in case one version produces different output - String sdkV1Base64 = com.amazonaws.util.Base64.encodeAsString(a); - String encryptionClientBase64 = Base64.encodeToString(a); - return StringUtils.equals(sdkV1Base64, encryptionClientBase64); - }); - } - - @Test - public void testBase64DecodeEquivalence() { - qt().forAll( - byteArrays( - integers().between(0, 10000), bytes(Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0))) - .as((b) -> java.util.Base64.getMimeEncoder().encodeToString(b)) - .check( - (s) -> { - // Check for equality using the MimeEncoder, which inserts newlines - // The encryptionClient's decoder is expected to ignore them - byte[] sdkV1Bytes = com.amazonaws.util.Base64.decode(s); - byte[] encryptionClientBase64 = Base64.decode(s); - return Arrays.equals(sdkV1Bytes, encryptionClientBase64); - }); - } - - @Test - public void testNullDecodeBehavior() { - byte[] decoded = Base64.decode(null); - assertThat(decoded, equalTo(null)); - } - - @Test - public void testNullDecodeBehaviorSdk1() { - byte[] decoded = com.amazonaws.util.Base64.decode((String) null); - assertThat(decoded, equalTo(null)); - - byte[] decoded2 = com.amazonaws.util.Base64.decode((byte[]) null); - assertThat(decoded2, equalTo(null)); - } - - @Test - public void testBase64PaddingBehavior() { - String testInput = "another one bites the dust"; - String expectedEncoding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q="; - assertThat( - Base64.encodeToString(testInput.getBytes(StandardCharsets.UTF_8)), - equalTo(expectedEncoding)); - - String encodingWithoutPadding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q"; - assertThat(Base64.decode(encodingWithoutPadding), equalTo(testInput.getBytes())); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testBase64PaddingBehaviorSdk1() { - String testInput = "another one bites the dust"; - String encodingWithoutPadding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q"; - com.amazonaws.util.Base64.decode(encodingWithoutPadding); - } - - @Test - public void rfc4648TestVectors() { - assertThat(Base64.encodeToString("".getBytes(StandardCharsets.UTF_8)), equalTo("")); - assertThat(Base64.encodeToString("f".getBytes(StandardCharsets.UTF_8)), equalTo("Zg==")); - assertThat(Base64.encodeToString("fo".getBytes(StandardCharsets.UTF_8)), equalTo("Zm8=")); - assertThat(Base64.encodeToString("foo".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9v")); - assertThat(Base64.encodeToString("foob".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYg==")); - assertThat( - Base64.encodeToString("fooba".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYmE=")); - assertThat( - Base64.encodeToString("foobar".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYmFy")); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java deleted file mode 100644 index 71b90c195e..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertFalse; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import org.testng.annotations.Test; - -public class ByteBufferInputStreamTest { - - @Test - public void testRead() throws IOException { - ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); - for (int x = 0; x < 10; ++x) { - assertEquals(10 - x, bis.available()); - assertEquals(x, bis.read()); - } - assertEquals(0, bis.available()); - bis.close(); - } - - @Test - public void testReadByteArray() throws IOException { - ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); - assertEquals(10, bis.available()); - - byte[] buff = new byte[4]; - - int len = bis.read(buff); - assertEquals(4, len); - assertEquals(6, bis.available()); - assertThat(buff, is(new byte[] {0, 1, 2, 3})); - - len = bis.read(buff); - assertEquals(4, len); - assertEquals(2, bis.available()); - assertThat(buff, is(new byte[] {4, 5, 6, 7})); - - len = bis.read(buff); - assertEquals(2, len); - assertEquals(0, bis.available()); - assertThat(buff, is(new byte[] {8, 9, 6, 7})); - bis.close(); - } - - @Test - public void testSkip() throws IOException { - ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{(byte) 0xFA, 15, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); - assertEquals(13, bis.available()); - assertEquals(0xFA, bis.read()); - assertEquals(12, bis.available()); - bis.skip(2); - assertEquals(10, bis.available()); - for (int x = 0; x < 10; ++x) { - assertEquals(x, bis.read()); - } - assertEquals(0, bis.available()); - assertEquals(-1, bis.read()); - bis.close(); - } - - @Test - public void testMarkSupported() throws IOException { - try (ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.allocate(0))) { - assertFalse(bis.markSupported()); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java deleted file mode 100644 index 7fcb5b89ab..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java +++ /dev/null @@ -1,244 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import edu.umd.cs.mtc.MultithreadedTestCase; -import edu.umd.cs.mtc.TestFramework; -import java.util.concurrent.TimeUnit; -import org.testng.annotations.Test; - -/* Test specific thread interleavings with behaviors we care about in the - * TTLCache. - */ -public class ConcurrentTTLCacheTest { - - private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); - private static final long ttlInMillis = 1000; - - @Test - public void testGracePeriodCase() throws Throwable { - TestFramework.runOnce(new GracePeriodCase()); - } - - @Test - public void testExpiredCase() throws Throwable { - TestFramework.runOnce(new ExpiredCase()); - } - - @Test - public void testNewEntryCase() throws Throwable { - TestFramework.runOnce(new NewEntryCase()); - } - - @Test - public void testPutLoadCase() throws Throwable { - TestFramework.runOnce(new PutLoadCase()); - } - - // Ensure the loader is only called once if two threads attempt to load during the grace period - class GracePeriodCase extends MultithreadedTestCase { - TTLCache cache; - TTLCache.EntryLoader loader; - MsClock clock = mock(MsClock.class); - - @Override - public void initialize() { - loader = - spy( - new TTLCache.EntryLoader() { - @Override - public String load(String entryKey) { - // Wait until thread2 finishes to complete load - waitForTick(2); - return "loadedValue"; - } - }); - when(clock.timestampNano()).thenReturn((long) 0); - cache = new TTLCache<>(3, ttlInMillis, loader); - cache.clock = clock; - - // Put an initial value into the cache at time 0 - cache.put("k1", "v1"); - } - - // The thread that first calls load in the grace period and acquires the lock - public void thread1() { - when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); - String loadedValue = cache.load("k1"); - assertTick(2); - // Expect to get back the value calculated from load - assertEquals("loadedValue", loadedValue); - } - - // The thread that calls load in the grace period after the lock has been acquired - public void thread2() { - // Wait until the first thread acquires the lock and starts load - waitForTick(1); - when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); - String loadedValue = cache.load("k1"); - // Expect to get back the original value in the cache - assertEquals("v1", loadedValue); - } - - @Override - public void finish() { - // Ensure the loader was only called once - verify(loader, times(1)).load("k1"); - } - } - - // Ensure the loader is only called once if two threads attempt to load an expired entry. - class ExpiredCase extends MultithreadedTestCase { - TTLCache cache; - TTLCache.EntryLoader loader; - MsClock clock = mock(MsClock.class); - - @Override - public void initialize() { - loader = - spy( - new TTLCache.EntryLoader() { - @Override - public String load(String entryKey) { - // Wait until thread2 is waiting for the lock to complete load - waitForTick(2); - return "loadedValue"; - } - }); - when(clock.timestampNano()).thenReturn((long) 0); - cache = new TTLCache<>(3, ttlInMillis, loader); - cache.clock = clock; - - // Put an initial value into the cache at time 0 - cache.put("k1", "v1"); - } - - // The thread that first calls load after expiration - public void thread1() { - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - String loadedValue = cache.load("k1"); - assertTick(2); - // Expect to get back the value calculated from load - assertEquals("loadedValue", loadedValue); - } - - // The thread that calls load after expiration, - // after the first thread calls load, but before - // the new value is put into the cache. - public void thread2() { - // Wait until the first thread acquires the lock and starts load - waitForTick(1); - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - String loadedValue = cache.load("k1"); - // Expect to get back the newly loaded value - assertEquals("loadedValue", loadedValue); - // assert that this thread only finishes once the first thread's load does - assertTick(2); - } - - @Override - public void finish() { - // Ensure the loader was only called once - verify(loader, times(1)).load("k1"); - } - } - - // Ensure the loader is only called once if two threads attempt to load the same new entry. - class NewEntryCase extends MultithreadedTestCase { - TTLCache cache; - TTLCache.EntryLoader loader; - MsClock clock = mock(MsClock.class); - - @Override - public void initialize() { - loader = - spy( - new TTLCache.EntryLoader() { - @Override - public String load(String entryKey) { - // Wait until thread2 is blocked to complete load - waitForTick(2); - return "loadedValue"; - } - }); - when(clock.timestampNano()).thenReturn((long) 0); - cache = new TTLCache<>(3, ttlInMillis, loader); - cache.clock = clock; - } - - // The thread that first calls load - public void thread1() { - String loadedValue = cache.load("k1"); - assertTick(2); - // Expect to get back the value calculated from load - assertEquals("loadedValue", loadedValue); - } - - // The thread that calls load after the first thread calls load, - // but before the new value is put into the cache. - public void thread2() { - // Wait until the first thread acquires the lock and starts load - waitForTick(1); - String loadedValue = cache.load("k1"); - // Expect to get back the newly loaded value - assertEquals("loadedValue", loadedValue); - // assert that this thread only finishes once the first thread's load does - assertTick(2); - } - - @Override - public void finish() { - // Ensure the loader was only called once - verify(loader, times(1)).load("k1"); - } - } - - // Ensure the loader blocks put on load/put of the same new entry - class PutLoadCase extends MultithreadedTestCase { - TTLCache cache; - TTLCache.EntryLoader loader; - MsClock clock = mock(MsClock.class); - - @Override - public void initialize() { - loader = - spy( - new TTLCache.EntryLoader() { - @Override - public String load(String entryKey) { - // Wait until the put blocks to complete load - waitForTick(2); - return "loadedValue"; - } - }); - when(clock.timestampNano()).thenReturn((long) 0); - cache = new TTLCache<>(3, ttlInMillis, loader); - cache.clock = clock; - } - - // The thread that first calls load - public void thread1() { - String loadedValue = cache.load("k1"); - // Expect to get back the value calculated from load - assertEquals("loadedValue", loadedValue); - verify(loader, times(1)).load("k1"); - } - - // The thread that calls put during the first thread's load - public void thread2() { - // Wait until the first thread is loading - waitForTick(1); - String previousValue = cache.put("k1", "v1"); - // Expect to get back the value loaded into the cache by thread1 - assertEquals("loadedValue", previousValue); - // assert that this thread was blocked by the first thread - assertTick(2); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java deleted file mode 100644 index b9fdcb1d09..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.testng.AssertJUnit.assertArrayEquals; - -import org.testng.annotations.Test; - -public class HkdfTests { - private static final testCase[] testCases = - new testCase[] { - new testCase( - "HmacSHA256", - fromCHex( - "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" - + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), - fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), - fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), - fromHex( - "3CB25F25FAACD57A90434F64D0362F2A2D2D0A90CF1A5A4C5DB02D56ECC4C5BF34007208D5B887185865")), - new testCase( - "HmacSHA256", - fromCHex( - "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" - + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" - + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" - + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" - + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" - + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), - fromCHex( - "\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d" - + "\\x6e\\x6f\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b" - + "\\x7c\\x7d\\x7e\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" - + "\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" - + "\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5" - + "\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf"), - fromCHex( - "\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd" - + "\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb" - + "\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9" - + "\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7" - + "\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5" - + "\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff"), - fromHex( - "B11E398DC80327A1C8E7F78C596A4934" - + "4F012EDA2D4EFAD8A050CC4C19AFA97C" - + "59045A99CAC7827271CB41C65E590E09" - + "DA3275600C2F09B8367793A9ACA3DB71" - + "CC30C58179EC3E87C14C01D5C1F3434F" - + "1D87")), - new testCase( - "HmacSHA256", - fromCHex( - "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" - + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), - new byte[0], - new byte[0], - fromHex( - "8DA4E775A563C18F715F802A063C5A31" - + "B8A11F5C5EE1879EC3454E5F3C738D2D" - + "9D201395FAA4B61A96C8")), - new testCase( - "HmacSHA1", - fromCHex("\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), - fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), - fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), - fromHex( - "085A01EA1B10F36933068B56EFA5AD81" - + "A4F14B822F5B091568A9CDD4F155FDA2" - + "C22E422478D305F3F896")), - new testCase( - "HmacSHA1", - fromCHex( - "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" - + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" - + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" - + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" - + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" - + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), - fromCHex( - "\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6A\\x6B\\x6C\\x6D" - + "\\x6E\\x6F\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7A\\x7B" - + "\\x7C\\x7D\\x7E\\x7F\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" - + "\\x8A\\x8B\\x8C\\x8D\\x8E\\x8F\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" - + "\\x98\\x99\\x9A\\x9B\\x9C\\x9D\\x9E\\x9F\\xA0\\xA1\\xA2\\xA3\\xA4\\xA5" - + "\\xA6\\xA7\\xA8\\xA9\\xAA\\xAB\\xAC\\xAD\\xAE\\xAF"), - fromCHex( - "\\xB0\\xB1\\xB2\\xB3\\xB4\\xB5\\xB6\\xB7\\xB8\\xB9\\xBA\\xBB\\xBC\\xBD" - + "\\xBE\\xBF\\xC0\\xC1\\xC2\\xC3\\xC4\\xC5\\xC6\\xC7\\xC8\\xC9\\xCA\\xCB" - + "\\xCC\\xCD\\xCE\\xCF\\xD0\\xD1\\xD2\\xD3\\xD4\\xD5\\xD6\\xD7\\xD8\\xD9" - + "\\xDA\\xDB\\xDC\\xDD\\xDE\\xDF\\xE0\\xE1\\xE2\\xE3\\xE4\\xE5\\xE6\\xE7" - + "\\xE8\\xE9\\xEA\\xEB\\xEC\\xED\\xEE\\xEF\\xF0\\xF1\\xF2\\xF3\\xF4\\xF5" - + "\\xF6\\xF7\\xF8\\xF9\\xFA\\xFB\\xFC\\xFD\\xFE\\xFF"), - fromHex( - "0BD770A74D1160F7C9F12CD5912A06EB" - + "FF6ADCAE899D92191FE4305673BA2FFE" - + "8FA3F1A4E5AD79F3F334B3B202B2173C" - + "486EA37CE3D397ED034C7F9DFEB15C5E" - + "927336D0441F4C4300E2CFF0D0900B52D3B4")), - new testCase( - "HmacSHA1", - fromCHex( - "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" - + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), - new byte[0], - new byte[0], - fromHex("0AC1AF7002B3D761D1E55298DA9D0506" + "B9AE52057220A306E07B6B87E8DF21D0")), - new testCase( - "HmacSHA1", - fromCHex( - "\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c" - + "\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c"), - null, - new byte[0], - fromHex( - "2C91117204D745F3500D636A62F64F0A" - + "B3BAE548AA53D423B0D1F27EBBA6F5E5" - + "673A081D70CCE7ACFC48")) - }; - - @Test - public void rfc5869Tests() throws Exception { - for (int x = 0; x < testCases.length; x++) { - testCase trial = testCases[x]; - System.out.println("Test case A." + (x + 1)); - Hkdf kdf = Hkdf.getInstance(trial.algo); - kdf.init(trial.ikm, trial.salt); - byte[] result = kdf.deriveKey(trial.info, trial.expected.length); - assertArrayEquals("Trial A." + x, trial.expected, result); - } - } - - @Test - public void nullTests() throws Exception { - testCase trial = testCases[0]; - Hkdf kdf = Hkdf.getInstance(trial.algo); - kdf.init(trial.ikm, trial.salt); - // Just ensuring no exceptions are thrown - kdf.deriveKey((String) null, 16); - kdf.deriveKey((byte[]) null, 16); - } - - @Test - public void defaultSalt() throws Exception { - // Tests all the different ways to get the default salt - - testCase trial = testCases[0]; - Hkdf kdf1 = Hkdf.getInstance(trial.algo); - kdf1.init(trial.ikm, null); - Hkdf kdf2 = Hkdf.getInstance(trial.algo); - kdf2.init(trial.ikm, new byte[0]); - Hkdf kdf3 = Hkdf.getInstance(trial.algo); - kdf3.init(trial.ikm); - Hkdf kdf4 = Hkdf.getInstance(trial.algo); - kdf4.init(trial.ikm, new byte[32]); - - byte[] key1 = kdf1.deriveKey("Test", 16); - byte[] key2 = kdf2.deriveKey("Test", 16); - byte[] key3 = kdf3.deriveKey("Test", 16); - byte[] key4 = kdf4.deriveKey("Test", 16); - - assertArrayEquals(key1, key2); - assertArrayEquals(key1, key3); - assertArrayEquals(key1, key4); - } - - private static byte[] fromHex(String data) { - byte[] result = new byte[data.length() / 2]; - for (int x = 0; x < result.length; x++) { - result[x] = (byte) Integer.parseInt(data.substring(2 * x, 2 * x + 2), 16); - } - return result; - } - - private static byte[] fromCHex(String data) { - byte[] result = new byte[data.length() / 4]; - for (int x = 0; x < result.length; x++) { - result[x] = (byte) Integer.parseInt(data.substring(4 * x + 2, 4 * x + 4), 16); - } - return result; - } - - private static class testCase { - public final String algo; - public final byte[] ikm; - public final byte[] salt; - public final byte[] info; - public final byte[] expected; - - public testCase(String algo, byte[] ikm, byte[] salt, byte[] info, byte[] expected) { - super(); - this.algo = algo; - this.ikm = ikm; - this.salt = salt; - this.info = info; - this.expected = expected; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java deleted file mode 100644 index 8f56d35b96..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import org.testng.annotations.Test; - -public class LRUCacheTest { - @Test - public void test() { - final LRUCache cache = new LRUCache(3); - assertEquals(0, cache.size()); - assertEquals(3, cache.getMaxSize()); - cache.add("k1", "v1"); - assertTrue(cache.size() == 1); - cache.add("k1", "v11"); - assertTrue(cache.size() == 1); - cache.add("k2", "v2"); - assertTrue(cache.size() == 2); - cache.add("k3", "v3"); - assertTrue(cache.size() == 3); - assertEquals("v11", cache.get("k1")); - assertEquals("v2", cache.get("k2")); - assertEquals("v3", cache.get("k3")); - cache.add("k4", "v4"); - assertTrue(cache.size() == 3); - assertNull(cache.get("k1")); - assertEquals("v4", cache.get("k4")); - assertEquals("v2", cache.get("k2")); - assertEquals("v3", cache.get("k3")); - assertTrue(cache.size() == 3); - cache.add("k5", "v5"); - assertNull(cache.get("k4")); - assertEquals("v5", cache.get("k5")); - assertEquals("v2", cache.get("k2")); - assertEquals("v3", cache.get("k3")); - cache.clear(); - assertEquals(0, cache.size()); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testZeroSize() { - new LRUCache(0); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testIllegalArgument() { - new LRUCache(-1); - } - - @Test - public void testSingleEntry() { - final LRUCache cache = new LRUCache(1); - assertTrue(cache.size() == 0); - cache.add("k1", "v1"); - assertTrue(cache.size() == 1); - cache.add("k1", "v11"); - assertTrue(cache.size() == 1); - assertEquals("v11", cache.get("k1")); - - cache.add("k2", "v2"); - assertTrue(cache.size() == 1); - assertEquals("v2", cache.get("k2")); - assertNull(cache.get("k1")); - - cache.add("k3", "v3"); - assertTrue(cache.size() == 1); - assertEquals("v3", cache.get("k3")); - assertNull(cache.get("k2")); - } - -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java deleted file mode 100644 index 55e246f391..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNull; -import static org.testng.AssertJUnit.assertTrue; - -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import org.testng.annotations.Test; - -public class TTLCacheTest { - - private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testInvalidSize() { - final TTLCache cache = new TTLCache(0, 1000, mock(TTLCache.EntryLoader.class)); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void testInvalidTTL() { - final TTLCache cache = new TTLCache(3, 0, mock(TTLCache.EntryLoader.class)); - } - - - @Test(expectedExceptions = NullPointerException.class) - public void testNullLoader() { - final TTLCache cache = new TTLCache(3, 1000, null); - } - - @Test - public void testConstructor() { - final TTLCache cache = - new TTLCache(1000, 1000, mock(TTLCache.EntryLoader.class)); - assertEquals(0, cache.size()); - assertEquals(1000, cache.getMaxSize()); - } - - @Test - public void testLoadPastMaxSize() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 1; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - cache.load("k1"); - verify(loader, times(1)).load("k1"); - assertTrue(cache.size() == 1); - - String result = cache.load("k2"); - verify(loader, times(1)).load("k2"); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - - // to verify result is in the cache, load one more time - // and expect the loader to not be called - String cachedValue = cache.load("k2"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, cachedValue); - } - - @Test - public void testLoadNoExistingEntry() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - String result = cache.load("k1"); - verify(loader, times(1)).load("k1"); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - - // to verify result is in the cache, load one more time - // and expect the loader to not be called - String cachedValue = cache.load("k1"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, cachedValue); - } - - @Test - public void testLoadNotExpired() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // when first creating the entry, time is 0 - when(clock.timestampNano()).thenReturn((long) 0); - cache.load("k1"); - assertTrue(cache.size() == 1); - verify(loader, times(1)).load("k1"); - - // on load, time is within TTL - when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis)); - String result = cache.load("k1"); - - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - } - - @Test - public void testLoadInGrace() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // when first creating the entry, time is zero - when(clock.timestampNano()).thenReturn((long) 0); - cache.load("k1"); - assertTrue(cache.size() == 1); - verify(loader, times(1)).load("k1"); - - // on load, time is past TTL but within the grace period - when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); - String result = cache.load("k1"); - - // Because this is tested in a single thread, - // this is expected to obtain the lock and load the new value - verify(loader, times(2)).load("k1"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - } - - @Test - public void testLoadExpired() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // when first creating the entry, time is zero - when(clock.timestampNano()).thenReturn((long) 0); - cache.load("k1"); - assertTrue(cache.size() == 1); - verify(loader, times(1)).load("k1"); - - // on load, time is past TTL and grace period - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - String result = cache.load("k1"); - - verify(loader, times(2)).load("k1"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(loadedValue, result); - } - - @Test - public void testLoadExpiredEviction() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())) - .thenReturn(loadedValue) - .thenThrow(new IllegalStateException("This loader is mocked to throw a failure.")); - MsClock clock = mock(MsClock.class); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // when first creating the entry, time is zero - when(clock.timestampNano()).thenReturn((long) 0); - cache.load("k1"); - verify(loader, times(1)).load("k1"); - assertTrue(cache.size() == 1); - - // on load, time is past TTL and grace period - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - assertThrows(IllegalStateException.class, () -> cache.load("k1")); - - verify(loader, times(2)).load("k1"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 0); - } - - @Test - public void testLoadWithFunction() { - final String loadedValue = "loaded value"; - final String functionValue = "function value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - final Function function = spy(Function.class); - when(function.apply(any())).thenReturn(functionValue); - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())) - .thenReturn(loadedValue) - .thenThrow(new IllegalStateException("This loader is mocked to throw a failure.")); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - String result = cache.load("k1", function); - verify(function, times(1)).apply("k1"); - assertTrue(cache.size() == 1); - assertEquals(functionValue, result); - - // to verify result is in the cache, load one more time - // and expect the loader to not be called - String cachedValue = cache.load("k1"); - verifyNoMoreInteractions(function); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(functionValue, cachedValue); - } - - @Test - public void testClear() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - - assertTrue(cache.size() == 0); - cache.load("k1"); - cache.load("k2"); - assertTrue(cache.size() == 2); - - cache.clear(); - assertTrue(cache.size() == 0); - } - - @Test - public void testPut() { - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - String oldValue = cache.put("k1", "v1"); - assertNull(oldValue); - assertTrue(cache.size() == 1); - - String oldValue2 = cache.put("k1", "v11"); - assertEquals("v1", oldValue2); - assertTrue(cache.size() == 1); - } - - @Test - public void testExpiredPut() { - final long ttlInMillis = 1000; - final int maxSize = 3; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - // First put is at time 0 - String oldValue = cache.put("k1", "v1"); - assertNull(oldValue); - assertTrue(cache.size() == 1); - - // Second put is at time past TTL and grace period - when(clock.timestampNano()) - .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); - String oldValue2 = cache.put("k1", "v11"); - assertNull(oldValue2); - assertTrue(cache.size() == 1); - } - - @Test - public void testPutPastMaxSize() { - final String loadedValue = "loaded value"; - final long ttlInMillis = 1000; - final int maxSize = 1; - TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); - when(loader.load(any())).thenReturn(loadedValue); - MsClock clock = mock(MsClock.class); - when(clock.timestampNano()).thenReturn((long) 0); - - final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); - cache.clock = clock; - - assertEquals(0, cache.size()); - assertEquals(maxSize, cache.getMaxSize()); - - cache.put("k1", "v1"); - assertTrue(cache.size() == 1); - - cache.put("k2", "v2"); - assertTrue(cache.size() == 1); - - // to verify put value is in the cache, load - // and expect the loader to not be called - String cachedValue = cache.load("k2"); - verifyNoMoreInteractions(loader); - assertTrue(cache.size() == 1); - assertEquals(cachedValue, "v2"); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java deleted file mode 100644 index 122364e6db..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -public class AttrMatcher extends BaseMatcher> { - private final Map expected; - private final boolean invert; - - public static AttrMatcher invert(Map expected) { - return new AttrMatcher(expected, true); - } - - public static AttrMatcher match(Map expected) { - return new AttrMatcher(expected, false); - } - - public AttrMatcher(Map expected, boolean invert) { - this.expected = expected; - this.invert = invert; - } - - @Override - public boolean matches(Object item) { - @SuppressWarnings("unchecked") - Map actual = (Map)item; - if (!expected.keySet().equals(actual.keySet())) { - return invert; - } - for (String key: expected.keySet()) { - AttributeValue e = expected.get(key); - AttributeValue a = actual.get(key); - if (!attrEquals(a, e)) { - return invert; - } - } - return !invert; - } - - public static boolean attrEquals(AttributeValue e, AttributeValue a) { - if (!isEqual(e.b(), a.b()) || - !isEqual(e.bool(), a.bool()) || - !isSetEqual(e.bs(), a.bs()) || - !isEqual(e.n(), a.n()) || - !isSetEqual(e.ns(), a.ns()) || - !isEqual(e.nul(), a.nul()) || - !isEqual(e.s(), a.s()) || - !isSetEqual(e.ss(), a.ss())) { - return false; - } - // Recursive types need special handling - if (e.m() == null ^ a.m() == null) { - return false; - } else if (e.m() != null) { - if (!e.m().keySet().equals(a.m().keySet())) { - return false; - } - for (final String key : e.m().keySet()) { - if (!attrEquals(e.m().get(key), a.m().get(key))) { - return false; - } - } - } - if (e.l() == null ^ a.l() == null) { - return false; - } else if (e.l() != null) { - if (e.l().size() != a.l().size()) { - return false; - } - for (int x = 0; x < e.l().size(); x++) { - if (!attrEquals(e.l().get(x), a.l().get(x))) { - return false; - } - } - } - return true; - } - - @Override - public void describeTo(Description description) { } - - private static boolean isEqual(Object o1, Object o2) { - if(o1 == null ^ o2 == null) { - return false; - } - if (o1 == o2) - return true; - return o1.equals(o2); - } - - private static boolean isSetEqual(Collection c1, Collection c2) { - if(c1 == null ^ c2 == null) { - return false; - } - if (c1 != null) { - Set s1 = new HashSet(c1); - Set s2 = new HashSet(c2); - if(!s1.equals(s2)) { - return false; - } - } - return true; - } -} \ No newline at end of file diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java deleted file mode 100644 index 3ff7e4dff8..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java +++ /dev/null @@ -1,67 +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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.util.List; -import java.util.Map; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - -/** - * Static helper methods to construct standard AttributeValues in a more compact way than specifying the full builder - * chain. - */ -public final class AttributeValueBuilder { - private AttributeValueBuilder() { - // Static helper class - } - - public static AttributeValue ofS(String value) { - return AttributeValue.builder().s(value).build(); - } - - public static AttributeValue ofN(String value) { - return AttributeValue.builder().n(value).build(); - } - - public static AttributeValue ofB(byte [] value) { - return AttributeValue.builder().b(SdkBytes.fromByteArray(value)).build(); - } - - public static AttributeValue ofBool(Boolean value) { - return AttributeValue.builder().bool(value).build(); - } - - public static AttributeValue ofNull() { - return AttributeValue.builder().nul(true).build(); - } - - public static AttributeValue ofL(List values) { - return AttributeValue.builder().l(values).build(); - } - - public static AttributeValue ofL(AttributeValue ...values) { - return AttributeValue.builder().l(values).build(); - } - - public static AttributeValue ofM(Map valueMap) { - return AttributeValue.builder().m(valueMap).build(); - } - - public static AttributeValue ofSS(String ...values) { - return AttributeValue.builder().ss(values).build(); - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java deleted file mode 100644 index 42e479ba70..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java +++ /dev/null @@ -1,58 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -public class AttributeValueDeserializer extends JsonDeserializer { - @Override - public AttributeValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode attribute = jp.getCodec().readTree(jp); - - for (Iterator> iter = attribute.fields(); iter.hasNext(); ) { - Map.Entry rawAttribute = iter.next(); - // If there is more than one entry in this map, there is an error with our test data - if (iter.hasNext()) { - throw new IllegalStateException("Attribute value JSON has more than one value mapped."); - } - String typeString = rawAttribute.getKey(); - JsonNode value = rawAttribute.getValue(); - switch (typeString) { - case "S": - return AttributeValue.builder().s(value.asText()).build(); - case "B": - SdkBytes b = SdkBytes.fromByteArray(java.util.Base64.getDecoder().decode(value.asText())); - return AttributeValue.builder().b(b).build(); - case "N": - return AttributeValue.builder().n(value.asText()).build(); - case "SS": - final Set stringSet = - objectMapper.readValue( - objectMapper.treeAsTokens(value), new TypeReference>() {}); - return AttributeValue.builder().ss(stringSet).build(); - case "NS": - final Set numSet = - objectMapper.readValue( - objectMapper.treeAsTokens(value), new TypeReference>() {}); - return AttributeValue.builder().ns(numSet).build(); - default: - throw new IllegalStateException( - "DDB JSON type " - + typeString - + " not implemented for test attribute value deserialization."); - } - } - return null; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java deleted file mode 100644 index 0dbea38209..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java +++ /dev/null @@ -1,101 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; - -import java.math.BigDecimal; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -public class AttributeValueMatcher extends BaseMatcher { - private final AttributeValue expected; - private final boolean invert; - - public static AttributeValueMatcher invert(AttributeValue expected) { - return new AttributeValueMatcher(expected, true); - } - - public static AttributeValueMatcher match(AttributeValue expected) { - return new AttributeValueMatcher(expected, false); - } - - public AttributeValueMatcher(AttributeValue expected, boolean invert) { - this.expected = expected; - this.invert = invert; - } - - @Override - public boolean matches(Object item) { - AttributeValue other = (AttributeValue) item; - return invert ^ attrEquals(expected, other); - } - - @Override - public void describeTo(Description description) {} - - public static boolean attrEquals(AttributeValue e, AttributeValue a) { - if (!isEqual(e.b(), a.b()) - || !isNumberEqual(e.n(), a.n()) - || !isEqual(e.s(), a.s()) - || !isEqual(e.bs(), a.bs()) - || !isNumberEqual(e.ns(), a.ns()) - || !isEqual(e.ss(), a.ss())) { - return false; - } - return true; - } - - private static boolean isNumberEqual(String o1, String o2) { - if (o1 == null ^ o2 == null) { - return false; - } - if (o1 == o2) return true; - BigDecimal d1 = new BigDecimal(o1); - BigDecimal d2 = new BigDecimal(o2); - return d1.equals(d2); - } - - private static boolean isEqual(Object o1, Object o2) { - if (o1 == null ^ o2 == null) { - return false; - } - if (o1 == o2) return true; - return o1.equals(o2); - } - - private static boolean isNumberEqual(Collection c1, Collection c2) { - if (c1 == null ^ c2 == null) { - return false; - } - if (c1 != null) { - Set s1 = new HashSet(); - Set s2 = new HashSet(); - for (String s : c1) { - s1.add(new BigDecimal(s)); - } - for (String s : c2) { - s2.add(new BigDecimal(s)); - } - if (!s1.equals(s2)) { - return false; - } - } - return true; - } - - private static boolean isEqual(Collection c1, Collection c2) { - if (c1 == null ^ c2 == null) { - return false; - } - if (c1 != null) { - Set s1 = new HashSet(c1); - Set s2 = new HashSet(c2); - if (!s1.equals(s2)) { - return false; - } - } - return true; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java deleted file mode 100644 index 95abc5471d..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java +++ /dev/null @@ -1,48 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import com.amazonaws.util.Base64; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.nio.ByteBuffer; - -public class AttributeValueSerializer extends JsonSerializer { - @Override - public void serialize(AttributeValue.Builder value, JsonGenerator jgen, SerializerProvider provider) - throws IOException { - if (value != null) { - jgen.writeStartObject(); - if (value.build().s() != null) { - jgen.writeStringField("S", value.build().s()); - } else if (value.build().b() != null) { - ByteBuffer valueBytes = value.build().b().asByteBuffer(); - byte[] arr = new byte[valueBytes.remaining()]; - valueBytes.get(arr); - jgen.writeStringField("B", Base64.encodeAsString(arr)); - } else if (value.build().n() != null) { - jgen.writeStringField("N", value.build().n()); - } else if (value.build().ss() != null) { - jgen.writeFieldName("SS"); - jgen.writeStartArray(); - for (String s : value.build().ss()) { - jgen.writeString(s); - } - jgen.writeEndArray(); - } else if (value.build().ns() != null) { - jgen.writeFieldName("NS"); - jgen.writeStartArray(); - for (String num : value.build().ns()) { - jgen.writeString(num); - } - jgen.writeEndArray(); - } else { - throw new IllegalStateException( - "AttributeValue has no value or type not implemented for serialization."); - } - jgen.writeEndObject(); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java deleted file mode 100644 index 75cc574a1c..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java +++ /dev/null @@ -1,47 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import java.util.Map; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; - -public class DdbRecordMatcher extends BaseMatcher>{ - private final Map expected; - private final boolean invert; - - public static DdbRecordMatcher invert(Map expected) { - return new DdbRecordMatcher(expected, true); - } - - public static DdbRecordMatcher match(Map expected) { - return new DdbRecordMatcher(expected, false); - } - - public DdbRecordMatcher(Map expected, boolean invert) { - this.expected = expected; - this.invert = invert; - } - - @Override - public boolean matches(Object item) { - @SuppressWarnings("unchecked") - Map actual = (Map) item; - if (!expected.keySet().equals(actual.keySet())) { - return invert; - } - for (String key : expected.keySet()) { - if (key.equals("version")) continue; - AttributeValue e = expected.get(key); - AttributeValue a = actual.get(key); - if (!AttributeValueMatcher.attrEquals(a, e)) { - return invert; - } - } - return !invert; - } - - @Override - public void describeTo(Description description) { - - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java deleted file mode 100644 index d05aff4113..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.nio.ByteBuffer; -import java.security.SecureRandom; -import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import software.amazon.awssdk.core.SdkBytes; -import software.amazon.awssdk.services.kms.KmsClient; -import software.amazon.awssdk.services.kms.model.CreateKeyRequest; -import software.amazon.awssdk.services.kms.model.CreateKeyResponse; -import software.amazon.awssdk.services.kms.model.DecryptRequest; -import software.amazon.awssdk.services.kms.model.DecryptResponse; -import software.amazon.awssdk.services.kms.model.EncryptRequest; -import software.amazon.awssdk.services.kms.model.EncryptResponse; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyWithoutPlaintextRequest; -import software.amazon.awssdk.services.kms.model.GenerateDataKeyWithoutPlaintextResponse; -import software.amazon.awssdk.services.kms.model.InvalidCiphertextException; -import software.amazon.awssdk.services.kms.model.KeyMetadata; -import software.amazon.awssdk.services.kms.model.KeyUsageType; - -public class FakeKMS implements KmsClient { - private static final SecureRandom rnd = new SecureRandom(); - private static final String ACCOUNT_ID = "01234567890"; - private final Map results_ = new HashMap<>(); - - @Override - public CreateKeyResponse createKey(CreateKeyRequest createKeyRequest) { - String keyId = UUID.randomUUID().toString(); - String arn = "arn:aws:testing:kms:" + ACCOUNT_ID + ":key/" + keyId; - return CreateKeyResponse.builder() - .keyMetadata(KeyMetadata.builder().awsAccountId(ACCOUNT_ID) - .creationDate(Instant.now()) - .description(createKeyRequest.description()) - .enabled(true) - .keyId(keyId) - .keyUsage(KeyUsageType.ENCRYPT_DECRYPT) - .arn(arn) - .build()) - .build(); - } - - @Override - public DecryptResponse decrypt(DecryptRequest decryptRequest) { - DecryptResponse result = results_.get(new DecryptMapKey(decryptRequest)); - if (result != null) { - return result; - } else { - throw InvalidCiphertextException.create("Invalid Ciphertext", new RuntimeException()); - } - } - - @Override - public EncryptResponse encrypt(EncryptRequest encryptRequest) { - final byte[] cipherText = new byte[512]; - rnd.nextBytes(cipherText); - DecryptResponse.Builder dec = DecryptResponse.builder(); - dec.keyId(encryptRequest.keyId()) - .plaintext(SdkBytes.fromByteBuffer(encryptRequest.plaintext().asByteBuffer().asReadOnlyBuffer())); - ByteBuffer ctBuff = ByteBuffer.wrap(cipherText); - - results_.put(new DecryptMapKey(ctBuff, encryptRequest.encryptionContext()), dec.build()); - - return EncryptResponse.builder() - .ciphertextBlob(SdkBytes.fromByteBuffer(ctBuff)) - .keyId(encryptRequest.keyId()) - .build(); - } - - @Override - public GenerateDataKeyResponse generateDataKey(GenerateDataKeyRequest generateDataKeyRequest) { - byte[] pt; - if (generateDataKeyRequest.keySpec() != null) { - if (generateDataKeyRequest.keySpec().toString().contains("256")) { - pt = new byte[32]; - } else if (generateDataKeyRequest.keySpec().toString().contains("128")) { - pt = new byte[16]; - } else { - throw new UnsupportedOperationException(); - } - } else { - pt = new byte[generateDataKeyRequest.numberOfBytes()]; - } - rnd.nextBytes(pt); - ByteBuffer ptBuff = ByteBuffer.wrap(pt); - EncryptResponse encryptresponse = encrypt(EncryptRequest.builder() - .keyId(generateDataKeyRequest.keyId()) - .plaintext(SdkBytes.fromByteBuffer(ptBuff)) - .encryptionContext(generateDataKeyRequest.encryptionContext()) - .build()); - return GenerateDataKeyResponse.builder().keyId(generateDataKeyRequest.keyId()) - .ciphertextBlob(encryptresponse.ciphertextBlob()) - .plaintext(SdkBytes.fromByteBuffer(ptBuff)) - .build(); - } - - @Override - public GenerateDataKeyWithoutPlaintextResponse generateDataKeyWithoutPlaintext( - GenerateDataKeyWithoutPlaintextRequest req) { - GenerateDataKeyResponse generateDataKey = generateDataKey(GenerateDataKeyRequest.builder() - .encryptionContext(req.encryptionContext()).numberOfBytes(req.numberOfBytes()).build()); - return GenerateDataKeyWithoutPlaintextResponse.builder().ciphertextBlob( - generateDataKey.ciphertextBlob()).keyId(req.keyId()).build(); - } - - public Map getSingleEc() { - if (results_.size() != 1) { - throw new IllegalStateException("Unexpected number of ciphertexts"); - } - for (final DecryptMapKey k : results_.keySet()) { - return k.ec; - } - throw new IllegalStateException("Unexpected number of ciphertexts"); - } - - @Override - public String serviceName() { - return KmsClient.SERVICE_NAME; - } - - @Override - public void close() { - // do nothing - } - - private static class DecryptMapKey { - private final ByteBuffer cipherText; - private final Map ec; - - public DecryptMapKey(DecryptRequest req) { - cipherText = req.ciphertextBlob().asByteBuffer(); - if (req.encryptionContext() != null) { - ec = Collections.unmodifiableMap(new HashMap<>(req.encryptionContext())); - } else { - ec = Collections.emptyMap(); - } - } - - public DecryptMapKey(ByteBuffer ctBuff, Map ec) { - cipherText = ctBuff.asReadOnlyBuffer(); - if (ec != null) { - this.ec = Collections.unmodifiableMap(new HashMap<>(ec)); - } else { - this.ec = Collections.emptyMap(); - } - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((cipherText == null) ? 0 : cipherText.hashCode()); - result = prime * result + ((ec == null) ? 0 : ec.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - DecryptMapKey other = (DecryptMapKey) obj; - if (cipherText == null) { - if (other.cipherText != null) - return false; - } else if (!cipherText.equals(other.cipherText)) - return false; - if (ec == null) { - if (other.ec != null) - return false; - } else if (!ec.equals(other.ec)) - return false; - return true; - } - - @Override - public String toString() { - return "DecryptMapKey [cipherText=" + cipherText + ", ec=" + ec + "]"; - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java deleted file mode 100644 index 561c9204cb..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.io.IOException; -import java.net.ServerSocket; -import java.net.URI; - -import com.amazonaws.services.dynamodbv2.local.main.ServerRunner; -import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer; - -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.awssdk.services.dynamodb.model.*; - -/** - * Wrapper for a local DynamoDb server used in testing. Each instance of this class will find a new port to run on, - * so multiple instances can be safely run simultaneously. Each instance of this service uses memory as a storage medium - * and is thus completely ephemeral; no data will be persisted between stops and starts. - * - * LocalDynamoDb localDynamoDb = new LocalDynamoDb(); - * localDynamoDb.start(); // Start the service running locally on host - * DynamoDbClient dynamoDbClient = localDynamoDb.createClient(); - * ... // Do your testing with the client - * localDynamoDb.stop(); // Stop the service and free up resources - * - * If possible it's recommended to keep a single running instance for all your tests, as it can be slow to teardown - * and create new servers for every test, but there have been observed problems when dropping tables between tests for - * this scenario, so it's best to write your tests to be resilient to tables that already have data in them. - */ -public class LocalDynamoDb { - private static DynamoDBProxyServer server; - private static int port; - - /** - * Start the local DynamoDb service and run in background - */ - public void start() { - port = getFreePort(); - String portString = Integer.toString(port); - - try { - server = createServer(portString); - server.start(); - } catch (Exception e) { - throw propagate(e); - } - } - - /** - * Create a standard AWS v2 SDK client pointing to the local DynamoDb instance - * @return A DynamoDbClient pointing to the local DynamoDb instance - */ - public DynamoDbClient createClient() { - start(); - String endpoint = String.format("http://localhost:%d", port); - return DynamoDbClient.builder() - .endpointOverride(URI.create(endpoint)) - // The region is meaningless for local DynamoDb but required for client builder validation - .region(Region.US_EAST_1) - .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create("dummykey", "dummysecret"))) - .build(); - } - - /** - * If you require a client object that can be mocked or spied using standard mocking frameworks, then you must call - * this method to create the client instead. Only some methods are supported by this client, but it is easy to add - * new ones. - * @return A mockable/spyable DynamoDbClient pointing to the Local DynamoDB service. - */ - public DynamoDbClient createLimitedWrappedClient() { - return new WrappedDynamoDbClient(createClient()); - } - - /** - * Stops the local DynamoDb service and frees up resources it is using. - */ - public void stop() { - try { - server.stop(); - } catch (Exception e) { - throw propagate(e); - } - } - - private static DynamoDBProxyServer createServer(String portString) throws Exception { - return ServerRunner.createServerFromCommandLineArgs( - new String[]{ - "-inMemory", - "-port", portString - }); - } - - private static int getFreePort() { - try { - ServerSocket socket = new ServerSocket(0); - int port = socket.getLocalPort(); - socket.close(); - return port; - } catch (IOException ioe) { - throw propagate(ioe); - } - } - - private static RuntimeException propagate(Exception e) { - if (e instanceof RuntimeException) { - throw (RuntimeException)e; - } - throw new RuntimeException(e); - } - - /** - * This class can wrap any other implementation of a DynamoDbClient. The default implementation of the real - * DynamoDbClient is a final class, therefore it cannot be easily spied upon unless you first wrap it in a class - * like this. If there's a method you need it to support, just add it to the wrapper here. - */ - private static class WrappedDynamoDbClient implements DynamoDbClient { - private final DynamoDbClient wrappedClient; - - private WrappedDynamoDbClient(DynamoDbClient wrappedClient) { - this.wrappedClient = wrappedClient; - } - - @Override - public String serviceName() { - return wrappedClient.serviceName(); - } - - @Override - public void close() { - wrappedClient.close(); - } - - @Override - public PutItemResponse putItem(PutItemRequest putItemRequest) { - return wrappedClient.putItem(putItemRequest); - } - - @Override - public GetItemResponse getItem(GetItemRequest getItemRequest) { - return wrappedClient.getItem(getItemRequest); - } - - @Override - public QueryResponse query(QueryRequest queryRequest) { - return wrappedClient.query(queryRequest); - } - - @Override - public ListTablesResponse listTables(ListTablesRequest listTablesRequest) { return wrappedClient.listTables(listTablesRequest); } - - @Override - public ScanResponse scan(ScanRequest scanRequest) { return wrappedClient.scan(scanRequest); } - - @Override - public CreateTableResponse createTable(CreateTableRequest createTableRequest) { - return wrappedClient.createTable(createTableRequest); - } - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java deleted file mode 100644 index 14c84d8605..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java +++ /dev/null @@ -1,77 +0,0 @@ -package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.List; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class ScenarioManifest { - - public static final String MOST_RECENT_PROVIDER_NAME = "most_recent"; - public static final String WRAPPED_PROVIDER_NAME = "wrapped"; - public static final String STATIC_PROVIDER_NAME = "static"; - public static final String AWS_KMS_PROVIDER_NAME = "awskms"; - public static final String SYMMETRIC_KEY_TYPE = "symmetric"; - - public List scenarios; - - @JsonProperty("keys") - public String keyDataPath; - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Scenario { - @JsonProperty("ciphertext") - public String ciphertextPath; - - @JsonProperty("provider") - public String providerName; - - public String version; - - @JsonProperty("material_name") - public String materialName; - - public Metastore metastore; - public Keys keys; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Metastore { - @JsonProperty("ciphertext") - public String path; - - @JsonProperty("table_name") - public String tableName; - - @JsonProperty("provider") - public String providerName; - - public Keys keys; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Keys { - @JsonProperty("encrypt") - public String encryptName; - - @JsonProperty("sign") - public String signName; - - @JsonProperty("decrypt") - public String decryptName; - - @JsonProperty("verify") - public String verifyName; - } - - public static class KeyData { - public String material; - public String algorithm; - public String encoding; - - @JsonProperty("type") - public String keyType; - - public String keyId; - } -} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java deleted file mode 100644 index c19c5565b3..0000000000 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; - -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.IvParameterSpec; - -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DelegatedKey; - -public class TestDelegatedKey implements DelegatedKey { - private static final long serialVersionUID = 1L; - - private final Key realKey; - - public TestDelegatedKey(Key key) { - this.realKey = key; - } - - @Override - public String getAlgorithm() { - return "DELEGATED:" + realKey.getAlgorithm(); - } - - @Override - public byte[] getEncoded() { - return realKey.getEncoded(); - } - - @Override - public String getFormat() { - return realKey.getFormat(); - } - - @Override - public byte[] encrypt(byte[] plainText, byte[] additionalAssociatedData, String algorithm) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, - NoSuchPaddingException { - Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); - cipher.init(Cipher.ENCRYPT_MODE, realKey); - byte[] iv = cipher.getIV(); - byte[] result = new byte[cipher.getOutputSize(plainText.length) + iv.length + 1]; - result[0] = (byte) iv.length; - System.arraycopy(iv, 0, result, 1, iv.length); - try { - cipher.doFinal(plainText, 0, plainText.length, result, iv.length + 1); - } catch (ShortBufferException e) { - throw new RuntimeException(e); - } - return result; - } - - @Override - public byte[] decrypt(byte[] cipherText, byte[] additionalAssociatedData, String algorithm) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, - NoSuchPaddingException, InvalidAlgorithmParameterException { - final byte ivLength = cipherText[0]; - IvParameterSpec iv = new IvParameterSpec(cipherText, 1, ivLength); - Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); - cipher.init(Cipher.DECRYPT_MODE, realKey, iv); - return cipher.doFinal(cipherText, ivLength + 1, cipherText.length - ivLength - 1); - } - - @Override - public byte[] wrap(Key key, byte[] additionalAssociatedData, String algorithm) throws InvalidKeyException, - NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException { - Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); - cipher.init(Cipher.WRAP_MODE, realKey); - return cipher.wrap(key); - } - - @Override - public Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType, - byte[] additionalAssociatedData, String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException, - InvalidKeyException { - Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); - cipher.init(Cipher.UNWRAP_MODE, realKey); - return cipher.unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType); - } - - @Override - public byte[] sign(byte[] dataToSign, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException { - Mac mac = Mac.getInstance(extractAlgorithm(algorithm)); - mac.init(realKey); - return mac.doFinal(dataToSign); - } - - @Override - public boolean verify(byte[] dataToSign, byte[] signature, String algorithm) { - try { - byte[] expected = sign(dataToSign, extractAlgorithm(algorithm)); - return MessageDigest.isEqual(expected, signature); - } catch (GeneralSecurityException ex) { - return false; - } - } - - private String extractAlgorithm(String alg) { - if (alg.startsWith(getAlgorithm())) { - return alg.substring(10); - } else { - return alg; - } - } -} From 7ebefe7a8eb364260cfd71d245961d07769594db Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 11:10:04 -0800 Subject: [PATCH 11/33] copy code from internal --- .../encryption/DelegatedKey.java | 146 +++ .../encryption/DynamoDbEncryptor.java | 595 +++++++++++ .../encryption/DynamoDbSigner.java | 261 +++++ .../encryption/EncryptionContext.java | 187 ++++ .../encryption/EncryptionFlags.java | 23 + .../DynamoDbEncryptionException.java | 47 + .../materials/AbstractRawMaterials.java | 73 ++ .../materials/AsymmetricRawMaterials.java | 49 + .../materials/CryptographicMaterials.java | 24 + .../materials/DecryptionMaterials.java | 27 + .../materials/EncryptionMaterials.java | 27 + .../materials/SymmetricRawMaterials.java | 58 ++ .../materials/WrappedRawMaterials.java | 212 ++++ .../providers/AsymmetricStaticProvider.java | 46 + .../providers/CachingMostRecentProvider.java | 183 ++++ .../providers/DirectKmsMaterialsProvider.java | 296 ++++++ .../EncryptionMaterialsProvider.java | 71 ++ .../providers/KeyStoreMaterialsProvider.java | 199 ++++ .../providers/SymmetricStaticProvider.java | 130 +++ .../providers/WrappedMaterialsProvider.java | 163 +++ .../encryption/providers/store/MetaStore.java | 434 ++++++++ .../providers/store/ProviderStore.java | 84 ++ .../utils/EncryptionContextOperators.java | 81 ++ .../internal/AttributeValueMarshaller.java | 331 +++++++ .../internal/Base64.java | 48 + .../internal/ByteBufferInputStream.java | 56 ++ .../internal/Hkdf.java | 316 ++++++ .../internal/LRUCache.java | 107 ++ .../internal/MsClock.java | 19 + .../internal/TTLCache.java | 242 +++++ .../internal/Utils.java | 39 + .../HolisticIT.java | 932 ++++++++++++++++++ .../encryption/DelegatedEncryptionTest.java | 296 ++++++ .../DelegatedEnvelopeEncryptionTest.java | 280 ++++++ .../encryption/DynamoDbEncryptorTest.java | 591 +++++++++++ .../encryption/DynamoDbSignerTest.java | 567 +++++++++++ .../materials/AsymmetricRawMaterialsTest.java | 138 +++ .../materials/SymmetricRawMaterialsTest.java | 104 ++ .../AsymmetricStaticProviderTest.java | 130 +++ .../CachingMostRecentProviderTests.java | 610 ++++++++++++ .../DirectKmsMaterialsProviderTest.java | 449 +++++++++ .../KeyStoreMaterialsProviderTest.java | 315 ++++++ .../SymmetricStaticProviderTest.java | 182 ++++ .../WrappedMaterialsProviderTest.java | 414 ++++++++ .../providers/store/MetaStoreTests.java | 346 +++++++ .../utils/EncryptionContextOperatorsTest.java | 164 +++ .../AttributeValueMarshallerTest.java | 393 ++++++++ .../internal/Base64Tests.java | 93 ++ .../internal/ByteBufferInputStreamTest.java | 86 ++ .../internal/ConcurrentTTLCacheTest.java | 244 +++++ .../internal/HkdfTests.java | 209 ++++ .../internal/LRUCacheTest.java | 85 ++ .../internal/TTLCacheTest.java | 372 +++++++ .../testing/AttrMatcher.java | 125 +++ .../testing/AttributeValueBuilder.java | 67 ++ .../testing/AttributeValueDeserializer.java | 58 ++ .../testing/AttributeValueMatcher.java | 101 ++ .../testing/AttributeValueSerializer.java | 48 + .../testing/DdbRecordMatcher.java | 47 + .../testing/FakeKMS.java | 201 ++++ .../testing/LocalDynamoDb.java | 175 ++++ .../testing/ScenarioManifest.java | 77 ++ .../testing/TestDelegatedKey.java | 128 +++ 63 files changed, 12601 insertions(+) create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java create mode 100644 DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java new file mode 100644 index 0000000000..52e02f2e8e --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedKey.java @@ -0,0 +1,146 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; + +/** + * Identifies keys which should not be used directly with {@link Cipher} but + * instead contain their own cryptographic logic. This can be used to wrap more + * complex logic, HSM integration, or service-calls. + * + *

+ * Most delegated keys will only support a subset of these operations. (For + * example, AES keys will generally not support {@link #sign(byte[], String)} or + * {@link #verify(byte[], byte[], String)} and HMAC keys will generally not + * support anything except sign and verify.) + * {@link UnsupportedOperationException} should be thrown in these cases. + * + * @author Greg Rubin + */ +public interface DelegatedKey extends SecretKey { + /** + * Encrypts the provided plaintext and returns a byte-array containing the ciphertext. + * + * @param plainText + * @param additionalAssociatedData + * Optional additional data which must then also be provided for successful + * decryption. Both null and arrays of length 0 are treated identically. + * Not all keys will support this parameter. + * @param algorithm + * the transformation to be used when encrypting the data + * @return ciphertext the ciphertext produced by this encryption operation + * @throws UnsupportedOperationException + * if encryption is not supported or if additionalAssociatedData is + * provided, but not supported. + */ + byte[] encrypt(byte[] plainText, byte[] additionalAssociatedData, String algorithm) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, + NoSuchPaddingException; + + /** + * Decrypts the provided ciphertext and returns a byte-array containing the + * plaintext. + * + * @param cipherText + * @param additionalAssociatedData + * Optional additional data which was provided during encryption. + * Both null and arrays of length 0 are treated + * identically. Not all keys will support this parameter. + * @param algorithm + * the transformation to be used when decrypting the data + * @return plaintext the result of decrypting the input ciphertext + * @throws UnsupportedOperationException + * if decryption is not supported or if + * additionalAssociatedData is provided, but not + * supported. + */ + byte[] decrypt(byte[] cipherText, byte[] additionalAssociatedData, String algorithm) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, + NoSuchPaddingException, InvalidAlgorithmParameterException; + + /** + * Wraps (encrypts) the provided key to make it safe for + * storage or transmission. + * + * @param key + * @param additionalAssociatedData + * Optional additional data which must then also be provided for + * successful unwrapping. Both null and arrays of + * length 0 are treated identically. Not all keys will support + * this parameter. + * @param algorithm + * the transformation to be used when wrapping the key + * @return the wrapped key + * @throws UnsupportedOperationException + * if wrapping is not supported or if + * additionalAssociatedData is provided, but not + * supported. + */ + byte[] wrap(Key key, byte[] additionalAssociatedData, String algorithm) throws InvalidKeyException, + NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException; + + /** + * Unwraps (decrypts) the provided wrappedKey to recover the + * original key. + * + * @param wrappedKey + * @param additionalAssociatedData + * Optional additional data which was provided during wrapping. + * Both null and arrays of length 0 are treated + * identically. Not all keys will support this parameter. + * @param algorithm + * the transformation to be used when unwrapping the key + * @return the unwrapped key + * @throws UnsupportedOperationException + * if wrapping is not supported or if + * additionalAssociatedData is provided, but not + * supported. + */ + Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType, + byte[] additionalAssociatedData, String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException, + InvalidKeyException; + + /** + * Calculates and returns a signature for dataToSign. + * + * @param dataToSign + * @param algorithm + * @return the signature + * @throws UnsupportedOperationException if signing is not supported + */ + byte[] sign(byte[] dataToSign, String algorithm) throws GeneralSecurityException; + + /** + * Checks the provided signature for correctness. + * + * @param dataToSign + * @param signature + * @param algorithm + * @return true if and only if the signature matches the dataToSign. + * @throws UnsupportedOperationException if signature validation is not supported + */ + boolean verify(byte[] dataToSign, byte[] signature, String algorithm); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java new file mode 100644 index 0000000000..95e6ec73c7 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java @@ -0,0 +1,595 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.SignatureException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.ByteBufferInputStream; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +/** + * The low-level API for performing crypto operations on the record attributes. + * + * @author Greg Rubin + */ +public class DynamoDbEncryptor { + private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA"; + private static final String DEFAULT_METADATA_FIELD = "*amzn-ddb-map-desc*"; + private static final String DEFAULT_SIGNATURE_FIELD = "*amzn-ddb-map-sig*"; + private static final String DEFAULT_DESCRIPTION_BASE = "amzn-ddb-map-"; // Same as the Mapper + private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final String SYMMETRIC_ENCRYPTION_MODE = "/CBC/PKCS5Padding"; + private static final ConcurrentHashMap BLOCK_SIZE_CACHE = new ConcurrentHashMap<>(); + private static final Function BLOCK_SIZE_CALCULATOR = (transformation) -> { + try { + final Cipher c = Cipher.getInstance(transformation); + return c.getBlockSize(); + } catch (final GeneralSecurityException ex) { + throw new IllegalArgumentException("Algorithm does not exist", ex); + } + }; + + private static final int CURRENT_VERSION = 0; + + private String signatureFieldName = DEFAULT_SIGNATURE_FIELD; + private String materialDescriptionFieldName = DEFAULT_METADATA_FIELD; + + private EncryptionMaterialsProvider encryptionMaterialsProvider; + private final String descriptionBase; + private final String symmetricEncryptionModeHeader; + private final String signingAlgorithmHeader; + + static final String DEFAULT_SIGNING_ALGORITHM_HEADER = DEFAULT_DESCRIPTION_BASE + "signingAlg"; + + private Function encryptionContextOverrideOperator; + + protected DynamoDbEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { + this.encryptionMaterialsProvider = provider; + this.descriptionBase = descriptionBase; + symmetricEncryptionModeHeader = this.descriptionBase + "sym-mode"; + signingAlgorithmHeader = this.descriptionBase + "signingAlg"; + } + + public static DynamoDbEncryptor getInstance( + EncryptionMaterialsProvider provider, String descriptionbase) { + return new DynamoDbEncryptor(provider, descriptionbase); + } + + public static DynamoDbEncryptor getInstance(EncryptionMaterialsProvider provider) { + return getInstance(provider, DEFAULT_DESCRIPTION_BASE); + } + + /** + * Returns a decrypted version of the provided DynamoDb record. The signature is verified across + * all provided fields. All fields (except those listed in doNotEncrypt are + * decrypted. + * + * @param itemAttributes the DynamoDbRecord + * @param context additional information used to successfully select the encryption materials and + * decrypt the data. This should include (at least) the tableName and the materialDescription. + * @param doNotDecrypt those fields which should not be encrypted + * @return a plaintext version of the DynamoDb record + * @throws SignatureException if the signature is invalid or cannot be verified + * @throws GeneralSecurityException + */ + public Map decryptAllFieldsExcept( + Map itemAttributes, EncryptionContext context, String... doNotDecrypt) + throws GeneralSecurityException { + return decryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotDecrypt)); + } + + /** @see #decryptAllFieldsExcept(Map, EncryptionContext, String...) */ + public Map decryptAllFieldsExcept( + Map itemAttributes, + EncryptionContext context, + Collection doNotDecrypt) + throws GeneralSecurityException { + Map> attributeFlags = + allDecryptionFlagsExcept(itemAttributes, doNotDecrypt); + return decryptRecord(itemAttributes, attributeFlags, context); + } + + /** + * Returns the decryption flags for all item attributes except for those explicitly specified to + * be excluded. + * + * @param doNotDecrypt fields to be excluded + */ + public Map> allDecryptionFlagsExcept( + Map itemAttributes, String... doNotDecrypt) { + return allDecryptionFlagsExcept(itemAttributes, Arrays.asList(doNotDecrypt)); + } + + /** + * Returns the decryption flags for all item attributes except for those explicitly specified to + * be excluded. + * + * @param doNotDecrypt fields to be excluded + */ + public Map> allDecryptionFlagsExcept( + Map itemAttributes, Collection doNotDecrypt) { + Map> attributeFlags = new HashMap>(); + + for (String fieldName : doNotDecrypt) { + attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN)); + } + + for (String fieldName : itemAttributes.keySet()) { + if (!attributeFlags.containsKey(fieldName) + && !fieldName.equals(getMaterialDescriptionFieldName()) + && !fieldName.equals(getSignatureFieldName())) { + attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); + } + } + return attributeFlags; + } + + /** + * Returns an encrypted version of the provided DynamoDb record. All fields are signed. All fields + * (except those listed in doNotEncrypt) are encrypted. + * + * @param itemAttributes a DynamoDb Record + * @param context additional information used to successfully select the encryption materials and + * encrypt the data. This should include (at least) the tableName. + * @param doNotEncrypt those fields which should not be encrypted + * @return a ciphertext version of the DynamoDb record + * @throws GeneralSecurityException + */ + public Map encryptAllFieldsExcept( + Map itemAttributes, EncryptionContext context, String... doNotEncrypt) + throws GeneralSecurityException { + + return encryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotEncrypt)); + } + + public Map encryptAllFieldsExcept( + Map itemAttributes, + EncryptionContext context, + Collection doNotEncrypt) + throws GeneralSecurityException { + Map> attributeFlags = + allEncryptionFlagsExcept(itemAttributes, doNotEncrypt); + return encryptRecord(itemAttributes, attributeFlags, context); + } + + /** + * Returns the encryption flags for all item attributes except for those explicitly specified to + * be excluded. + * + * @param doNotEncrypt fields to be excluded + */ + public Map> allEncryptionFlagsExcept( + Map itemAttributes, String... doNotEncrypt) { + return allEncryptionFlagsExcept(itemAttributes, Arrays.asList(doNotEncrypt)); + } + + /** + * Returns the encryption flags for all item attributes except for those explicitly specified to + * be excluded. + * + * @param doNotEncrypt fields to be excluded + */ + public Map> allEncryptionFlagsExcept( + Map itemAttributes, Collection doNotEncrypt) { + Map> attributeFlags = new HashMap>(); + for (String fieldName : doNotEncrypt) { + attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN)); + } + + for (String fieldName : itemAttributes.keySet()) { + if (!attributeFlags.containsKey(fieldName)) { + attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); + } + } + return attributeFlags; + } + + public Map decryptRecord( + Map itemAttributes, + Map> attributeFlags, + EncryptionContext context) + throws GeneralSecurityException { + if (!itemContainsFieldsToDecryptOrSign(itemAttributes.keySet(), attributeFlags)) { + return itemAttributes; + } + // Copy to avoid changing anyone elses objects + itemAttributes = new HashMap(itemAttributes); + + Map materialDescription = Collections.emptyMap(); + DecryptionMaterials materials; + SecretKey decryptionKey; + + DynamoDbSigner signer = DynamoDbSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng()); + + if (itemAttributes.containsKey(materialDescriptionFieldName)) { + materialDescription = unmarshallDescription(itemAttributes.get(materialDescriptionFieldName)); + } + // Copy the material description and attribute values into the context + context = + new EncryptionContext.Builder(context) + .materialDescription(materialDescription) + .attributeValues(itemAttributes) + .build(); + + Function encryptionContextOverrideOperator = + getEncryptionContextOverrideOperator(); + if (encryptionContextOverrideOperator != null) { + context = encryptionContextOverrideOperator.apply(context); + } + + materials = encryptionMaterialsProvider.getDecryptionMaterials(context); + decryptionKey = materials.getDecryptionKey(); + if (materialDescription.containsKey(signingAlgorithmHeader)) { + String signingAlg = materialDescription.get(signingAlgorithmHeader); + signer = DynamoDbSigner.getInstance(signingAlg, Utils.getRng()); + } + + ByteBuffer signature; + if (!itemAttributes.containsKey(signatureFieldName) + || itemAttributes.get(signatureFieldName).b() == null) { + signature = ByteBuffer.allocate(0); + } else { + signature = itemAttributes.get(signatureFieldName).b().asByteBuffer().asReadOnlyBuffer(); + } + itemAttributes.remove(signatureFieldName); + + String associatedData = "TABLE>" + context.getTableName() + " attributeNamesToCheck, Map> attributeFlags) { + return attributeNamesToCheck.stream() + .filter(attributeFlags::containsKey) + .anyMatch(attributeName -> !attributeFlags.get(attributeName).isEmpty()); + } + + public Map encryptRecord( + Map itemAttributes, + Map> attributeFlags, + EncryptionContext context) { + if (attributeFlags.isEmpty()) { + return itemAttributes; + } + // Copy to avoid changing anyone elses objects + itemAttributes = new HashMap<>(itemAttributes); + + // Copy the attribute values into the context + context = context.toBuilder() + .attributeValues(itemAttributes) + .build(); + + Function encryptionContextOverrideOperator = + getEncryptionContextOverrideOperator(); + if (encryptionContextOverrideOperator != null) { + context = encryptionContextOverrideOperator.apply(context); + } + + EncryptionMaterials materials = encryptionMaterialsProvider.getEncryptionMaterials(context); + // We need to copy this because we modify it to record other encryption details + Map materialDescription = new HashMap<>( + materials.getMaterialDescription()); + SecretKey encryptionKey = materials.getEncryptionKey(); + + try { + actualEncryption(itemAttributes, attributeFlags, materialDescription, encryptionKey); + + // The description must be stored after encryption because its data + // is necessary for proper decryption. + final String signingAlgo = materialDescription.get(signingAlgorithmHeader); + DynamoDbSigner signer; + if (signingAlgo != null) { + signer = DynamoDbSigner.getInstance(signingAlgo, Utils.getRng()); + } else { + signer = DynamoDbSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng()); + } + + if (materials.getSigningKey() instanceof PrivateKey) { + materialDescription.put(signingAlgorithmHeader, signer.getSigningAlgorithm()); + } + if (! materialDescription.isEmpty()) { + itemAttributes.put(materialDescriptionFieldName, marshallDescription(materialDescription)); + } + + String associatedData = "TABLE>" + context.getTableName() + " itemAttributes, + Map> attributeFlags, SecretKey encryptionKey, + Map materialDescription) throws GeneralSecurityException { + final String encryptionMode = encryptionKey != null ? encryptionKey.getAlgorithm() + + materialDescription.get(symmetricEncryptionModeHeader) : null; + Cipher cipher = null; + int blockSize = -1; + + for (Map.Entry entry: itemAttributes.entrySet()) { + Set flags = attributeFlags.get(entry.getKey()); + if (flags != null && flags.contains(EncryptionFlags.ENCRYPT)) { + if (!flags.contains(EncryptionFlags.SIGN)) { + throw new IllegalArgumentException("All encrypted fields must be signed. Bad field: " + entry.getKey()); + } + ByteBuffer plainText; + ByteBuffer cipherText = entry.getValue().b().asByteBuffer(); + cipherText.rewind(); + if (encryptionKey instanceof DelegatedKey) { + plainText = ByteBuffer.wrap(((DelegatedKey)encryptionKey).decrypt(toByteArray(cipherText), null, encryptionMode)); + } else { + if (cipher == null) { + blockSize = getBlockSize(encryptionMode); + cipher = Cipher.getInstance(encryptionMode); + } + byte[] iv = new byte[blockSize]; + cipherText.get(iv); + cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng()); + plainText = ByteBuffer.allocate(cipher.getOutputSize(cipherText.remaining())); + cipher.doFinal(cipherText, plainText); + plainText.rewind(); + } + entry.setValue(AttributeValueMarshaller.unmarshall(plainText)); + } + } + } + + private static int getBlockSize(final String encryptionMode) { + return BLOCK_SIZE_CACHE.computeIfAbsent(encryptionMode, BLOCK_SIZE_CALCULATOR); + } + + /** + * This method has the side effect of replacing the plaintext + * attribute-values of "itemAttributes" with ciphertext attribute-values + * (which are always in the form of ByteBuffer) as per the corresponding + * attribute flags. + */ + private void actualEncryption(Map itemAttributes, + Map> attributeFlags, + Map materialDescription, + SecretKey encryptionKey) throws GeneralSecurityException { + String encryptionMode = null; + if (encryptionKey != null) { + materialDescription.put(this.symmetricEncryptionModeHeader, + SYMMETRIC_ENCRYPTION_MODE); + encryptionMode = encryptionKey.getAlgorithm() + SYMMETRIC_ENCRYPTION_MODE; + } + Cipher cipher = null; + int blockSize = -1; + + for (Map.Entry entry: itemAttributes.entrySet()) { + Set flags = attributeFlags.get(entry.getKey()); + if (flags != null && flags.contains(EncryptionFlags.ENCRYPT)) { + if (!flags.contains(EncryptionFlags.SIGN)) { + throw new IllegalArgumentException("All encrypted fields must be signed. Bad field: " + entry.getKey()); + } + ByteBuffer plainText = AttributeValueMarshaller.marshall(entry.getValue()); + plainText.rewind(); + ByteBuffer cipherText; + if (encryptionKey instanceof DelegatedKey) { + DelegatedKey dk = (DelegatedKey) encryptionKey; + cipherText = ByteBuffer.wrap( + dk.encrypt(toByteArray(plainText), null, encryptionMode)); + } else { + if (cipher == null) { + blockSize = getBlockSize(encryptionMode); + cipher = Cipher.getInstance(encryptionMode); + } + // Encryption format: + // Note a unique iv is generated per attribute + cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, Utils.getRng()); + cipherText = ByteBuffer.allocate(blockSize + cipher.getOutputSize(plainText.remaining())); + cipherText.position(blockSize); + cipher.doFinal(plainText, cipherText); + cipherText.flip(); + final byte[] iv = cipher.getIV(); + if (iv.length != blockSize) { + throw new IllegalStateException(String.format("Generated IV length (%d) not equal to block size (%d)", + iv.length, blockSize)); + } + cipherText.put(iv); + cipherText.rewind(); + } + // Replace the plaintext attribute value with the encrypted content + entry.setValue(AttributeValue.builder().b(SdkBytes.fromByteBuffer(cipherText)).build()); + } + } + } + + /** + * Get the name of the DynamoDB field used to store the signature. + * Defaults to {@link #DEFAULT_SIGNATURE_FIELD}. + * + * @return the name of the DynamoDB field used to store the signature + */ + String getSignatureFieldName() { + return signatureFieldName; + } + + /** + * Set the name of the DynamoDB field used to store the signature. + * + * @param signatureFieldName + */ + void setSignatureFieldName(final String signatureFieldName) { + this.signatureFieldName = signatureFieldName; + } + + /** + * Get the name of the DynamoDB field used to store metadata used by the + * DynamoDBEncryptedMapper. Defaults to {@link #DEFAULT_METADATA_FIELD}. + * + * @return the name of the DynamoDB field used to store metadata used by the + * DynamoDBEncryptedMapper + */ + String getMaterialDescriptionFieldName() { + return materialDescriptionFieldName; + } + + /** + * Set the name of the DynamoDB field used to store metadata used by the + * DynamoDBEncryptedMapper + * + * @param materialDescriptionFieldName + */ + void setMaterialDescriptionFieldName(final String materialDescriptionFieldName) { + this.materialDescriptionFieldName = materialDescriptionFieldName; + } + + /** + * Marshalls the description into a ByteBuffer by outputting + * each key (modified UTF-8) followed by its value (also in modified UTF-8). + * + * @param description + * @return the description encoded as an AttributeValue with a ByteBuffer value + * @see java.io.DataOutput#writeUTF(String) + */ + private static AttributeValue marshallDescription(Map description) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(bos); + out.writeInt(CURRENT_VERSION); + for (Map.Entry entry : description.entrySet()) { + byte[] bytes = entry.getKey().getBytes(UTF8); + out.writeInt(bytes.length); + out.write(bytes); + bytes = entry.getValue().getBytes(UTF8); + out.writeInt(bytes.length); + out.write(bytes); + } + out.close(); + return AttributeValue.builder().b(SdkBytes.fromByteArray(bos.toByteArray())).build(); + } catch (IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + /** + * @see #marshallDescription(Map) + */ + private static Map unmarshallDescription(AttributeValue attributeValue) { + try (DataInputStream in = new DataInputStream( + new ByteBufferInputStream(attributeValue.b().asByteBuffer())) ) { + Map result = new HashMap<>(); + int version = in.readInt(); + if (version != CURRENT_VERSION) { + throw new IllegalArgumentException("Unsupported description version"); + } + + String key, value; + int keyLength, valueLength; + try { + while(in.available() > 0) { + keyLength = in.readInt(); + byte[] bytes = new byte[keyLength]; + if (in.read(bytes) != keyLength) { + throw new IllegalArgumentException("Malformed description"); + } + key = new String(bytes, UTF8); + valueLength = in.readInt(); + bytes = new byte[valueLength]; + if (in.read(bytes) != valueLength) { + throw new IllegalArgumentException("Malformed description"); + } + value = new String(bytes, UTF8); + result.put(key, value); + } + } catch (EOFException eof) { + throw new IllegalArgumentException("Malformed description", eof); + } + return result; + } catch (IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + /** + * @param encryptionContextOverrideOperator the nullable operator which will be used to override + * the EncryptionContext. + * @see EncryptionContextOperators + */ + void setEncryptionContextOverrideOperator( + Function encryptionContextOverrideOperator) { + this.encryptionContextOverrideOperator = encryptionContextOverrideOperator; + } + + /** + * @return the operator used to override the EncryptionContext + * @see #setEncryptionContextOverrideOperator(Function) + */ + private Function getEncryptionContextOverrideOperator() { + return encryptionContextOverrideOperator; + } + + private static byte[] toByteArray(ByteBuffer buffer) { + buffer = buffer.duplicate(); + // We can only return the array directly if: + // 1. The ByteBuffer exposes an array + // 2. The ByteBuffer starts at the beginning of the array + // 3. The ByteBuffer uses the entire array + if (buffer.hasArray() && buffer.arrayOffset() == 0) { + byte[] result = buffer.array(); + if (buffer.remaining() == result.length) { + return result; + } + } + + byte[] result = new byte[buffer.remaining()]; + buffer.get(result); + return result; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java new file mode 100644 index 0000000000..d2998057b0 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSigner.java @@ -0,0 +1,261 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +/** + * @author Greg Rubin + */ +// NOTE: This class must remain thread-safe. +class DynamoDbSigner { + private static final ConcurrentHashMap cache = + new ConcurrentHashMap(); + + protected static final Charset UTF8 = Charset.forName("UTF-8"); + private final SecureRandom rnd; + private final SecretKey hmacComparisonKey; + private final String signingAlgorithm; + + /** + * @param signingAlgorithm is the algorithm used for asymmetric signing (ex: SHA256withRSA). This + * is ignored for symmetric HMACs as that algorithm is fully specified by the key. + */ + static DynamoDbSigner getInstance(String signingAlgorithm, SecureRandom rnd) { + DynamoDbSigner result = cache.get(signingAlgorithm); + if (result == null) { + result = new DynamoDbSigner(signingAlgorithm, rnd); + cache.putIfAbsent(signingAlgorithm, result); + } + return result; + } + + /** + * @param signingAlgorithm is the algorithm used for asymmetric signing (ex: SHA256withRSA). This + * is ignored for symmetric HMACs as that algorithm is fully specified by the key. + */ + private DynamoDbSigner(String signingAlgorithm, SecureRandom rnd) { + if (rnd == null) { + rnd = Utils.getRng(); + } + this.rnd = rnd; + this.signingAlgorithm = signingAlgorithm; + // Shorter than the output of SHA256 to avoid weak keys. + // http://cs.nyu.edu/~dodis/ps/h-of-h.pdf + // http://link.springer.com/chapter/10.1007%2F978-3-642-32009-5_21 + byte[] tmpKey = new byte[31]; + rnd.nextBytes(tmpKey); + hmacComparisonKey = new SecretKeySpec(tmpKey, "HmacSHA256"); + } + + void verifySignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + Key verificationKey, + ByteBuffer signature) + throws GeneralSecurityException { + if (verificationKey instanceof DelegatedKey) { + DelegatedKey dKey = (DelegatedKey) verificationKey; + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + if (!dKey.verify(stringToSign, toByteArray(signature), dKey.getAlgorithm())) { + throw new SignatureException("Bad signature"); + } + } else if (verificationKey instanceof SecretKey) { + byte[] calculatedSig = + calculateSignature( + itemAttributes, attributeFlags, associatedData, (SecretKey) verificationKey); + if (!safeEquals(signature, calculatedSig)) { + throw new SignatureException("Bad signature"); + } + } else if (verificationKey instanceof PublicKey) { + PublicKey integrityKey = (PublicKey) verificationKey; + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + Signature sig = Signature.getInstance(getSigningAlgorithm()); + sig.initVerify(integrityKey); + sig.update(stringToSign); + if (!sig.verify(toByteArray(signature))) { + throw new SignatureException("Bad signature"); + } + } else { + throw new IllegalArgumentException("No integrity key provided"); + } + } + + static byte[] calculateStringToSign( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData) + throws NoSuchAlgorithmException { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + List attrNames = new ArrayList(itemAttributes.keySet()); + Collections.sort(attrNames); + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + if (associatedData != null) { + out.write(sha256.digest(associatedData)); + } else { + out.write(sha256.digest()); + } + sha256.reset(); + + for (String name : attrNames) { + Set set = attributeFlags.get(name); + if (set != null && set.contains(EncryptionFlags.SIGN)) { + AttributeValue tmp = itemAttributes.get(name); + out.write(sha256.digest(name.getBytes(UTF8))); + sha256.reset(); + if (set.contains(EncryptionFlags.ENCRYPT)) { + sha256.update("ENCRYPTED".getBytes(UTF8)); + } else { + sha256.update("PLAINTEXT".getBytes(UTF8)); + } + out.write(sha256.digest()); + + sha256.reset(); + + sha256.update(AttributeValueMarshaller.marshall(tmp)); + out.write(sha256.digest()); + sha256.reset(); + } + } + return out.toByteArray(); + } catch (IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + /** The itemAttributes have already been encrypted, if necessary, before the signing. */ + byte[] calculateSignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + Key key) + throws GeneralSecurityException { + if (key instanceof DelegatedKey) { + return calculateSignature(itemAttributes, attributeFlags, associatedData, (DelegatedKey) key); + } else if (key instanceof SecretKey) { + return calculateSignature(itemAttributes, attributeFlags, associatedData, (SecretKey) key); + } else if (key instanceof PrivateKey) { + return calculateSignature(itemAttributes, attributeFlags, associatedData, (PrivateKey) key); + } else { + throw new IllegalArgumentException("No integrity key provided"); + } + } + + byte[] calculateSignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + DelegatedKey key) + throws GeneralSecurityException { + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + return key.sign(stringToSign, key.getAlgorithm()); + } + + byte[] calculateSignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + SecretKey key) + throws GeneralSecurityException { + if (key instanceof DelegatedKey) { + return calculateSignature(itemAttributes, attributeFlags, associatedData, (DelegatedKey) key); + } + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + Mac hmac = Mac.getInstance(key.getAlgorithm()); + hmac.init(key); + hmac.update(stringToSign); + return hmac.doFinal(); + } + + byte[] calculateSignature( + Map itemAttributes, + Map> attributeFlags, + byte[] associatedData, + PrivateKey key) + throws GeneralSecurityException { + byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData); + Signature sig = Signature.getInstance(signingAlgorithm); + sig.initSign(key, rnd); + sig.update(stringToSign); + return sig.sign(); + } + + String getSigningAlgorithm() { + return signingAlgorithm; + } + + /** Constant-time equality check. */ + private boolean safeEquals(ByteBuffer signature, byte[] calculatedSig) { + try { + signature.rewind(); + Mac hmac = Mac.getInstance(hmacComparisonKey.getAlgorithm()); + hmac.init(hmacComparisonKey); + hmac.update(signature); + byte[] signatureHash = hmac.doFinal(); + + hmac.reset(); + hmac.update(calculatedSig); + byte[] calculatedHash = hmac.doFinal(); + + return MessageDigest.isEqual(signatureHash, calculatedHash); + } catch (GeneralSecurityException ex) { + // We've hardcoded these algorithms, so the error should not be possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + private static byte[] toByteArray(ByteBuffer buffer) { + if (buffer.hasArray()) { + byte[] result = buffer.array(); + buffer.rewind(); + return result; + } else { + byte[] result = new byte[buffer.remaining()]; + buffer.get(result); + buffer.rewind(); + return result; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java new file mode 100644 index 0000000000..9a78ad9b04 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java @@ -0,0 +1,187 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +/** + * This class serves to provide additional useful data to + * {@link EncryptionMaterialsProvider}s so they can more intelligently select + * the proper {@link EncryptionMaterials} or {@link DecryptionMaterials} for + * use. Any of the methods are permitted to return null. + *

+ * For the simplest cases, all a developer needs to provide in the context are: + *

    + *
  • TableName
  • + *
  • HashKeyName
  • + *
  • RangeKeyName (if present)
  • + *
+ * + * This class is immutable. + * + * @author Greg Rubin + */ +public final class EncryptionContext { + private final String tableName; + private final Map attributeValues; + private final Object developerContext; + private final String hashKeyName; + private final String rangeKeyName; + private final Map materialDescription; + + /** + * Return a new builder that can be used to construct an {@link EncryptionContext} + * @return A newly initialized {@link EncryptionContext.Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + private EncryptionContext(Builder builder) { + tableName = builder.tableName; + attributeValues = builder.attributeValues; + developerContext = builder.developerContext; + hashKeyName = builder.hashKeyName; + rangeKeyName = builder.rangeKeyName; + materialDescription = builder.materialDescription; + } + + /** + * Returns the name of the DynamoDB Table this record is associated with. + */ + public String getTableName() { + return tableName; + } + + /** + * Returns the DynamoDB record about to be encrypted/decrypted. + */ + public Map getAttributeValues() { + return attributeValues; + } + + /** + * This object has no meaning (and will not be set or examined) by any core libraries. + * It exists to allow custom object mappers and data access layers to pass + * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDbEncryptor}. + */ + public Object getDeveloperContext() { + return developerContext; + } + + /** + * Returns the name of the HashKey attribute for the record to be encrypted/decrypted. + */ + public String getHashKeyName() { + return hashKeyName; + } + + /** + * Returns the name of the RangeKey attribute for the record to be encrypted/decrypted. + */ + public String getRangeKeyName() { + return rangeKeyName; + } + + public Map getMaterialDescription() { + return materialDescription; + } + + /** + * Converts an existing {@link EncryptionContext} into a builder that can be used to mutate and make a new version. + * @return A new {@link EncryptionContext.Builder} with all the fields filled out to match the current object. + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Builder class for {@link EncryptionContext}. + * Mutable objects (other than developerContext) will undergo + * a defensive copy prior to being stored in the builder. + * + * This class is not thread-safe. + */ + public static final class Builder { + private String tableName = null; + private Map attributeValues = null; + private Object developerContext = null; + private String hashKeyName = null; + private String rangeKeyName = null; + private Map materialDescription = null; + + public Builder() { + } + + public Builder(EncryptionContext context) { + tableName = context.getTableName(); + attributeValues = context.getAttributeValues(); + hashKeyName = context.getHashKeyName(); + rangeKeyName = context.getRangeKeyName(); + developerContext = context.getDeveloperContext(); + materialDescription = context.getMaterialDescription(); + } + + public EncryptionContext build() { + return new EncryptionContext(this); + } + + public Builder tableName(String tableName) { + this.tableName = tableName; + return this; + } + + public Builder attributeValues(Map attributeValues) { + this.attributeValues = Collections.unmodifiableMap(new HashMap<>(attributeValues)); + return this; + } + + public Builder developerContext(Object developerContext) { + this.developerContext = developerContext; + return this; + } + + public Builder hashKeyName(String hashKeyName) { + this.hashKeyName = hashKeyName; + return this; + } + + public Builder rangeKeyName(String rangeKeyName) { + this.rangeKeyName = rangeKeyName; + return this; + } + + public Builder materialDescription(Map materialDescription) { + this.materialDescription = Collections.unmodifiableMap(new HashMap<>(materialDescription)); + return this; + } + } + + @Override + public String toString() { + return "EncryptionContext [tableName=" + tableName + ", attributeValues=" + attributeValues + + ", developerContext=" + developerContext + + ", hashKeyName=" + hashKeyName + ", rangeKeyName=" + rangeKeyName + + ", materialDescription=" + materialDescription + "]"; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java new file mode 100644 index 0000000000..47329f7128 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java @@ -0,0 +1,23 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +/** + * @author Greg Rubin + */ +public enum EncryptionFlags { + ENCRYPT, + SIGN +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java new file mode 100644 index 0000000000..f245d66e31 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java @@ -0,0 +1,47 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions; + +/** + * Generic exception thrown for any problem the DynamoDB encryption client has performing tasks + */ +public class DynamoDbEncryptionException extends RuntimeException { + private static final long serialVersionUID = - 7565904179772520868L; + + /** + * Standard constructor + * @param cause exception cause + */ + public DynamoDbEncryptionException(Throwable cause) { + super(cause); + } + + /** + * Standard constructor + * @param message exception message + */ + public DynamoDbEncryptionException(String message) { + super(message); + } + + /** + * Standard constructor + * @param message exception message + * @param cause exception cause + */ + public DynamoDbEncryptionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java new file mode 100644 index 0000000000..5dfbb19709 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AbstractRawMaterials.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.Key; +import java.security.KeyPair; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public abstract class AbstractRawMaterials implements DecryptionMaterials, EncryptionMaterials { + private Map description; + private final Key signingKey; + private final Key verificationKey; + + @SuppressWarnings("unchecked") + protected AbstractRawMaterials(KeyPair signingPair) { + this(signingPair, Collections.EMPTY_MAP); + } + + protected AbstractRawMaterials(KeyPair signingPair, Map description) { + this.signingKey = signingPair.getPrivate(); + this.verificationKey = signingPair.getPublic(); + setMaterialDescription(description); + } + + @SuppressWarnings("unchecked") + protected AbstractRawMaterials(SecretKey macKey) { + this(macKey, Collections.EMPTY_MAP); + } + + protected AbstractRawMaterials(SecretKey macKey, Map description) { + this.signingKey = macKey; + this.verificationKey = macKey; + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + } + + @Override + public Map getMaterialDescription() { + return new HashMap<>(description); + } + + public void setMaterialDescription(Map description) { + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + } + + @Override + public Key getSigningKey() { + return signingKey; + } + + @Override + public Key getVerificationKey() { + return verificationKey; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java new file mode 100644 index 0000000000..003d0b60cc --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterials.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public class AsymmetricRawMaterials extends WrappedRawMaterials { + @SuppressWarnings("unchecked") + public AsymmetricRawMaterials(KeyPair encryptionKey, KeyPair signingPair) + throws GeneralSecurityException { + this(encryptionKey, signingPair, Collections.EMPTY_MAP); + } + + public AsymmetricRawMaterials(KeyPair encryptionKey, KeyPair signingPair, Map description) + throws GeneralSecurityException { + super(encryptionKey.getPublic(), encryptionKey.getPrivate(), signingPair, description); + } + + @SuppressWarnings("unchecked") + public AsymmetricRawMaterials(KeyPair encryptionKey, SecretKey macKey) + throws GeneralSecurityException { + this(encryptionKey, macKey, Collections.EMPTY_MAP); + } + + public AsymmetricRawMaterials(KeyPair encryptionKey, SecretKey macKey, Map description) + throws GeneralSecurityException { + super(encryptionKey.getPublic(), encryptionKey.getPrivate(), macKey, description); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java new file mode 100644 index 0000000000..033d331f5b --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.util.Map; + +/** + * @author Greg Rubin + */ +public interface CryptographicMaterials { + Map getMaterialDescription(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java new file mode 100644 index 0000000000..00f8548bc7 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.Key; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public interface DecryptionMaterials extends CryptographicMaterials { + SecretKey getDecryptionKey(); + Key getVerificationKey(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java new file mode 100644 index 0000000000..ecef9e9fc8 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.Key; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public interface EncryptionMaterials extends CryptographicMaterials { + SecretKey getEncryptionKey(); + Key getSigningKey(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java new file mode 100644 index 0000000000..b3daab44ba --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterials.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.KeyPair; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; + +/** + * @author Greg Rubin + */ +public class SymmetricRawMaterials extends AbstractRawMaterials { + private final SecretKey cryptoKey; + + @SuppressWarnings("unchecked") + public SymmetricRawMaterials(SecretKey encryptionKey, KeyPair signingPair) { + this(encryptionKey, signingPair, Collections.EMPTY_MAP); + } + + public SymmetricRawMaterials(SecretKey encryptionKey, KeyPair signingPair, Map description) { + super(signingPair, description); + this.cryptoKey = encryptionKey; + } + + @SuppressWarnings("unchecked") + public SymmetricRawMaterials(SecretKey encryptionKey, SecretKey macKey) { + this(encryptionKey, macKey, Collections.EMPTY_MAP); + } + + public SymmetricRawMaterials(SecretKey encryptionKey, SecretKey macKey, Map description) { + super(macKey, description); + this.cryptoKey = encryptionKey; + } + + @Override + public SecretKey getEncryptionKey() { + return cryptoKey; + } + + @Override + public SecretKey getDecryptionKey() { + return cryptoKey; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java new file mode 100644 index 0000000000..fd17521ca1 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/WrappedRawMaterials.java @@ -0,0 +1,212 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DelegatedKey; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +/** + * Represents cryptographic materials used to manage unique record-level keys. + * This class specifically implements Envelope Encryption where a unique content + * key is randomly generated each time this class is constructed which is then + * encrypted with the Wrapping Key and then persisted in the Description. If a + * wrapped key is present in the Description, then that content key is unwrapped + * and used to decrypt the actual data in the record. + * + * Other possibly implementations might use a Key-Derivation Function to derive + * a unique key per record. + * + * @author Greg Rubin + */ +public class WrappedRawMaterials extends AbstractRawMaterials { + /** + * The key-name in the Description which contains the algorithm use to wrap + * content key. Example values are "AESWrap", or + * "RSA/ECB/OAEPWithSHA-256AndMGF1Padding". + */ + public static final String KEY_WRAPPING_ALGORITHM = "amzn-ddb-wrap-alg"; + /** + * The key-name in the Description which contains the algorithm used by the + * content key. Example values are "AES", or "Blowfish". + */ + public static final String CONTENT_KEY_ALGORITHM = "amzn-ddb-env-alg"; + /** + * The key-name in the Description which which contains the wrapped content + * key. + */ + public static final String ENVELOPE_KEY = "amzn-ddb-env-key"; + + private static final String DEFAULT_ALGORITHM = "AES/256"; + + protected final Key wrappingKey; + protected final Key unwrappingKey; + private final SecretKey envelopeKey; + + public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, KeyPair signingPair) + throws GeneralSecurityException { + this(wrappingKey, unwrappingKey, signingPair, Collections.emptyMap()); + } + + public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, KeyPair signingPair, + Map description) throws GeneralSecurityException { + super(signingPair, description); + this.wrappingKey = wrappingKey; + this.unwrappingKey = unwrappingKey; + this.envelopeKey = initEnvelopeKey(); + } + + public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, SecretKey macKey) + throws GeneralSecurityException { + this(wrappingKey, unwrappingKey, macKey, Collections.emptyMap()); + } + + public WrappedRawMaterials(Key wrappingKey, Key unwrappingKey, SecretKey macKey, + Map description) throws GeneralSecurityException { + super(macKey, description); + this.wrappingKey = wrappingKey; + this.unwrappingKey = unwrappingKey; + this.envelopeKey = initEnvelopeKey(); + } + + @Override + public SecretKey getDecryptionKey() { + return envelopeKey; + } + + @Override + public SecretKey getEncryptionKey() { + return envelopeKey; + } + + /** + * Called by the constructors. If there is already a key associated with + * this record (usually signified by a value stored in the description in + * the key {@link #ENVELOPE_KEY}) it extracts it and returns it. Otherwise + * it generates a new key, stores a wrapped version in the Description, and + * returns the key to the caller. + * + * @return the content key (which is returned by both + * {@link #getDecryptionKey()} and {@link #getEncryptionKey()}. + * @throws GeneralSecurityException if there is a problem + */ + protected SecretKey initEnvelopeKey() throws GeneralSecurityException { + Map description = getMaterialDescription(); + if (description.containsKey(ENVELOPE_KEY)) { + if (unwrappingKey == null) { + throw new IllegalStateException("No private decryption key provided."); + } + byte[] encryptedKey = Base64.decode(description.get(ENVELOPE_KEY)); + String wrappingAlgorithm = unwrappingKey.getAlgorithm(); + if (description.containsKey(KEY_WRAPPING_ALGORITHM)) { + wrappingAlgorithm = description.get(KEY_WRAPPING_ALGORITHM); + } + return unwrapKey(description, encryptedKey, wrappingAlgorithm); + } else { + SecretKey key = description.containsKey(CONTENT_KEY_ALGORITHM) ? + generateContentKey(description.get(CONTENT_KEY_ALGORITHM)) : + generateContentKey(DEFAULT_ALGORITHM); + + String wrappingAlg = description.containsKey(KEY_WRAPPING_ALGORITHM) ? + description.get(KEY_WRAPPING_ALGORITHM) : + getTransformation(wrappingKey.getAlgorithm()); + byte[] encryptedKey = wrapKey(key, wrappingAlg); + description.put(ENVELOPE_KEY, Base64.encodeToString(encryptedKey)); + description.put(CONTENT_KEY_ALGORITHM, key.getAlgorithm()); + description.put(KEY_WRAPPING_ALGORITHM, wrappingAlg); + setMaterialDescription(description); + return key; + } + } + + public byte[] wrapKey(SecretKey key, String wrappingAlg) throws NoSuchAlgorithmException, NoSuchPaddingException, + InvalidKeyException, IllegalBlockSizeException { + if (wrappingKey instanceof DelegatedKey) { + return ((DelegatedKey)wrappingKey).wrap(key, null, wrappingAlg); + } else { + Cipher cipher = Cipher.getInstance(wrappingAlg); + cipher.init(Cipher.WRAP_MODE, wrappingKey, Utils.getRng()); + return cipher.wrap(key); + } + } + + protected SecretKey unwrapKey( + Map description, byte[] encryptedKey, String wrappingAlgorithm) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { + if (unwrappingKey instanceof DelegatedKey) { + return (SecretKey) + ((DelegatedKey) unwrappingKey) + .unwrap( + encryptedKey, + description.get(CONTENT_KEY_ALGORITHM), + Cipher.SECRET_KEY, + null, + wrappingAlgorithm); + } else { + Cipher cipher = Cipher.getInstance(wrappingAlgorithm); + + // This can be of the form "AES/256" as well as "AES" e.g., + // but we want to set the SecretKey with just "AES" in either case + String[] algPieces = description.get(CONTENT_KEY_ALGORITHM).split("/", 2); + String contentKeyAlgorithm = algPieces[0]; + + cipher.init(Cipher.UNWRAP_MODE, unwrappingKey, Utils.getRng()); + return (SecretKey) cipher.unwrap(encryptedKey, contentKeyAlgorithm, Cipher.SECRET_KEY); + } + } + + protected SecretKey generateContentKey(final String algorithm) throws NoSuchAlgorithmException { + String[] pieces = algorithm.split("/", 2); + KeyGenerator kg = KeyGenerator.getInstance(pieces[0]); + int keyLen = 0; + if (pieces.length == 2) { + try { + keyLen = Integer.parseInt(pieces[1]); + } catch (NumberFormatException ignored) { + } + } + + if (keyLen > 0) { + kg.init(keyLen, Utils.getRng()); + } else { + kg.init(Utils.getRng()); + } + return kg.generateKey(); + } + + private static String getTransformation(final String algorithm) { + if (algorithm.equalsIgnoreCase("RSA")) { + return "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; + } else if (algorithm.equalsIgnoreCase("AES")) { + return "AESWrap"; + } else { + return algorithm; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java new file mode 100644 index 0000000000..b49e2b9a20 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import java.security.KeyPair; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; + +/** + * This is a thin wrapper around the {@link WrappedMaterialsProvider}, using + * the provided encryptionKey for wrapping and unwrapping the + * record key. Please see that class for detailed documentation. + * + * @author Greg Rubin + */ +public class AsymmetricStaticProvider extends WrappedMaterialsProvider { + public AsymmetricStaticProvider(KeyPair encryptionKey, KeyPair signingPair) { + this(encryptionKey, signingPair, Collections.emptyMap()); + } + + public AsymmetricStaticProvider(KeyPair encryptionKey, SecretKey macKey) { + this(encryptionKey, macKey, Collections.emptyMap()); + } + + public AsymmetricStaticProvider(KeyPair encryptionKey, KeyPair signingPair, Map description) { + super(encryptionKey.getPublic(), encryptionKey.getPrivate(), signingPair, description); + } + + public AsymmetricStaticProvider(KeyPair encryptionKey, SecretKey macKey, Map description) { + super(encryptionKey.getPublic(), encryptionKey.getPrivate(), macKey, description); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java new file mode 100644 index 0000000000..653e754c26 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProvider.java @@ -0,0 +1,183 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import io.netty.util.internal.ObjectUtil; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.TTLCache; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.TTLCache.EntryLoader; +import java.util.concurrent.TimeUnit; + +/** + * This meta-Provider encrypts data with the most recent version of keying materials from a {@link + * ProviderStore} and decrypts using whichever version is appropriate. It also caches the results + * from the {@link ProviderStore} to avoid excessive load on the backing systems. + */ +public class CachingMostRecentProvider implements EncryptionMaterialsProvider { + private static final long INITIAL_VERSION = 0; + private static final String PROVIDER_CACHE_KEY_DELIM = "#"; + private static final int DEFAULT_CACHE_MAX_SIZE = 1000; + + private final long ttlInNanos; + private final ProviderStore keystore; + protected final String defaultMaterialName; + private final TTLCache providerCache; + private final TTLCache versionCache; + + private final EntryLoader versionLoader = + new EntryLoader() { + @Override + public Long load(String entryKey) { + return keystore.getMaxVersion(entryKey); + } + }; + private final EntryLoader providerLoader = + new EntryLoader() { + @Override + public EncryptionMaterialsProvider load(String entryKey) { + final String[] parts = entryKey.split(PROVIDER_CACHE_KEY_DELIM, 2); + if (parts.length != 2) { + throw new IllegalStateException("Invalid cache key for provider cache: " + entryKey); + } + return keystore.getProvider(parts[0], Long.parseLong(parts[1])); + } + }; + + /** + * Creates a new {@link CachingMostRecentProvider}. + * + * @param keystore The key store that this provider will use to determine which material and which + * version of material to use + * @param materialName The name of the materials associated with this provider + * @param ttlInMillis The length of time in milliseconds to cache the most recent provider + */ + public CachingMostRecentProvider( + final ProviderStore keystore, final String materialName, final long ttlInMillis) { + this(keystore, materialName, ttlInMillis, DEFAULT_CACHE_MAX_SIZE); + } + + /** + * Creates a new {@link CachingMostRecentProvider}. + * + * @param keystore The key store that this provider will use to determine which material and which + * version of material to use + * @param materialName The name of the materials associated with this provider + * @param ttlInMillis The length of time in milliseconds to cache the most recent provider + * @param maxCacheSize The maximum size of the underlying caches this provider uses. Entries will + * be evicted from the cache once this size is exceeded. + */ + public CachingMostRecentProvider( + final ProviderStore keystore, + final String materialName, + final long ttlInMillis, + final int maxCacheSize) { + this.keystore = ObjectUtil.checkNotNull(keystore, "keystore must not be null"); + this.defaultMaterialName = materialName; + this.ttlInNanos = TimeUnit.MILLISECONDS.toNanos(ttlInMillis); + + this.providerCache = new TTLCache<>(maxCacheSize, ttlInMillis, providerLoader); + this.versionCache = new TTLCache<>(maxCacheSize, ttlInMillis, versionLoader); + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + final long version = + keystore.getVersionFromMaterialDescription(context.getMaterialDescription()); + final String materialName = getMaterialName(context); + final String cacheKey = buildCacheKey(materialName, version); + + EncryptionMaterialsProvider provider = providerCache.load(cacheKey); + return provider.getDecryptionMaterials(context); + } + + + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + final String materialName = getMaterialName(context); + final long currentVersion = versionCache.load(materialName); + + if (currentVersion < 0) { + // The material hasn't been created yet, so specify a loading function + // to create the first version of materials and update both caches. + // We want this to be done as part of the cache load to ensure that this logic + // only happens once in a multithreaded environment, + // in order to limit calls to the keystore's dependencies. + final String cacheKey = buildCacheKey(materialName, INITIAL_VERSION); + EncryptionMaterialsProvider newProvider = + providerCache.load( + cacheKey, + s -> { + // Create the new material in the keystore + final String[] parts = s.split(PROVIDER_CACHE_KEY_DELIM, 2); + if (parts.length != 2) { + throw new IllegalStateException("Invalid cache key for provider cache: " + s); + } + EncryptionMaterialsProvider provider = + keystore.getOrCreate(parts[0], Long.parseLong(parts[1])); + + // We now should have version 0 in our keystore. + // Update the version cache for this material as a side effect + versionCache.put(materialName, INITIAL_VERSION); + + // Return the new materials to be put into the cache + return provider; + }); + + return newProvider.getEncryptionMaterials(context); + } else { + final String cacheKey = buildCacheKey(materialName, currentVersion); + return providerCache.load(cacheKey).getEncryptionMaterials(context); + } + } + + @Override + public void refresh() { + versionCache.clear(); + providerCache.clear(); + } + + public String getMaterialName() { + return defaultMaterialName; + } + + public long getTtlInMills() { + return TimeUnit.NANOSECONDS.toMillis(ttlInNanos); + } + + /** + * The current version of the materials being used for encryption. Returns -1 if we do not + * currently have a current version. + */ + public long getCurrentVersion() { + return versionCache.load(getMaterialName()); + } + + /** + * The last time the current version was updated. Returns 0 if we do not currently have a current + * version. + */ + public long getLastUpdated() { + // We cache a version of -1 to mean that there is not a current version + if (versionCache.load(getMaterialName()) < 0) { + return 0; + } + // Otherwise, return the last update time of that entry + return TimeUnit.NANOSECONDS.toMillis(versionCache.getLastUpdated(getMaterialName())); + } + + protected String getMaterialName(final EncryptionContext context) { + return defaultMaterialName; + } + + private static String buildCacheKey(final String materialName, final long version) { + StringBuilder result = new StringBuilder(materialName); + result.append(PROVIDER_CACHE_KEY_DELIM); + result.append(version); + return result.toString(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java new file mode 100644 index 0000000000..425a4119f2 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java @@ -0,0 +1,296 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.CONTENT_KEY_ALGORITHM; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.ENVELOPE_KEY; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials.KEY_WRAPPING_ALGORITHM; + +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Hkdf; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.DecryptRequest; +import software.amazon.awssdk.services.kms.model.DecryptResponse; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; + +/** + * Generates a unique data key for each record in DynamoDB and protects that key + * using {@link KmsClient}. Currently, the HashKey, RangeKey, and TableName will be + * included in the KMS EncryptionContext for wrapping/unwrapping the key. This + * means that records cannot be copied/moved between tables without re-encryption. + * + * @see KMS Encryption Context + */ +public class DirectKmsMaterialsProvider implements EncryptionMaterialsProvider { + private static final String COVERED_ATTR_CTX_KEY = "aws-kms-ec-attr"; + private static final String SIGNING_KEY_ALGORITHM = "amzn-ddb-sig-alg"; + private static final String TABLE_NAME_EC_KEY = "*aws-kms-table*"; + + private static final String DEFAULT_ENC_ALG = "AES/256"; + private static final String DEFAULT_SIG_ALG = "HmacSHA256/256"; + private static final String KEY_COVERAGE = "*keys*"; + private static final String KDF_ALG = "HmacSHA256"; + private static final String KDF_SIG_INFO = "Signing"; + private static final String KDF_ENC_INFO = "Encryption"; + + private final KmsClient kms; + private final String encryptionKeyId; + private final Map description; + private final String dataKeyAlg; + private final int dataKeyLength; + private final String dataKeyDesc; + private final String sigKeyAlg; + private final int sigKeyLength; + private final String sigKeyDesc; + + public DirectKmsMaterialsProvider(KmsClient kms) { + this(kms, null); + } + + public DirectKmsMaterialsProvider(KmsClient kms, String encryptionKeyId, Map materialDescription) { + this.kms = kms; + this.encryptionKeyId = encryptionKeyId; + this.description = materialDescription != null ? + Collections.unmodifiableMap(new HashMap<>(materialDescription)) : + Collections.emptyMap(); + + dataKeyDesc = description.getOrDefault(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, DEFAULT_ENC_ALG); + + String[] parts = dataKeyDesc.split("/", 2); + this.dataKeyAlg = parts[0]; + this.dataKeyLength = parts.length == 2 ? Integer.parseInt(parts[1]) : 256; + + sigKeyDesc = description.getOrDefault(SIGNING_KEY_ALGORITHM, DEFAULT_SIG_ALG); + + parts = sigKeyDesc.split("/", 2); + this.sigKeyAlg = parts[0]; + this.sigKeyLength = parts.length == 2 ? Integer.parseInt(parts[1]) : 256; + } + + public DirectKmsMaterialsProvider(KmsClient kms, String encryptionKeyId) { + this(kms, encryptionKeyId, Collections.emptyMap()); + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + final Map materialDescription = context.getMaterialDescription(); + + final Map ec = new HashMap<>(); + final String providedEncAlg = materialDescription.get(CONTENT_KEY_ALGORITHM); + final String providedSigAlg = materialDescription.get(SIGNING_KEY_ALGORITHM); + + ec.put("*" + CONTENT_KEY_ALGORITHM + "*", providedEncAlg); + ec.put("*" + SIGNING_KEY_ALGORITHM + "*", providedSigAlg); + + populateKmsEcFromEc(context, ec); + + DecryptRequest.Builder request = DecryptRequest.builder(); + request.ciphertextBlob(SdkBytes.fromByteArray(Base64.decode(materialDescription.get(ENVELOPE_KEY)))); + request.encryptionContext(ec); + final DecryptResponse decryptResponse = decrypt(request.build(), context); + validateEncryptionKeyId(decryptResponse.keyId(), context); + + final Hkdf kdf; + try { + kdf = Hkdf.getInstance(KDF_ALG); + } catch (NoSuchAlgorithmException e) { + throw new DynamoDbEncryptionException(e); + } + kdf.init(decryptResponse.plaintext().asByteArray()); + + final String[] encAlgParts = providedEncAlg.split("/", 2); + int encLength = encAlgParts.length == 2 ? Integer.parseInt(encAlgParts[1]) : 256; + final String[] sigAlgParts = providedSigAlg.split("/", 2); + int sigLength = sigAlgParts.length == 2 ? Integer.parseInt(sigAlgParts[1]) : 256; + + final SecretKey encryptionKey = new SecretKeySpec(kdf.deriveKey(KDF_ENC_INFO, encLength / 8), encAlgParts[0]); + final SecretKey macKey = new SecretKeySpec(kdf.deriveKey(KDF_SIG_INFO, sigLength / 8), sigAlgParts[0]); + + return new SymmetricRawMaterials(encryptionKey, macKey, materialDescription); + } + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + final Map ec = new HashMap<>(); + ec.put("*" + CONTENT_KEY_ALGORITHM + "*", dataKeyDesc); + ec.put("*" + SIGNING_KEY_ALGORITHM + "*", sigKeyDesc); + populateKmsEcFromEc(context, ec); + + final String keyId = selectEncryptionKeyId(context); + if (keyId == null || keyId.isEmpty()) { + throw new DynamoDbEncryptionException("Encryption key id is empty."); + } + + final GenerateDataKeyRequest.Builder req = GenerateDataKeyRequest.builder(); + req.keyId(keyId); + // NumberOfBytes parameter is used because we're not using this key as an AES-256 key, + // we're using it as an HKDF-SHA256 key. + req.numberOfBytes(256 / 8); + req.encryptionContext(ec); + + final GenerateDataKeyResponse dataKeyResult = generateDataKey(req.build(), context); + + final Map materialDescription = new HashMap<>(description); + materialDescription.put(COVERED_ATTR_CTX_KEY, KEY_COVERAGE); + materialDescription.put(KEY_WRAPPING_ALGORITHM, "kms"); + materialDescription.put(CONTENT_KEY_ALGORITHM, dataKeyDesc); + materialDescription.put(SIGNING_KEY_ALGORITHM, sigKeyDesc); + materialDescription.put(ENVELOPE_KEY, + Base64.encodeToString(dataKeyResult.ciphertextBlob().asByteArray())); + + final Hkdf kdf; + try { + kdf = Hkdf.getInstance(KDF_ALG); + } catch (NoSuchAlgorithmException e) { + throw new DynamoDbEncryptionException(e); + } + + kdf.init(dataKeyResult.plaintext().asByteArray()); + + final SecretKey encryptionKey = new SecretKeySpec(kdf.deriveKey(KDF_ENC_INFO, dataKeyLength / 8), dataKeyAlg); + final SecretKey signatureKey = new SecretKeySpec(kdf.deriveKey(KDF_SIG_INFO, sigKeyLength / 8), sigKeyAlg); + return new SymmetricRawMaterials(encryptionKey, signatureKey, materialDescription); + } + + /** + * Get encryption key id that is used to create the {@link EncryptionMaterials}. + * + * @return encryption key id. + */ + protected String getEncryptionKeyId() { + return this.encryptionKeyId; + } + + /** + * Select encryption key id to be used to generate data key. The default implementation of this method returns + * {@link DirectKmsMaterialsProvider#encryptionKeyId}. + * + * @param context encryption context. + * @return the encryptionKeyId. + * @throws DynamoDbEncryptionException when we fails to select a valid encryption key id. + */ + protected String selectEncryptionKeyId(EncryptionContext context) throws DynamoDbEncryptionException { + return getEncryptionKeyId(); + } + + /** + * Validate the encryption key id. The default implementation of this method does not validate + * encryption key id. + * + * @param encryptionKeyId encryption key id from {@link DecryptResponse}. + * @param context encryption context. + * @throws DynamoDbEncryptionException when encryptionKeyId is invalid. + */ + protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext context) + throws DynamoDbEncryptionException { + // No action taken. + } + + /** + * Decrypts ciphertext. The default implementation calls KMS to decrypt the ciphertext using the parameters + * provided in the {@link DecryptRequest}. Subclass can override the default implementation to provide + * additional request parameters using attributes within the {@link EncryptionContext}. + * + * @param request request parameters to decrypt the given ciphertext. + * @param context additional useful data to decrypt the ciphertext. + * @return the decrypted plaintext for the given ciphertext. + */ + protected DecryptResponse decrypt(final DecryptRequest request, final EncryptionContext context) { + return kms.decrypt(request); + } + + /** + * Returns a data encryption key that you can use in your application to encrypt data locally. The default + * implementation calls KMS to generate the data key using the parameters provided in the + * {@link GenerateDataKeyRequest}. Subclass can override the default implementation to provide additional + * request parameters using attributes within the {@link EncryptionContext}. + * + * @param request request parameters to generate the data key. + * @param context additional useful data to generate the data key. + * @return the newly generated data key which includes both the plaintext and ciphertext. + */ + protected GenerateDataKeyResponse generateDataKey(final GenerateDataKeyRequest request, + final EncryptionContext context) { + return kms.generateDataKey(request); + } + + /** + * Extracts relevant information from {@code context} and uses it to populate fields in + * {@code kmsEc}. Currently, these fields are: + *
+ *
{@code HashKeyName}
+ *
{@code HashKeyValue}
+ *
{@code RangeKeyName}
+ *
{@code RangeKeyValue}
+ *
{@link #TABLE_NAME_EC_KEY}
+ *
{@code TableName}
+ */ + private static void populateKmsEcFromEc(EncryptionContext context, Map kmsEc) { + final String hashKeyName = context.getHashKeyName(); + if (hashKeyName != null) { + final AttributeValue hashKey = context.getAttributeValues().get(hashKeyName); + if (hashKey.n() != null) { + kmsEc.put(hashKeyName, hashKey.n()); + } else if (hashKey.s() != null) { + kmsEc.put(hashKeyName, hashKey.s()); + } else if (hashKey.b() != null) { + kmsEc.put(hashKeyName, Base64.encodeToString(hashKey.b().asByteArray())); + } else { + throw new UnsupportedOperationException("DirectKmsMaterialsProvider only supports String, Number, and Binary HashKeys"); + } + } + final String rangeKeyName = context.getRangeKeyName(); + if (rangeKeyName != null) { + final AttributeValue rangeKey = context.getAttributeValues().get(rangeKeyName); + if (rangeKey.n() != null) { + kmsEc.put(rangeKeyName, rangeKey.n()); + } else if (rangeKey.s() != null) { + kmsEc.put(rangeKeyName, rangeKey.s()); + } else if (rangeKey.b() != null) { + kmsEc.put(rangeKeyName, Base64.encodeToString(rangeKey.b().asByteArray())); + } else { + throw new UnsupportedOperationException("DirectKmsMaterialsProvider only supports String, Number, and Binary RangeKeys"); + } + } + + final String tableName = context.getTableName(); + if (tableName != null) { + kmsEc.put(TABLE_NAME_EC_KEY, tableName); + } + } + + @Override + public void refresh() { + // No action needed + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java new file mode 100644 index 0000000000..b60fee3ee0 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/EncryptionMaterialsProvider.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; + +/** + * Interface for providing encryption materials. + * Implementations are free to use any strategy for providing encryption + * materials, such as simply providing static material that doesn't change, + * or more complicated implementations, such as integrating with existing + * key management systems. + * + * @author Greg Rubin + */ +public interface EncryptionMaterialsProvider { + + /** + * Retrieves encryption materials matching the specified description from some source. + * + * @param context + * Information to assist in selecting a the proper return value. The implementation + * is free to determine the minimum necessary for successful processing. + * + * @return + * The encryption materials that match the description, or null if no matching encryption materials found. + */ + DecryptionMaterials getDecryptionMaterials(EncryptionContext context); + + /** + * Returns EncryptionMaterials which the caller can use for encryption. + * Each implementation of EncryptionMaterialsProvider can choose its own + * strategy for loading encryption material. For example, an + * implementation might load encryption material from an existing key + * management system, or load new encryption material when keys are + * rotated. + * + * @param context + * Information to assist in selecting a the proper return value. The implementation + * is free to determine the minimum necessary for successful processing. + * + * @return EncryptionMaterials which the caller can use to encrypt or + * decrypt data. + */ + EncryptionMaterials getEncryptionMaterials(EncryptionContext context); + + /** + * Forces this encryption materials provider to refresh its encryption + * material. For many implementations of encryption materials provider, + * this may simply be a no-op, such as any encryption materials provider + * implementation that vends static/non-changing encryption material. + * For other implementations that vend different encryption material + * throughout their lifetime, this method should force the encryption + * materials provider to refresh its encryption material. + */ + void refresh(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java new file mode 100644 index 0000000000..483b81b51a --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProvider.java @@ -0,0 +1,199 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.KeyStore.Entry; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.ProtectionParameter; +import java.security.KeyStore.SecretKeyEntry; +import java.security.KeyStore.TrustedCertificateEntry; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.UnrecoverableEntryException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.AsymmetricRawMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; + +/** + * @author Greg Rubin + */ +public class KeyStoreMaterialsProvider implements EncryptionMaterialsProvider { + private final Map description; + private final String encryptionAlias; + private final String signingAlias; + private final ProtectionParameter encryptionProtection; + private final ProtectionParameter signingProtection; + private final KeyStore keyStore; + private final AtomicReference currMaterials = + new AtomicReference<>(); + + public KeyStoreMaterialsProvider(KeyStore keyStore, String encryptionAlias, String signingAlias, Map description) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { + this(keyStore, encryptionAlias, signingAlias, null, null, description); + } + + public KeyStoreMaterialsProvider(KeyStore keyStore, String encryptionAlias, String signingAlias, + ProtectionParameter encryptionProtection, ProtectionParameter signingProtection, + Map description) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException { + super(); + this.keyStore = keyStore; + this.encryptionAlias = encryptionAlias; + this.signingAlias = signingAlias; + this.encryptionProtection = encryptionProtection; + this.signingProtection = signingProtection; + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + + validateKeys(); + loadKeys(); + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + CurrentMaterials materials = currMaterials.get(); + if (context.getMaterialDescription().entrySet().containsAll(description.entrySet())) { + if (materials.encryptionEntry instanceof SecretKeyEntry) { + return materials.symRawMaterials; + } else { + try { + return makeAsymMaterials(materials, context.getMaterialDescription()); + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to decrypt envelope key", ex); + } + } + } else { + return null; + } + } + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + CurrentMaterials materials = currMaterials.get(); + if (materials.encryptionEntry instanceof SecretKeyEntry) { + return materials.symRawMaterials; + } else { + try { + return makeAsymMaterials(materials, description); + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to encrypt envelope key", ex); + } + } + } + + private AsymmetricRawMaterials makeAsymMaterials(CurrentMaterials materials, + Map description) throws GeneralSecurityException { + KeyPair encryptionPair = entry2Pair(materials.encryptionEntry); + if (materials.signingEntry instanceof SecretKeyEntry) { + return new AsymmetricRawMaterials(encryptionPair, + ((SecretKeyEntry) materials.signingEntry).getSecretKey(), description); + } else { + return new AsymmetricRawMaterials(encryptionPair, entry2Pair(materials.signingEntry), + description); + } + } + + private static KeyPair entry2Pair(Entry entry) { + PublicKey pub = null; + PrivateKey priv = null; + + if (entry instanceof PrivateKeyEntry) { + PrivateKeyEntry pk = (PrivateKeyEntry) entry; + if (pk.getCertificate() != null) { + pub = pk.getCertificate().getPublicKey(); + } + priv = pk.getPrivateKey(); + } else if (entry instanceof TrustedCertificateEntry) { + TrustedCertificateEntry tc = (TrustedCertificateEntry) entry; + pub = tc.getTrustedCertificate().getPublicKey(); + } else { + throw new IllegalArgumentException( + "Only entry types PrivateKeyEntry and TrustedCertificateEntry are supported."); + } + return new KeyPair(pub, priv); + } + + /** + * Reloads the keys from the underlying keystore by calling + * {@link KeyStore#getEntry(String, ProtectionParameter)} again for each of them. + */ + @Override + public void refresh() { + try { + loadKeys(); + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to load keys from keystore", ex); + } + } + + private void validateKeys() throws KeyStoreException { + if (!keyStore.containsAlias(encryptionAlias)) { + throw new IllegalArgumentException("Keystore does not contain alias: " + + encryptionAlias); + } + if (!keyStore.containsAlias(signingAlias)) { + throw new IllegalArgumentException("Keystore does not contain alias: " + + signingAlias); + } + } + + private void loadKeys() throws NoSuchAlgorithmException, UnrecoverableEntryException, + KeyStoreException { + Entry encryptionEntry = keyStore.getEntry(encryptionAlias, encryptionProtection); + Entry signingEntry = keyStore.getEntry(signingAlias, signingProtection); + CurrentMaterials newMaterials = new CurrentMaterials(encryptionEntry, signingEntry); + currMaterials.set(newMaterials); + } + + private class CurrentMaterials { + public final Entry encryptionEntry; + public final Entry signingEntry; + public final SymmetricRawMaterials symRawMaterials; + + public CurrentMaterials(Entry encryptionEntry, Entry signingEntry) { + super(); + this.encryptionEntry = encryptionEntry; + this.signingEntry = signingEntry; + + if (encryptionEntry instanceof SecretKeyEntry) { + if (signingEntry instanceof SecretKeyEntry) { + this.symRawMaterials = new SymmetricRawMaterials( + ((SecretKeyEntry) encryptionEntry).getSecretKey(), + ((SecretKeyEntry) signingEntry).getSecretKey(), + description); + } else { + this.symRawMaterials = new SymmetricRawMaterials( + ((SecretKeyEntry) encryptionEntry).getSecretKey(), + entry2Pair(signingEntry), + description); + } + } else { + this.symRawMaterials = null; + } + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java new file mode 100644 index 0000000000..8a63a0328c --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProvider.java @@ -0,0 +1,130 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import java.security.KeyPair; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.CryptographicMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.SymmetricRawMaterials; + +/** + * A provider which always returns the same provided symmetric + * encryption/decryption key and the same signing/verification key(s). + * + * @author Greg Rubin + */ +public class SymmetricStaticProvider implements EncryptionMaterialsProvider { + private final SymmetricRawMaterials materials; + + /** + * @param encryptionKey + * the value to be returned by + * {@link #getEncryptionMaterials(EncryptionContext)} and + * {@link #getDecryptionMaterials(EncryptionContext)} + * @param signingPair + * the keypair used to sign/verify the data stored in Dynamo. If + * only the public key is provided, then this provider may be + * used for decryption, but not encryption. + */ + public SymmetricStaticProvider(SecretKey encryptionKey, KeyPair signingPair) { + this(encryptionKey, signingPair, Collections.emptyMap()); + } + + /** + * @param encryptionKey + * the value to be returned by + * {@link #getEncryptionMaterials(EncryptionContext)} and + * {@link #getDecryptionMaterials(EncryptionContext)} + * @param signingPair + * the keypair used to sign/verify the data stored in Dynamo. If + * only the public key is provided, then this provider may be + * used for decryption, but not encryption. + * @param description + * the value to be returned by + * {@link CryptographicMaterials#getMaterialDescription()} for + * any {@link CryptographicMaterials} returned by this object. + */ + public SymmetricStaticProvider(SecretKey encryptionKey, + KeyPair signingPair, Map description) { + materials = new SymmetricRawMaterials(encryptionKey, signingPair, + description); + } + + /** + * @param encryptionKey + * the value to be returned by + * {@link #getEncryptionMaterials(EncryptionContext)} and + * {@link #getDecryptionMaterials(EncryptionContext)} + * @param macKey + * the key used to sign/verify the data stored in Dynamo. + */ + public SymmetricStaticProvider(SecretKey encryptionKey, SecretKey macKey) { + this(encryptionKey, macKey, Collections.emptyMap()); + } + + /** + * @param encryptionKey + * the value to be returned by + * {@link #getEncryptionMaterials(EncryptionContext)} and + * {@link #getDecryptionMaterials(EncryptionContext)} + * @param macKey + * the key used to sign/verify the data stored in Dynamo. + * @param description + * the value to be returned by + * {@link CryptographicMaterials#getMaterialDescription()} for + * any {@link CryptographicMaterials} returned by this object. + */ + public SymmetricStaticProvider(SecretKey encryptionKey, SecretKey macKey, Map description) { + materials = new SymmetricRawMaterials(encryptionKey, macKey, description); + } + + /** + * Returns the encryptionKey provided to the constructor if and only if + * materialDescription is a super-set (may be equal) to the + * description provided to the constructor. + */ + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + if (context.getMaterialDescription().entrySet().containsAll(materials.getMaterialDescription().entrySet())) { + return materials; + } + else { + return null; + } + } + + /** + * Returns the encryptionKey provided to the constructor. + */ + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + return materials; + } + + /** + * Does nothing. + */ + @Override + public void refresh() { + // Do Nothing + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java new file mode 100644 index 0000000000..1c92fb3f4a --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProvider.java @@ -0,0 +1,163 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyPair; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.SecretKey; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.CryptographicMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; + +/** + * This provider will use create a unique (random) symmetric key upon each call to + * {@link #getEncryptionMaterials(EncryptionContext)}. Practically, this means each record in DynamoDB will be + * encrypted under a unique record key. A wrapped/encrypted copy of this record key is stored in the + * MaterialsDescription field of that record and is unwrapped/decrypted upon reading that record. + * + * This is generally a more secure way of encrypting data than with the + * {@link SymmetricStaticProvider}. + * + * @see WrappedRawMaterials + * + * @author Greg Rubin + */ +public class WrappedMaterialsProvider implements EncryptionMaterialsProvider { + private final Key wrappingKey; + private final Key unwrappingKey; + private final KeyPair sigPair; + private final SecretKey macKey; + private final Map description; + + /** + * @param wrappingKey + * The key used to wrap/encrypt the symmetric record key. (May be the same as the + * unwrappingKey.) + * @param unwrappingKey + * The key used to unwrap/decrypt the symmetric record key. (May be the same as the + * wrappingKey.) If null, then this provider may only be used for + * decryption, but not encryption. + * @param signingPair + * the keypair used to sign/verify the data stored in Dynamo. If only the public key + * is provided, then this provider may only be used for decryption, but not + * encryption. + */ + public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, KeyPair signingPair) { + this(wrappingKey, unwrappingKey, signingPair, Collections.emptyMap()); + } + + /** + * @param wrappingKey + * The key used to wrap/encrypt the symmetric record key. (May be the same as the + * unwrappingKey.) + * @param unwrappingKey + * The key used to unwrap/decrypt the symmetric record key. (May be the same as the + * wrappingKey.) If null, then this provider may only be used for + * decryption, but not encryption. + * @param signingPair + * the keypair used to sign/verify the data stored in Dynamo. If only the public key + * is provided, then this provider may only be used for decryption, but not + * encryption. + * @param description + * description the value to be returned by + * {@link CryptographicMaterials#getMaterialDescription()} for any + * {@link CryptographicMaterials} returned by this object. + */ + public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, KeyPair signingPair, Map description) { + this.wrappingKey = wrappingKey; + this.unwrappingKey = unwrappingKey; + this.sigPair = signingPair; + this.macKey = null; + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + } + + /** + * @param wrappingKey + * The key used to wrap/encrypt the symmetric record key. (May be the same as the + * unwrappingKey.) + * @param unwrappingKey + * The key used to unwrap/decrypt the symmetric record key. (May be the same as the + * wrappingKey.) If null, then this provider may only be used for + * decryption, but not encryption. + * @param macKey + * the key used to sign/verify the data stored in Dynamo. + */ + public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, SecretKey macKey) { + this(wrappingKey, unwrappingKey, macKey, Collections.emptyMap()); + } + + /** + * @param wrappingKey + * The key used to wrap/encrypt the symmetric record key. (May be the same as the + * unwrappingKey.) + * @param unwrappingKey + * The key used to unwrap/decrypt the symmetric record key. (May be the same as the + * wrappingKey.) If null, then this provider may only be used for + * decryption, but not encryption. + * @param macKey + * the key used to sign/verify the data stored in Dynamo. + * @param description + * description the value to be returned by + * {@link CryptographicMaterials#getMaterialDescription()} for any + * {@link CryptographicMaterials} returned by this object. + */ + public WrappedMaterialsProvider(Key wrappingKey, Key unwrappingKey, SecretKey macKey, Map description) { + this.wrappingKey = wrappingKey; + this.unwrappingKey = unwrappingKey; + this.sigPair = null; + this.macKey = macKey; + this.description = Collections.unmodifiableMap(new HashMap<>(description)); + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + try { + if (macKey != null) { + return new WrappedRawMaterials(wrappingKey, unwrappingKey, macKey, context.getMaterialDescription()); + } else { + return new WrappedRawMaterials(wrappingKey, unwrappingKey, sigPair, context.getMaterialDescription()); + } + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to decrypt envelope key", ex); + } + } + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + try { + if (macKey != null) { + return new WrappedRawMaterials(wrappingKey, unwrappingKey, macKey, description); + } else { + return new WrappedRawMaterials(wrappingKey, unwrappingKey, sigPair, description); + } + } catch (GeneralSecurityException ex) { + throw new DynamoDbEncryptionException("Unable to encrypt envelope key", ex); + } + } + + @Override + public void refresh() { + // Do nothing + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java new file mode 100644 index 0000000000..c0fbe5e06f --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java @@ -0,0 +1,434 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; + +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ComparisonOperator; +import software.amazon.awssdk.services.dynamodb.model.Condition; +import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; +import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; +import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse; +import software.amazon.awssdk.services.dynamodb.model.ExpectedAttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; +import software.amazon.awssdk.services.dynamodb.model.KeyType; +import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.dynamodb.model.QueryRequest; +import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + + +/** + * Provides a simple collection of EncryptionMaterialProviders backed by an encrypted DynamoDB + * table. This can be used to build key hierarchies or meta providers. + * + * Currently, this only supports AES-256 in AESWrap mode and HmacSHA256 for the providers persisted + * in the table. + * + * @author rubin + */ +public class MetaStore extends ProviderStore { + private static final String INTEGRITY_ALGORITHM_FIELD = "intAlg"; + private static final String INTEGRITY_KEY_FIELD = "int"; + private static final String ENCRYPTION_ALGORITHM_FIELD = "encAlg"; + private static final String ENCRYPTION_KEY_FIELD = "enc"; + private static final Pattern COMBINED_PATTERN = Pattern.compile("([^#]+)#(\\d*)"); + private static final String DEFAULT_INTEGRITY = "HmacSHA256"; + private static final String DEFAULT_ENCRYPTION = "AES"; + private static final String MATERIAL_TYPE_VERSION = "t"; + private static final String META_ID = "amzn-ddb-meta-id"; + + private static final String DEFAULT_HASH_KEY = "N"; + private static final String DEFAULT_RANGE_KEY = "V"; + + /** Default no-op implementation of {@link ExtraDataSupplier}. */ + private static final EmptyExtraDataSupplier EMPTY_EXTRA_DATA_SUPPLIER + = new EmptyExtraDataSupplier(); + + /** DDB fields that must be encrypted. */ + private static final Set ENCRYPTED_FIELDS; + static { + final Set tempEncryptedFields = new HashSet<>(); + tempEncryptedFields.add(MATERIAL_TYPE_VERSION); + tempEncryptedFields.add(ENCRYPTION_KEY_FIELD); + tempEncryptedFields.add(ENCRYPTION_ALGORITHM_FIELD); + tempEncryptedFields.add(INTEGRITY_KEY_FIELD); + tempEncryptedFields.add(INTEGRITY_ALGORITHM_FIELD); + ENCRYPTED_FIELDS = tempEncryptedFields; + } + + private final Map doesNotExist; + private final Set doNotEncrypt; +// private final DynamoDbEncryptionConfiguration encryptionConfiguration; + private final String tableName; + private final DynamoDbClient ddb; + private final DynamoDbEncryptor encryptor; + private final EncryptionContext ddbCtx; + private final ExtraDataSupplier extraDataSupplier; + + /** + * Provides extra data that should be persisted along with the standard material data. + */ + public interface ExtraDataSupplier { + + /** + * Gets the extra data attributes for the specified material name. + * + * @param materialName material name. + * @param version version number. + * @return plain text of the extra data. + */ + Map getAttributes(final String materialName, final long version); + + /** + * Gets the extra data field names that should be signed only but not encrypted. + * + * @return signed only fields. + */ + Set getSignedOnlyFieldNames(); + } + + /** + * Create a new MetaStore with specified table name. + * + * @param ddb Interface for accessing DynamoDB. + * @param tableName DynamoDB table name for this {@link MetaStore}. + * @param encryptor used to perform crypto operations on the record attributes. + */ + public MetaStore(final DynamoDbClient ddb, final String tableName, + final DynamoDbEncryptor encryptor) { + this(ddb, tableName, encryptor, EMPTY_EXTRA_DATA_SUPPLIER); + } + + /** + * Create a new MetaStore with specified table name and extra data supplier. + * + * @param ddb Interface for accessing DynamoDB. + * @param tableName DynamoDB table name for this {@link MetaStore}. + * @param encryptor used to perform crypto operations on the record attributes + * @param extraDataSupplier provides extra data that should be stored along with the material. + */ + public MetaStore(final DynamoDbClient ddb, final String tableName, + final DynamoDbEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { + this.ddb = checkNotNull(ddb, "ddb must not be null"); + this.tableName = checkNotNull(tableName, "tableName must not be null"); + this.encryptor = checkNotNull(encryptor, "encryptor must not be null"); + this.extraDataSupplier = checkNotNull(extraDataSupplier, "extraDataSupplier must not be null"); + this.ddbCtx = + new EncryptionContext.Builder() + .tableName(this.tableName) + .hashKeyName(DEFAULT_HASH_KEY) + .rangeKeyName(DEFAULT_RANGE_KEY) + .build(); + + final Map tmpExpected = new HashMap<>(); + tmpExpected.put(DEFAULT_HASH_KEY, ExpectedAttributeValue.builder().exists(false).build()); + tmpExpected.put(DEFAULT_RANGE_KEY, ExpectedAttributeValue.builder().exists(false).build()); + doesNotExist = Collections.unmodifiableMap(tmpExpected); + + this.doNotEncrypt = getSignedOnlyFields(extraDataSupplier); + } + + @Override + public EncryptionMaterialsProvider getProvider(final String materialName, final long version) { + final Map item = getMaterialItem(materialName, version); + return decryptProvider(item); + } + + @Override + public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) { + final Map plaintext = createMaterialItem(materialName, nextId); + final Map ciphertext = conditionalPut(getEncryptedText(plaintext)); + return decryptProvider(ciphertext); + } + + @Override + public long getMaxVersion(final String materialName) { + + final List> items = + ddb.query( + QueryRequest.builder() + .tableName(tableName) + .consistentRead(Boolean.TRUE) + .keyConditions( + Collections.singletonMap( + DEFAULT_HASH_KEY, + Condition.builder() + .comparisonOperator(ComparisonOperator.EQ) + .attributeValueList(AttributeValue.builder().s(materialName).build()) + .build())) + .limit(1) + .scanIndexForward(false) + .attributesToGet(DEFAULT_RANGE_KEY) + .build()) + .items(); + + if (items.isEmpty()) { + return -1L; + } else { + return Long.parseLong(items.get(0).get(DEFAULT_RANGE_KEY).n()); + } + } + + @Override + public long getVersionFromMaterialDescription(final Map description) { + final Matcher m = COMBINED_PATTERN.matcher(description.get(META_ID)); + if (m.matches()) { + return Long.parseLong(m.group(2)); + } else { + throw new IllegalArgumentException("No meta id found"); + } + } + + /** + * This API retrieves the intermediate keys from the source region and replicates it in the target region. + * + * @param materialName material name of the encryption material. + * @param version version of the encryption material. + * @param targetMetaStore target MetaStore where the encryption material to be stored. + */ + public void replicate(final String materialName, final long version, final MetaStore targetMetaStore) { + try { + final Map item = getMaterialItem(materialName, version); + + final Map plainText = getPlainText(item); + final Map encryptedText = targetMetaStore.getEncryptedText(plainText); + final PutItemRequest put = PutItemRequest.builder() + .tableName(targetMetaStore.tableName) + .item(encryptedText) + .expected(doesNotExist) + .build(); + targetMetaStore.ddb.putItem(put); + } catch (ConditionalCheckFailedException e) { + //Item already present. + } + } + + /** + * Creates a DynamoDB Table with the correct properties to be used with a ProviderStore. + * + * @param ddb interface for accessing DynamoDB + * @param tableName name of table that stores the meta data of the material. + * @param provisionedThroughput required provisioned throughput of the this table. + * @return result of create table request. + */ + public static CreateTableResponse createTable(final DynamoDbClient ddb, final String tableName, + final ProvisionedThroughput provisionedThroughput) { + return ddb.createTable( + CreateTableRequest.builder() + .tableName(tableName) + .attributeDefinitions(Arrays.asList( + AttributeDefinition.builder() + .attributeName(DEFAULT_HASH_KEY) + .attributeType(ScalarAttributeType.S) + .build(), + AttributeDefinition.builder() + .attributeName(DEFAULT_RANGE_KEY) + .attributeType(ScalarAttributeType.N).build())) + .keySchema(Arrays.asList( + KeySchemaElement.builder() + .attributeName(DEFAULT_HASH_KEY) + .keyType(KeyType.HASH) + .build(), + KeySchemaElement.builder() + .attributeName(DEFAULT_RANGE_KEY) + .keyType(KeyType.RANGE) + .build())) + .provisionedThroughput(provisionedThroughput).build()); + } + + private Map getMaterialItem(final String materialName, final long version) { + final Map ddbKey = new HashMap<>(); + ddbKey.put(DEFAULT_HASH_KEY, AttributeValue.builder().s(materialName).build()); + ddbKey.put(DEFAULT_RANGE_KEY, AttributeValue.builder().n(Long.toString(version)).build()); + final Map item = ddbGet(ddbKey); + if (item == null || item.isEmpty()) { + throw new IndexOutOfBoundsException("No material found: " + materialName + "#" + version); + } + return item; + } + + + /** + * Empty extra data supplier. This default class is intended to simplify the default + * implementation of {@link MetaStore}. + */ + private static class EmptyExtraDataSupplier implements ExtraDataSupplier { + @Override + public Map getAttributes(String materialName, long version) { + return Collections.emptyMap(); + } + + @Override + public Set getSignedOnlyFieldNames() { + return Collections.emptySet(); + } + } + + /** + * Get a set of fields that must be signed but not encrypted. + * + * @param extraDataSupplier extra data supplier that is used to return sign only field names. + * @return fields that must be signed. + */ + private static Set getSignedOnlyFields(final ExtraDataSupplier extraDataSupplier) { + final Set signedOnlyFields = extraDataSupplier.getSignedOnlyFieldNames(); + for (final String signedOnlyField : signedOnlyFields) { + if (ENCRYPTED_FIELDS.contains(signedOnlyField)) { + throw new IllegalArgumentException(signedOnlyField + " must be encrypted"); + } + } + + // fields that should not be encrypted + final Set doNotEncryptFields = new HashSet<>(); + doNotEncryptFields.add(DEFAULT_HASH_KEY); + doNotEncryptFields.add(DEFAULT_RANGE_KEY); + doNotEncryptFields.addAll(signedOnlyFields); + return Collections.unmodifiableSet(doNotEncryptFields); + } + + private Map conditionalPut(final Map item) { + try { + final PutItemRequest put = PutItemRequest.builder().tableName(tableName).item(item) + .expected(doesNotExist).build(); + ddb.putItem(put); + return item; + } catch (final ConditionalCheckFailedException ex) { + final Map ddbKey = new HashMap<>(); + ddbKey.put(DEFAULT_HASH_KEY, item.get(DEFAULT_HASH_KEY)); + ddbKey.put(DEFAULT_RANGE_KEY, item.get(DEFAULT_RANGE_KEY)); + return ddbGet(ddbKey); + } + } + + private Map ddbGet(final Map ddbKey) { + return ddb.getItem( + GetItemRequest.builder().tableName(tableName).consistentRead(true) + .key(ddbKey).build()).item(); + } + + /** + * Build an material item for a given material name and version with newly generated + * encryption and integrity keys. + * + * @param materialName material name. + * @param version version of the material. + * @return newly generated plaintext material item. + */ + private Map createMaterialItem(final String materialName, final long version) { + final SecretKeySpec encryptionKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_ENCRYPTION); + final SecretKeySpec integrityKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_INTEGRITY); + + final Map plaintext = new HashMap<>(); + plaintext.put(DEFAULT_HASH_KEY, AttributeValue.builder().s(materialName).build()); + plaintext.put(DEFAULT_RANGE_KEY, AttributeValue.builder().n(Long.toString(version)).build()); + plaintext.put(MATERIAL_TYPE_VERSION, AttributeValue.builder().s("0").build()); + plaintext.put(ENCRYPTION_KEY_FIELD, + AttributeValue.builder().b(SdkBytes.fromByteArray(encryptionKey.getEncoded())).build()); + plaintext.put(ENCRYPTION_ALGORITHM_FIELD, AttributeValue.builder().s(encryptionKey.getAlgorithm()).build()); + plaintext.put(INTEGRITY_KEY_FIELD, + AttributeValue.builder().b(SdkBytes.fromByteArray(integrityKey.getEncoded())).build()); + plaintext.put(INTEGRITY_ALGORITHM_FIELD, AttributeValue.builder().s(integrityKey.getAlgorithm()).build()); + plaintext.putAll(extraDataSupplier.getAttributes(materialName, version)); + + return plaintext; + } + + private EncryptionMaterialsProvider decryptProvider(final Map item) { + final Map plaintext = getPlainText(item); + + final String type = plaintext.get(MATERIAL_TYPE_VERSION).s(); + final SecretKey encryptionKey; + final SecretKey integrityKey; + // This switch statement is to make future extensibility easier and more obvious + switch (type) { + case "0": // Only currently supported type + encryptionKey = new SecretKeySpec(plaintext.get(ENCRYPTION_KEY_FIELD).b().asByteArray(), + plaintext.get(ENCRYPTION_ALGORITHM_FIELD).s()); + integrityKey = new SecretKeySpec(plaintext.get(INTEGRITY_KEY_FIELD).b().asByteArray(), plaintext + .get(INTEGRITY_ALGORITHM_FIELD).s()); + break; + default: + throw new IllegalStateException("Unsupported material type: " + type); + } + return new WrappedMaterialsProvider(encryptionKey, encryptionKey, integrityKey, + buildDescription(plaintext)); + } + + /** + * Decrypts attributes in the ciphertext item using {@link DynamoDbEncryptor}. except the + * attribute names specified in doNotEncrypt. + * + * @param ciphertext the ciphertext to be decrypted. + * @throws SdkClientException when failed to decrypt material item. + * @return decrypted item. + */ + private Map getPlainText(final Map ciphertext) { + try { + return encryptor.decryptAllFieldsExcept(ciphertext, ddbCtx, doNotEncrypt); + } catch (final GeneralSecurityException e) { + throw SdkClientException.create("Error retrieving PlainText", e); + } + } + + /** + * Encrypts attributes in the plaintext item using {@link DynamoDbEncryptor}. except the attribute + * names specified in doNotEncrypt. + * + * @throws SdkClientException when failed to encrypt material item. + * @param plaintext plaintext to be encrypted. + */ + private Map getEncryptedText(Map plaintext) { + try { + return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, doNotEncrypt); + } catch (final GeneralSecurityException e) { + throw SdkClientException.create("Error retrieving PlainText", e); + } + } + + private Map buildDescription(final Map plaintext) { + return Collections.singletonMap(META_ID, plaintext.get(DEFAULT_HASH_KEY).s() + "#" + + plaintext.get(DEFAULT_RANGE_KEY).n()); + } + + private static V checkNotNull(final V ref, final String errMsg) { + if (ref == null) { + throw new NullPointerException(errMsg); + } else { + return ref; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java new file mode 100644 index 0000000000..a29fe9b34d --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/ProviderStore.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; + +import java.util.Map; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; + +/** + * Provides a standard way to retrieve and optionally create {@link EncryptionMaterialsProvider}s + * backed by some form of persistent storage. + * + * @author rubin + * + */ +public abstract class ProviderStore { + + /** + * Returns the most recent provider with the specified name. If there are no providers with this + * name, it will create one with version 0. + */ + public EncryptionMaterialsProvider getProvider(final String materialName) { + final long currVersion = getMaxVersion(materialName); + if (currVersion >= 0) { + return getProvider(materialName, currVersion); + } else { + return getOrCreate(materialName, 0); + } + } + + /** + * Returns the provider with the specified name and version. + * + * @throws IndexOutOfBoundsException + * if {@code version} is not a valid version + */ + public abstract EncryptionMaterialsProvider getProvider(final String materialName, final long version); + + /** + * Creates a new provider with a version one greater than the current max version. If multiple + * clients attempt to create a provider with this same version simultaneously, they will + * properly coordinate and the result will be that a single provider is created and that all + * ProviderStores return the same one. + */ + public EncryptionMaterialsProvider newProvider(final String materialName) { + final long nextId = getMaxVersion(materialName) + 1; + return getOrCreate(materialName, nextId); + } + + /** + * Returns the provider with the specified name and version and creates it if it doesn't exist. + * + * @throws UnsupportedOperationException + * if a new provider cannot be created + */ + public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) { + try { + return getProvider(materialName, nextId); + } catch (final IndexOutOfBoundsException ex) { + throw new UnsupportedOperationException("This ProviderStore does not support creation.", ex); + } + } + + /** + * Returns the maximum version number associated with {@code materialName}. If there are no + * versions, returns -1. + */ + public abstract long getMaxVersion(final String materialName); + + /** + * Extracts the material version from {@code description}. + */ + public abstract long getVersionFromMaterialDescription(final Map description); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java new file mode 100644 index 0000000000..d29bb818cb --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperators.java @@ -0,0 +1,81 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; + +import java.util.Map; +import java.util.function.UnaryOperator; + +/** + * Implementations of common operators for overriding the EncryptionContext + */ +public class EncryptionContextOperators { + + // Prevent instantiation + private EncryptionContextOperators() { + } + + /** + * An operator for overriding EncryptionContext's table name for a specific DynamoDbEncryptor. If any table names or + * the encryption context itself is null, then it returns the original EncryptionContext. + * + * @param originalTableName the name of the table that should be overridden in the Encryption Context + * @param newTableName the table name that should be used in the Encryption Context + * @return A UnaryOperator that produces a new EncryptionContext with the supplied table name + */ + public static UnaryOperator overrideEncryptionContextTableName( + String originalTableName, + String newTableName) { + return encryptionContext -> { + if (encryptionContext == null + || encryptionContext.getTableName() == null + || originalTableName == null + || newTableName == null) { + return encryptionContext; + } + if (originalTableName.equals(encryptionContext.getTableName())) { + return encryptionContext.toBuilder().tableName(newTableName).build(); + } else { + return encryptionContext; + } + }; + } + + /** + * An operator for mapping multiple table names in the Encryption Context to a new table name. If the table name for + * a given EncryptionContext is missing, then it returns the original EncryptionContext. Similarly, it returns the + * original EncryptionContext if the value it is overridden to is null, or if the original table name is null. + * + * @param tableNameOverrideMap a map specifying the names of tables that should be overridden, + * and the values to which they should be overridden. If the given table name + * corresponds to null, or isn't in the map, then the table name won't be overridden. + * @return A UnaryOperator that produces a new EncryptionContext with the supplied table name + */ + public static UnaryOperator overrideEncryptionContextTableNameUsingMap( + Map tableNameOverrideMap) { + return encryptionContext -> { + if (tableNameOverrideMap == null || encryptionContext == null || encryptionContext.getTableName() == null) { + return encryptionContext; + } + String newTableName = tableNameOverrideMap.get(encryptionContext.getTableName()); + if (newTableName != null) { + return encryptionContext.toBuilder().tableName(newTableName).build(); + } else { + return encryptionContext; + } + }; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java new file mode 100644 index 0000000000..e9348af05d --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshaller.java @@ -0,0 +1,331 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import software.amazon.awssdk.core.BytesWrapper; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.util.DefaultSdkAutoConstructList; +import software.amazon.awssdk.core.util.DefaultSdkAutoConstructMap; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + + +/** + * @author Greg Rubin + */ +public class AttributeValueMarshaller { + private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final int TRUE_FLAG = 1; + private static final int FALSE_FLAG = 0; + + private AttributeValueMarshaller() { + // Prevent instantiation + } + + /** + * Marshalls the data using a TLV (Tag-Length-Value) encoding. The tag may be 'b', 'n', 's', + * '?', '\0' to represent a ByteBuffer, Number, String, Boolean, or Null respectively. The tag + * may also be capitalized (for 'b', 'n', and 's',) to represent an array of that type. If an + * array is stored, then a four-byte big-endian integer is written representing the number of + * array elements. If a ByteBuffer is stored, the length of the buffer is stored as a four-byte + * big-endian integer and the buffer then copied directly. Both Numbers and Strings are treated + * identically and are stored as UTF8 encoded Unicode, proceeded by the length of the encoded + * string (in bytes) as a four-byte big-endian integer. Boolean is encoded as a single byte, 0 + * for false and 1 for true (and so has no Length parameter). The + * Null tag ('\0') takes neither a Length nor a Value parameter. + * + * The tags 'L' and 'M' are for the document types List and Map respectively. These are encoded + * recursively with the Length being the size of the collection. In the case of List, the value + * is a Length number of marshalled AttributeValues. If the case of Map, the value is a Length + * number of AttributeValue Pairs where the first must always have a String value. + * + * This implementation does not recognize loops. If an AttributeValue contains itself + * (even indirectly) this code will recurse infinitely. + * + * @param attributeValue an AttributeValue instance + * @return the serialized AttributeValue + * @see java.io.DataInput + */ + public static ByteBuffer marshall(final AttributeValue attributeValue) { + try (ByteArrayOutputStream resultBytes = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(resultBytes);) { + marshall(attributeValue, out); + out.close(); + resultBytes.close(); + return ByteBuffer.wrap(resultBytes.toByteArray()); + } catch (final IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + private static void marshall(final AttributeValue attributeValue, final DataOutputStream out) + throws IOException { + + if (attributeValue.b() != null) { + out.writeChar('b'); + writeBytes(attributeValue.b().asByteBuffer(), out); + } else if (hasAttributeValueSet(attributeValue.bs())) { + out.writeChar('B'); + writeBytesList(attributeValue.bs().stream() + .map(BytesWrapper::asByteBuffer).collect(Collectors.toList()), out); + } else if (attributeValue.n() != null) { + out.writeChar('n'); + writeString(trimZeros(attributeValue.n()), out); + } else if (hasAttributeValueSet(attributeValue.ns())) { + out.writeChar('N'); + + final List ns = new ArrayList<>(attributeValue.ns().size()); + for (final String n : attributeValue.ns()) { + ns.add(trimZeros(n)); + } + writeStringList(ns, out); + } else if (attributeValue.s() != null) { + out.writeChar('s'); + writeString(attributeValue.s(), out); + } else if (hasAttributeValueSet(attributeValue.ss())) { + out.writeChar('S'); + writeStringList(attributeValue.ss(), out); + } else if (attributeValue.bool() != null) { + out.writeChar('?'); + out.writeByte((attributeValue.bool() ? TRUE_FLAG : FALSE_FLAG)); + } else if (Boolean.TRUE.equals(attributeValue.nul())) { + out.writeChar('\0'); + } else if (hasAttributeValueSet(attributeValue.l())) { + final List l = attributeValue.l(); + out.writeChar('L'); + out.writeInt(l.size()); + for (final AttributeValue attr : l) { + if (attr == null) { + throw new NullPointerException( + "Encountered null list entry value while marshalling attribute value " + + attributeValue); + } + marshall(attr, out); + } + } else if (hasAttributeValueMap(attributeValue.m())) { + final Map m = attributeValue.m(); + final List mKeys = new ArrayList<>(m.keySet()); + Collections.sort(mKeys); + out.writeChar('M'); + out.writeInt(m.size()); + for (final String mKey : mKeys) { + marshall(AttributeValue.builder().s(mKey).build(), out); + + final AttributeValue mValue = m.get(mKey); + + if (mValue == null) { + throw new NullPointerException( + "Encountered null map value for key " + + mKey + + " while marshalling attribute value " + + attributeValue); + } + marshall(mValue, out); + } + } else { + throw new IllegalArgumentException("A seemingly empty AttributeValue is indicative of invalid input or potential errors"); + } + } + + /** + * @see #marshall(AttributeValue) + */ + public static AttributeValue unmarshall(final ByteBuffer plainText) { + try (final DataInputStream in = new DataInputStream( + new ByteBufferInputStream(plainText.asReadOnlyBuffer()))) { + return unmarshall(in); + } catch (IOException ex) { + // Due to the objects in use, an IOException is not possible. + throw new RuntimeException("Unexpected exception", ex); + } + } + + private static AttributeValue unmarshall(final DataInputStream in) throws IOException { + char type = in.readChar(); + AttributeValue.Builder result = AttributeValue.builder(); + switch (type) { + case '\0': + result.nul(Boolean.TRUE); + break; + case 'b': + result.b(SdkBytes.fromByteBuffer(readBytes(in))); + break; + case 'B': + result.bs(readBytesList(in).stream().map(SdkBytes::fromByteBuffer).collect(Collectors.toList())); + break; + case 'n': + result.n(readString(in)); + break; + case 'N': + result.ns(readStringList(in)); + break; + case 's': + result.s(readString(in)); + break; + case 'S': + result.ss(readStringList(in)); + break; + case '?': + final byte boolValue = in.readByte(); + + if (boolValue == TRUE_FLAG) { + result.bool(Boolean.TRUE); + } else if (boolValue == FALSE_FLAG) { + result.bool(Boolean.FALSE); + } else { + throw new IllegalArgumentException("Improperly formatted data"); + } + break; + case 'L': + final int lCount = in.readInt(); + final List l = new ArrayList<>(lCount); + for (int lIdx = 0; lIdx < lCount; lIdx++) { + l.add(unmarshall(in)); + } + result.l(l); + break; + case 'M': + final int mCount = in.readInt(); + final Map m = new HashMap<>(); + for (int mIdx = 0; mIdx < mCount; mIdx++) { + final AttributeValue key = unmarshall(in); + if (key.s() == null) { + throw new IllegalArgumentException("Improperly formatted data"); + } + AttributeValue value = unmarshall(in); + m.put(key.s(), value); + } + result.m(m); + break; + default: + throw new IllegalArgumentException("Unsupported data encoding"); + } + + return result.build(); + } + + private static String trimZeros(final String n) { + BigDecimal number = new BigDecimal(n); + if (number.compareTo(BigDecimal.ZERO) == 0) { + return "0"; + } + return number.stripTrailingZeros().toPlainString(); + } + + private static void writeStringList(List values, final DataOutputStream out) throws IOException { + final List sorted = new ArrayList<>(values); + Collections.sort(sorted); + out.writeInt(sorted.size()); + for (final String v : sorted) { + writeString(v, out); + } + } + + private static List readStringList(final DataInputStream in) throws IOException, + IllegalArgumentException { + final int nCount = in.readInt(); + List ns = new ArrayList<>(nCount); + for (int nIdx = 0; nIdx < nCount; nIdx++) { + ns.add(readString(in)); + } + return ns; + } + + private static void writeString(String value, final DataOutputStream out) throws IOException { + final byte[] bytes = value.getBytes(UTF8); + out.writeInt(bytes.length); + out.write(bytes); + } + + private static String readString(final DataInputStream in) throws IOException, + IllegalArgumentException { + byte[] bytes; + int length; + length = in.readInt(); + bytes = new byte[length]; + if(in.read(bytes) != length) { + throw new IllegalArgumentException("Improperly formatted data"); + } + return new String(bytes, UTF8); + } + + private static void writeBytesList(List values, final DataOutputStream out) throws IOException { + final List sorted = new ArrayList<>(values); + Collections.sort(sorted); + out.writeInt(sorted.size()); + for (final ByteBuffer v : sorted) { + writeBytes(v, out); + } + } + + private static List readBytesList(final DataInputStream in) throws IOException { + final int bCount = in.readInt(); + List bs = new ArrayList<>(bCount); + for (int bIdx = 0; bIdx < bCount; bIdx++) { + bs.add(readBytes(in)); + } + return bs; + } + + private static void writeBytes(ByteBuffer value, final DataOutputStream out) throws IOException { + value = value.asReadOnlyBuffer(); + value.rewind(); + out.writeInt(value.remaining()); + while (value.hasRemaining()) { + out.writeByte(value.get()); + } + } + + private static ByteBuffer readBytes(final DataInputStream in) throws IOException { + final int length = in.readInt(); + final byte[] buf = new byte[length]; + in.readFully(buf); + return ByteBuffer.wrap(buf); + } + + /** + * Determines if the value of a 'set' type AttributeValue (various S types) has been explicitly set or not. + * @param value the actual value portion of an AttributeValue of the appropriate type + * @return true if the value of this type field has been explicitly set, false if it has not + */ + private static boolean hasAttributeValueSet(Collection value) { + return value != null && value != DefaultSdkAutoConstructList.getInstance(); + } + + /** + * Determines if the value of a 'map' type AttributeValue (M type) has been explicitly set or not. + * @param value the actual value portion of a AttributeValue of the appropriate type + * @return true if the value of this type field has been explicitly set, false if it has not + */ + private static boolean hasAttributeValueMap(Map value) { + return value != null && value != DefaultSdkAutoConstructMap.getInstance(); + } + +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java new file mode 100644 index 0000000000..ee94a86a02 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64.java @@ -0,0 +1,48 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static java.util.Base64.*; + +/** + * A class for decoding Base64 strings and encoding bytes as Base64 strings. + */ +public class Base64 { + private static final Decoder DECODER = getMimeDecoder(); + private static final Encoder ENCODER = getEncoder(); + + private Base64() { } + + /** + * Encode the bytes as a Base64 string. + *

+ * See the Basic encoder in {@link java.util.Base64} + */ + public static String encodeToString(byte[] bytes) { + return ENCODER.encodeToString(bytes); + } + + /** + * Decode the Base64 string as bytes, ignoring illegal characters. + *

+ * See the Mime Decoder in {@link java.util.Base64} + */ + public static byte[] decode(String str) { + if(str == null) { + return null; + } + return DECODER.decode(str); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java new file mode 100644 index 0000000000..ff70306841 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStream.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * @author Greg Rubin + */ +public class ByteBufferInputStream extends InputStream { + private final ByteBuffer buffer; + + public ByteBufferInputStream(ByteBuffer buffer) { + this.buffer = buffer; + } + + @Override + public int read() { + if (buffer.hasRemaining()) { + int tmp = buffer.get(); + if (tmp < 0) { + tmp += 256; + } + return tmp; + } else { + return -1; + } + } + + @Override + public int read(byte[] b, int off, int len) { + if (available() < len) { + len = available(); + } + buffer.get(b, off, len); + return len; + } + + @Override + public int available() { + return buffer.remaining(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java new file mode 100644 index 0000000000..15422aaab7 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Hkdf.java @@ -0,0 +1,316 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.SecretKeySpec; + +/** + * HMAC-based Key Derivation Function. + * + * @see RFC 5869 + */ +public final class Hkdf { + private static final byte[] EMPTY_ARRAY = new byte[0]; + private final String algorithm; + private final Provider provider; + + private SecretKey prk = null; + + /** + * Returns an Hkdf object using the specified algorithm. + * + * @param algorithm + * the standard name of the requested MAC algorithm. See the Mac + * section in the Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm + * names. + * @return the new Hkdf object + * @throws NoSuchAlgorithmException + * if no Provider supports a MacSpi implementation for the + * specified algorithm. + */ + public static Hkdf getInstance(final String algorithm) + throws NoSuchAlgorithmException { + // Constructed specifically to sanity-test arguments. + Mac mac = Mac.getInstance(algorithm); + return new Hkdf(algorithm, mac.getProvider()); + } + + /** + * Returns an Hkdf object using the specified algorithm. + * + * @param algorithm + * the standard name of the requested MAC algorithm. See the Mac + * section in the Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm + * names. + * @param provider + * the name of the provider + * @return the new Hkdf object + * @throws NoSuchAlgorithmException + * if a MacSpi implementation for the specified algorithm is not + * available from the specified provider. + * @throws NoSuchProviderException + * if the specified provider is not registered in the security + * provider list. + */ + public static Hkdf getInstance(final String algorithm, final String provider) + throws NoSuchAlgorithmException, NoSuchProviderException { + // Constructed specifically to sanity-test arguments. + Mac mac = Mac.getInstance(algorithm, provider); + return new Hkdf(algorithm, mac.getProvider()); + } + + /** + * Returns an Hkdf object using the specified algorithm. + * + * @param algorithm + * the standard name of the requested MAC algorithm. See the Mac + * section in the Java Cryptography Architecture Standard Algorithm Name + * Documentation for information about standard algorithm + * names. + * @param provider + * the provider + * @return the new Hkdf object + * @throws NoSuchAlgorithmException + * if a MacSpi implementation for the specified algorithm is not + * available from the specified provider. + */ + public static Hkdf getInstance(final String algorithm, + final Provider provider) throws NoSuchAlgorithmException { + // Constructed specifically to sanity-test arguments. + Mac mac = Mac.getInstance(algorithm, provider); + return new Hkdf(algorithm, mac.getProvider()); + } + + /** + * Initializes this Hkdf with input keying material. A default salt of + * HashLen zeros will be used (where HashLen is the length of the return + * value of the supplied algorithm). + * + * @param ikm + * the Input Keying Material + */ + public void init(final byte[] ikm) { + init(ikm, null); + } + + /** + * Initializes this Hkdf with input keying material and a salt. If + * salt is null or of length 0, then a default salt of + * HashLen zeros will be used (where HashLen is the length of the return + * value of the supplied algorithm). + * + * @param salt + * the salt used for key extraction (optional) + * @param ikm + * the Input Keying Material + */ + public void init(final byte[] ikm, final byte[] salt) { + byte[] realSalt = (salt == null) ? EMPTY_ARRAY : salt.clone(); + byte[] rawKeyMaterial = EMPTY_ARRAY; + try { + Mac extractionMac = Mac.getInstance(algorithm, provider); + if (realSalt.length == 0) { + realSalt = new byte[extractionMac.getMacLength()]; + Arrays.fill(realSalt, (byte) 0); + } + extractionMac.init(new SecretKeySpec(realSalt, algorithm)); + rawKeyMaterial = extractionMac.doFinal(ikm); + SecretKeySpec key = new SecretKeySpec(rawKeyMaterial, algorithm); + Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array + unsafeInitWithoutKeyExtraction(key); + } catch (GeneralSecurityException e) { + // We've already checked all of the parameters so no exceptions + // should be possible here. + throw new RuntimeException("Unexpected exception", e); + } finally { + Arrays.fill(rawKeyMaterial, (byte) 0); // Zeroize temporary array + } + } + + /** + * Initializes this Hkdf to use the provided key directly for creation of + * new keys. If rawKey is not securely generated and uniformly + * distributed over the total key-space, then this will result in an + * insecure key derivation function (KDF). DO NOT USE THIS UNLESS YOU + * ARE ABSOLUTELY POSITIVE THIS IS THE CORRECT THING TO DO. + * + * @param rawKey + * the pseudorandom key directly used to derive keys + * @throws InvalidKeyException + * if the algorithm for rawKey does not match the + * algorithm this Hkdf was created with + */ + public void unsafeInitWithoutKeyExtraction(final SecretKey rawKey) + throws InvalidKeyException { + if (!rawKey.getAlgorithm().equals(algorithm)) { + throw new InvalidKeyException( + "Algorithm for the provided key must match the algorithm for this Hkdf. Expected " + + algorithm + " but found " + rawKey.getAlgorithm()); + } + + this.prk = rawKey; + } + + private Hkdf(final String algorithm, final Provider provider) { + if (!algorithm.startsWith("Hmac")) { + throw new IllegalArgumentException("Invalid algorithm " + algorithm + + ". Hkdf may only be used with Hmac algorithms."); + } + this.algorithm = algorithm; + this.provider = provider; + } + + /** + * Returns a pseudorandom key of length bytes. + * + * @param info + * optional context and application specific information (can be + * a zero-length string). This will be treated as UTF-8. + * @param length + * the length of the output key in bytes + * @return a pseudorandom key of length bytes. + * @throws IllegalStateException + * if this object has not been initialized + */ + public byte[] deriveKey(final String info, final int length) throws IllegalStateException { + return deriveKey((info != null ? info.getBytes(StandardCharsets.UTF_8) : null), length); + } + + /** + * Returns a pseudorandom key of length bytes. + * + * @param info + * optional context and application specific information (can be + * a zero-length array). + * @param length + * the length of the output key in bytes + * @return a pseudorandom key of length bytes. + * @throws IllegalStateException + * if this object has not been initialized + */ + public byte[] deriveKey(final byte[] info, final int length) throws IllegalStateException { + byte[] result = new byte[length]; + try { + deriveKey(info, length, result, 0); + } catch (ShortBufferException ex) { + // This exception is impossible as we ensure the buffer is long + // enough + throw new RuntimeException(ex); + } + return result; + } + + /** + * Derives a pseudorandom key of length bytes and stores the + * result in output. + * + * @param info + * optional context and application specific information (can be + * a zero-length array). + * @param length + * the length of the output key in bytes + * @param output + * the buffer where the pseudorandom key will be stored + * @param offset + * the offset in output where the key will be stored + * @throws ShortBufferException + * if the given output buffer is too small to hold the result + * @throws IllegalStateException + * if this object has not been initialized + */ + public void deriveKey(final byte[] info, final int length, + final byte[] output, final int offset) throws ShortBufferException, + IllegalStateException { + assertInitialized(); + if (length < 0) { + throw new IllegalArgumentException("Length must be a non-negative value."); + } + if (output.length < offset + length) { + throw new ShortBufferException(); + } + Mac mac = createMac(); + + if (length > 255 * mac.getMacLength()) { + throw new IllegalArgumentException( + "Requested keys may not be longer than 255 times the underlying HMAC length."); + } + + byte[] t = EMPTY_ARRAY; + try { + int loc = 0; + byte i = 1; + while (loc < length) { + mac.update(t); + mac.update(info); + mac.update(i); + t = mac.doFinal(); + + for (int x = 0; x < t.length && loc < length; x++, loc++) { + output[loc] = t[x]; + } + + i++; + } + } finally { + Arrays.fill(t, (byte) 0); // Zeroize temporary array + } + } + + private Mac createMac() { + try { + Mac mac = Mac.getInstance(algorithm, provider); + mac.init(prk); + return mac; + } catch (NoSuchAlgorithmException ex) { + // We've already validated that this algorithm is correct. + throw new RuntimeException(ex); + } catch (InvalidKeyException ex) { + // We've already validated that this key is correct. + throw new RuntimeException(ex); + } + } + + /** + * Throws an IllegalStateException if this object has not been + * initialized. + * + * @throws IllegalStateException + * if this object has not been initialized + */ + private void assertInitialized() throws IllegalStateException { + if (prk == null) { + throw new IllegalStateException("Hkdf has not been initialized"); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java new file mode 100644 index 0000000000..e191a84215 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCache.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import software.amazon.awssdk.annotations.ThreadSafe; + +/** + * A bounded cache that has a LRU eviction policy when the cache is full. + * + * @param + * value type + */ +@ThreadSafe +public final class LRUCache { + /** + * Used for the internal cache. + */ + private final Map map; + + /** + * Maximum size of the cache. + */ + private final int maxSize; + + /** + * @param maxSize + * the maximum number of entries of the cache + */ + public LRUCache(final int maxSize) { + if (maxSize < 1) { + throw new IllegalArgumentException("maxSize " + maxSize + " must be at least 1"); + } + this.maxSize = maxSize; + map = Collections.synchronizedMap(new LRUHashMap(maxSize)); + } + + /** + * Adds an entry to the cache, evicting the earliest entry if necessary. + */ + public T add(final String key, final T value) { + return map.put(key, value); + } + + /** Returns the value of the given key; or null of no such entry exists. */ + public T get(final String key) { + return map.get(key); + } + + /** + * Returns the current size of the cache. + */ + public int size() { + return map.size(); + } + + /** + * Returns the maximum size of the cache. + */ + public int getMaxSize() { + return maxSize; + } + + public void clear() { + map.clear(); + } + + public T remove(String key) { + return map.remove(key); + } + + @Override + public String toString() { + return map.toString(); + } + + @SuppressWarnings("serial") + private static class LRUHashMap extends LinkedHashMap { + private final int maxSize; + + private LRUHashMap(final int maxSize) { + super(10, 0.75F, true); + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(final Entry eldest) { + return size() > maxSize; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java new file mode 100644 index 0000000000..3d776c0dc8 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/MsClock.java @@ -0,0 +1,19 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +interface MsClock { + MsClock WALLCLOCK = System::nanoTime; + + public long timestampNano(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java new file mode 100644 index 0000000000..f529047c8a --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCache.java @@ -0,0 +1,242 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import io.netty.util.internal.ObjectUtil; +import software.amazon.awssdk.annotations.ThreadSafe; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; + +/** + * A cache, backed by an LRUCache, that uses a loader to calculate values on cache miss or expired + * TTL. + * + *

Note that this cache does not proactively evict expired entries, however will immediately + * evict entries discovered to be expired on load. + * + * @param value type + */ +@ThreadSafe +public final class TTLCache { + /** Used for the internal cache. */ + private final LRUCache> cache; + + /** Time to live for entries in the cache. */ + private final long ttlInNanos; + + /** Used for loading new values into the cache on cache miss or expiration. */ + private final EntryLoader defaultLoader; + + // Mockable time source, to allow us to test TTL behavior. + // package access for tests + MsClock clock = MsClock.WALLCLOCK; + + private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); + + /** + * @param maxSize the maximum number of entries of the cache + * @param ttlInMillis the time to live value for entries of the cache, in milliseconds + */ + public TTLCache(final int maxSize, final long ttlInMillis, final EntryLoader loader) { + if (maxSize < 1) { + throw new IllegalArgumentException("maxSize " + maxSize + " must be at least 1"); + } + if (ttlInMillis < 1) { + throw new IllegalArgumentException("ttlInMillis " + maxSize + " must be at least 1"); + } + this.ttlInNanos = TimeUnit.MILLISECONDS.toNanos(ttlInMillis); + this.cache = new LRUCache<>(maxSize); + this.defaultLoader = ObjectUtil.checkNotNull(loader, "loader must not be null"); + } + + /** + * Uses the default loader to calculate the value at key and insert it into the cache, if it + * doesn't already exist or is expired according to the TTL. + * + *

This immediately evicts entries past the TTL such that a load failure results in the removal + * of the entry. + * + *

Entries that are not expired according to the TTL are returned without recalculating the + * value. + * + *

Within a grace period past the TTL, the cache may either return the cached value without + * recalculating or use the loader to recalculate the value. This is implemented such that, in a + * multi-threaded environment, only one thread per cache key uses the loader to recalculate the + * value at one time. + * + * @param key The cache key to load the value at + * @return The value of the given value (already existing or re-calculated). + */ + public T load(final String key) { + return load(key, defaultLoader::load); + } + + /** + * Uses the inputted function to calculate the value at key and insert it into the cache, if it + * doesn't already exist or is expired according to the TTL. + * + *

This immediately evicts entries past the TTL such that a load failure results in the removal + * of the entry. + * + *

Entries that are not expired according to the TTL are returned without recalculating the + * value. + * + *

Within a grace period past the TTL, the cache may either return the cached value without + * recalculating or use the loader to recalculate the value. This is implemented such that, in a + * multi-threaded environment, only one thread per cache key uses the loader to recalculate the + * value at one time. + * + *

Returns the value of the given key (already existing or re-calculated). + * + * @param key The cache key to load the value at + * @param f The function to use to load the value, given key as input + * @return The value of the given value (already existing or re-calculated). + */ + public T load(final String key, Function f) { + final LockedState ls = cache.get(key); + + if (ls == null) { + // The entry doesn't exist yet, so load a new one. + return loadNewEntryIfAbsent(key, f); + } else if (clock.timestampNano() - ls.getState().lastUpdatedNano + > ttlInNanos + TTL_GRACE_IN_NANO) { + // The data has expired past the grace period. + // Evict the old entry and load a new entry. + cache.remove(key); + return loadNewEntryIfAbsent(key, f); + } else if (clock.timestampNano() - ls.getState().lastUpdatedNano <= ttlInNanos) { + // The data hasn't expired. Return as-is from the cache. + return ls.getState().data; + } else if (!ls.tryLock()) { + // We are in the TTL grace period. If we couldn't grab the lock, then some other + // thread is currently loading the new value. Because we are in the grace period, + // use the cached data instead of waiting for the lock. + return ls.getState().data; + } + + // We are in the grace period and have acquired a lock. + // Update the cache with the value determined by the loading function. + try { + T loadedData = f.apply(key); + ls.update(loadedData, clock.timestampNano()); + return ls.getState().data; + } finally { + ls.unlock(); + } + } + + // Synchronously calculate the value for a new entry in the cache if it doesn't already exist. + // Otherwise return the cached value. + // It is important that this is the only place where we use the loader for a new entry, + // given that we don't have the entry yet to lock on. + // This ensures that the loading function is only called once if multiple threads + // attempt to add a new entry for the same key at the same time. + private synchronized T loadNewEntryIfAbsent(final String key, Function f) { + // If the entry already exists in the cache, return it + final LockedState cachedState = cache.get(key); + if (cachedState != null) { + return cachedState.getState().data; + } + + // Otherwise, load the data and create a new entry + T loadedData = f.apply(key); + LockedState ls = new LockedState<>(loadedData, clock.timestampNano()); + cache.add(key, ls); + return loadedData; + } + + + /** + * Put a new entry in the cache. Returns the value previously at that key in the cache, or null if + * the entry previously didn't exist or is expired. + */ + public synchronized T put(final String key, final T value) { + LockedState ls = new LockedState<>(value, clock.timestampNano()); + LockedState oldLockedState = cache.add(key, ls); + if (oldLockedState == null + || clock.timestampNano() - oldLockedState.getState().lastUpdatedNano + > ttlInNanos + TTL_GRACE_IN_NANO) { + return null; + } + return oldLockedState.getState().data; + } + + /** + * Get when the entry at this key was last updated. Returns 0 if the entry doesn't exist at key. + */ + public long getLastUpdated(String key) { + LockedState ls = cache.get(key); + if (ls == null) { + return 0; + } + return ls.getState().lastUpdatedNano; + } + + /** Returns the current size of the cache. */ + public int size() { + return cache.size(); + } + + /** Returns the maximum size of the cache. */ + public int getMaxSize() { + return cache.getMaxSize(); + } + + /** Clears all entries from the cache. */ + public void clear() { + cache.clear(); + } + + @Override + public String toString() { + return cache.toString(); + } + + public interface EntryLoader { + T load(String entryKey); + } + + // An object which stores a state alongside a lock, + // and performs updates to that state atomically. + // The state may only be updated if the lock is acquired by the current thread. + private static class LockedState { + private final ReentrantLock lock = new ReentrantLock(true); + private final AtomicReference> state; + + public LockedState(T data, long createTimeNano) { + state = new AtomicReference<>(new State<>(data, createTimeNano)); + } + + public State getState() { + return state.get(); + } + + public void unlock() { + lock.unlock(); + } + + public boolean tryLock() { + return lock.tryLock(); + } + + public void update(T data, long createTimeNano) { + if (!lock.isHeldByCurrentThread()) { + throw new IllegalStateException("Lock not held by current thread"); + } + state.set(new State<>(data, createTimeNano)); + } + } + + // An object that holds some data and the time at which this object was created + private static class State { + public final T data; + public final long lastUpdatedNano; + + public State(T data, long lastUpdatedNano) { + this.data = data; + this.lastUpdatedNano = lastUpdatedNano; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java new file mode 100644 index 0000000000..6d092cc06b --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Utils.java @@ -0,0 +1,39 @@ +/* + * 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. + */ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import java.security.SecureRandom; + +public class Utils { + private static final ThreadLocal RND = ThreadLocal.withInitial(() -> { + final SecureRandom result = new SecureRandom(); + result.nextBoolean(); // Force seeding + return result; + }); + + private Utils() { + // Prevent instantiation + } + + public static SecureRandom getRng() { + return RND.get(); + } + + public static byte[] getRandom(int len) { + final byte[] result = new byte[len]; + getRng().nextBytes(result); + return result; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java new file mode 100644 index 0000000000..b9906bade0 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/HolisticIT.java @@ -0,0 +1,932 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + +import com.amazonaws.util.Base64; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.*; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.*; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.AsymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.CachingMostRecentProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.*; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.KeyData; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.Keys; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.ScenarioManifest.Scenario; + +public class HolisticIT { + + private static final SecretKey aesKey = + new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "AES"); + private static final SecretKey hmacKey = + new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); + private static final String rsaEncPub = + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtiNSLSvT9cExXOcD0dGZ" + + "9DFEMHw8895gAZcCdSppDrxbD7XgZiQYTlgt058i5fS+l11guAUJtKt5sZ2u8Fx0" + + "K9pxMdlczGtvQJdx/LQETEnLnfzAijvHisJ8h6dQOVczM7t01KIkS24QZElyO+kY" + + "qMWLytUV4RSHnrnIuUtPHCe6LieDWT2+1UBguxgtFt1xdXlquACLVv/Em3wp40Xc" + + "bIwzhqLitb98rTY/wqSiGTz1uvvBX46n+f2j3geZKCEDGkWcXYw3dH4lRtDWTbqw" + + "eRcaNDT/MJswQlBk/Up9KCyN7gjX67gttiCO6jMoTNDejGeJhG4Dd2o0vmn8WJlr" + + "5wIDAQAB"; + private static final String rsaEncPriv = + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2I1ItK9P1wTFc" + + "5wPR0Zn0MUQwfDzz3mABlwJ1KmkOvFsPteBmJBhOWC3TnyLl9L6XXWC4BQm0q3mx" + + "na7wXHQr2nEx2VzMa29Al3H8tARMScud/MCKO8eKwnyHp1A5VzMzu3TUoiRLbhBk" + + "SXI76RioxYvK1RXhFIeeuci5S08cJ7ouJ4NZPb7VQGC7GC0W3XF1eWq4AItW/8Sb" + + "fCnjRdxsjDOGouK1v3ytNj/CpKIZPPW6+8Ffjqf5/aPeB5koIQMaRZxdjDd0fiVG" + + "0NZNurB5Fxo0NP8wmzBCUGT9Sn0oLI3uCNfruC22II7qMyhM0N6MZ4mEbgN3ajS+" + + "afxYmWvnAgMBAAECggEBAIIU293zDWDZZ73oJ+w0fHXQsdjHAmlRitPX3CN99KZX" + + "k9m2ldudL9bUV3Zqk2wUzgIg6LDEuFfWmAVojsaP4VBopKtriEFfAYfqIbjPgLpT" + + "gh8FoyWW6D6MBJCFyGALjUAHQ7uRScathvt5ESMEqV3wKJTmdsfX97w/B8J+rLN3" + + "3fT3ZJUck5duZ8XKD+UtX1Y3UE1hTWo3Ae2MFND964XyUqy+HaYXjH0x6dhZzqyJ" + + "/OJ/MPGeMJgxp+nUbMWerwxrLQceNFVgnQgHj8e8k4fd04rkowkkPua912gNtmz7" + + "DuIEvcMnY64z585cn+cnXUPJwtu3JbAmn/AyLsV9FLECgYEA798Ut/r+vORB16JD" + + "KFu38pQCgIbdCPkXeI0DC6u1cW8JFhgRqi+AqSrEy5SzY3IY7NVMSRsBI9Y026Bl" + + "R9OQwTrOzLRAw26NPSDvbTkeYXlY9+hX7IovHjGkho/OxyTJ7bKRDYLoNCz56BC1" + + "khIWvECpcf/fZU0nqOFVFqF3H/UCgYEAwmJ4rjl5fksTNtNRL6ivkqkHIPKXzk5w" + + "C+L90HKNicic9bqyX8K4JRkGKSNYN3mkjrguAzUlEld390qNBw5Lu7PwATv0e2i+" + + "6hdwJsjTKNpj7Nh4Mieq6d7lWe1L8FLyHEhxgIeQ4BgqrVtPPOH8IBGpuzVZdWwI" + + "dgOvEvAi/usCgYBdfk3NB/+SEEW5jn0uldE0s4vmHKq6fJwxWIT/X4XxGJ4qBmec" + + "NbeoOAtMbkEdWbNtXBXHyMbA+RTRJctUG5ooNou0Le2wPr6+PMAVilXVGD8dIWpj" + + "v9htpFvENvkZlbU++IKhCY0ICR++3ARpUrOZ3Hou/NRN36y9nlZT48tSoQKBgES2" + + "Bi6fxmBsLUiN/f64xAc1lH2DA0I728N343xRYdK4hTMfYXoUHH+QjurvwXkqmI6S" + + "cEFWAdqv7IoPYjaCSSb6ffYRuWP+LK4WxuAO0QV53SSViDdCalntHmlhRhyXVVnG" + + "CckDIqT0JfHNev7savDzDWpNe2fUXlFJEBPDqrstAoGBAOpd5+QBHF/tP5oPILH4" + + "aD/zmqMH7VtB+b/fOPwtIM+B/WnU7hHLO5t2lJYu18Be3amPkfoQIB7bpkM3Cer2" + + "G7Jw+TcHrY+EtIziDB5vwau1fl4VcbA9SfWpBojJ5Ifo9ELVxGiK95WxeQNSmLUy" + + "7AJzhK1Gwey8a/v+xfqiu9sE"; + private static final PrivateKey rsaPriv; + private static final PublicKey rsaPub; + private static final KeyPair rsaPair; + private static final EncryptionMaterialsProvider symProv; + private static final EncryptionMaterialsProvider asymProv; + private static final EncryptionMaterialsProvider symWrappedProv; + private static final String HASH_KEY = "hashKey"; + private static final String RANGE_KEY = "rangeKey"; + private static final String RSA = "RSA"; + private static final String tableName = "TableName"; + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = + EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + + private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); + private DynamoDbClient client; + private static KmsClient kmsClient = KmsClient.builder().build(); + + private static Map keyDataMap = new HashMap<>(); + + private static final Map ENCRYPTED_TEST_VALUE = new HashMap<>(); + private static final Map MIXED_TEST_VALUE = new HashMap<>(); + private static final Map SIGNED_TEST_VALUE = new HashMap<>(); + private static final Map UNTOUCHED_TEST_VALUE = new HashMap<>(); + + private static final Map ENCRYPTED_TEST_VALUE_2 = new HashMap<>(); + private static final Map MIXED_TEST_VALUE_2 = new HashMap<>(); + private static final Map SIGNED_TEST_VALUE_2 = new HashMap<>(); + private static final Map UNTOUCHED_TEST_VALUE_2 = new HashMap<>(); + + private static final String TEST_VECTOR_MANIFEST_DIR = "/vectors/encrypted_item/"; + private static final String SCENARIO_MANIFEST_PATH = TEST_VECTOR_MANIFEST_DIR + "scenarios.json"; + private static final String JAVA_DIR = "java"; + + static { + try { + KeyFactory rsaFact = KeyFactory.getInstance("RSA"); + rsaPub = rsaFact.generatePublic(new X509EncodedKeySpec(Base64.decode(rsaEncPub))); + rsaPriv = rsaFact.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(rsaEncPriv))); + rsaPair = new KeyPair(rsaPub, rsaPriv); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + symProv = new SymmetricStaticProvider(aesKey, hmacKey); + asymProv = new AsymmetricStaticProvider(rsaPair, rsaPair); + symWrappedProv = new WrappedMaterialsProvider(aesKey, aesKey, hmacKey); + + ENCRYPTED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("5").build()); + ENCRYPTED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("7").build()); + ENCRYPTED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); + ENCRYPTED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); + ENCRYPTED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + ENCRYPTED_TEST_VALUE.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + ENCRYPTED_TEST_VALUE.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + ENCRYPTED_TEST_VALUE.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + + MIXED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("6").build()); + MIXED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("8").build()); + MIXED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); + MIXED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); + MIXED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + MIXED_TEST_VALUE.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + MIXED_TEST_VALUE.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + MIXED_TEST_VALUE.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + + SIGNED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("8").build()); + SIGNED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("10").build()); + SIGNED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); + SIGNED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); + SIGNED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + SIGNED_TEST_VALUE.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + SIGNED_TEST_VALUE.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + SIGNED_TEST_VALUE.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + + UNTOUCHED_TEST_VALUE.put("hashKey", AttributeValue.builder().n("7").build()); + UNTOUCHED_TEST_VALUE.put("rangeKey", AttributeValue.builder().n("9").build()); + UNTOUCHED_TEST_VALUE.put("version", AttributeValue.builder().n("0").build()); + UNTOUCHED_TEST_VALUE.put("intValue", AttributeValue.builder().n("123").build()); + UNTOUCHED_TEST_VALUE.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + UNTOUCHED_TEST_VALUE.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + UNTOUCHED_TEST_VALUE.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + UNTOUCHED_TEST_VALUE.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + + // STORING DOUBLES + ENCRYPTED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("5").build()); + ENCRYPTED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("7").build()); + ENCRYPTED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); + ENCRYPTED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); + ENCRYPTED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + ENCRYPTED_TEST_VALUE_2.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + ENCRYPTED_TEST_VALUE_2.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + ENCRYPTED_TEST_VALUE_2.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + ENCRYPTED_TEST_VALUE_2.put( + "doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); + ENCRYPTED_TEST_VALUE_2.put( + "doubleSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) + .build()); + + MIXED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("6").build()); + MIXED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("8").build()); + MIXED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); + MIXED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); + MIXED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + MIXED_TEST_VALUE_2.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + MIXED_TEST_VALUE_2.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + MIXED_TEST_VALUE_2.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + MIXED_TEST_VALUE_2.put("doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); + MIXED_TEST_VALUE_2.put( + "doubleSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) + .build()); + + SIGNED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("8").build()); + SIGNED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("10").build()); + SIGNED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); + SIGNED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); + SIGNED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + SIGNED_TEST_VALUE_2.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + SIGNED_TEST_VALUE_2.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet<>(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + SIGNED_TEST_VALUE_2.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet<>(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + SIGNED_TEST_VALUE_2.put("doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); + SIGNED_TEST_VALUE_2.put( + "doubleSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) + .build()); + + UNTOUCHED_TEST_VALUE_2.put("hashKey", AttributeValue.builder().n("7").build()); + UNTOUCHED_TEST_VALUE_2.put("rangeKey", AttributeValue.builder().n("9").build()); + UNTOUCHED_TEST_VALUE_2.put("version", AttributeValue.builder().n("0").build()); + UNTOUCHED_TEST_VALUE_2.put("intValue", AttributeValue.builder().n("123").build()); + UNTOUCHED_TEST_VALUE_2.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + UNTOUCHED_TEST_VALUE_2.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + UNTOUCHED_TEST_VALUE_2.put( + "stringSet", + AttributeValue.builder() + .ss(new HashSet(Arrays.asList("Goodbye", "Cruel", "World", "?"))) + .build()); + UNTOUCHED_TEST_VALUE_2.put( + "intSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("1", "200", "10", "15", "0"))) + .build()); + UNTOUCHED_TEST_VALUE_2.put( + "doubleValue", AttributeValue.builder().n(String.valueOf(15)).build()); + UNTOUCHED_TEST_VALUE_2.put( + "doubleSet", + AttributeValue.builder() + .ns(new HashSet(Arrays.asList("15", "7.6", "-3", "-34.2", "0"))) + .build()); + } + + @DataProvider(name = "getEncryptTestVectors") + public static Object[][] getEncryptTestVectors() throws IOException { + ScenarioManifest scenarioManifest = + getManifestFromFile(SCENARIO_MANIFEST_PATH, new TypeReference() {}); + loadKeyData(scenarioManifest.keyDataPath); + + // Only use Java generated test vectors to dedupe the scenarios for encrypt, + // we only care that we are able to generate data using the different provider configurations + return scenarioManifest.scenarios.stream() + .filter(s -> s.ciphertextPath.contains(JAVA_DIR)) + .map(s -> new Object[] {s}) + .toArray(Object[][]::new); + } + + @DataProvider(name = "getDecryptTestVectors") + public static Object[][] getDecryptTestVectors() throws IOException { + ScenarioManifest scenarioManifest = + getManifestFromFile(SCENARIO_MANIFEST_PATH, new TypeReference() {}); + loadKeyData(scenarioManifest.keyDataPath); + + return scenarioManifest.scenarios.stream().map(s -> new Object[] {s}).toArray(Object[][]::new); + } + + @Test(dataProvider = "getDecryptTestVectors") + public void decryptTestVector(Scenario scenario) throws IOException, GeneralSecurityException { + localDynamoDb.start(); + client = localDynamoDb.createLimitedWrappedClient(); + + // load data into ciphertext tables + createCiphertextTables(client); + + // load data from vector file + putDataFromFile(client, scenario.ciphertextPath); + + // create and load metastore table if necessary + ProviderStore metastore = null; + if (scenario.metastore != null) { + MetaStore.createTable( + client, + scenario.metastore.tableName, + ProvisionedThroughput.builder().readCapacityUnits(100L).writeCapacityUnits(100L).build()); + putDataFromFile(client, scenario.metastore.path); + EncryptionMaterialsProvider metaProvider = + createProvider( + scenario.metastore.providerName, + scenario.materialName, + scenario.metastore.keys, + null); + metastore = + new MetaStore( + client, scenario.metastore.tableName, DynamoDbEncryptor.getInstance(metaProvider)); + } + + // Create the mapper with the provider under test + EncryptionMaterialsProvider provider = + createProvider(scenario.providerName, scenario.materialName, scenario.keys, metastore); + + // Verify successful decryption + switch (scenario.version) { + case "v0": + assertVersionCompatibility(provider, tableName); + break; + case "v1": + assertVersionCompatibility_2(provider, tableName); + break; + default: + throw new IllegalStateException( + "Version " + scenario.version + " not yet implemented in test vector runner"); + } + client.close(); + localDynamoDb.stop(); + } + + @Test(dataProvider = "getEncryptTestVectors") + public void encryptWithTestVector(Scenario scenario) throws IOException { + localDynamoDb.start(); + client = localDynamoDb.createLimitedWrappedClient(); + + // load data into ciphertext tables + createCiphertextTables(client); + + // create and load metastore table if necessary + ProviderStore metastore = null; + if (scenario.metastore != null) { + MetaStore.createTable( + client, + scenario.metastore.tableName, + ProvisionedThroughput.builder().readCapacityUnits(100L).writeCapacityUnits(100L).build()); + putDataFromFile(client, scenario.metastore.path); + EncryptionMaterialsProvider metaProvider = + createProvider( + scenario.metastore.providerName, + scenario.materialName, + scenario.metastore.keys, + null); + metastore = + new MetaStore( + client, scenario.metastore.tableName, DynamoDbEncryptor.getInstance(metaProvider)); + } + + // Encrypt data with the provider under test, only ensure that no exception is thrown + EncryptionMaterialsProvider provider = + createProvider(scenario.providerName, scenario.materialName, scenario.keys, metastore); + generateStandardData(provider); + client.close(); + localDynamoDb.stop(); + } + + private EncryptionMaterialsProvider createProvider( + String providerName, String materialName, Keys keys, ProviderStore metastore) { + switch (providerName) { + case ScenarioManifest.MOST_RECENT_PROVIDER_NAME: + return new CachingMostRecentProvider(metastore, materialName, 1000); + case ScenarioManifest.STATIC_PROVIDER_NAME: + KeyData decryptKeyData = keyDataMap.get(keys.decryptName); + KeyData verifyKeyData = keyDataMap.get(keys.verifyName); + SecretKey decryptKey = + new SecretKeySpec(Base64.decode(decryptKeyData.material), decryptKeyData.algorithm); + SecretKey verifyKey = + new SecretKeySpec(Base64.decode(verifyKeyData.material), verifyKeyData.algorithm); + return new SymmetricStaticProvider(decryptKey, verifyKey); + case ScenarioManifest.WRAPPED_PROVIDER_NAME: + decryptKeyData = keyDataMap.get(keys.decryptName); + verifyKeyData = keyDataMap.get(keys.verifyName); + + // This can be either the asymmetric provider, where we should test using it's explicit + // constructor, + // or a wrapped symmetric where we use the wrapped materials constructor. + if (decryptKeyData.keyType.equals(ScenarioManifest.SYMMETRIC_KEY_TYPE)) { + decryptKey = + new SecretKeySpec(Base64.decode(decryptKeyData.material), decryptKeyData.algorithm); + verifyKey = + new SecretKeySpec(Base64.decode(verifyKeyData.material), verifyKeyData.algorithm); + return new WrappedMaterialsProvider(decryptKey, decryptKey, verifyKey); + } else { + KeyData encryptKeyData = keyDataMap.get(keys.encryptName); + KeyData signKeyData = keyDataMap.get(keys.signName); + try { + // Hardcoded to use RSA for asymmetric keys. If we include vectors with a different + // asymmetric scheme this will need to be updated. + KeyFactory rsaFact = KeyFactory.getInstance(RSA); + + PublicKey encryptMaterial = + rsaFact.generatePublic( + new X509EncodedKeySpec(Base64.decode(encryptKeyData.material))); + PrivateKey decryptMaterial = + rsaFact.generatePrivate( + new PKCS8EncodedKeySpec(Base64.decode(decryptKeyData.material))); + KeyPair decryptPair = new KeyPair(encryptMaterial, decryptMaterial); + + PublicKey verifyMaterial = + rsaFact.generatePublic( + new X509EncodedKeySpec(Base64.decode(verifyKeyData.material))); + PrivateKey signingMaterial = + rsaFact.generatePrivate( + new PKCS8EncodedKeySpec(Base64.decode(signKeyData.material))); + KeyPair sigPair = new KeyPair(verifyMaterial, signingMaterial); + + return new AsymmetricStaticProvider(decryptPair, sigPair); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } + case ScenarioManifest.AWS_KMS_PROVIDER_NAME: + return new DirectKmsMaterialsProvider(kmsClient, keyDataMap.get(keys.decryptName).keyId); + default: + throw new IllegalStateException( + "Provider " + providerName + " not yet implemented in test vector runner"); + } + } + + // Create empty tables for the ciphertext. + // The underlying structure to these tables is hardcoded, + // and we run all test vectors assuming the ciphertext matches the key schema for these tables. + private void createCiphertextTables(DynamoDbClient localDynamoDb) { + // TableName Setup + ArrayList attrDef = new ArrayList<>(); + attrDef.add( + AttributeDefinition.builder() + .attributeName(HASH_KEY) + .attributeType(ScalarAttributeType.N) + .build()); + + attrDef.add( + AttributeDefinition.builder() + .attributeName(RANGE_KEY) + .attributeType(ScalarAttributeType.N) + .build()); + ArrayList keySchema = new ArrayList<>(); + keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); + keySchema.add( + KeySchemaElement.builder().attributeName(RANGE_KEY).keyType(KeyType.RANGE).build()); + + localDynamoDb.createTable( + CreateTableRequest.builder() + .tableName("TableName") + .attributeDefinitions(attrDef) + .keySchema(keySchema) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(100L) + .writeCapacityUnits(100L) + .build()) + .build()); + + // HashKeyOnly SetUp + attrDef = new ArrayList<>(); + attrDef.add( + AttributeDefinition.builder() + .attributeName(HASH_KEY) + .attributeType(ScalarAttributeType.S) + .build()); + + keySchema = new ArrayList<>(); + keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); + + localDynamoDb.createTable( + CreateTableRequest.builder() + .tableName("HashKeyOnly") + .attributeDefinitions(attrDef) + .keySchema(keySchema) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(100L) + .writeCapacityUnits(100L) + .build()) + .build()); + + // DeterministicTable SetUp + attrDef = new ArrayList<>(); + attrDef.add( + AttributeDefinition.builder() + .attributeName(HASH_KEY) + .attributeType(ScalarAttributeType.B) + .build()); + attrDef.add( + AttributeDefinition.builder() + .attributeName(RANGE_KEY) + .attributeType(ScalarAttributeType.N) + .build()); + + keySchema = new ArrayList<>(); + keySchema.add(KeySchemaElement.builder().attributeName(HASH_KEY).keyType(KeyType.HASH).build()); + keySchema.add( + KeySchemaElement.builder().attributeName(RANGE_KEY).keyType(KeyType.RANGE).build()); + + localDynamoDb.createTable( + CreateTableRequest.builder() + .tableName("DeterministicTable") + .attributeDefinitions(attrDef) + .keySchema(keySchema) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(100L) + .writeCapacityUnits(100L) + .build()) + .build()); + } + + // Given a file in the test vector ciphertext format, put those entries into their tables. + // This assumes the expected tables have already been created. + private void putDataFromFile(DynamoDbClient localDynamoDb, String filename) throws IOException { + Map>> manifest = + getCiphertextManifestFromFile(filename); + for (String tableName : manifest.keySet()) { + for (Map attributes : manifest.get(tableName)) { + localDynamoDb.putItem( + PutItemRequest.builder().tableName(tableName).item(attributes).build()); + } + } + } + + private Map>> getCiphertextManifestFromFile( + String filename) throws IOException { + return getManifestFromFile( + TEST_VECTOR_MANIFEST_DIR + stripFilePath(filename), + new TypeReference>>>() {}); + } + + private static T getManifestFromFile(String filename, TypeReference typeRef) + throws IOException { + final URL url = HolisticIT.class.getResource(filename); + if (url == null) { + throw new IllegalStateException("Missing file " + filename + " in src/test/resources."); + } + final File manifestFile = new File(url.getPath()); + final ObjectMapper manifestMapper = new ObjectMapper(); + return (T) manifestMapper.readValue(manifestFile, typeRef); + } + + private static void loadKeyData(String filename) throws IOException { + keyDataMap = + getManifestFromFile( + TEST_VECTOR_MANIFEST_DIR + stripFilePath(filename), + new TypeReference>() {}); + } + + public void generateStandardData(EncryptionMaterialsProvider prov) { + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + Map encryptedRecord; + Map> actions; + EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + Map hashKey1 = new HashMap<>(); + Map hashKey2 = new HashMap<>(); + Map hashKey3 = new HashMap<>(); + + hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); + hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); + hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); + + // encrypted record + actions = new HashMap<>(); + for (final String attr : ENCRYPTED_TEST_VALUE_2.keySet()) { + switch (attr) { + case "hashKey": + case "rangeKey": + case "version": + actions.put(attr, signOnly); + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + encryptedRecord = encryptor.encryptRecord(ENCRYPTED_TEST_VALUE_2, actions, encryptionContext); + putItems(encryptedRecord, tableName); + + // mixed test record + actions = new HashMap<>(); + for (final String attr : MIXED_TEST_VALUE_2.keySet()) { + switch (attr) { + case "rangeKey": + case "hashKey": + case "version": + case "stringValue": + case "doubleValue": + case "doubleSet": + actions.put(attr, signOnly); + break; + case "intValue": + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + encryptedRecord = encryptor.encryptRecord(MIXED_TEST_VALUE_2, actions, encryptionContext); + putItems(encryptedRecord, tableName); + + // sign only record + actions = new HashMap<>(); + for (final String attr : SIGNED_TEST_VALUE_2.keySet()) { + actions.put(attr, signOnly); + } + encryptedRecord = encryptor.encryptRecord(SIGNED_TEST_VALUE_2, actions, encryptionContext); + putItems(encryptedRecord, tableName); + + // untouched record + putItems(UNTOUCHED_TEST_VALUE_2, tableName); + } + + private void putItems(Map map, String tableName) { + PutItemRequest request = PutItemRequest.builder().item(map).tableName(tableName).build(); + client.putItem(request); + } + + private Map getItems(Map map, String tableName) { + GetItemRequest request = GetItemRequest.builder().key(map).tableName(tableName).build(); + return client.getItem(request).item(); + } + + private void assertVersionCompatibility(EncryptionMaterialsProvider provider, String tableName) + throws GeneralSecurityException { + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + Map response; + Map decryptedRecord; + EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + + // Set up maps for table items + HashMap untouched = new HashMap<>(); + HashMap signed = new HashMap<>(); + HashMap mixed = new HashMap<>(); + HashMap encrypted = new HashMap<>(); + HashMap hashKey1 = new HashMap<>(); + HashMap hashKey2 = new HashMap<>(); + HashMap hashKey3 = new HashMap<>(); + untouched.put("hashKey", UNTOUCHED_TEST_VALUE.get("hashKey")); + untouched.put("rangeKey", UNTOUCHED_TEST_VALUE.get("rangeKey")); + + signed.put("hashKey", SIGNED_TEST_VALUE.get("hashKey")); + signed.put("rangeKey", SIGNED_TEST_VALUE.get("rangeKey")); + + mixed.put("hashKey", MIXED_TEST_VALUE.get("hashKey")); + mixed.put("rangeKey", MIXED_TEST_VALUE.get("rangeKey")); + + encrypted.put("hashKey", ENCRYPTED_TEST_VALUE.get("hashKey")); + encrypted.put("rangeKey", ENCRYPTED_TEST_VALUE.get("rangeKey")); + + hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); + hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); + hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); + + // check untouched attr + assertTrue( + new DdbRecordMatcher(UNTOUCHED_TEST_VALUE, false).matches(getItems(untouched, tableName))); + + // check signed attr + // Describe what actions need to be taken for each attribute + Map> actions = new HashMap<>(); + for (final String attr : SIGNED_TEST_VALUE.keySet()) { + actions.put(attr, signOnly); + } + response = getItems(signed, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(SIGNED_TEST_VALUE, false).matches(decryptedRecord)); + + // check mixed attr + actions = new HashMap<>(); + for (final String attr : MIXED_TEST_VALUE.keySet()) { + switch (attr) { + case "rangeKey": + case "hashKey": + case "version": + case "stringValue": + actions.put(attr, signOnly); + break; + case "intValue": + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + response = getItems(mixed, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(MIXED_TEST_VALUE, false).matches(decryptedRecord)); + + // check encrypted attr + actions = new HashMap<>(); + for (final String attr : ENCRYPTED_TEST_VALUE.keySet()) { + switch (attr) { + case "hashKey": + case "rangeKey": + case "version": + actions.put(attr, signOnly); + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + response = getItems(encrypted, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(ENCRYPTED_TEST_VALUE, false).matches(decryptedRecord)); + + assertEquals("Foo", getItems(hashKey1, "HashKeyOnly").get("hashKey").s()); + assertEquals("Bar", getItems(hashKey2, "HashKeyOnly").get("hashKey").s()); + assertEquals("Baz", getItems(hashKey3, "HashKeyOnly").get("hashKey").s()); + + Map key = new HashMap<>(); + for (int i = 1; i <= 3; ++i) { + key.put("hashKey", AttributeValue.builder().n("0").build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, "TableName"); + assertEquals(0, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + + key.put("hashKey", AttributeValue.builder().n("1").build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, "TableName"); + assertEquals(1, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + + key.put("hashKey", AttributeValue.builder().n(String.valueOf(4 + i)).build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, "TableName"); + assertEquals(4 + i, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + } + } + + private void assertVersionCompatibility_2(EncryptionMaterialsProvider provider, String tableName) + throws GeneralSecurityException { + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + Map response; + Map decryptedRecord; + EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + + // Set up maps for table items + HashMap untouched = new HashMap<>(); + HashMap signed = new HashMap<>(); + HashMap mixed = new HashMap<>(); + HashMap encrypted = new HashMap<>(); + HashMap hashKey1 = new HashMap<>(); + HashMap hashKey2 = new HashMap<>(); + HashMap hashKey3 = new HashMap<>(); + + untouched.put("hashKey", UNTOUCHED_TEST_VALUE_2.get("hashKey")); + untouched.put("rangeKey", UNTOUCHED_TEST_VALUE_2.get("rangeKey")); + + signed.put("hashKey", SIGNED_TEST_VALUE_2.get("hashKey")); + signed.put("rangeKey", SIGNED_TEST_VALUE_2.get("rangeKey")); + + mixed.put("hashKey", MIXED_TEST_VALUE_2.get("hashKey")); + mixed.put("rangeKey", MIXED_TEST_VALUE_2.get("rangeKey")); + + encrypted.put("hashKey", ENCRYPTED_TEST_VALUE_2.get("hashKey")); + encrypted.put("rangeKey", ENCRYPTED_TEST_VALUE_2.get("rangeKey")); + + hashKey1.put("hashKey", AttributeValue.builder().s("Foo").build()); + hashKey2.put("hashKey", AttributeValue.builder().s("Bar").build()); + hashKey3.put("hashKey", AttributeValue.builder().s("Baz").build()); + + // check untouched attr + assert new DdbRecordMatcher(UNTOUCHED_TEST_VALUE_2, false) + .matches(getItems(untouched, tableName)); + + // check signed attr + // Describe what actions need to be taken for each attribute + Map> actions = new HashMap<>(); + for (final String attr : SIGNED_TEST_VALUE_2.keySet()) { + actions.put(attr, signOnly); + } + response = getItems(signed, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(SIGNED_TEST_VALUE_2, false).matches(decryptedRecord)); + + // check mixed attr + actions = new HashMap<>(); + for (final String attr : MIXED_TEST_VALUE_2.keySet()) { + switch (attr) { + case "rangeKey": + case "hashKey": + case "version": + case "stringValue": + case "doubleValue": + case "doubleSet": + actions.put(attr, signOnly); + break; + case "intValue": + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + response = getItems(mixed, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(MIXED_TEST_VALUE_2, false).matches(decryptedRecord)); + + // check encrypted attr + actions = new HashMap<>(); + for (final String attr : ENCRYPTED_TEST_VALUE_2.keySet()) { + switch (attr) { + case "hashKey": + case "rangeKey": + case "version": + actions.put(attr, signOnly); + break; + default: + actions.put(attr, encryptAndSign); + break; + } + } + response = getItems(encrypted, tableName); + decryptedRecord = encryptor.decryptRecord(response, actions, encryptionContext); + assertTrue(new DdbRecordMatcher(ENCRYPTED_TEST_VALUE_2, false).matches(decryptedRecord)); + + // check HashKey Table + assertEquals("Foo", getItems(hashKey1, "HashKeyOnly").get("hashKey").s()); + assertEquals("Bar", getItems(hashKey2, "HashKeyOnly").get("hashKey").s()); + assertEquals("Baz", getItems(hashKey3, "HashKeyOnly").get("hashKey").s()); + + // Check Hash and Range Key Values + Map key = new HashMap<>(); + for (int i = 1; i <= 3; ++i) { + key.put("hashKey", AttributeValue.builder().n("0").build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, tableName); + assertEquals(0, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + + key.put("hashKey", AttributeValue.builder().n("1").build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, tableName); + assertEquals(1, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + + key.put("hashKey", AttributeValue.builder().n(String.valueOf(4 + i)).build()); + key.put("rangeKey", AttributeValue.builder().n(String.valueOf(i)).build()); + response = getItems(key, tableName); + assertEquals(4 + i, Integer.parseInt(response.get("hashKey").n())); + assertEquals(i, Integer.parseInt(response.get("rangeKey").n())); + } + } + + private static String stripFilePath(String path) { + return path.replaceFirst("file://", ""); + } + + @JsonDeserialize(using = AttributeValueDeserializer.class) + public abstract static class DeserializedAttributeValue implements AttributeValue.Builder {} +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java new file mode 100644 index 0000000000..fd3bf37ace --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java @@ -0,0 +1,296 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.SignatureException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.crypto.spec.SecretKeySpec; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.TestDelegatedKey; + +public class DelegatedEncryptionTest { + private static SecretKeySpec rawEncryptionKey; + private static SecretKeySpec rawMacKey; + private static DelegatedKey encryptionKey; + private static DelegatedKey macKey; + + private EncryptionMaterialsProvider prov; + private DynamoDbEncryptor encryptor; + private Map attribs; + private EncryptionContext context; + + @BeforeClass + public static void setupClass() { + rawEncryptionKey = new SecretKeySpec(Utils.getRandom(32), "AES"); + encryptionKey = new TestDelegatedKey(rawEncryptionKey); + + rawMacKey = new SecretKeySpec(Utils.getRandom(32), "HmacSHA256"); + macKey = new TestDelegatedKey(rawMacKey); + } + + @BeforeMethod + public void setUp() { + prov = new SymmetricStaticProvider(encryptionKey, macKey, + Collections.emptyMap()); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + + attribs = new HashMap<>(); + attribs.put("intValue", AttributeValue.builder().n("123").build()); + attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + attribs.put("byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); + attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); + attribs.put("hashKey", AttributeValue.builder().n("5").build()); + attribs.put("rangeKey", AttributeValue.builder().n("7").build()); + attribs.put("version", AttributeValue.builder().n("0").build()); + + context = EncryptionContext.builder() + .tableName("TableName") + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + } + + @Test + public void testSetSignatureFieldName() { + assertNotNull(encryptor.getSignatureFieldName()); + encryptor.setSignatureFieldName("A different value"); + assertEquals("A different value", encryptor.getSignatureFieldName()); + } + + @Test + public void testSetMaterialDescriptionFieldName() { + assertNotNull(encryptor.getMaterialDescriptionFieldName()); + encryptor.setMaterialDescriptionFieldName("A different value"); + assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); + } + + @Test + public void fullEncryption() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has been encrypted (we'll assume the others are correct as well) + assertTrue(encryptedAttributes.containsKey("stringValue")); + assertNull(encryptedAttributes.get("stringValue").s()); + assertNotNull(encryptedAttributes.get("stringValue").b()); + } + + @Test(expectedExceptions = SignatureException.class) + public void fullEncryptionBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void badVersionNumber() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + SdkBytes materialDescription = + encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); + byte[] rawArray = materialDescription.asByteArray(); + assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. + rawArray[0] = 100; + encryptedAttributes.put( + encryptor.getMaterialDescriptionFieldName(), + AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test + public void signedOnly() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test + public void signedOnlyNullCryptoKey() throws GeneralSecurityException { + prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyNoSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.remove(encryptor.getSignatureFieldName()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test + public void RsaSignedOnly() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-" + ); + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-" + ); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.replace("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { + assertEquals(o1.b(), o2.b()); + assertSetsEqual(o1.bs(), o2.bs()); + assertEquals(o1.n(), o2.n()); + assertSetsEqual(o1.ns(), o2.ns()); + assertEquals(o1.s(), o2.s()); + assertSetsEqual(o1.ss(), o2.ss()); + } + + private void assertSetsEqual(Collection c1, Collection c2) { + assertFalse(c1 == null ^ c2 == null); + if (c1 != null) { + Set s1 = new HashSet<>(c1); + Set s2 = new HashSet<>(c2); + assertEquals(s1, s2); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java new file mode 100644 index 0000000000..ce22c396fa --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java @@ -0,0 +1,280 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.SignatureException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.crypto.spec.SecretKeySpec; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.TestDelegatedKey; + +public class DelegatedEnvelopeEncryptionTest { + private static SecretKeySpec rawEncryptionKey; + private static SecretKeySpec rawMacKey; + private static DelegatedKey encryptionKey; + private static DelegatedKey macKey; + + private EncryptionMaterialsProvider prov; + private DynamoDbEncryptor encryptor; + private Map attribs; + private EncryptionContext context; + + @BeforeClass + public static void setupClass() { + rawEncryptionKey = new SecretKeySpec(Utils.getRandom(32), "AES"); + encryptionKey = new TestDelegatedKey(rawEncryptionKey); + + rawMacKey = new SecretKeySpec(Utils.getRandom(32), "HmacSHA256"); + macKey = new TestDelegatedKey(rawMacKey); + } + + @BeforeMethod + public void setUp() throws Exception { + prov = + new WrappedMaterialsProvider( + encryptionKey, encryptionKey, macKey, Collections.emptyMap()); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + + attribs = new HashMap(); + attribs.put("intValue", AttributeValue.builder().n("123").build()); + attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + attribs.put( + "byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5}))).build()); + attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); + attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); + attribs.put("hashKey", AttributeValue.builder().n("5").build()); + attribs.put("rangeKey", AttributeValue.builder().n("7").build()); + attribs.put("version", AttributeValue.builder().n("0").build()); + + context = + new EncryptionContext.Builder() + .tableName("TableName") + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + } + + @Test + public void testSetSignatureFieldName() { + assertNotNull(encryptor.getSignatureFieldName()); + encryptor.setSignatureFieldName("A different value"); + assertEquals("A different value", encryptor.getSignatureFieldName()); + } + + @Test + public void testSetMaterialDescriptionFieldName() { + assertNotNull(encryptor.getMaterialDescriptionFieldName()); + encryptor.setMaterialDescriptionFieldName("A different value"); + assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); + } + + @Test + public void fullEncryption() throws GeneralSecurityException{ + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has been encrypted (we'll assume the others are correct as well) + assertTrue(encryptedAttributes.containsKey("stringValue")); + assertNull(encryptedAttributes.get("stringValue").s()); + assertNotNull(encryptedAttributes.get("stringValue").b()); + } + + @Test(expectedExceptions = SignatureException.class) + public void fullEncryptionBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void badVersionNumber() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + SdkBytes materialDescription = + encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); + byte[] rawArray = materialDescription.asByteArray(); + assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. + rawArray[0] = 100; + encryptedAttributes.put( + encryptor.getMaterialDescriptionFieldName(), + AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test + public void signedOnlyNullCryptoKey() throws GeneralSecurityException { + prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyNoSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.remove(encryptor.getSignatureFieldName()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test + public void RsaSignedOnly() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-"); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-"); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.replace("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { + assertEquals(o1.b(), o2.b()); + assertSetsEqual(o1.bs(), o2.bs()); + assertEquals(o1.n(), o2.n()); + assertSetsEqual(o1.ns(), o2.ns()); + assertEquals(o1.s(), o2.s()); + assertSetsEqual(o1.ss(), o2.ss()); + } + + private void assertSetsEqual(Collection c1, Collection c2) { + assertFalse(c1 == null ^ c2 == null); + if (c1 != null) { + Set s1 = new HashSet<>(c1); + Set s2 = new HashSet<>(c2); + assertEquals(s1, s2); + } + } + +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java new file mode 100644 index 0000000000..87fb8353bb --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java @@ -0,0 +1,591 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import static java.util.stream.Collectors.toMap; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; +import static org.testng.collections.Sets.newHashSet; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableName; + +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.security.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.mockito.internal.util.collections.Sets; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttrMatcher; + +public class DynamoDbEncryptorTest { + private static SecretKey encryptionKey; + private static SecretKey macKey; + + private InstrumentedEncryptionMaterialsProvider prov; + private DynamoDbEncryptor encryptor; + private Map attribs; + private EncryptionContext context; + private static final String OVERRIDDEN_TABLE_NAME = "TheBestTableName"; + + @BeforeClass + public static void setUpClass() throws Exception { + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, Utils.getRng()); + encryptionKey = aesGen.generateKey(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + prov = new InstrumentedEncryptionMaterialsProvider( + new SymmetricStaticProvider(encryptionKey, macKey, + Collections.emptyMap())); + encryptor = DynamoDbEncryptor.getInstance(prov, "enryptor-"); + + attribs = new HashMap<>(); + attribs.put("intValue", AttributeValue.builder().n("123").build()); + attribs.put("stringValue", AttributeValue.builder().s("Hello world!").build()); + attribs.put("byteArrayValue", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build()); + attribs.put("stringSet", AttributeValue.builder().ss("Goodbye", "Cruel", "World", "?").build()); + attribs.put("intSet", AttributeValue.builder().ns("1", "200", "10", "15", "0").build()); + attribs.put("hashKey", AttributeValue.builder().n("5").build()); + attribs.put("rangeKey", AttributeValue.builder().n("7").build()); + attribs.put("version", AttributeValue.builder().n("0").build()); + + // New(er) data types + attribs.put("booleanTrue", AttributeValue.builder().bool(true).build()); + attribs.put("booleanFalse", AttributeValue.builder().bool(false).build()); + attribs.put("nullValue", AttributeValue.builder().nul(true).build()); + Map tmpMap = new HashMap<>(attribs); + attribs.put("listValue", AttributeValue.builder().l( + AttributeValue.builder().s("I'm a string").build(), + AttributeValue.builder().n("42").build(), + AttributeValue.builder().s("Another string").build(), + AttributeValue.builder().ns("1", "4", "7").build(), + AttributeValue.builder().m(tmpMap).build(), + AttributeValue.builder().l( + AttributeValue.builder().n("123").build(), + AttributeValue.builder().ns("1", "200", "10", "15", "0").build(), + AttributeValue.builder().ss("Goodbye", "Cruel", "World", "!").build() + ).build()).build()); + tmpMap = new HashMap<>(); + tmpMap.put("another string", AttributeValue.builder().s("All around the cobbler's bench").build()); + tmpMap.put("next line", AttributeValue.builder().ss("the monkey", "chased", "the weasel").build()); + tmpMap.put("more lyrics", AttributeValue.builder().l( + AttributeValue.builder().s("the monkey").build(), + AttributeValue.builder().s("thought twas").build(), + AttributeValue.builder().s("all in fun").build() + ).build()); + tmpMap.put("weasel", AttributeValue.builder().m(Collections.singletonMap("pop", AttributeValue.builder().bool(true).build())).build()); + attribs.put("song", AttributeValue.builder().m(tmpMap).build()); + + context = EncryptionContext.builder() + .tableName("TableName") + .hashKeyName("hashKey") + .rangeKeyName("rangeKey") + .build(); + } + + @Test + public void testSetSignatureFieldName() { + assertNotNull(encryptor.getSignatureFieldName()); + encryptor.setSignatureFieldName("A different value"); + assertEquals("A different value", encryptor.getSignatureFieldName()); + } + + @Test + public void testSetMaterialDescriptionFieldName() { + assertNotNull(encryptor.getMaterialDescriptionFieldName()); + encryptor.setMaterialDescriptionFieldName("A different value"); + assertEquals("A different value", encryptor.getMaterialDescriptionFieldName()); + } + + @Test + public void fullEncryption() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has been encrypted (we'll assume the others are correct as well) + assertTrue(encryptedAttributes.containsKey("stringValue")); + assertNull(encryptedAttributes.get("stringValue").s()); + assertNotNull(encryptedAttributes.get("stringValue").b()); + + // Make sure we're calling the proper getEncryptionMaterials method + assertEquals( + "Wrong getEncryptionMaterials() called", + 1, + prov.getCallCount("getEncryptionMaterials(EncryptionContext context)")); + } + + @Test + public void ensureEncryptedAttributesUnmodified() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + String encryptedString = encryptedAttributes.toString(); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + + assertEquals(encryptedString, encryptedAttributes.toString()); + } + + @Test(expectedExceptions = SignatureException.class) + public void fullEncryptionBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void badVersionNumber() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept( + Collections.unmodifiableMap(attribs), context, "hashKey", "rangeKey", "version"); + SdkBytes materialDescription = + encryptedAttributes.get(encryptor.getMaterialDescriptionFieldName()).b(); + byte[] rawArray = materialDescription.asByteArray(); + assertEquals(0, rawArray[0]); // This will need to be kept in sync with the current version. + rawArray[0] = 100; + encryptedAttributes.put( + encryptor.getMaterialDescriptionFieldName(), + AttributeValue.builder().b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(rawArray))).build()); + encryptor.decryptAllFieldsExcept( + Collections.unmodifiableMap(encryptedAttributes), + context, + "hashKey", + "rangeKey", + "version"); + } + + @Test + public void signedOnly() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test + public void signedOnlyNullCryptoKey() throws GeneralSecurityException { + prov = + new InstrumentedEncryptionMaterialsProvider( + new SymmetricStaticProvider(null, macKey, Collections.emptyMap())); + encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyBadSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test(expectedExceptions = SignatureException.class) + public void signedOnlyNoSignature() throws GeneralSecurityException { + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.remove(encryptor.getSignatureFieldName()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test + public void RsaSignedOnly() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-"); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + encryptor = + DynamoDbEncryptor.getInstance( + new SymmetricStaticProvider( + encryptionKey, sigPair, Collections.emptyMap()), + "encryptor-"); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + /** + * Tests that no exception is thrown when the encryption context override operator is null + * + * @throws GeneralSecurityException + */ + @Test + public void testNullEncryptionContextOperator() throws GeneralSecurityException { + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + encryptor.setEncryptionContextOverrideOperator(null); + encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); + } + + /** + * Tests decrypt and encrypt with an encryption context override operator + */ + @Test + public void testTableNameOverriddenEncryptionContextOperator() throws GeneralSecurityException { + // Ensure that the table name is different from what we override the table to. + assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + encryptor.setEncryptionContextOverrideOperator( + overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); + Map encryptedItems = + encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); + Map decryptedItems = + encryptor.decryptAllFieldsExcept(encryptedItems, context, Collections.emptyList()); + assertThat(decryptedItems, AttrMatcher.match(attribs)); + } + + + /** + * Tests encrypt with an encryption context override operator, and a second encryptor without an override + */ + @Test + public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor() + throws GeneralSecurityException { + // Ensure that the table name is different from what we override the table to. + assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + encryptor.setEncryptionContextOverrideOperator( + overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); + Map encryptedItems = + encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); + + EncryptionContext expectedOverriddenContext = + new EncryptionContext.Builder(context).tableName("TheBestTableName").build(); + Map decryptedItems = + encryptorWithoutOverride.decryptAllFieldsExcept( + encryptedItems, expectedOverriddenContext, Collections.emptyList()); + assertThat(decryptedItems, AttrMatcher.match(attribs)); + } + + /** + * Tests encrypt with an encryption context override operator, and a second encryptor without an override + */ + @Test(expectedExceptions = SignatureException.class) + public void + testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptorButTheOriginalEncryptionContext() + throws GeneralSecurityException { + // Ensure that the table name is different from what we override the table to. + assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); + DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + encryptor.setEncryptionContextOverrideOperator( + overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); + Map encryptedItems = + encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); + + // Use the original encryption context, and expect a signature failure + Map decryptedItems = + encryptorWithoutOverride.decryptAllFieldsExcept( + encryptedItems, context, Collections.emptyList()); + } + + @Test + public void EcdsaSignedOnly() throws GeneralSecurityException { + + encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + Map decryptedAttributes = + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + + // Make sure keys and version are not encrypted + assertAttrEquals(attribs.get("hashKey"), encryptedAttributes.get("hashKey")); + assertAttrEquals(attribs.get("rangeKey"), encryptedAttributes.get("rangeKey")); + assertAttrEquals(attribs.get("version"), encryptedAttributes.get("version")); + + // Make sure String has not been encrypted (we'll assume the others are correct as well) + assertAttrEquals(attribs.get("stringValue"), encryptedAttributes.get("stringValue")); + } + + @Test(expectedExceptions = SignatureException.class) + public void EcdsaSignedOnlyBadSignature() throws GeneralSecurityException { + + encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + + Map encryptedAttributes = + encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); + assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); + encryptedAttributes.put("hashKey", AttributeValue.builder().n("666").build()); + encryptor.decryptAllFieldsExcept( + encryptedAttributes, context, attribs.keySet().toArray(new String[0])); + } + + @Test + public void toByteArray() throws ReflectiveOperationException { + final byte[] expected = new byte[] {0, 1, 2, 3, 4, 5}; + assertToByteArray("Wrap", expected, ByteBuffer.wrap(expected)); + assertToByteArray("Wrap-RO", expected, ByteBuffer.wrap(expected).asReadOnlyBuffer()); + + assertToByteArray("Wrap-Truncated-Sliced", expected, ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 6).slice()); + assertToByteArray("Wrap-Offset-Sliced", expected, ByteBuffer.wrap(new byte[] {6, 0, 1, 2, 3, 4, 5, 6}, 1, 6).slice()); + assertToByteArray("Wrap-Truncated", expected, ByteBuffer.wrap(new byte[] {0, 1, 2, 3, 4, 5, 6}, 0, 6)); + assertToByteArray("Wrap-Offset", expected, ByteBuffer.wrap(new byte[] {6, 0, 1, 2, 3, 4, 5, 6}, 1, 6)); + + ByteBuffer buff = ByteBuffer.allocate(expected.length + 10); + buff.put(expected); + buff.flip(); + assertToByteArray("Normal", expected, buff); + + buff = ByteBuffer.allocateDirect(expected.length + 10); + buff.put(expected); + buff.flip(); + assertToByteArray("Direct", expected, buff); + } + + @Test + public void testDecryptWithPlaintextItem() throws GeneralSecurityException { + Map> attributeWithEmptyEncryptionFlags = + attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet())); + + Map decryptedAttributes = + encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + } + + /* + Test decrypt with a map that contains a new key (not included in attribs) with an encryption flag set that contains ENCRYPT and SIGN. + */ + @Test + public void testDecryptWithPlainTextItemAndAdditionNewAttributeHavingEncryptionFlag() + throws GeneralSecurityException { + Map> attributeWithEmptyEncryptionFlags = + attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet())); + attributeWithEmptyEncryptionFlags.put( + "newAttribute", Sets.newSet(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); + + Map decryptedAttributes = + encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context); + assertThat(decryptedAttributes, AttrMatcher.match(attribs)); + } + private void assertToByteArray( + final String msg, final byte[] expected, final ByteBuffer testValue) + throws ReflectiveOperationException { + Method m = DynamoDbEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); + m.setAccessible(true); + + int oldPosition = testValue.position(); + int oldLimit = testValue.limit(); + + assertThat(m.invoke(null, testValue), is(expected)); + assertEquals(msg + ":Position", oldPosition, testValue.position()); + assertEquals(msg + ":Limit", oldLimit, testValue.limit()); + } + + private void assertAttrEquals(AttributeValue o1, AttributeValue o2) { + assertEquals(o1.b(), o2.b()); + assertSetsEqual(o1.bs(), o2.bs()); + assertEquals(o1.n(), o2.n()); + assertSetsEqual(o1.ns(), o2.ns()); + assertEquals(o1.s(), o2.s()); + assertSetsEqual(o1.ss(), o2.ss()); + } + + private void assertSetsEqual(Collection c1, Collection c2) { + assertFalse(c1 == null ^ c2 == null); + if (c1 != null) { + Set s1 = new HashSet<>(c1); + Set s2 = new HashSet<>(c2); + assertEquals(s1, s2); + } + } + + private EncryptionMaterialsProvider getMaterialProviderwithECDSA() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException { + Security.addProvider(new BouncyCastleProvider()); + ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp384r1"); + KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC"); + g.initialize(ecSpec, Utils.getRng()); + KeyPair keypair = g.generateKeyPair(); + Map description = new HashMap<>(); + description.put(DynamoDbEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); + return new SymmetricStaticProvider(null, keypair, description); + } + + private static final class InstrumentedEncryptionMaterialsProvider implements EncryptionMaterialsProvider { + private final EncryptionMaterialsProvider delegate; + private final ConcurrentHashMap calls = new ConcurrentHashMap<>(); + + InstrumentedEncryptionMaterialsProvider(EncryptionMaterialsProvider delegate) { + this.delegate = delegate; + } + + @Override + public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { + incrementMethodCount("getDecryptionMaterials()"); + return delegate.getDecryptionMaterials(context); + } + + @Override + public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { + incrementMethodCount("getEncryptionMaterials(EncryptionContext context)"); + return delegate.getEncryptionMaterials(context); + } + + @Override + public void refresh() { + incrementMethodCount("refresh()"); + delegate.refresh(); + } + + int getCallCount(String method) { + AtomicInteger count = calls.get(method); + if (count != null) { + return count.intValue(); + } else { + return 0; + } + } + + @SuppressWarnings("unused") + public void resetCallCounts() { + calls.clear(); + } + + private void incrementMethodCount(String method) { + AtomicInteger oldValue = calls.putIfAbsent(method, new AtomicInteger(1)); + if (oldValue != null) { + oldValue.incrementAndGet(); + } + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java new file mode 100644 index 0000000000..8320e79526 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbSignerTest.java @@ -0,0 +1,567 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption; + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Security; +import java.security.SignatureException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.crypto.KeyGenerator; + +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class DynamoDbSignerTest { + // These use the Key type (rather than PublicKey, PrivateKey, and SecretKey) + // to test the routing logic within the signer. + private static Key pubKeyRsa; + private static Key privKeyRsa; + private static Key macKey; + private DynamoDbSigner signerRsa; + private DynamoDbSigner signerEcdsa; + private static Key pubKeyEcdsa; + private static Key privKeyEcdsa; + + @BeforeClass + public static void setUpClass() throws Exception { + + // RSA key generation + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + KeyPair sigPair = rsaGen.generateKeyPair(); + pubKeyRsa = sigPair.getPublic(); + privKeyRsa = sigPair.getPrivate(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + + Security.addProvider(new BouncyCastleProvider()); + ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp384r1"); + KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC"); + g.initialize(ecSpec, Utils.getRng()); + KeyPair keypair = g.generateKeyPair(); + pubKeyEcdsa = keypair.getPublic(); + privKeyEcdsa = keypair.getPrivate(); + } + + @BeforeMethod + public void setUp() { + signerRsa = DynamoDbSigner.getInstance("SHA256withRSA", Utils.getRng()); + signerEcdsa = DynamoDbSigner.getInstance("SHA384withECDSA", Utils.getRng()); + } + + @Test + public void mac() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", + AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void macLists() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().ss("Value1", "Value2", "Value3").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().ns("100", "200", "300").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", + AttributeValue.builder() + .bs( + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})), + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void macListsUnsorted() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().ss("Value3", "Value1", "Value2").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().ns("100", "300", "200").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", + AttributeValue.builder() + .bs( + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1})), + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + Map scrambledAttributes = new HashMap(); + scrambledAttributes.put("Key1", AttributeValue.builder().ss("Value1", "Value2", "Value3").build()); + scrambledAttributes.put("Key2", AttributeValue.builder().ns("100", "200", "300").build()); + scrambledAttributes.put( + "Key3", + AttributeValue.builder() + .bs( + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3})), + SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {3, 2, 1}))) + .build()); + + signerRsa.verifySignature( + scrambledAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void macNoAdMatchesEmptyAd() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = signerRsa.calculateSignature(itemAttributes, attributeFlags, null, macKey); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void macWithIgnoredChange() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void macChangedValue() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void macChangedFlag() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], macKey); + + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], macKey, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void macChangedAssociatedData() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[] {3, 2, 1}, macKey); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[] {1, 2, 3}, macKey, ByteBuffer.wrap(signature)); + } + + @Test + public void sig() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test + public void sigWithReadOnlySignature() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + signerRsa.verifySignature( + itemAttributes, + attributeFlags, + new byte[0], + pubKeyRsa, + ByteBuffer.wrap(signature).asReadOnlyBuffer()); + } + + @Test + public void sigNoAdMatchesEmptyAd() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, null, privKeyRsa); + + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test + public void sigWithIgnoredChange() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigChangedValue() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigChangedFlag() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + signerRsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyRsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigChangedAssociatedData() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN, EncryptionFlags.ENCRYPT)); + byte[] signature = + signerRsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyRsa); + + signerRsa.verifySignature( + itemAttributes, + attributeFlags, + new byte[] {1, 2, 3}, + pubKeyRsa, + ByteBuffer.wrap(signature)); + } + + @Test + public void sigEcdsa() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + signerEcdsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); + } + + @Test + public void sigEcdsaWithReadOnlySignature() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + signerEcdsa.verifySignature( + itemAttributes, + attributeFlags, + new byte[0], + pubKeyEcdsa, + ByteBuffer.wrap(signature).asReadOnlyBuffer()); + } + + @Test + public void sigEcdsaNoAdMatchesEmptyAd() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, null, privKeyEcdsa); + + signerEcdsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); + } + + @Test + public void sigEcdsaWithIgnoredChange() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key4", AttributeValue.builder().s("Ignored Value").build()); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + itemAttributes.put("Key4", AttributeValue.builder().s("New Ignored Value").build()); + signerEcdsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigEcdsaChangedValue() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + itemAttributes.put("Key2", AttributeValue.builder().n("99").build()); + signerEcdsa.verifySignature( + itemAttributes, attributeFlags, new byte[0], pubKeyEcdsa, ByteBuffer.wrap(signature)); + } + + @Test(expectedExceptions = SignatureException.class) + public void sigEcdsaChangedAssociatedData() throws GeneralSecurityException { + Map itemAttributes = new HashMap(); + Map> attributeFlags = new HashMap>(); + + itemAttributes.put("Key1", AttributeValue.builder().s("Value1").build()); + attributeFlags.put("Key1", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put("Key2", AttributeValue.builder().n("100").build()); + attributeFlags.put("Key2", EnumSet.of(EncryptionFlags.SIGN)); + itemAttributes.put( + "Key3", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap(new byte[] {0, 1, 2, 3}))) + .build()); + attributeFlags.put("Key3", EnumSet.of(EncryptionFlags.SIGN)); + byte[] signature = + signerEcdsa.calculateSignature(itemAttributes, attributeFlags, new byte[0], privKeyEcdsa); + + signerEcdsa.verifySignature( + itemAttributes, + attributeFlags, + new byte[] {1, 2, 3}, + pubKeyEcdsa, + ByteBuffer.wrap(signature)); + } +} \ No newline at end of file diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java new file mode 100644 index 0000000000..b9258c5f83 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/AsymmetricRawMaterialsTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class AsymmetricRawMaterialsTest { + private static SecureRandom rnd; + private static KeyPair encryptionPair; + private static SecretKey macKey; + private static KeyPair sigPair; + private Map description; + + @BeforeClass + public static void setUpClass() throws NoSuchAlgorithmException { + rnd = new SecureRandom(); + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, rnd); + encryptionPair = rsaGen.generateKeyPair(); + sigPair = rsaGen.generateKeyPair(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, rnd); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + } + + @Test + public void macNoDescription() throws GeneralSecurityException { + AsymmetricRawMaterials matEncryption = new AsymmetricRawMaterials(encryptionPair, macKey); + assertEquals(macKey, matEncryption.getSigningKey()); + assertEquals(macKey, matEncryption.getVerificationKey()); + assertFalse(matEncryption.getMaterialDescription().isEmpty()); + + SecretKey envelopeKey = matEncryption.getEncryptionKey(); + assertEquals(envelopeKey, matEncryption.getDecryptionKey()); + + AsymmetricRawMaterials matDecryption = + new AsymmetricRawMaterials(encryptionPair, macKey, matEncryption.getMaterialDescription()); + assertEquals(macKey, matDecryption.getSigningKey()); + assertEquals(macKey, matDecryption.getVerificationKey()); + assertEquals(envelopeKey, matDecryption.getEncryptionKey()); + assertEquals(envelopeKey, matDecryption.getDecryptionKey()); + } + + @Test + public void macWithDescription() throws GeneralSecurityException { + AsymmetricRawMaterials matEncryption = + new AsymmetricRawMaterials(encryptionPair, macKey, description); + assertEquals(macKey, matEncryption.getSigningKey()); + assertEquals(macKey, matEncryption.getVerificationKey()); + assertFalse(matEncryption.getMaterialDescription().isEmpty()); + assertEquals("test value", matEncryption.getMaterialDescription().get("TestKey")); + + SecretKey envelopeKey = matEncryption.getEncryptionKey(); + assertEquals(envelopeKey, matEncryption.getDecryptionKey()); + + AsymmetricRawMaterials matDecryption = + new AsymmetricRawMaterials(encryptionPair, macKey, matEncryption.getMaterialDescription()); + assertEquals(macKey, matDecryption.getSigningKey()); + assertEquals(macKey, matDecryption.getVerificationKey()); + assertEquals(envelopeKey, matDecryption.getEncryptionKey()); + assertEquals(envelopeKey, matDecryption.getDecryptionKey()); + assertEquals("test value", matDecryption.getMaterialDescription().get("TestKey")); + } + + @Test + public void sigNoDescription() throws GeneralSecurityException { + AsymmetricRawMaterials matEncryption = new AsymmetricRawMaterials(encryptionPair, sigPair); + assertEquals(sigPair.getPrivate(), matEncryption.getSigningKey()); + assertEquals(sigPair.getPublic(), matEncryption.getVerificationKey()); + assertFalse(matEncryption.getMaterialDescription().isEmpty()); + + SecretKey envelopeKey = matEncryption.getEncryptionKey(); + assertEquals(envelopeKey, matEncryption.getDecryptionKey()); + + AsymmetricRawMaterials matDecryption = + new AsymmetricRawMaterials(encryptionPair, sigPair, matEncryption.getMaterialDescription()); + assertEquals(sigPair.getPrivate(), matDecryption.getSigningKey()); + assertEquals(sigPair.getPublic(), matDecryption.getVerificationKey()); + assertEquals(envelopeKey, matDecryption.getEncryptionKey()); + assertEquals(envelopeKey, matDecryption.getDecryptionKey()); + } + + @Test + public void sigWithDescription() throws GeneralSecurityException { + AsymmetricRawMaterials matEncryption = + new AsymmetricRawMaterials(encryptionPair, sigPair, description); + assertEquals(sigPair.getPrivate(), matEncryption.getSigningKey()); + assertEquals(sigPair.getPublic(), matEncryption.getVerificationKey()); + assertFalse(matEncryption.getMaterialDescription().isEmpty()); + assertEquals("test value", matEncryption.getMaterialDescription().get("TestKey")); + + SecretKey envelopeKey = matEncryption.getEncryptionKey(); + assertEquals(envelopeKey, matEncryption.getDecryptionKey()); + + AsymmetricRawMaterials matDecryption = + new AsymmetricRawMaterials(encryptionPair, sigPair, matEncryption.getMaterialDescription()); + assertEquals(sigPair.getPrivate(), matDecryption.getSigningKey()); + assertEquals(sigPair.getPublic(), matDecryption.getVerificationKey()); + assertEquals(envelopeKey, matDecryption.getEncryptionKey()); + assertEquals(envelopeKey, matDecryption.getDecryptionKey()); + assertEquals("test value", matDecryption.getMaterialDescription().get("TestKey")); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java new file mode 100644 index 0000000000..a6987ce792 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/SymmetricRawMaterialsTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertTrue; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class SymmetricRawMaterialsTest { + private static SecretKey encryptionKey; + private static SecretKey macKey; + private static KeyPair sigPair; + private static SecureRandom rnd; + private Map description; + + @BeforeClass + public static void setUpClass() throws NoSuchAlgorithmException { + rnd = new SecureRandom(); + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, rnd); + sigPair = rsaGen.generateKeyPair(); + + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, rnd); + encryptionKey = aesGen.generateKey(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, rnd); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + } + + @Test + public void macNoDescription() throws NoSuchAlgorithmException { + SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, macKey); + assertEquals(encryptionKey, mat.getEncryptionKey()); + assertEquals(encryptionKey, mat.getDecryptionKey()); + assertEquals(macKey, mat.getSigningKey()); + assertEquals(macKey, mat.getVerificationKey()); + assertTrue(mat.getMaterialDescription().isEmpty()); + } + + @Test + public void macWithDescription() throws NoSuchAlgorithmException { + SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, macKey, description); + assertEquals(encryptionKey, mat.getEncryptionKey()); + assertEquals(encryptionKey, mat.getDecryptionKey()); + assertEquals(macKey, mat.getSigningKey()); + assertEquals(macKey, mat.getVerificationKey()); + assertEquals(description, mat.getMaterialDescription()); + assertEquals("test value", mat.getMaterialDescription().get("TestKey")); + } + + @Test + public void sigNoDescription() throws NoSuchAlgorithmException { + SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, sigPair); + assertEquals(encryptionKey, mat.getEncryptionKey()); + assertEquals(encryptionKey, mat.getDecryptionKey()); + assertEquals(sigPair.getPrivate(), mat.getSigningKey()); + assertEquals(sigPair.getPublic(), mat.getVerificationKey()); + assertTrue(mat.getMaterialDescription().isEmpty()); + } + + @Test + public void sigWithDescription() throws NoSuchAlgorithmException { + SymmetricRawMaterials mat = new SymmetricRawMaterials(encryptionKey, sigPair, description); + assertEquals(encryptionKey, mat.getEncryptionKey()); + assertEquals(encryptionKey, mat.getDecryptionKey()); + assertEquals(sigPair.getPrivate(), mat.getSigningKey()); + assertEquals(sigPair.getPublic(), mat.getVerificationKey()); + assertEquals(description, mat.getMaterialDescription()); + assertEquals("test value", mat.getMaterialDescription().get("TestKey")); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java new file mode 100644 index 0000000000..8f71ac7b28 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProviderTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +public class AsymmetricStaticProviderTest { + private static KeyPair encryptionPair; + private static SecretKey macKey; + private static KeyPair sigPair; + private Map description; + private EncryptionContext ctx; + + @BeforeClass + public static void setUpClass() throws Exception { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + sigPair = rsaGen.generateKeyPair(); + encryptionPair = rsaGen.generateKeyPair(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + description = Collections.unmodifiableMap(description); + ctx = new EncryptionContext.Builder().build(); + } + + @Test + public void constructWithMac() throws GeneralSecurityException { + AsymmetricStaticProvider prov = + new AsymmetricStaticProvider( + encryptionPair, macKey, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void constructWithSigPair() throws GeneralSecurityException { + AsymmetricStaticProvider prov = + new AsymmetricStaticProvider( + encryptionPair, sigPair, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void randomEnvelopeKeys() throws GeneralSecurityException { + AsymmetricStaticProvider prov = + new AsymmetricStaticProvider( + encryptionPair, macKey, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey2 = eMat2.getEncryptionKey(); + assertEquals(macKey, eMat.getSigningKey()); + + assertFalse("Envelope keys must be different", encryptionKey.equals(encryptionKey2)); + } + + @Test + public void testRefresh() { + // This does nothing, make sure we don't throw and exception. + AsymmetricStaticProvider prov = + new AsymmetricStaticProvider(encryptionPair, macKey, description); + prov.refresh(); + } + + private static EncryptionContext ctx(EncryptionMaterials mat) { + return new EncryptionContext.Builder() + .materialDescription(mat.getMaterialDescription()) + .build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java new file mode 100644 index 0000000000..f286648332 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java @@ -0,0 +1,610 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.ProviderStore; +import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; + +public class CachingMostRecentProviderTests { + private static final String TABLE_NAME = "keystoreTable"; + private static final String MATERIAL_NAME = "material"; + private static final String MATERIAL_PARAM = "materialName"; + private static final SecretKey AES_KEY = + new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "AES"); + private static final SecretKey HMAC_KEY = + new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); + private static final EncryptionMaterialsProvider BASE_PROVIDER = + new SymmetricStaticProvider(AES_KEY, HMAC_KEY); + private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); + + private DynamoDbClient client; + private Map methodCalls; + private ProvisionedThroughput throughput; + private ProviderStore store; + private EncryptionContext ctx; + + @BeforeMethod + public void setup() { + methodCalls = new HashMap(); + throughput = ProvisionedThroughput.builder().readCapacityUnits(1L).writeCapacityUnits(1L).build(); + + client = instrument(DynamoDBEmbedded.create().dynamoDbClient(), DynamoDbClient.class, methodCalls); + MetaStore.createTable(client, TABLE_NAME, throughput); + store = new MetaStore(client, TABLE_NAME, ENCRYPTOR); + ctx = new EncryptionContext.Builder().build(); + methodCalls.clear(); + } + + @Test + public void testConstructors() { + final CachingMostRecentProvider prov = + new CachingMostRecentProvider(store, MATERIAL_NAME, 100, 1000); + assertEquals(MATERIAL_NAME, prov.getMaterialName()); + assertEquals(100, prov.getTtlInMills()); + assertEquals(-1, prov.getCurrentVersion()); + assertEquals(0, prov.getLastUpdated()); + + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + assertEquals(MATERIAL_NAME, prov2.getMaterialName()); + assertEquals(500, prov2.getTtlInMills()); + assertEquals(-1, prov2.getCurrentVersion()); + assertEquals(0, prov2.getLastUpdated()); + } + + + @Test + public void testSmallMaxCacheSize() { + final Map attr1 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); + final EncryptionContext ctx1 = ctx(attr1); + final Map attr2 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); + final EncryptionContext ctx2 = ctx(attr2); + + final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 1); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the two materials are, in fact, different + assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); + + // Ensure the second set of materials are cached + final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + + // Ensure the first set of materials are no longer cached, due to being the LRU + final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); + } + + @Test + public void testSingleVersion() throws InterruptedException { + final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the cache is working + final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + // Let the TTL be exceeded + Thread.sleep(500); + final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); + assertEquals(2, methodCalls.size()); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); // To get provider + assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); + + assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); + assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey()); + // Check algorithms. Right now we only support AES and HmacSHA256 + assertEquals("AES", eMat1.getEncryptionKey().getAlgorithm()); + assertEquals("HmacSHA256", eMat1.getSigningKey().getAlgorithm()); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); + methodCalls.clear(); + assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); + assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); + final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); + assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); + assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); + final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); + assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); + assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + } + + @Test + public void testSingleVersionWithRefresh() throws InterruptedException { + final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the cache is working + final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + prov.refresh(); + final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); + prov.refresh(); + + assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); + assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey()); + + // Ensure that after cache refresh we only get one more hit as opposed to multiple + prov.getEncryptionMaterials(ctx); + Thread.sleep(700); + // Force refresh + prov.getEncryptionMaterials(ctx); + methodCalls.clear(); + // Check to ensure no more hits + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); + methodCalls.clear(); + assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); + assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); + final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); + assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); + assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); + final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); + assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); + assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + } + + @Test + public void testTwoVersions() throws InterruptedException { + final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Create the new material + store.newProvider(MATERIAL_NAME); + methodCalls.clear(); + + // Ensure the cache is working + final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + // Let the TTL be exceeded + Thread.sleep(500); + final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); + + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); // To retrieve current version + assertNull(methodCalls.get("putItem")); // No attempt to create a new item + assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); + + assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); + assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey())); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); + methodCalls.clear(); + assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); + assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); + final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); + assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); + assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); + final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); + assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); + assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); + // Get item will be hit once for the one old key + assertEquals(1, methodCalls.size()); + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + } + + @Test + public void testTwoVersionsWithRefresh() throws InterruptedException { + final CachingMostRecentProvider prov = new CachingMostRecentProvider(store, MATERIAL_NAME, 100); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Create the new material + store.newProvider(MATERIAL_NAME); + methodCalls.clear(); + // Ensure the cache is working + final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + prov.refresh(); + final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription())); + + assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey()); + assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey())); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = + new CachingMostRecentProvider(store, MATERIAL_NAME, 500); + final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1)); + methodCalls.clear(); + assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey()); + assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey()); + final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2)); + assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey()); + assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey()); + final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3)); + assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey()); + assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey()); + // Get item will be hit once for the one old key + assertEquals(1, methodCalls.size()); + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + } + + @Test + public void testSingleVersionTwoMaterials() throws InterruptedException { + final Map attr1 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); + final EncryptionContext ctx1 = ctx(attr1); + final Map attr2 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); + final EncryptionContext ctx2 = ctx(attr2); + + final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the two materials are, in fact, different + assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); + + // Ensure the cache is working + final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); + final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); + + // Let the TTL be exceeded + Thread.sleep(500); + final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); + assertEquals(2, methodCalls.size()); + assertEquals(1, (int) methodCalls.get("query")); // To find current version + assertEquals(1, (int) methodCalls.get("getItem")); // To get the provider + assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); + methodCalls.clear(); + final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); + assertEquals(2, methodCalls.size()); + assertEquals(1, (int) methodCalls.get("query")); // To find current version + assertEquals(1, (int) methodCalls.get("getItem")); // To get the provider + assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); + + assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey()); + // Check algorithms. Right now we only support AES and HmacSHA256 + assertEquals("AES", eMat1_1.getEncryptionKey().getAlgorithm()); + assertEquals("AES", eMat1_2.getEncryptionKey().getAlgorithm()); + assertEquals("HmacSHA256", eMat1_1.getSigningKey().getAlgorithm()); + assertEquals("HmacSHA256", eMat1_2.getSigningKey().getAlgorithm()); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); + final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); + final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); + methodCalls.clear(); + assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); + assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); + assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); + assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); + final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); + final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); + assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); + assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); + assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); + assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); + final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); + final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); + assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); + assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); + assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); + assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + } + + @Test + public void testSingleVersionWithTwoMaterialsWithRefresh() throws InterruptedException { + final Map attr1 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); + final EncryptionContext ctx1 = ctx(attr1); + final Map attr2 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); + final EncryptionContext ctx2 = ctx(attr2); + + final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Ensure the two materials are, in fact, different + assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey())); + + // Ensure the cache is working + final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); + final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); + + prov.refresh(); + final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); + assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); + final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); + assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); + prov.refresh(); + + assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey()); + + // Ensure that after cache refresh we only get one more hit as opposed to multiple + prov.getEncryptionMaterials(ctx1); + prov.getEncryptionMaterials(ctx2); + Thread.sleep(700); + // Force refresh + prov.getEncryptionMaterials(ctx1); + prov.getEncryptionMaterials(ctx2); + methodCalls.clear(); + // Check to ensure no more hits + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey()); + + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); + final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); + final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); + methodCalls.clear(); + assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); + assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); + assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); + assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); + final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); + final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); + assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); + assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); + assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); + assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); + final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); + final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); + assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); + assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); + assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); + assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + } + + @Test + public void testTwoVersionsWithTwoMaterialsWithRefresh() throws InterruptedException { + final Map attr1 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material1").build()); + final EncryptionContext ctx1 = ctx(attr1); + final Map attr2 = + Collections.singletonMap(MATERIAL_PARAM, AttributeValue.builder().s("material2").build()); + final EncryptionContext ctx2 = ctx(attr2); + + final CachingMostRecentProvider prov = new ExtendedProvider(store, 500, 100); + assertNull(methodCalls.get("putItem")); + final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2); + // It's a new provider, so we see a single putItem + assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0)); + methodCalls.clear(); + // Create the new material + store.newProvider("material1"); + store.newProvider("material2"); + methodCalls.clear(); + // Ensure the cache is working + final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1); + final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2); + assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty()); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription())); + assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription())); + prov.refresh(); + final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1); + final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2); + assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version + assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); + assertEquals(1, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription())); + assertEquals(1, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription())); + + assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey()); + assertFalse(eMat1_1.getSigningKey().equals(eMat3_1.getSigningKey())); + assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey()); + assertFalse(eMat1_2.getSigningKey().equals(eMat3_2.getSigningKey())); + + // Ensure we can decrypt all of them without hitting ddb more than the minimum + final CachingMostRecentProvider prov2 = new ExtendedProvider(store, 500, 100); + final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1)); + final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2)); + methodCalls.clear(); + assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey()); + assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey()); + assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey()); + assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey()); + final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1)); + final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2)); + assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey()); + assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey()); + assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey()); + assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey()); + final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1)); + final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2)); + assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey()); + assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey()); + assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey()); + assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey()); + // Get item will be hit twice, once for each old key + assertEquals(1, methodCalls.size()); + assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0)); + } + + private static EncryptionContext ctx(final Map attr) { + return new EncryptionContext.Builder().attributeValues(attr).build(); + } + + private static EncryptionContext ctx( + final EncryptionMaterials mat, Map attr) { + return new EncryptionContext.Builder() + .attributeValues(attr) + .materialDescription(mat.getMaterialDescription()) + .build(); + } + + private static EncryptionContext ctx(final EncryptionMaterials mat) { + return new EncryptionContext.Builder() + .materialDescription(mat.getMaterialDescription()) + .build(); + } + + private static class ExtendedProvider extends CachingMostRecentProvider { + public ExtendedProvider(ProviderStore keystore, long ttlInMillis, int maxCacheSize) { + super(keystore, null, ttlInMillis, maxCacheSize); + } + + @Override + public long getCurrentVersion() { + throw new UnsupportedOperationException(); + } + + @Override + protected String getMaterialName(final EncryptionContext context) { + return context.getAttributeValues().get(MATERIAL_PARAM).s(); + } + } + + @SuppressWarnings("unchecked") + private static T instrument( + final T obj, final Class clazz, final Map map) { + return (T) + Proxy.newProxyInstance( + clazz.getClassLoader(), + new Class[] {clazz}, + new InvocationHandler() { + private final Object lock = new Object(); + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) + throws Throwable { + synchronized (lock) { + try { + final Integer oldCount = map.get(method.getName()); + if (oldCount != null) { + map.put(method.getName(), oldCount + 1); + } else { + map.put(method.getName(), 1); + } + return method.invoke(obj, args); + } catch (final InvocationTargetException ex) { + throw ex.getCause(); + } + } + } + }); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java new file mode 100644 index 0000000000..f5832a1e62 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProviderTest.java @@ -0,0 +1,449 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.DecryptRequest; +import software.amazon.awssdk.services.kms.model.DecryptResponse; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Base64; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.FakeKMS; + +public class DirectKmsMaterialsProviderTest { + private FakeKMS kms; + private String keyId; + private Map description; + private EncryptionContext ctx; + + @BeforeMethod + public void setUp() { + description = new HashMap<>(); + description.put("TestKey", "test value"); + description = Collections.unmodifiableMap(description); + ctx = new EncryptionContext.Builder().build(); + kms = new FakeKMS(); + keyId = kms.createKey().keyMetadata().keyId(); + } + + @Test + public void simple() { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + + String expectedEncAlg = + encryptionKey.getAlgorithm() + "/" + (encryptionKey.getEncoded().length * 8); + String expectedSigAlg = signingKey.getAlgorithm() + "/" + (signingKey.getEncoded().length * 8); + + Map kmsCtx = kms.getSingleEc(); + assertEquals(expectedEncAlg, kmsCtx.get("*" + WrappedRawMaterials.CONTENT_KEY_ALGORITHM + "*")); + assertEquals(expectedSigAlg, kmsCtx.get("*amzn-ddb-sig-alg*")); + } + + @Test + public void simpleWithKmsEc() { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().s("HashKeyValue").build()); + attrVals.put("rk", AttributeValue.builder().s("RangeKeyValue").build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + Map kmsCtx = kms.getSingleEc(); + assertEquals("HashKeyValue", kmsCtx.get("hk")); + assertEquals("RangeKeyValue", kmsCtx.get("rk")); + assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + } + + @Test + public void simpleWithKmsEc2() throws GeneralSecurityException { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().n("10").build()); + attrVals.put("rk", AttributeValue.builder().n("20").build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + Map kmsCtx = kms.getSingleEc(); + assertEquals("10", kmsCtx.get("hk")); + assertEquals("20", kmsCtx.get("rk")); + assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + } + + @Test + public void simpleWithKmsEc3() throws GeneralSecurityException { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + Map attrVals = new HashMap<>(); + attrVals.put( + "hk", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap("Foo".getBytes(StandardCharsets.UTF_8)))) + .build()); + attrVals.put( + "rk", AttributeValue.builder() + .b(SdkBytes.fromByteBuffer(ByteBuffer.wrap("Bar".getBytes(StandardCharsets.UTF_8)))) + .build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + assertNotNull(signingKey); + Map kmsCtx = kms.getSingleEc(); + assertEquals(Base64.encodeToString("Foo".getBytes(StandardCharsets.UTF_8)), kmsCtx.get("hk")); + assertEquals(Base64.encodeToString("Bar".getBytes(StandardCharsets.UTF_8)), kmsCtx.get("rk")); + assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + } + + @Test + public void randomEnvelopeKeys() throws GeneralSecurityException { + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey2 = eMat2.getEncryptionKey(); + + assertFalse("Envelope keys must be different", encryptionKey.equals(encryptionKey2)); + } + + @Test + public void testRefresh() { + // This does nothing, make sure we don't throw and exception. + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId); + prov.refresh(); + } + + @Test + public void explicitContentKeyAlgorithm() throws GeneralSecurityException { + Map desc = new HashMap<>(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); + + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + } + + @Test + public void explicitContentKeyLength128() throws GeneralSecurityException { + Map desc = new HashMap<>(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(16, encryptionKey.getEncoded().length); // 128 Bits + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES/128", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", eMat.getEncryptionKey().getAlgorithm()); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + } + + @Test + public void explicitContentKeyLength256() throws GeneralSecurityException { + Map desc = new HashMap<>(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + DirectKmsMaterialsProvider prov = new DirectKmsMaterialsProvider(kms, keyId, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(32, encryptionKey.getEncoded().length); // 256 Bits + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES/256", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", eMat.getEncryptionKey().getAlgorithm()); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + } + + @Test + public void extendedWithDerivedEncryptionKeyId() { + ExtendedKmsMaterialsProvider prov = + new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); + String customKeyId = kms.createKey().keyMetadata().keyId(); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().n("10").build()); + attrVals.put("rk", AttributeValue.builder().n("20").build()); + attrVals.put("encryptionKeyId", AttributeValue.builder().s(customKeyId).build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + Key signingKey = eMat.getSigningKey(); + assertNotNull(signingKey); + Map kmsCtx = kms.getSingleEc(); + assertEquals("10", kmsCtx.get("hk")); + assertEquals("20", kmsCtx.get("rk")); + assertEquals("KmsTableName", kmsCtx.get("*aws-kms-table*")); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(dCtx); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(signingKey, dMat.getVerificationKey()); + } + + @Test(expectedExceptions = SdkException.class) + public void encryptionKeyIdMismatch() throws SdkException { + DirectKmsMaterialsProvider directProvider = new DirectKmsMaterialsProvider(kms, keyId); + String customKeyId = kms.createKey().keyMetadata().keyId(); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().n("10").build()); + attrVals.put("rk", AttributeValue.builder().n("20").build()); + attrVals.put("encryptionKeyId", AttributeValue.builder().s(customKeyId).build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + EncryptionMaterials eMat = directProvider.getEncryptionMaterials(ctx); + + EncryptionContext dCtx = + new EncryptionContext.Builder(ctx(eMat)) + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + + ExtendedKmsMaterialsProvider extendedProvider = + new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); + + extendedProvider.getDecryptionMaterials(dCtx); + } + + @Test(expectedExceptions = SdkException.class) + public void missingEncryptionKeyId() throws SdkException { + ExtendedKmsMaterialsProvider prov = + new ExtendedKmsMaterialsProvider(kms, keyId, "encryptionKeyId"); + + Map attrVals = new HashMap<>(); + attrVals.put("hk", AttributeValue.builder().n("10").build()); + attrVals.put("rk", AttributeValue.builder().n("20").build()); + + ctx = + new EncryptionContext.Builder() + .hashKeyName("hk") + .rangeKeyName("rk") + .tableName("KmsTableName") + .attributeValues(attrVals) + .build(); + prov.getEncryptionMaterials(ctx); + } + + @Test + public void generateDataKeyIsCalledWith256NumberOfBits() { + final AtomicBoolean gdkCalled = new AtomicBoolean(false); + KmsClient kmsSpy = + new FakeKMS() { + @Override + public GenerateDataKeyResponse generateDataKey(GenerateDataKeyRequest r) { + gdkCalled.set(true); + assertEquals((Integer) 32, r.numberOfBytes()); + assertNull(r.keySpec()); + return super.generateDataKey(r); + } + }; + assertFalse(gdkCalled.get()); + new DirectKmsMaterialsProvider(kmsSpy, keyId).getEncryptionMaterials(ctx); + assertTrue(gdkCalled.get()); + } + + private static class ExtendedKmsMaterialsProvider extends DirectKmsMaterialsProvider { + private final String encryptionKeyIdAttributeName; + + public ExtendedKmsMaterialsProvider( + KmsClient kms, String encryptionKeyId, String encryptionKeyIdAttributeName) { + super(kms, encryptionKeyId); + + this.encryptionKeyIdAttributeName = encryptionKeyIdAttributeName; + } + + @Override + protected String selectEncryptionKeyId(EncryptionContext context) + throws DynamoDbException { + if (!context.getAttributeValues().containsKey(encryptionKeyIdAttributeName)) { + throw DynamoDbException.create("encryption key attribute is not provided", new Exception()); + } + + return context.getAttributeValues().get(encryptionKeyIdAttributeName).s(); + } + + @Override + protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext context) + throws DynamoDbException { + if (!context.getAttributeValues().containsKey(encryptionKeyIdAttributeName)) { + throw DynamoDbException.create("encryption key attribute is not provided", new Exception()); + } + + String customEncryptionKeyId = + context.getAttributeValues().get(encryptionKeyIdAttributeName).s(); + if (!customEncryptionKeyId.equals(encryptionKeyId)) { + throw DynamoDbException.create("encryption key ids do not match.", new Exception()); + } + } + + @Override + protected DecryptResponse decrypt(DecryptRequest request, EncryptionContext context) { + return super.decrypt(request, context); + } + + @Override + protected GenerateDataKeyResponse generateDataKey( + GenerateDataKeyRequest request, EncryptionContext context) { + return super.generateDataKey(request, context); + } + } + + private static EncryptionContext ctx(EncryptionMaterials mat) { + return new EncryptionContext.Builder() + .materialDescription(mat.getMaterialDescription()) + .build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java new file mode 100644 index 0000000000..406052452e --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/KeyStoreMaterialsProviderTest.java @@ -0,0 +1,315 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.fail; + +import java.io.ByteArrayInputStream; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStore.PasswordProtection; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.SecretKeyEntry; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +public class KeyStoreMaterialsProviderTest { + private static final String certPem = + "MIIDbTCCAlWgAwIBAgIJANdRvzVsW1CIMA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV" + + "BAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMQwwCgYDVQQKDANBV1MxGzAZBgNV" + + "BAMMEktleVN0b3JlIFRlc3QgQ2VydDAeFw0xMzA1MDgyMzMyMjBaFw0xMzA2MDcy" + + "MzMyMjBaME0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMQwwCgYD" + + "VQQKDANBV1MxGzAZBgNVBAMMEktleVN0b3JlIFRlc3QgQ2VydDCCASIwDQYJKoZI" + + "hvcNAQEBBQADggEPADCCAQoCggEBAJ8+umOX8x/Ma4OZishtYpcA676bwK5KScf3" + + "w+YGM37L12KTdnOyieiGtRW8p0fS0YvnhmVTvaky09I33bH+qy9gliuNL2QkyMxp" + + "uu1IwkTKKuB67CaKT6osYJLFxV/OwHcaZnTszzDgbAVg/Z+8IZxhPgxMzMa+7nDn" + + "hEm9Jd+EONq3PnRagnFeLNbMIePprdJzXHyNNiZKRRGQ/Mo9rr7mqMLSKnFNsmzB" + + "OIfeZM8nXeg+cvlmtXl72obwnGGw2ksJfaxTPm4eEhzRoAgkbjPPLHbwiJlc+GwF" + + "i8kh0Y3vQTj/gOFE4nzipkm7ux1lsGHNRVpVDWpjNd8Fl9JFELkCAwEAAaNQME4w" + + "HQYDVR0OBBYEFM0oGUuFAWlLXZaMXoJgGZxWqfOxMB8GA1UdIwQYMBaAFM0oGUuF" + + "AWlLXZaMXoJgGZxWqfOxMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB" + + "AAXCsXeC8ZRxovP0Wc6C5qv3d7dtgJJVzHwoIRt2YR3yScBa1XI40GKT80jP3MYH" + + "8xMu3mBQtcYrgRKZBy4GpHAyxoFTnPcuzq5Fg7dw7fx4E4OKIbWOahdxwtbVxQfZ" + + "UHnGG88Z0bq2twj7dALGyJhUDdiccckJGmJPOFMzjqsvoAu0n/p7eS6y5WZ5ewqw" + + "p7VwYOP3N9wVV7Podmkh1os+eCcp9GoFf0MHBMFXi2Ps2azKx8wHRIA5D1MZv/Va" + + "4L4/oTBKCjORpFlP7EhMksHBYnjqXLDP6awPMAgQNYB5J9zX6GfJsAgly3t4Rjr5" + + "cLuNYBmRuByFGo+SOdrj6D8="; + private static final String keyPem = + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCfPrpjl/MfzGuD" + + "mYrIbWKXAOu+m8CuSknH98PmBjN+y9dik3ZzsonohrUVvKdH0tGL54ZlU72pMtPS" + + "N92x/qsvYJYrjS9kJMjMabrtSMJEyirgeuwmik+qLGCSxcVfzsB3GmZ07M8w4GwF" + + "YP2fvCGcYT4MTMzGvu5w54RJvSXfhDjatz50WoJxXizWzCHj6a3Sc1x8jTYmSkUR" + + "kPzKPa6+5qjC0ipxTbJswTiH3mTPJ13oPnL5ZrV5e9qG8JxhsNpLCX2sUz5uHhIc" + + "0aAIJG4zzyx28IiZXPhsBYvJIdGN70E4/4DhROJ84qZJu7sdZbBhzUVaVQ1qYzXf" + + "BZfSRRC5AgMBAAECggEBAJMwx9eGe5LIwBfDtCPN93LbxwtHq7FtuQS8XrYexTpN" + + "76eN5c7LF+11lauh1HzuwAEw32iJHqVl9aQ5PxFm85O3ExbuSP+ngHJwx/bLacVr" + + "mHYlKGH3Net1WU5Qvz7vO7bbEBjDSj9DMJVIMSWUHv0MZO25jw2lLX/ufrgpvPf7" + + "KXSgXg/8uV7PbnTbBDNlg02u8eOc+IbH4O8XDKAhD+YQ8AE3pxtopJbb912U/cJs" + + "Y0hQ01zbkWYH7wL9BeQmR7+TEjjtr/IInNjnXmaOmSX867/rTSTuozaVrl1Ce7r8" + + "EmUDg9ZLZeKfoNYovMy08wnxWVX2J+WnNDjNiSOm+IECgYEA0v3jtGrOnKbd0d9E" + + "dbyIuhjgnwp+UsgALIiBeJYjhFS9NcWgs+02q/0ztqOK7g088KBBQOmiA+frLIVb" + + "uNCt/3jF6kJvHYkHMZ0eBEstxjVSM2UcxzJ6ceHZ68pmrru74382TewVosxccNy0" + + "glsUWNN0t5KQDcetaycRYg50MmcCgYEAwTb8klpNyQE8AWxVQlbOIEV24iarXxex" + + "7HynIg9lSeTzquZOXjp0m5omQ04psil2gZ08xjiudG+Dm7QKgYQcxQYUtZPQe15K" + + "m+2hQM0jA7tRfM1NAZHoTmUlYhzRNX6GWAqQXOgjOqBocT4ySBXRaSQq9zuZu36s" + + "fI17knap798CgYArDa2yOf0xEAfBdJqmn7MSrlLfgSenwrHuZGhu78wNi7EUUOBq" + + "9qOqUr+DrDmEO+VMgJbwJPxvaZqeehPuUX6/26gfFjFQSI7UO+hNHf4YLPc6D47g" + + "wtcjd9+c8q8jRqGfWWz+V4dOsf7G9PJMi0NKoNN3RgvpE+66J72vUZ26TwKBgEUq" + + "DdfGA7pEetp3kT2iHT9oHlpuRUJRFRv2s015/WQqVR+EOeF5Q2zADZpiTIK+XPGg" + + "+7Rpbem4UYBXPruGM1ZECv3E4AiJhGO0+Nhdln8reswWIc7CEEqf4nXwouNnW2gA" + + "wBTB9Hp0GW8QOKedR80/aTH/X9TCT7R2YRnY6JQ5AoGBAKjgPySgrNDhlJkW7jXR" + + "WiGpjGSAFPT9NMTvEHDo7oLTQ8AcYzcGQ7ISMRdVXR6GJOlFVsH4NLwuHGtcMTPK" + + "zoHbPHJyOn1SgC5tARD/1vm5CsG2hATRpWRQCTJFg5VRJ4R7Pz+HuxY4SoABcPQd" + + "K+MP8GlGqTldC6NaB1s7KuAX"; + + private static SecretKey encryptionKey; + private static SecretKey macKey; + private static KeyStore keyStore; + private static final String password = "Password"; + private static final PasswordProtection passwordProtection = new PasswordProtection(password.toCharArray()); + + private Map description; + private EncryptionContext ctx; + private static PrivateKey privateKey; + private static Certificate certificate; + + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, Utils.getRng()); + encryptionKey = aesGen.generateKey(); + + keyStore = KeyStore.getInstance("jceks"); + keyStore.load(null, password.toCharArray()); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec rsaSpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyPem)); + privateKey = kf.generatePrivate(rsaSpec); + CertificateFactory cf = CertificateFactory.getInstance("X509"); + certificate = cf.generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(certPem))); + + keyStore.setEntry("enc", new SecretKeyEntry(encryptionKey), passwordProtection); + keyStore.setEntry("sig", new SecretKeyEntry(macKey), passwordProtection); + keyStore.setEntry( + "enc-a", + new PrivateKeyEntry(privateKey, new Certificate[] {certificate}), + passwordProtection); + keyStore.setEntry( + "sig-a", + new PrivateKeyEntry(privateKey, new Certificate[] {certificate}), + passwordProtection); + keyStore.setCertificateEntry("trustedCert", certificate); + } + + @BeforeMethod + public void setUp() { + description = new HashMap<>(); + description.put("TestKey", "test value"); + description = Collections.unmodifiableMap(description); + ctx = EncryptionContext.builder().build(); + } + + + @Test + @SuppressWarnings("unchecked") + public void simpleSymMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, Collections.EMPTY_MAP); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); + } + + @Test + @SuppressWarnings("unchecked") + public void simpleSymSig() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig-a", passwordProtection, passwordProtection, Collections.EMPTY_MAP); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(privateKey, encryptionMaterials.getSigningKey()); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); + assertEquals(certificate.getPublicKey(), prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); + } + + @Test + public void equalSymDescMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(encryptionMaterials)).getVerificationKey()); + } + + @Test + public void superSetSymDescMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + Map tmpDesc = + new HashMap<>(encryptionMaterials.getMaterialDescription()); + tmpDesc.put("randomValue", "random"); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(tmpDesc)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(tmpDesc)).getVerificationKey()); + } + + @Test + @SuppressWarnings("unchecked") + public void subSetSymDescMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + + assertNull(prov.getDecryptionMaterials(ctx(Collections.EMPTY_MAP))); + } + + + @Test + public void noMatchSymDescMac() throws Exception { + KeyStoreMaterialsProvider prov = new + KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials encryptionMaterials = prov.getEncryptionMaterials(ctx); + assertEquals(encryptionKey, encryptionMaterials.getEncryptionKey()); + assertEquals(macKey, encryptionMaterials.getSigningKey()); + Map tmpDesc = new HashMap<>(); + tmpDesc.put("randomValue", "random"); + + assertNull(prov.getDecryptionMaterials(ctx(tmpDesc))); + } + + @Test + public void testRefresh() throws Exception { + // Mostly make sure we don't throw an exception + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc", "sig", passwordProtection, passwordProtection, description); + prov.refresh(); + } + + @Test + public void asymSimpleMac() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc-a", "sig", passwordProtection, passwordProtection, description); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void asymSimpleSig() throws Exception { + KeyStoreMaterialsProvider prov = new KeyStoreMaterialsProvider(keyStore, "enc-a", "sig-a", passwordProtection, passwordProtection, description); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(privateKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(certificate.getPublicKey(), dMat.getVerificationKey()); + } + + @Test + public void asymSigVerifyOnly() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "enc-a", "trustedCert", passwordProtection, null, description); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertNull(eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(certificate.getPublicKey(), dMat.getVerificationKey()); + } + + @Test + public void asymSigEncryptOnly() throws Exception { + KeyStoreMaterialsProvider prov = + new KeyStoreMaterialsProvider( + keyStore, "trustedCert", "sig-a", null, passwordProtection, description); + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + assertEquals(privateKey, eMat.getSigningKey()); + + try { + prov.getDecryptionMaterials(ctx(eMat)); + fail("Expected exception"); + } catch (IllegalStateException ex) { + assertEquals("No private decryption key provided.", ex.getMessage()); + } + } + + private static EncryptionContext ctx(EncryptionMaterials mat) { + return ctx(mat.getMaterialDescription()); + } + + private static EncryptionContext ctx(Map desc) { + return EncryptionContext.builder() + .materialDescription(desc).build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java new file mode 100644 index 0000000000..0485d4dff7 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/SymmetricStaticProviderTest.java @@ -0,0 +1,182 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.Utils; + +public class SymmetricStaticProviderTest { + private static SecretKey encryptionKey; + private static SecretKey macKey; + private static KeyPair sigPair; + private Map description; + private EncryptionContext ctx; + + @BeforeClass + public static void setUpClass() throws Exception { + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, Utils.getRng()); + sigPair = rsaGen.generateKeyPair(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, Utils.getRng()); + macKey = macGen.generateKey(); + + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, Utils.getRng()); + encryptionKey = aesGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + description = Collections.unmodifiableMap(description); + ctx = new EncryptionContext.Builder().build(); + } + + @Test + public void simpleMac() { + SymmetricStaticProvider prov = + new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap()); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + + assertEquals( + encryptionKey, + prov.getDecryptionMaterials(ctx(Collections.emptyMap())) + .getDecryptionKey()); + assertEquals( + macKey, + prov.getDecryptionMaterials(ctx(Collections.emptyMap())) + .getVerificationKey()); + } + + @Test + public void simpleSig() { + SymmetricStaticProvider prov = + new SymmetricStaticProvider(encryptionKey, sigPair, Collections.emptyMap()); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(sigPair.getPrivate(), prov.getEncryptionMaterials(ctx).getSigningKey()); + + assertEquals( + encryptionKey, + prov.getDecryptionMaterials(ctx(Collections.emptyMap())) + .getDecryptionKey()); + assertEquals( + sigPair.getPublic(), + prov.getDecryptionMaterials(ctx(Collections.emptyMap())) + .getVerificationKey()); + } + + @Test + public void equalDescMac() { + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue( + prov.getEncryptionMaterials(ctx) + .getMaterialDescription() + .entrySet() + .containsAll(description.entrySet())); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(description)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(description)).getVerificationKey()); + } + + @Test + public void supersetDescMac() { + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue( + prov.getEncryptionMaterials(ctx) + .getMaterialDescription() + .entrySet() + .containsAll(description.entrySet())); + + Map superSet = new HashMap(description); + superSet.put("NewValue", "super!"); + + assertEquals(encryptionKey, prov.getDecryptionMaterials(ctx(superSet)).getDecryptionKey()); + assertEquals(macKey, prov.getDecryptionMaterials(ctx(superSet)).getVerificationKey()); + } + + @Test + public void subsetDescMac() { + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue( + prov.getEncryptionMaterials(ctx) + .getMaterialDescription() + .entrySet() + .containsAll(description.entrySet())); + + assertNull(prov.getDecryptionMaterials(ctx(Collections.emptyMap()))); + } + + @Test + public void noMatchDescMac() { + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + assertEquals(encryptionKey, prov.getEncryptionMaterials(ctx).getEncryptionKey()); + assertEquals(macKey, prov.getEncryptionMaterials(ctx).getSigningKey()); + assertTrue( + prov.getEncryptionMaterials(ctx) + .getMaterialDescription() + .entrySet() + .containsAll(description.entrySet())); + + Map noMatch = new HashMap(); + noMatch.put("NewValue", "no match!"); + + assertNull(prov.getDecryptionMaterials(ctx(noMatch))); + } + + @Test + public void testRefresh() { + // This does nothing, make sure we don't throw and exception. + SymmetricStaticProvider prov = new SymmetricStaticProvider(encryptionKey, macKey, description); + prov.refresh(); + } + + @SuppressWarnings("unused") + private static EncryptionContext ctx(EncryptionMaterials mat) { + return ctx(mat.getMaterialDescription()); + } + + private static EncryptionContext ctx(Map desc) { + return EncryptionContext.builder() + .materialDescription(desc).build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java new file mode 100644 index 0000000000..5f82b47dd8 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/WrappedMaterialsProviderTest.java @@ -0,0 +1,414 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.WrappedRawMaterials; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class WrappedMaterialsProviderTest { + private static SecretKey symEncryptionKey; + private static SecretKey macKey; + private static KeyPair sigPair; + private static KeyPair encryptionPair; + private static SecureRandom rnd; + private Map description; + private EncryptionContext ctx; + + @BeforeClass + public static void setUpClass() throws NoSuchAlgorithmException { + rnd = new SecureRandom(); + KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA"); + rsaGen.initialize(2048, rnd); + sigPair = rsaGen.generateKeyPair(); + encryptionPair = rsaGen.generateKeyPair(); + + KeyGenerator aesGen = KeyGenerator.getInstance("AES"); + aesGen.init(128, rnd); + symEncryptionKey = aesGen.generateKey(); + + KeyGenerator macGen = KeyGenerator.getInstance("HmacSHA256"); + macGen.init(256, rnd); + macKey = macGen.generateKey(); + } + + @BeforeMethod + public void setUp() { + description = new HashMap(); + description.put("TestKey", "test value"); + ctx = new EncryptionContext.Builder().build(); + } + + @Test + public void simpleMac() { + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void simpleSigPair() { + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + symEncryptionKey, symEncryptionKey, sigPair, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void randomEnvelopeKeys() { + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(macKey, eMat.getSigningKey()); + + EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey2 = eMat2.getEncryptionKey(); + assertEquals(macKey, eMat.getSigningKey()); + + assertFalse( + "Envelope keys must be different", contentEncryptionKey.equals(contentEncryptionKey2)); + } + + @Test + public void testRefresh() { + // This does nothing, make sure we don't throw an exception. + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + symEncryptionKey, symEncryptionKey, macKey, Collections.emptyMap()); + prov.refresh(); + } + + @Test + public void wrapUnwrapAsymMatExplicitWrappingAlgorithmPkcs1() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM, "RSA/ECB/PKCS1Padding"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "RSA/ECB/PKCS1Padding", + eMat.getMaterialDescription().get(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapAsymMatExplicitWrappingAlgorithmPkcs2() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM, "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", + eMat.getMaterialDescription().get(WrappedRawMaterials.KEY_WRAPPING_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapAsymMatExplicitContentKeyAlgorithm() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), + encryptionPair.getPrivate(), + sigPair, + Collections.emptyMap()); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapAsymMatExplicitContentKeyLength128() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(16, contentEncryptionKey.getEncoded().length); // 128 Bits + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapAsymMatExplicitContentKeyLength256() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(32, contentEncryptionKey.getEncoded().length); // 256 Bits + assertEquals(sigPair.getPrivate(), eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void unwrapAsymMatExplicitEncAlgAes128() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + // Get materials we can test unwrapping on + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + + // Ensure "AES/128" on the created materials creates the expected key + Map aes128Desc = eMat.getMaterialDescription(); + aes128Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + EncryptionContext aes128Ctx = + new EncryptionContext.Builder().materialDescription(aes128Desc).build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(aes128Ctx); + assertEquals( + "AES/128", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void unwrapAsymMatExplicitEncAlgAes256() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider( + encryptionPair.getPublic(), encryptionPair.getPrivate(), sigPair, desc); + + // Get materials we can test unwrapping on + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + + // Ensure "AES/256" on the created materials creates the expected key + Map aes256Desc = eMat.getMaterialDescription(); + aes256Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + EncryptionContext aes256Ctx = + new EncryptionContext.Builder().materialDescription(aes256Desc).build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(aes256Ctx); + assertEquals( + "AES/256", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(sigPair.getPublic(), dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapSymMatExplicitContentKeyAlgorithm() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapSymMatExplicitContentKeyLength128() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(16, contentEncryptionKey.getEncoded().length); // 128 Bits + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void wrapUnwrapSymMatExplicitContentKeyLength256() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + SecretKey contentEncryptionKey = eMat.getEncryptionKey(); + assertNotNull(contentEncryptionKey); + assertEquals("AES", contentEncryptionKey.getAlgorithm()); + assertEquals( + "AES", eMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(32, contentEncryptionKey.getEncoded().length); // 256 Bits + assertEquals(macKey, eMat.getSigningKey()); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals( + "AES", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals(contentEncryptionKey, dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void unwrapSymMatExplicitEncAlgAes128() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + // Get materials we can test unwrapping on + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + + // Ensure "AES/128" on the created materials creates the expected key + Map aes128Desc = eMat.getMaterialDescription(); + aes128Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); + EncryptionContext aes128Ctx = + new EncryptionContext.Builder().materialDescription(aes128Desc).build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(aes128Ctx); + assertEquals( + "AES/128", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + @Test + public void unwrapSymMatExplicitEncAlgAes256() { + Map desc = new HashMap(); + desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + + WrappedMaterialsProvider prov = + new WrappedMaterialsProvider(symEncryptionKey, symEncryptionKey, macKey, desc); + + EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + + Map aes256Desc = eMat.getMaterialDescription(); + aes256Desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); + EncryptionContext aes256Ctx = + new EncryptionContext.Builder().materialDescription(aes256Desc).build(); + + DecryptionMaterials dMat = prov.getDecryptionMaterials(aes256Ctx); + assertEquals( + "AES/256", dMat.getMaterialDescription().get(WrappedRawMaterials.CONTENT_KEY_ALGORITHM)); + assertEquals("AES", dMat.getDecryptionKey().getAlgorithm()); + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(macKey, dMat.getVerificationKey()); + } + + private static EncryptionContext ctx(EncryptionMaterials mat) { + return new EncryptionContext.Builder() + .materialDescription(mat.getMaterialDescription()) + .build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java new file mode 100644 index 0000000000..3449908a6d --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java @@ -0,0 +1,346 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.fail; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.SymmetricStaticProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttributeValueBuilder; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.LocalDynamoDb; + +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; + +public class MetaStoreTests { + private static final String SOURCE_TABLE_NAME = "keystoreTable"; + private static final String DESTINATION_TABLE_NAME = "keystoreDestinationTable"; + private static final String MATERIAL_NAME = "material"; + private static final SecretKey AES_KEY = new SecretKeySpec(new byte[] { 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, "AES"); + private static final SecretKey TARGET_AES_KEY = new SecretKeySpec(new byte[] { 0, + 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, "AES"); + private static final SecretKey HMAC_KEY = new SecretKeySpec(new byte[] { 0, + 1, 2, 3, 4, 5, 6, 7 }, "HmacSHA256"); + private static final SecretKey TARGET_HMAC_KEY = new SecretKeySpec(new byte[] { 0, + 2, 4, 6, 8, 10, 12, 14 }, "HmacSHA256"); + private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); + private static final EncryptionMaterialsProvider TARGET_BASE_PROVIDER = new SymmetricStaticProvider(TARGET_AES_KEY, TARGET_HMAC_KEY); + private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); + private static final DynamoDbEncryptor TARGET_ENCRYPTOR = DynamoDbEncryptor.getInstance(TARGET_BASE_PROVIDER); + + private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); + private final LocalDynamoDb targetLocalDynamoDb = new LocalDynamoDb(); + private DynamoDbClient client; + private DynamoDbClient targetClient; + private MetaStore store; + private MetaStore targetStore; + private EncryptionContext ctx; + + private static class TestExtraDataSupplier implements MetaStore.ExtraDataSupplier { + + private final Map attributeValueMap; + private final Set signedOnlyFieldNames; + + TestExtraDataSupplier(final Map attributeValueMap, + final Set signedOnlyFieldNames) { + this.attributeValueMap = attributeValueMap; + this.signedOnlyFieldNames = signedOnlyFieldNames; + } + + @Override + public Map getAttributes(String materialName, long version) { + return this.attributeValueMap; + } + + @Override + public Set getSignedOnlyFieldNames() { + return this.signedOnlyFieldNames; + } + } + + @BeforeMethod + public void setup() { + localDynamoDb.start(); + targetLocalDynamoDb.start(); + client = localDynamoDb.createClient(); + targetClient = targetLocalDynamoDb.createClient(); + + MetaStore.createTable(client, SOURCE_TABLE_NAME, ProvisionedThroughput.builder() + .readCapacityUnits(1L) + .writeCapacityUnits(1L) + .build()); + //Creating Targeted DynamoDB Object + MetaStore.createTable(targetClient, DESTINATION_TABLE_NAME, ProvisionedThroughput.builder() + .readCapacityUnits(1L) + .writeCapacityUnits(1L) + .build()); + store = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR); + targetStore = new MetaStore(targetClient, DESTINATION_TABLE_NAME, TARGET_ENCRYPTOR); + ctx = EncryptionContext.builder().build(); + } + + @AfterMethod + public void stopLocalDynamoDb() { + localDynamoDb.stop(); + targetLocalDynamoDb.stop(); + } + + @Test + public void testNoMaterials() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + } + + @Test + public void singleMaterial() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov = store.newProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void singleMaterialExplicitAccess() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = store.getProvider(MATERIAL_NAME); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void singleMaterialExplicitAccessWithVersion() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = store.getProvider(MATERIAL_NAME, 0); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void singleMaterialWithImplicitCreation() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov = store.getProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterials eMat = prov.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov.getDecryptionMaterials(ctx(eMat)); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void twoDifferentMaterials() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = store.newProvider(MATERIAL_NAME); + assertEquals(1, store.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + assertEquals(0, store.getVersionFromMaterialDescription(eMat.getMaterialDescription())); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + try { + prov2.getDecryptionMaterials(ctx(eMat)); + fail("Missing expected exception"); + } catch (final DynamoDbEncryptionException ex) { + // Expected Exception + } + final EncryptionMaterials eMat2 = prov2.getEncryptionMaterials(ctx); + assertEquals(1, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription())); + } + + @Test + public void getOrCreateCollision() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = store.getOrCreate(MATERIAL_NAME, 0); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = store.getOrCreate(MATERIAL_NAME, 0); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void getOrCreateWithContextSupplier() { + final Map attributeValueMap = new HashMap<>(); + attributeValueMap.put("CustomKeyId", AttributeValueBuilder.ofS("testCustomKeyId")); + attributeValueMap.put("KeyToken", AttributeValueBuilder.ofS("testKeyToken")); + + final Set signedOnlyAttributes = new HashSet<>(); + signedOnlyAttributes.add("CustomKeyId"); + + final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( + attributeValueMap, signedOnlyAttributes); + + final MetaStore metaStore = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); + + assertEquals(-1, metaStore.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = metaStore.getOrCreate(MATERIAL_NAME, 0); + assertEquals(0, metaStore.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = metaStore.getOrCreate(MATERIAL_NAME, 0); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test + public void replicateIntermediateKeysTest() { + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterialsProvider prov1 = store.getOrCreate(MATERIAL_NAME, 0); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + + store.replicate(MATERIAL_NAME, 0, targetStore); + assertEquals(0, targetStore.getMaxVersion(MATERIAL_NAME)); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final DecryptionMaterials dMat = targetStore.getProvider(MATERIAL_NAME, 0).getDecryptionMaterials(ctx(eMat)); + + assertEquals(eMat.getEncryptionKey(), dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test(expectedExceptions = IndexOutOfBoundsException.class) + public void replicateIntermediateKeysWhenMaterialNotFoundTest() { + store.replicate(MATERIAL_NAME, 0, targetStore); + } + + @Test + public void newProviderCollision() throws InterruptedException { + final SlowNewProvider slowProv = new SlowNewProvider(); + assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); + assertEquals(-1, slowProv.slowStore.getMaxVersion(MATERIAL_NAME)); + + slowProv.start(); + Thread.sleep(100); + final EncryptionMaterialsProvider prov1 = store.newProvider(MATERIAL_NAME); + slowProv.join(); + assertEquals(0, store.getMaxVersion(MATERIAL_NAME)); + assertEquals(0, slowProv.slowStore.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = slowProv.result; + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + + @Test(expectedExceptions= IndexOutOfBoundsException.class) + public void invalidVersion() { + store.getProvider(MATERIAL_NAME, 1000); + } + + @Test(expectedExceptions= IllegalArgumentException.class) + public void invalidSignedOnlyField() { + final Map attributeValueMap = new HashMap<>(); + attributeValueMap.put("enc", AttributeValueBuilder.ofS("testEncryptionKey")); + + final Set signedOnlyAttributes = new HashSet<>(); + signedOnlyAttributes.add("enc"); + + final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( + attributeValueMap, signedOnlyAttributes); + + new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); + } + + private static EncryptionContext ctx(final EncryptionMaterials mat) { + return EncryptionContext.builder() + .materialDescription(mat.getMaterialDescription()).build(); + } + + private class SlowNewProvider extends Thread { + public volatile EncryptionMaterialsProvider result; + public ProviderStore slowStore = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR) { + @Override + public EncryptionMaterialsProvider newProvider(final String materialName) { + final long nextId = getMaxVersion(materialName) + 1; + try { + Thread.sleep(1000); + } catch (final InterruptedException e) { + // Ignored + } + return getOrCreate(materialName, nextId); + } + }; + + @Override + public void run() { + result = slowStore.newProvider(MATERIAL_NAME); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java new file mode 100644 index 0000000000..2ed128e9d3 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/utils/EncryptionContextOperatorsTest.java @@ -0,0 +1,164 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils; + +import static org.testng.AssertJUnit.assertEquals; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableName; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableNameUsingMap; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.testng.annotations.Test; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; + +public class EncryptionContextOperatorsTest { + + @Test + public void testCreateEncryptionContextTableNameOverride_expectedOverride() { + Function myNewTableName = overrideEncryptionContextTableName("OriginalTableName", "MyNewTableName"); + + EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName").build(); + + EncryptionContext newContext = myNewTableName.apply(context); + + assertEquals("OriginalTableName", context.getTableName()); + assertEquals("MyNewTableName", newContext.getTableName()); + } + + /** + * Some pretty clear repetition in null cases. May make sense to replace with data providers or parameterized + * classes for null cases + */ + @Test + public void testNullCasesCreateEncryptionContextTableNameOverride_nullOriginalTableName() { + assertEncryptionContextUnchanged(EncryptionContext.builder().tableName("example").build(), + null, + "MyNewTableName"); + } + + @Test + public void testCreateEncryptionContextTableNameOverride_differentOriginalTableName() { + assertEncryptionContextUnchanged(EncryptionContext.builder().tableName("example").build(), + "DifferentTableName", + "MyNewTableName"); + } + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverride_nullEncryptionContext() { + assertEncryptionContextUnchanged(null, + "DifferentTableName", + "MyNewTableName"); + } + + @Test + public void testCreateEncryptionContextTableNameOverrideMap_expectedOverride() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("OriginalTableName", "MyNewTableName"); + + + Function nameOverrideMap = + overrideEncryptionContextTableNameUsingMap(tableNameOverrides); + + EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName").build(); + + EncryptionContext newContext = nameOverrideMap.apply(context); + + assertEquals("OriginalTableName", context.getTableName()); + assertEquals("MyNewTableName", newContext.getTableName()); + } + + @Test + public void testCreateEncryptionContextTableNameOverrideMap_multipleOverrides() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("OriginalTableName1", "MyNewTableName1"); + tableNameOverrides.put("OriginalTableName2", "MyNewTableName2"); + + + Function overrideOperator = + overrideEncryptionContextTableNameUsingMap(tableNameOverrides); + + EncryptionContext context = EncryptionContext.builder().tableName("OriginalTableName1").build(); + + EncryptionContext newContext = overrideOperator.apply(context); + + assertEquals("OriginalTableName1", context.getTableName()); + assertEquals("MyNewTableName1", newContext.getTableName()); + + EncryptionContext context2 = EncryptionContext.builder().tableName("OriginalTableName2").build(); + + EncryptionContext newContext2 = overrideOperator.apply(context2); + + assertEquals("OriginalTableName2", context2.getTableName()); + assertEquals("MyNewTableName2", newContext2.getTableName()); + + } + + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullEncryptionContextTableName() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("DifferentTableName", "MyNewTableName"); + assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().build(), + tableNameOverrides); + } + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullEncryptionContext() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("DifferentTableName", "MyNewTableName"); + assertEncryptionContextUnchangedFromMap(null, + tableNameOverrides); + } + + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullOriginalTableName() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put(null, "MyNewTableName"); + assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("example").build(), + tableNameOverrides); + } + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullNewTableName() { + Map tableNameOverrides = new HashMap<>(); + tableNameOverrides.put("MyOriginalTableName", null); + assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("MyOriginalTableName").build(), + tableNameOverrides); + } + + + @Test + public void testNullCasesCreateEncryptionContextTableNameOverrideFromMap_nullMap() { + assertEncryptionContextUnchangedFromMap(EncryptionContext.builder().tableName("MyOriginalTableName").build(), + null); + } + + + private void assertEncryptionContextUnchanged(EncryptionContext encryptionContext, String originalTableName, String newTableName) { + Function encryptionContextTableNameOverride = overrideEncryptionContextTableName(originalTableName, newTableName); + EncryptionContext newEncryptionContext = encryptionContextTableNameOverride.apply(encryptionContext); + assertEquals(encryptionContext, newEncryptionContext); + } + + + private void assertEncryptionContextUnchangedFromMap(EncryptionContext encryptionContext, Map overrideMap) { + Function encryptionContextTableNameOverrideFromMap = overrideEncryptionContextTableNameUsingMap(overrideMap); + EncryptionContext newEncryptionContext = encryptionContextTableNameOverrideFromMap.apply(encryptionContext); + assertEquals(encryptionContext, newEncryptionContext); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java new file mode 100644 index 0000000000..e098816275 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/AttributeValueMarshallerTest.java @@ -0,0 +1,393 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.fail; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller.marshall; +import static software.amazon.cryptools.dynamodbencryptionclientsdk2.internal.AttributeValueMarshaller.unmarshall; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.testng.annotations.Test; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.testing.AttributeValueBuilder; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class AttributeValueMarshallerTest { + @Test(expectedExceptions = IllegalArgumentException.class) + public void testEmpty() { + AttributeValue av = AttributeValue.builder().build(); + marshall(av); + } + + @Test + public void testNumber() { + AttributeValue av = AttributeValue.builder().n("1337").build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testString() { + AttributeValue av = AttributeValue.builder().s("1337").build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testByteBuffer() { + AttributeValue av = AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5})).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + // We can't use straight .equals for comparison because Attribute Values represents Sets + // as Lists and so incorrectly does an ordered comparison + + @Test + public void testNumberS() { + AttributeValue av = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1337", "1", "5"))).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testNumberSOrdering() { + AttributeValue av1 = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1337", "1", "5"))).build(); + AttributeValue av2 = AttributeValue.builder().ns(unmodifiableList(Arrays.asList("1", "5", "1337"))).build(); + assertAttributesAreEqual(av1, av2); + ByteBuffer buff1 = marshall(av1); + ByteBuffer buff2 = marshall(av2); + assertEquals(buff1, buff2); + } + + @Test + public void testStringS() { + AttributeValue av = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Bob", "Ann", "5"))).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testStringSOrdering() { + AttributeValue av1 = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Bob", "Ann", "5"))).build(); + AttributeValue av2 = AttributeValue.builder().ss(unmodifiableList(Arrays.asList("Ann", "Bob", "5"))).build(); + assertAttributesAreEqual(av1, av2); + ByteBuffer buff1 = marshall(av1); + ByteBuffer buff2 = marshall(av2); + assertEquals(buff1, buff2); + } + + @Test + public void testByteBufferS() { + AttributeValue av = AttributeValue.builder().bs(unmodifiableList( + Arrays.asList(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5}), + SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7})))).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testByteBufferSOrdering() { + AttributeValue av1 = AttributeValue.builder().bs(unmodifiableList( + Arrays.asList(SdkBytes.fromByteArray(new byte[] {0, 1, 2, 3, 4, 5}), + SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7})))).build(); + AttributeValue av2 = AttributeValue.builder().bs(unmodifiableList( + Arrays.asList(SdkBytes.fromByteArray(new byte[] {5, 4, 3, 2, 1, 0, 0, 0, 5, 6, 7}), + SdkBytes.fromByteArray(new byte[]{0, 1, 2, 3, 4, 5})))).build(); + + assertAttributesAreEqual(av1, av2); + ByteBuffer buff1 = marshall(av1); + ByteBuffer buff2 = marshall(av2); + assertEquals(buff1, buff2); + } + + @Test + public void testBoolTrue() { + AttributeValue av = AttributeValue.builder().bool(Boolean.TRUE).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testBoolFalse() { + AttributeValue av = AttributeValue.builder().bool(Boolean.FALSE).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testNULL() { + AttributeValue av = AttributeValue.builder().nul(Boolean.TRUE).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void testActualNULL() { + unmarshall(marshall(null)); + } + + @Test + public void testEmptyList() { + AttributeValue av = AttributeValue.builder().l(emptyList()).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testListOfString() { + AttributeValue av = + AttributeValue.builder().l(singletonList(AttributeValue.builder().s("StringValue").build())).build(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testList() { + AttributeValue av = AttributeValueBuilder.ofL( + AttributeValueBuilder.ofS("StringValue"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofBool(Boolean.TRUE)); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testListWithNull() { + final AttributeValue av = AttributeValueBuilder.ofL( + AttributeValueBuilder.ofS("StringValue"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofBool(Boolean.TRUE), + null); + + try { + marshall(av); + } catch (NullPointerException e) { + assertThat(e.getMessage(), + startsWith("Encountered null list entry value while marshalling attribute value")); + } + } + + @Test + public void testListDuplicates() { + AttributeValue av = AttributeValueBuilder.ofL( + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofN("1000")); + AttributeValue result = unmarshall(marshall(av)); + assertAttributesAreEqual(av, result); + assertEquals(4, result.l().size()); + } + + @Test + public void testComplexList() { + final List list1 = Arrays.asList( + AttributeValueBuilder.ofS("StringValue"), + AttributeValueBuilder.ofN("1000"), + AttributeValueBuilder.ofBool(Boolean.TRUE)); + final List list22 = Arrays.asList( + AttributeValueBuilder.ofS("AWS"), + AttributeValueBuilder.ofN("-3700"), + AttributeValueBuilder.ofBool(Boolean.FALSE)); + final List list2 = Arrays.asList( + AttributeValueBuilder.ofL(list22), + AttributeValueBuilder.ofNull()); + AttributeValue av = AttributeValueBuilder.ofL( + AttributeValueBuilder.ofS("StringValue1"), + AttributeValueBuilder.ofL(list1), + AttributeValueBuilder.ofN("50"), + AttributeValueBuilder.ofL(list2)); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testEmptyMap() { + Map map = new HashMap<>(); + AttributeValue av = AttributeValueBuilder.ofM(map); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testSimpleMap() { + Map map = new HashMap<>(); + map.put("KeyValue", AttributeValueBuilder.ofS("ValueValue")); + AttributeValue av = AttributeValueBuilder.ofM(map); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + @Test + public void testSimpleMapWithNull() { + final Map map = new HashMap<>(); + map.put("KeyValue", AttributeValueBuilder.ofS("ValueValue")); + map.put("NullKeyValue", null); + + final AttributeValue av = AttributeValueBuilder.ofM(map); + + try { + marshall(av); + fail("NullPointerException should have been thrown"); + } catch (NullPointerException e) { + assertThat(e.getMessage(), startsWith("Encountered null map value for key NullKeyValue while marshalling " + + "attribute value")); + } + } + + @Test + public void testMapOrdering() { + LinkedHashMap m1 = new LinkedHashMap<>(); + LinkedHashMap m2 = new LinkedHashMap<>(); + + m1.put("Value1", AttributeValueBuilder.ofN("1")); + m1.put("Value2", AttributeValueBuilder.ofBool(Boolean.TRUE)); + + m2.put("Value2", AttributeValueBuilder.ofBool(Boolean.TRUE)); + m2.put("Value1", AttributeValueBuilder.ofN("1")); + + AttributeValue av1 = AttributeValueBuilder.ofM(m1); + AttributeValue av2 = AttributeValueBuilder.ofM(m2); + + ByteBuffer buff1 = marshall(av1); + ByteBuffer buff2 = marshall(av2); + assertEquals(buff1, buff2); + assertAttributesAreEqual(av1, unmarshall(buff1)); + assertAttributesAreEqual(av1, unmarshall(buff2)); + assertAttributesAreEqual(av2, unmarshall(buff1)); + assertAttributesAreEqual(av2, unmarshall(buff2)); + } + + @Test + public void testComplexMap() { + AttributeValue av = buildComplexAttributeValue(); + assertAttributesAreEqual(av, unmarshall(marshall(av))); + } + + // This test ensures that an AttributeValue marshalled by an older + // version of this library still unmarshalls correctly. It also + // ensures that old and new marshalling is identical. + @Test + public void testVersioningCompatibility() { + AttributeValue newObject = buildComplexAttributeValue(); + byte[] oldBytes = Base64.getDecoder().decode(COMPLEX_ATTRIBUTE_MARSHALLED); + byte[] newBytes = marshall(newObject).array(); + assertThat(oldBytes, is(newBytes)); + + AttributeValue oldObject = unmarshall(ByteBuffer.wrap(oldBytes)); + assertAttributesAreEqual(oldObject, newObject); + } + + private static final String COMPLEX_ATTRIBUTE_MARSHALLED = "AE0AAAADAHM" + + "AAAAJSW5uZXJMaXN0AEwAAAAGAHMAAAALQ29tcGxleExpc3QAbgAAAAE1AGIAA" + + "AAGAAECAwQFAEwAAAAFAD8BAAAAAABMAAAAAQA/AABNAAAAAwBzAAAABFBpbms" + + "AcwAAAAVGbG95ZABzAAAABFRlc3QAPwEAcwAAAAdWZXJzaW9uAG4AAAABMQAAA" + + "E0AAAADAHMAAAAETGlzdABMAAAABQBuAAAAATUAbgAAAAE0AG4AAAABMwBuAAA" + + "AATIAbgAAAAExAHMAAAADTWFwAE0AAAABAHMAAAAGTmVzdGVkAD8BAHMAAAAEV" + + "HJ1ZQA/AQBzAAAACVNpbmdsZU1hcABNAAAAAQBzAAAAA0ZPTwBzAAAAA0JBUgB" + + "zAAAACVN0cmluZ1NldABTAAAAAwAAAANiYXIAAAADYmF6AAAAA2Zvbw=="; + + private static AttributeValue buildComplexAttributeValue() { + Map floydMap = new HashMap<>(); + floydMap.put("Pink", AttributeValueBuilder.ofS("Floyd")); + floydMap.put("Version", AttributeValueBuilder.ofN("1")); + floydMap.put("Test", AttributeValueBuilder.ofBool(Boolean.TRUE)); + List floydList = Arrays.asList( + AttributeValueBuilder.ofBool(Boolean.TRUE), + AttributeValueBuilder.ofNull(), + AttributeValueBuilder.ofNull(), + AttributeValueBuilder.ofL(AttributeValueBuilder.ofBool(Boolean.FALSE)), + AttributeValueBuilder.ofM(floydMap) + ); + + List nestedList = Arrays.asList( + AttributeValueBuilder.ofN("5"), + AttributeValueBuilder.ofN("4"), + AttributeValueBuilder.ofN("3"), + AttributeValueBuilder.ofN("2"), + AttributeValueBuilder.ofN("1") + ); + Map nestedMap = new HashMap<>(); + nestedMap.put("True", AttributeValueBuilder.ofBool(Boolean.TRUE)); + nestedMap.put("List", AttributeValueBuilder.ofL(nestedList)); + nestedMap.put("Map", AttributeValueBuilder.ofM( + Collections.singletonMap("Nested", + AttributeValueBuilder.ofBool(Boolean.TRUE)))); + + List innerList = Arrays.asList( + AttributeValueBuilder.ofS("ComplexList"), + AttributeValueBuilder.ofN("5"), + AttributeValueBuilder.ofB(new byte[] {0, 1, 2, 3, 4, 5}), + AttributeValueBuilder.ofL(floydList), + AttributeValueBuilder.ofNull(), + AttributeValueBuilder.ofM(nestedMap) + ); + + Map result = new HashMap<>(); + result.put("SingleMap", AttributeValueBuilder.ofM( + Collections.singletonMap("FOO", AttributeValueBuilder.ofS("BAR")))); + result.put("InnerList", AttributeValueBuilder.ofL(innerList)); + result.put("StringSet", AttributeValueBuilder.ofSS("foo", "bar", "baz")); + return AttributeValue.builder().m(Collections.unmodifiableMap(result)).build(); + } + + private void assertAttributesAreEqual(AttributeValue o1, AttributeValue o2) { + assertEquals(o1.b(), o2.b()); + assertSetsEqual(o1.bs(), o2.bs()); + assertEquals(o1.n(), o2.n()); + assertSetsEqual(o1.ns(), o2.ns()); + assertEquals(o1.s(), o2.s()); + assertSetsEqual(o1.ss(), o2.ss()); + assertEquals(o1.bool(), o2.bool()); + assertEquals(o1.nul(), o2.nul()); + + if (o1.l() != null) { + assertNotNull(o2.l()); + final List l1 = o1.l(); + final List l2 = o2.l(); + assertEquals(l1.size(), l2.size()); + for (int x = 0; x < l1.size(); ++x) { + assertAttributesAreEqual(l1.get(x), l2.get(x)); + } + } + + if (o1.m() != null) { + assertNotNull(o2.m()); + final Map m1 = o1.m(); + final Map m2 = o2.m(); + assertEquals(m1.size(), m2.size()); + for (Map.Entry entry : m1.entrySet()) { + assertAttributesAreEqual(entry.getValue(), m2.get(entry.getKey())); + } + } + } + + private void assertSetsEqual(Collection c1, Collection c2) { + assertFalse(c1 == null ^ c2 == null); + if (c1 != null) { + Set s1 = new HashSet<>(c1); + Set s2 = new HashSet<>(c2); + assertEquals(s1, s2); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java new file mode 100644 index 0000000000..4ec9c03ae4 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/Base64Tests.java @@ -0,0 +1,93 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.quicktheories.QuickTheory.qt; +import static org.quicktheories.generators.Generate.byteArrays; +import static org.quicktheories.generators.Generate.bytes; +import static org.quicktheories.generators.SourceDSL.integers; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import org.apache.commons.lang3.StringUtils; +import org.testng.annotations.Test; + +public class Base64Tests { + @Test + public void testBase64EncodeEquivalence() { + qt().forAll( + byteArrays( + integers().between(0, 1000000), bytes(Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0))) + .check( + (a) -> { + // Base64 encode using both implementations and check for equality of output + // in case one version produces different output + String sdkV1Base64 = com.amazonaws.util.Base64.encodeAsString(a); + String encryptionClientBase64 = Base64.encodeToString(a); + return StringUtils.equals(sdkV1Base64, encryptionClientBase64); + }); + } + + @Test + public void testBase64DecodeEquivalence() { + qt().forAll( + byteArrays( + integers().between(0, 10000), bytes(Byte.MIN_VALUE, Byte.MAX_VALUE, (byte) 0))) + .as((b) -> java.util.Base64.getMimeEncoder().encodeToString(b)) + .check( + (s) -> { + // Check for equality using the MimeEncoder, which inserts newlines + // The encryptionClient's decoder is expected to ignore them + byte[] sdkV1Bytes = com.amazonaws.util.Base64.decode(s); + byte[] encryptionClientBase64 = Base64.decode(s); + return Arrays.equals(sdkV1Bytes, encryptionClientBase64); + }); + } + + @Test + public void testNullDecodeBehavior() { + byte[] decoded = Base64.decode(null); + assertThat(decoded, equalTo(null)); + } + + @Test + public void testNullDecodeBehaviorSdk1() { + byte[] decoded = com.amazonaws.util.Base64.decode((String) null); + assertThat(decoded, equalTo(null)); + + byte[] decoded2 = com.amazonaws.util.Base64.decode((byte[]) null); + assertThat(decoded2, equalTo(null)); + } + + @Test + public void testBase64PaddingBehavior() { + String testInput = "another one bites the dust"; + String expectedEncoding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q="; + assertThat( + Base64.encodeToString(testInput.getBytes(StandardCharsets.UTF_8)), + equalTo(expectedEncoding)); + + String encodingWithoutPadding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q"; + assertThat(Base64.decode(encodingWithoutPadding), equalTo(testInput.getBytes())); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testBase64PaddingBehaviorSdk1() { + String testInput = "another one bites the dust"; + String encodingWithoutPadding = "YW5vdGhlciBvbmUgYml0ZXMgdGhlIGR1c3Q"; + com.amazonaws.util.Base64.decode(encodingWithoutPadding); + } + + @Test + public void rfc4648TestVectors() { + assertThat(Base64.encodeToString("".getBytes(StandardCharsets.UTF_8)), equalTo("")); + assertThat(Base64.encodeToString("f".getBytes(StandardCharsets.UTF_8)), equalTo("Zg==")); + assertThat(Base64.encodeToString("fo".getBytes(StandardCharsets.UTF_8)), equalTo("Zm8=")); + assertThat(Base64.encodeToString("foo".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9v")); + assertThat(Base64.encodeToString("foob".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYg==")); + assertThat( + Base64.encodeToString("fooba".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYmE=")); + assertThat( + Base64.encodeToString("foobar".getBytes(StandardCharsets.UTF_8)), equalTo("Zm9vYmFy")); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java new file mode 100644 index 0000000000..71b90c195e --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ByteBufferInputStreamTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.testng.annotations.Test; + +public class ByteBufferInputStreamTest { + + @Test + public void testRead() throws IOException { + ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + for (int x = 0; x < 10; ++x) { + assertEquals(10 - x, bis.available()); + assertEquals(x, bis.read()); + } + assertEquals(0, bis.available()); + bis.close(); + } + + @Test + public void testReadByteArray() throws IOException { + ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + assertEquals(10, bis.available()); + + byte[] buff = new byte[4]; + + int len = bis.read(buff); + assertEquals(4, len); + assertEquals(6, bis.available()); + assertThat(buff, is(new byte[] {0, 1, 2, 3})); + + len = bis.read(buff); + assertEquals(4, len); + assertEquals(2, bis.available()); + assertThat(buff, is(new byte[] {4, 5, 6, 7})); + + len = bis.read(buff); + assertEquals(2, len); + assertEquals(0, bis.available()); + assertThat(buff, is(new byte[] {8, 9, 6, 7})); + bis.close(); + } + + @Test + public void testSkip() throws IOException { + ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.wrap(new byte[]{(byte) 0xFA, 15, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + assertEquals(13, bis.available()); + assertEquals(0xFA, bis.read()); + assertEquals(12, bis.available()); + bis.skip(2); + assertEquals(10, bis.available()); + for (int x = 0; x < 10; ++x) { + assertEquals(x, bis.read()); + } + assertEquals(0, bis.available()); + assertEquals(-1, bis.read()); + bis.close(); + } + + @Test + public void testMarkSupported() throws IOException { + try (ByteBufferInputStream bis = new ByteBufferInputStream(ByteBuffer.allocate(0))) { + assertFalse(bis.markSupported()); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java new file mode 100644 index 0000000000..7fcb5b89ab --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/ConcurrentTTLCacheTest.java @@ -0,0 +1,244 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import edu.umd.cs.mtc.MultithreadedTestCase; +import edu.umd.cs.mtc.TestFramework; +import java.util.concurrent.TimeUnit; +import org.testng.annotations.Test; + +/* Test specific thread interleavings with behaviors we care about in the + * TTLCache. + */ +public class ConcurrentTTLCacheTest { + + private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); + private static final long ttlInMillis = 1000; + + @Test + public void testGracePeriodCase() throws Throwable { + TestFramework.runOnce(new GracePeriodCase()); + } + + @Test + public void testExpiredCase() throws Throwable { + TestFramework.runOnce(new ExpiredCase()); + } + + @Test + public void testNewEntryCase() throws Throwable { + TestFramework.runOnce(new NewEntryCase()); + } + + @Test + public void testPutLoadCase() throws Throwable { + TestFramework.runOnce(new PutLoadCase()); + } + + // Ensure the loader is only called once if two threads attempt to load during the grace period + class GracePeriodCase extends MultithreadedTestCase { + TTLCache cache; + TTLCache.EntryLoader loader; + MsClock clock = mock(MsClock.class); + + @Override + public void initialize() { + loader = + spy( + new TTLCache.EntryLoader() { + @Override + public String load(String entryKey) { + // Wait until thread2 finishes to complete load + waitForTick(2); + return "loadedValue"; + } + }); + when(clock.timestampNano()).thenReturn((long) 0); + cache = new TTLCache<>(3, ttlInMillis, loader); + cache.clock = clock; + + // Put an initial value into the cache at time 0 + cache.put("k1", "v1"); + } + + // The thread that first calls load in the grace period and acquires the lock + public void thread1() { + when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); + String loadedValue = cache.load("k1"); + assertTick(2); + // Expect to get back the value calculated from load + assertEquals("loadedValue", loadedValue); + } + + // The thread that calls load in the grace period after the lock has been acquired + public void thread2() { + // Wait until the first thread acquires the lock and starts load + waitForTick(1); + when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); + String loadedValue = cache.load("k1"); + // Expect to get back the original value in the cache + assertEquals("v1", loadedValue); + } + + @Override + public void finish() { + // Ensure the loader was only called once + verify(loader, times(1)).load("k1"); + } + } + + // Ensure the loader is only called once if two threads attempt to load an expired entry. + class ExpiredCase extends MultithreadedTestCase { + TTLCache cache; + TTLCache.EntryLoader loader; + MsClock clock = mock(MsClock.class); + + @Override + public void initialize() { + loader = + spy( + new TTLCache.EntryLoader() { + @Override + public String load(String entryKey) { + // Wait until thread2 is waiting for the lock to complete load + waitForTick(2); + return "loadedValue"; + } + }); + when(clock.timestampNano()).thenReturn((long) 0); + cache = new TTLCache<>(3, ttlInMillis, loader); + cache.clock = clock; + + // Put an initial value into the cache at time 0 + cache.put("k1", "v1"); + } + + // The thread that first calls load after expiration + public void thread1() { + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + String loadedValue = cache.load("k1"); + assertTick(2); + // Expect to get back the value calculated from load + assertEquals("loadedValue", loadedValue); + } + + // The thread that calls load after expiration, + // after the first thread calls load, but before + // the new value is put into the cache. + public void thread2() { + // Wait until the first thread acquires the lock and starts load + waitForTick(1); + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + String loadedValue = cache.load("k1"); + // Expect to get back the newly loaded value + assertEquals("loadedValue", loadedValue); + // assert that this thread only finishes once the first thread's load does + assertTick(2); + } + + @Override + public void finish() { + // Ensure the loader was only called once + verify(loader, times(1)).load("k1"); + } + } + + // Ensure the loader is only called once if two threads attempt to load the same new entry. + class NewEntryCase extends MultithreadedTestCase { + TTLCache cache; + TTLCache.EntryLoader loader; + MsClock clock = mock(MsClock.class); + + @Override + public void initialize() { + loader = + spy( + new TTLCache.EntryLoader() { + @Override + public String load(String entryKey) { + // Wait until thread2 is blocked to complete load + waitForTick(2); + return "loadedValue"; + } + }); + when(clock.timestampNano()).thenReturn((long) 0); + cache = new TTLCache<>(3, ttlInMillis, loader); + cache.clock = clock; + } + + // The thread that first calls load + public void thread1() { + String loadedValue = cache.load("k1"); + assertTick(2); + // Expect to get back the value calculated from load + assertEquals("loadedValue", loadedValue); + } + + // The thread that calls load after the first thread calls load, + // but before the new value is put into the cache. + public void thread2() { + // Wait until the first thread acquires the lock and starts load + waitForTick(1); + String loadedValue = cache.load("k1"); + // Expect to get back the newly loaded value + assertEquals("loadedValue", loadedValue); + // assert that this thread only finishes once the first thread's load does + assertTick(2); + } + + @Override + public void finish() { + // Ensure the loader was only called once + verify(loader, times(1)).load("k1"); + } + } + + // Ensure the loader blocks put on load/put of the same new entry + class PutLoadCase extends MultithreadedTestCase { + TTLCache cache; + TTLCache.EntryLoader loader; + MsClock clock = mock(MsClock.class); + + @Override + public void initialize() { + loader = + spy( + new TTLCache.EntryLoader() { + @Override + public String load(String entryKey) { + // Wait until the put blocks to complete load + waitForTick(2); + return "loadedValue"; + } + }); + when(clock.timestampNano()).thenReturn((long) 0); + cache = new TTLCache<>(3, ttlInMillis, loader); + cache.clock = clock; + } + + // The thread that first calls load + public void thread1() { + String loadedValue = cache.load("k1"); + // Expect to get back the value calculated from load + assertEquals("loadedValue", loadedValue); + verify(loader, times(1)).load("k1"); + } + + // The thread that calls put during the first thread's load + public void thread2() { + // Wait until the first thread is loading + waitForTick(1); + String previousValue = cache.put("k1", "v1"); + // Expect to get back the value loaded into the cache by thread1 + assertEquals("loadedValue", previousValue); + // assert that this thread was blocked by the first thread + assertTick(2); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java new file mode 100644 index 0000000000..b9fdcb1d09 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/HkdfTests.java @@ -0,0 +1,209 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.testng.AssertJUnit.assertArrayEquals; + +import org.testng.annotations.Test; + +public class HkdfTests { + private static final testCase[] testCases = + new testCase[] { + new testCase( + "HmacSHA256", + fromCHex( + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" + + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), + fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), + fromHex( + "3CB25F25FAACD57A90434F64D0362F2A2D2D0A90CF1A5A4C5DB02D56ECC4C5BF34007208D5B887185865")), + new testCase( + "HmacSHA256", + fromCHex( + "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" + + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" + + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" + + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" + + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" + + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), + fromCHex( + "\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6a\\x6b\\x6c\\x6d" + + "\\x6e\\x6f\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7a\\x7b" + + "\\x7c\\x7d\\x7e\\x7f\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" + + "\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" + + "\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5" + + "\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf"), + fromCHex( + "\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd" + + "\\xbe\\xbf\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb" + + "\\xcc\\xcd\\xce\\xcf\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9" + + "\\xda\\xdb\\xdc\\xdd\\xde\\xdf\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7" + + "\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5" + + "\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff"), + fromHex( + "B11E398DC80327A1C8E7F78C596A4934" + + "4F012EDA2D4EFAD8A050CC4C19AFA97C" + + "59045A99CAC7827271CB41C65E590E09" + + "DA3275600C2F09B8367793A9ACA3DB71" + + "CC30C58179EC3E87C14C01D5C1F3434F" + + "1D87")), + new testCase( + "HmacSHA256", + fromCHex( + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" + + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + new byte[0], + new byte[0], + fromHex( + "8DA4E775A563C18F715F802A063C5A31" + + "B8A11F5C5EE1879EC3454E5F3C738D2D" + + "9D201395FAA4B61A96C8")), + new testCase( + "HmacSHA1", + fromCHex("\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + fromCHex("\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c"), + fromCHex("\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9"), + fromHex( + "085A01EA1B10F36933068B56EFA5AD81" + + "A4F14B822F5B091568A9CDD4F155FDA2" + + "C22E422478D305F3F896")), + new testCase( + "HmacSHA1", + fromCHex( + "\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d" + + "\\x0e\\x0f\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b" + + "\\x1c\\x1d\\x1e\\x1f\\x20\\x21\\x22\\x23\\x24\\x25\\x26\\x27\\x28\\x29" + + "\\x2a\\x2b\\x2c\\x2d\\x2e\\x2f\\x30\\x31\\x32\\x33\\x34\\x35\\x36\\x37" + + "\\x38\\x39\\x3a\\x3b\\x3c\\x3d\\x3e\\x3f\\x40\\x41\\x42\\x43\\x44\\x45" + + "\\x46\\x47\\x48\\x49\\x4a\\x4b\\x4c\\x4d\\x4e\\x4f"), + fromCHex( + "\\x60\\x61\\x62\\x63\\x64\\x65\\x66\\x67\\x68\\x69\\x6A\\x6B\\x6C\\x6D" + + "\\x6E\\x6F\\x70\\x71\\x72\\x73\\x74\\x75\\x76\\x77\\x78\\x79\\x7A\\x7B" + + "\\x7C\\x7D\\x7E\\x7F\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89" + + "\\x8A\\x8B\\x8C\\x8D\\x8E\\x8F\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97" + + "\\x98\\x99\\x9A\\x9B\\x9C\\x9D\\x9E\\x9F\\xA0\\xA1\\xA2\\xA3\\xA4\\xA5" + + "\\xA6\\xA7\\xA8\\xA9\\xAA\\xAB\\xAC\\xAD\\xAE\\xAF"), + fromCHex( + "\\xB0\\xB1\\xB2\\xB3\\xB4\\xB5\\xB6\\xB7\\xB8\\xB9\\xBA\\xBB\\xBC\\xBD" + + "\\xBE\\xBF\\xC0\\xC1\\xC2\\xC3\\xC4\\xC5\\xC6\\xC7\\xC8\\xC9\\xCA\\xCB" + + "\\xCC\\xCD\\xCE\\xCF\\xD0\\xD1\\xD2\\xD3\\xD4\\xD5\\xD6\\xD7\\xD8\\xD9" + + "\\xDA\\xDB\\xDC\\xDD\\xDE\\xDF\\xE0\\xE1\\xE2\\xE3\\xE4\\xE5\\xE6\\xE7" + + "\\xE8\\xE9\\xEA\\xEB\\xEC\\xED\\xEE\\xEF\\xF0\\xF1\\xF2\\xF3\\xF4\\xF5" + + "\\xF6\\xF7\\xF8\\xF9\\xFA\\xFB\\xFC\\xFD\\xFE\\xFF"), + fromHex( + "0BD770A74D1160F7C9F12CD5912A06EB" + + "FF6ADCAE899D92191FE4305673BA2FFE" + + "8FA3F1A4E5AD79F3F334B3B202B2173C" + + "486EA37CE3D397ED034C7F9DFEB15C5E" + + "927336D0441F4C4300E2CFF0D0900B52D3B4")), + new testCase( + "HmacSHA1", + fromCHex( + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b" + + "\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b\\x0b"), + new byte[0], + new byte[0], + fromHex("0AC1AF7002B3D761D1E55298DA9D0506" + "B9AE52057220A306E07B6B87E8DF21D0")), + new testCase( + "HmacSHA1", + fromCHex( + "\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c" + + "\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c\\x0c"), + null, + new byte[0], + fromHex( + "2C91117204D745F3500D636A62F64F0A" + + "B3BAE548AA53D423B0D1F27EBBA6F5E5" + + "673A081D70CCE7ACFC48")) + }; + + @Test + public void rfc5869Tests() throws Exception { + for (int x = 0; x < testCases.length; x++) { + testCase trial = testCases[x]; + System.out.println("Test case A." + (x + 1)); + Hkdf kdf = Hkdf.getInstance(trial.algo); + kdf.init(trial.ikm, trial.salt); + byte[] result = kdf.deriveKey(trial.info, trial.expected.length); + assertArrayEquals("Trial A." + x, trial.expected, result); + } + } + + @Test + public void nullTests() throws Exception { + testCase trial = testCases[0]; + Hkdf kdf = Hkdf.getInstance(trial.algo); + kdf.init(trial.ikm, trial.salt); + // Just ensuring no exceptions are thrown + kdf.deriveKey((String) null, 16); + kdf.deriveKey((byte[]) null, 16); + } + + @Test + public void defaultSalt() throws Exception { + // Tests all the different ways to get the default salt + + testCase trial = testCases[0]; + Hkdf kdf1 = Hkdf.getInstance(trial.algo); + kdf1.init(trial.ikm, null); + Hkdf kdf2 = Hkdf.getInstance(trial.algo); + kdf2.init(trial.ikm, new byte[0]); + Hkdf kdf3 = Hkdf.getInstance(trial.algo); + kdf3.init(trial.ikm); + Hkdf kdf4 = Hkdf.getInstance(trial.algo); + kdf4.init(trial.ikm, new byte[32]); + + byte[] key1 = kdf1.deriveKey("Test", 16); + byte[] key2 = kdf2.deriveKey("Test", 16); + byte[] key3 = kdf3.deriveKey("Test", 16); + byte[] key4 = kdf4.deriveKey("Test", 16); + + assertArrayEquals(key1, key2); + assertArrayEquals(key1, key3); + assertArrayEquals(key1, key4); + } + + private static byte[] fromHex(String data) { + byte[] result = new byte[data.length() / 2]; + for (int x = 0; x < result.length; x++) { + result[x] = (byte) Integer.parseInt(data.substring(2 * x, 2 * x + 2), 16); + } + return result; + } + + private static byte[] fromCHex(String data) { + byte[] result = new byte[data.length() / 4]; + for (int x = 0; x < result.length; x++) { + result[x] = (byte) Integer.parseInt(data.substring(4 * x + 2, 4 * x + 4), 16); + } + return result; + } + + private static class testCase { + public final String algo; + public final byte[] ikm; + public final byte[] salt; + public final byte[] info; + public final byte[] expected; + + public testCase(String algo, byte[] ikm, byte[] salt, byte[] info, byte[] expected) { + super(); + this.algo = algo; + this.ikm = ikm; + this.salt = salt; + this.info = info; + this.expected = expected; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java new file mode 100644 index 0000000000..8f56d35b96 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/LRUCacheTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import org.testng.annotations.Test; + +public class LRUCacheTest { + @Test + public void test() { + final LRUCache cache = new LRUCache(3); + assertEquals(0, cache.size()); + assertEquals(3, cache.getMaxSize()); + cache.add("k1", "v1"); + assertTrue(cache.size() == 1); + cache.add("k1", "v11"); + assertTrue(cache.size() == 1); + cache.add("k2", "v2"); + assertTrue(cache.size() == 2); + cache.add("k3", "v3"); + assertTrue(cache.size() == 3); + assertEquals("v11", cache.get("k1")); + assertEquals("v2", cache.get("k2")); + assertEquals("v3", cache.get("k3")); + cache.add("k4", "v4"); + assertTrue(cache.size() == 3); + assertNull(cache.get("k1")); + assertEquals("v4", cache.get("k4")); + assertEquals("v2", cache.get("k2")); + assertEquals("v3", cache.get("k3")); + assertTrue(cache.size() == 3); + cache.add("k5", "v5"); + assertNull(cache.get("k4")); + assertEquals("v5", cache.get("k5")); + assertEquals("v2", cache.get("k2")); + assertEquals("v3", cache.get("k3")); + cache.clear(); + assertEquals(0, cache.size()); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testZeroSize() { + new LRUCache(0); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testIllegalArgument() { + new LRUCache(-1); + } + + @Test + public void testSingleEntry() { + final LRUCache cache = new LRUCache(1); + assertTrue(cache.size() == 0); + cache.add("k1", "v1"); + assertTrue(cache.size() == 1); + cache.add("k1", "v11"); + assertTrue(cache.size() == 1); + assertEquals("v11", cache.get("k1")); + + cache.add("k2", "v2"); + assertTrue(cache.size() == 1); + assertEquals("v2", cache.get("k2")); + assertNull(cache.get("k1")); + + cache.add("k3", "v3"); + assertTrue(cache.size() == 1); + assertEquals("v3", cache.get("k3")); + assertNull(cache.get("k2")); + } + +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java new file mode 100644 index 0000000000..55e246f391 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/internal/TTLCacheTest.java @@ -0,0 +1,372 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.cryptools.dynamodbencryptionclientsdk2.internal; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertThrows; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import org.testng.annotations.Test; + +public class TTLCacheTest { + + private static final long TTL_GRACE_IN_NANO = TimeUnit.MILLISECONDS.toNanos(500); + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidSize() { + final TTLCache cache = new TTLCache(0, 1000, mock(TTLCache.EntryLoader.class)); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidTTL() { + final TTLCache cache = new TTLCache(3, 0, mock(TTLCache.EntryLoader.class)); + } + + + @Test(expectedExceptions = NullPointerException.class) + public void testNullLoader() { + final TTLCache cache = new TTLCache(3, 1000, null); + } + + @Test + public void testConstructor() { + final TTLCache cache = + new TTLCache(1000, 1000, mock(TTLCache.EntryLoader.class)); + assertEquals(0, cache.size()); + assertEquals(1000, cache.getMaxSize()); + } + + @Test + public void testLoadPastMaxSize() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 1; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + cache.load("k1"); + verify(loader, times(1)).load("k1"); + assertTrue(cache.size() == 1); + + String result = cache.load("k2"); + verify(loader, times(1)).load("k2"); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + + // to verify result is in the cache, load one more time + // and expect the loader to not be called + String cachedValue = cache.load("k2"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, cachedValue); + } + + @Test + public void testLoadNoExistingEntry() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + String result = cache.load("k1"); + verify(loader, times(1)).load("k1"); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + + // to verify result is in the cache, load one more time + // and expect the loader to not be called + String cachedValue = cache.load("k1"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, cachedValue); + } + + @Test + public void testLoadNotExpired() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // when first creating the entry, time is 0 + when(clock.timestampNano()).thenReturn((long) 0); + cache.load("k1"); + assertTrue(cache.size() == 1); + verify(loader, times(1)).load("k1"); + + // on load, time is within TTL + when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis)); + String result = cache.load("k1"); + + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + } + + @Test + public void testLoadInGrace() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // when first creating the entry, time is zero + when(clock.timestampNano()).thenReturn((long) 0); + cache.load("k1"); + assertTrue(cache.size() == 1); + verify(loader, times(1)).load("k1"); + + // on load, time is past TTL but within the grace period + when(clock.timestampNano()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + 1); + String result = cache.load("k1"); + + // Because this is tested in a single thread, + // this is expected to obtain the lock and load the new value + verify(loader, times(2)).load("k1"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + } + + @Test + public void testLoadExpired() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // when first creating the entry, time is zero + when(clock.timestampNano()).thenReturn((long) 0); + cache.load("k1"); + assertTrue(cache.size() == 1); + verify(loader, times(1)).load("k1"); + + // on load, time is past TTL and grace period + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + String result = cache.load("k1"); + + verify(loader, times(2)).load("k1"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(loadedValue, result); + } + + @Test + public void testLoadExpiredEviction() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())) + .thenReturn(loadedValue) + .thenThrow(new IllegalStateException("This loader is mocked to throw a failure.")); + MsClock clock = mock(MsClock.class); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // when first creating the entry, time is zero + when(clock.timestampNano()).thenReturn((long) 0); + cache.load("k1"); + verify(loader, times(1)).load("k1"); + assertTrue(cache.size() == 1); + + // on load, time is past TTL and grace period + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + assertThrows(IllegalStateException.class, () -> cache.load("k1")); + + verify(loader, times(2)).load("k1"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 0); + } + + @Test + public void testLoadWithFunction() { + final String loadedValue = "loaded value"; + final String functionValue = "function value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + final Function function = spy(Function.class); + when(function.apply(any())).thenReturn(functionValue); + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())) + .thenReturn(loadedValue) + .thenThrow(new IllegalStateException("This loader is mocked to throw a failure.")); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + String result = cache.load("k1", function); + verify(function, times(1)).apply("k1"); + assertTrue(cache.size() == 1); + assertEquals(functionValue, result); + + // to verify result is in the cache, load one more time + // and expect the loader to not be called + String cachedValue = cache.load("k1"); + verifyNoMoreInteractions(function); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(functionValue, cachedValue); + } + + @Test + public void testClear() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + + assertTrue(cache.size() == 0); + cache.load("k1"); + cache.load("k2"); + assertTrue(cache.size() == 2); + + cache.clear(); + assertTrue(cache.size() == 0); + } + + @Test + public void testPut() { + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + String oldValue = cache.put("k1", "v1"); + assertNull(oldValue); + assertTrue(cache.size() == 1); + + String oldValue2 = cache.put("k1", "v11"); + assertEquals("v1", oldValue2); + assertTrue(cache.size() == 1); + } + + @Test + public void testExpiredPut() { + final long ttlInMillis = 1000; + final int maxSize = 3; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + // First put is at time 0 + String oldValue = cache.put("k1", "v1"); + assertNull(oldValue); + assertTrue(cache.size() == 1); + + // Second put is at time past TTL and grace period + when(clock.timestampNano()) + .thenReturn(TimeUnit.MILLISECONDS.toNanos(ttlInMillis) + TTL_GRACE_IN_NANO + 1); + String oldValue2 = cache.put("k1", "v11"); + assertNull(oldValue2); + assertTrue(cache.size() == 1); + } + + @Test + public void testPutPastMaxSize() { + final String loadedValue = "loaded value"; + final long ttlInMillis = 1000; + final int maxSize = 1; + TTLCache.EntryLoader loader = spy(TTLCache.EntryLoader.class); + when(loader.load(any())).thenReturn(loadedValue); + MsClock clock = mock(MsClock.class); + when(clock.timestampNano()).thenReturn((long) 0); + + final TTLCache cache = new TTLCache(maxSize, ttlInMillis, loader); + cache.clock = clock; + + assertEquals(0, cache.size()); + assertEquals(maxSize, cache.getMaxSize()); + + cache.put("k1", "v1"); + assertTrue(cache.size() == 1); + + cache.put("k2", "v2"); + assertTrue(cache.size() == 1); + + // to verify put value is in the cache, load + // and expect the loader to not be called + String cachedValue = cache.load("k2"); + verifyNoMoreInteractions(loader); + assertTrue(cache.size() == 1); + assertEquals(cachedValue, "v2"); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java new file mode 100644 index 0000000000..122364e6db --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttrMatcher.java @@ -0,0 +1,125 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public class AttrMatcher extends BaseMatcher> { + private final Map expected; + private final boolean invert; + + public static AttrMatcher invert(Map expected) { + return new AttrMatcher(expected, true); + } + + public static AttrMatcher match(Map expected) { + return new AttrMatcher(expected, false); + } + + public AttrMatcher(Map expected, boolean invert) { + this.expected = expected; + this.invert = invert; + } + + @Override + public boolean matches(Object item) { + @SuppressWarnings("unchecked") + Map actual = (Map)item; + if (!expected.keySet().equals(actual.keySet())) { + return invert; + } + for (String key: expected.keySet()) { + AttributeValue e = expected.get(key); + AttributeValue a = actual.get(key); + if (!attrEquals(a, e)) { + return invert; + } + } + return !invert; + } + + public static boolean attrEquals(AttributeValue e, AttributeValue a) { + if (!isEqual(e.b(), a.b()) || + !isEqual(e.bool(), a.bool()) || + !isSetEqual(e.bs(), a.bs()) || + !isEqual(e.n(), a.n()) || + !isSetEqual(e.ns(), a.ns()) || + !isEqual(e.nul(), a.nul()) || + !isEqual(e.s(), a.s()) || + !isSetEqual(e.ss(), a.ss())) { + return false; + } + // Recursive types need special handling + if (e.m() == null ^ a.m() == null) { + return false; + } else if (e.m() != null) { + if (!e.m().keySet().equals(a.m().keySet())) { + return false; + } + for (final String key : e.m().keySet()) { + if (!attrEquals(e.m().get(key), a.m().get(key))) { + return false; + } + } + } + if (e.l() == null ^ a.l() == null) { + return false; + } else if (e.l() != null) { + if (e.l().size() != a.l().size()) { + return false; + } + for (int x = 0; x < e.l().size(); x++) { + if (!attrEquals(e.l().get(x), a.l().get(x))) { + return false; + } + } + } + return true; + } + + @Override + public void describeTo(Description description) { } + + private static boolean isEqual(Object o1, Object o2) { + if(o1 == null ^ o2 == null) { + return false; + } + if (o1 == o2) + return true; + return o1.equals(o2); + } + + private static boolean isSetEqual(Collection c1, Collection c2) { + if(c1 == null ^ c2 == null) { + return false; + } + if (c1 != null) { + Set s1 = new HashSet(c1); + Set s2 = new HashSet(c2); + if(!s1.equals(s2)) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java new file mode 100644 index 0000000000..3ff7e4dff8 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueBuilder.java @@ -0,0 +1,67 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.util.List; +import java.util.Map; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +/** + * Static helper methods to construct standard AttributeValues in a more compact way than specifying the full builder + * chain. + */ +public final class AttributeValueBuilder { + private AttributeValueBuilder() { + // Static helper class + } + + public static AttributeValue ofS(String value) { + return AttributeValue.builder().s(value).build(); + } + + public static AttributeValue ofN(String value) { + return AttributeValue.builder().n(value).build(); + } + + public static AttributeValue ofB(byte [] value) { + return AttributeValue.builder().b(SdkBytes.fromByteArray(value)).build(); + } + + public static AttributeValue ofBool(Boolean value) { + return AttributeValue.builder().bool(value).build(); + } + + public static AttributeValue ofNull() { + return AttributeValue.builder().nul(true).build(); + } + + public static AttributeValue ofL(List values) { + return AttributeValue.builder().l(values).build(); + } + + public static AttributeValue ofL(AttributeValue ...values) { + return AttributeValue.builder().l(values).build(); + } + + public static AttributeValue ofM(Map valueMap) { + return AttributeValue.builder().m(valueMap).build(); + } + + public static AttributeValue ofSS(String ...values) { + return AttributeValue.builder().ss(values).build(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java new file mode 100644 index 0000000000..42e479ba70 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueDeserializer.java @@ -0,0 +1,58 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class AttributeValueDeserializer extends JsonDeserializer { + @Override + public AttributeValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode attribute = jp.getCodec().readTree(jp); + + for (Iterator> iter = attribute.fields(); iter.hasNext(); ) { + Map.Entry rawAttribute = iter.next(); + // If there is more than one entry in this map, there is an error with our test data + if (iter.hasNext()) { + throw new IllegalStateException("Attribute value JSON has more than one value mapped."); + } + String typeString = rawAttribute.getKey(); + JsonNode value = rawAttribute.getValue(); + switch (typeString) { + case "S": + return AttributeValue.builder().s(value.asText()).build(); + case "B": + SdkBytes b = SdkBytes.fromByteArray(java.util.Base64.getDecoder().decode(value.asText())); + return AttributeValue.builder().b(b).build(); + case "N": + return AttributeValue.builder().n(value.asText()).build(); + case "SS": + final Set stringSet = + objectMapper.readValue( + objectMapper.treeAsTokens(value), new TypeReference>() {}); + return AttributeValue.builder().ss(stringSet).build(); + case "NS": + final Set numSet = + objectMapper.readValue( + objectMapper.treeAsTokens(value), new TypeReference>() {}); + return AttributeValue.builder().ns(numSet).build(); + default: + throw new IllegalStateException( + "DDB JSON type " + + typeString + + " not implemented for test attribute value deserialization."); + } + } + return null; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java new file mode 100644 index 0000000000..0dbea38209 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueMatcher.java @@ -0,0 +1,101 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class AttributeValueMatcher extends BaseMatcher { + private final AttributeValue expected; + private final boolean invert; + + public static AttributeValueMatcher invert(AttributeValue expected) { + return new AttributeValueMatcher(expected, true); + } + + public static AttributeValueMatcher match(AttributeValue expected) { + return new AttributeValueMatcher(expected, false); + } + + public AttributeValueMatcher(AttributeValue expected, boolean invert) { + this.expected = expected; + this.invert = invert; + } + + @Override + public boolean matches(Object item) { + AttributeValue other = (AttributeValue) item; + return invert ^ attrEquals(expected, other); + } + + @Override + public void describeTo(Description description) {} + + public static boolean attrEquals(AttributeValue e, AttributeValue a) { + if (!isEqual(e.b(), a.b()) + || !isNumberEqual(e.n(), a.n()) + || !isEqual(e.s(), a.s()) + || !isEqual(e.bs(), a.bs()) + || !isNumberEqual(e.ns(), a.ns()) + || !isEqual(e.ss(), a.ss())) { + return false; + } + return true; + } + + private static boolean isNumberEqual(String o1, String o2) { + if (o1 == null ^ o2 == null) { + return false; + } + if (o1 == o2) return true; + BigDecimal d1 = new BigDecimal(o1); + BigDecimal d2 = new BigDecimal(o2); + return d1.equals(d2); + } + + private static boolean isEqual(Object o1, Object o2) { + if (o1 == null ^ o2 == null) { + return false; + } + if (o1 == o2) return true; + return o1.equals(o2); + } + + private static boolean isNumberEqual(Collection c1, Collection c2) { + if (c1 == null ^ c2 == null) { + return false; + } + if (c1 != null) { + Set s1 = new HashSet(); + Set s2 = new HashSet(); + for (String s : c1) { + s1.add(new BigDecimal(s)); + } + for (String s : c2) { + s2.add(new BigDecimal(s)); + } + if (!s1.equals(s2)) { + return false; + } + } + return true; + } + + private static boolean isEqual(Collection c1, Collection c2) { + if (c1 == null ^ c2 == null) { + return false; + } + if (c1 != null) { + Set s1 = new HashSet(c1); + Set s2 = new HashSet(c2); + if (!s1.equals(s2)) { + return false; + } + } + return true; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java new file mode 100644 index 0000000000..95abc5471d --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/AttributeValueSerializer.java @@ -0,0 +1,48 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import com.amazonaws.util.Base64; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class AttributeValueSerializer extends JsonSerializer { + @Override + public void serialize(AttributeValue.Builder value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { + if (value != null) { + jgen.writeStartObject(); + if (value.build().s() != null) { + jgen.writeStringField("S", value.build().s()); + } else if (value.build().b() != null) { + ByteBuffer valueBytes = value.build().b().asByteBuffer(); + byte[] arr = new byte[valueBytes.remaining()]; + valueBytes.get(arr); + jgen.writeStringField("B", Base64.encodeAsString(arr)); + } else if (value.build().n() != null) { + jgen.writeStringField("N", value.build().n()); + } else if (value.build().ss() != null) { + jgen.writeFieldName("SS"); + jgen.writeStartArray(); + for (String s : value.build().ss()) { + jgen.writeString(s); + } + jgen.writeEndArray(); + } else if (value.build().ns() != null) { + jgen.writeFieldName("NS"); + jgen.writeStartArray(); + for (String num : value.build().ns()) { + jgen.writeString(num); + } + jgen.writeEndArray(); + } else { + throw new IllegalStateException( + "AttributeValue has no value or type not implemented for serialization."); + } + jgen.writeEndObject(); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java new file mode 100644 index 0000000000..75cc574a1c --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/DdbRecordMatcher.java @@ -0,0 +1,47 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import java.util.Map; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +public class DdbRecordMatcher extends BaseMatcher>{ + private final Map expected; + private final boolean invert; + + public static DdbRecordMatcher invert(Map expected) { + return new DdbRecordMatcher(expected, true); + } + + public static DdbRecordMatcher match(Map expected) { + return new DdbRecordMatcher(expected, false); + } + + public DdbRecordMatcher(Map expected, boolean invert) { + this.expected = expected; + this.invert = invert; + } + + @Override + public boolean matches(Object item) { + @SuppressWarnings("unchecked") + Map actual = (Map) item; + if (!expected.keySet().equals(actual.keySet())) { + return invert; + } + for (String key : expected.keySet()) { + if (key.equals("version")) continue; + AttributeValue e = expected.get(key); + AttributeValue a = actual.get(key); + if (!AttributeValueMatcher.attrEquals(a, e)) { + return invert; + } + } + return !invert; + } + + @Override + public void describeTo(Description description) { + + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java new file mode 100644 index 0000000000..d05aff4113 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/FakeKMS.java @@ -0,0 +1,201 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.CreateKeyRequest; +import software.amazon.awssdk.services.kms.model.CreateKeyResponse; +import software.amazon.awssdk.services.kms.model.DecryptRequest; +import software.amazon.awssdk.services.kms.model.DecryptResponse; +import software.amazon.awssdk.services.kms.model.EncryptRequest; +import software.amazon.awssdk.services.kms.model.EncryptResponse; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyRequest; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyResponse; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyWithoutPlaintextRequest; +import software.amazon.awssdk.services.kms.model.GenerateDataKeyWithoutPlaintextResponse; +import software.amazon.awssdk.services.kms.model.InvalidCiphertextException; +import software.amazon.awssdk.services.kms.model.KeyMetadata; +import software.amazon.awssdk.services.kms.model.KeyUsageType; + +public class FakeKMS implements KmsClient { + private static final SecureRandom rnd = new SecureRandom(); + private static final String ACCOUNT_ID = "01234567890"; + private final Map results_ = new HashMap<>(); + + @Override + public CreateKeyResponse createKey(CreateKeyRequest createKeyRequest) { + String keyId = UUID.randomUUID().toString(); + String arn = "arn:aws:testing:kms:" + ACCOUNT_ID + ":key/" + keyId; + return CreateKeyResponse.builder() + .keyMetadata(KeyMetadata.builder().awsAccountId(ACCOUNT_ID) + .creationDate(Instant.now()) + .description(createKeyRequest.description()) + .enabled(true) + .keyId(keyId) + .keyUsage(KeyUsageType.ENCRYPT_DECRYPT) + .arn(arn) + .build()) + .build(); + } + + @Override + public DecryptResponse decrypt(DecryptRequest decryptRequest) { + DecryptResponse result = results_.get(new DecryptMapKey(decryptRequest)); + if (result != null) { + return result; + } else { + throw InvalidCiphertextException.create("Invalid Ciphertext", new RuntimeException()); + } + } + + @Override + public EncryptResponse encrypt(EncryptRequest encryptRequest) { + final byte[] cipherText = new byte[512]; + rnd.nextBytes(cipherText); + DecryptResponse.Builder dec = DecryptResponse.builder(); + dec.keyId(encryptRequest.keyId()) + .plaintext(SdkBytes.fromByteBuffer(encryptRequest.plaintext().asByteBuffer().asReadOnlyBuffer())); + ByteBuffer ctBuff = ByteBuffer.wrap(cipherText); + + results_.put(new DecryptMapKey(ctBuff, encryptRequest.encryptionContext()), dec.build()); + + return EncryptResponse.builder() + .ciphertextBlob(SdkBytes.fromByteBuffer(ctBuff)) + .keyId(encryptRequest.keyId()) + .build(); + } + + @Override + public GenerateDataKeyResponse generateDataKey(GenerateDataKeyRequest generateDataKeyRequest) { + byte[] pt; + if (generateDataKeyRequest.keySpec() != null) { + if (generateDataKeyRequest.keySpec().toString().contains("256")) { + pt = new byte[32]; + } else if (generateDataKeyRequest.keySpec().toString().contains("128")) { + pt = new byte[16]; + } else { + throw new UnsupportedOperationException(); + } + } else { + pt = new byte[generateDataKeyRequest.numberOfBytes()]; + } + rnd.nextBytes(pt); + ByteBuffer ptBuff = ByteBuffer.wrap(pt); + EncryptResponse encryptresponse = encrypt(EncryptRequest.builder() + .keyId(generateDataKeyRequest.keyId()) + .plaintext(SdkBytes.fromByteBuffer(ptBuff)) + .encryptionContext(generateDataKeyRequest.encryptionContext()) + .build()); + return GenerateDataKeyResponse.builder().keyId(generateDataKeyRequest.keyId()) + .ciphertextBlob(encryptresponse.ciphertextBlob()) + .plaintext(SdkBytes.fromByteBuffer(ptBuff)) + .build(); + } + + @Override + public GenerateDataKeyWithoutPlaintextResponse generateDataKeyWithoutPlaintext( + GenerateDataKeyWithoutPlaintextRequest req) { + GenerateDataKeyResponse generateDataKey = generateDataKey(GenerateDataKeyRequest.builder() + .encryptionContext(req.encryptionContext()).numberOfBytes(req.numberOfBytes()).build()); + return GenerateDataKeyWithoutPlaintextResponse.builder().ciphertextBlob( + generateDataKey.ciphertextBlob()).keyId(req.keyId()).build(); + } + + public Map getSingleEc() { + if (results_.size() != 1) { + throw new IllegalStateException("Unexpected number of ciphertexts"); + } + for (final DecryptMapKey k : results_.keySet()) { + return k.ec; + } + throw new IllegalStateException("Unexpected number of ciphertexts"); + } + + @Override + public String serviceName() { + return KmsClient.SERVICE_NAME; + } + + @Override + public void close() { + // do nothing + } + + private static class DecryptMapKey { + private final ByteBuffer cipherText; + private final Map ec; + + public DecryptMapKey(DecryptRequest req) { + cipherText = req.ciphertextBlob().asByteBuffer(); + if (req.encryptionContext() != null) { + ec = Collections.unmodifiableMap(new HashMap<>(req.encryptionContext())); + } else { + ec = Collections.emptyMap(); + } + } + + public DecryptMapKey(ByteBuffer ctBuff, Map ec) { + cipherText = ctBuff.asReadOnlyBuffer(); + if (ec != null) { + this.ec = Collections.unmodifiableMap(new HashMap<>(ec)); + } else { + this.ec = Collections.emptyMap(); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((cipherText == null) ? 0 : cipherText.hashCode()); + result = prime * result + ((ec == null) ? 0 : ec.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DecryptMapKey other = (DecryptMapKey) obj; + if (cipherText == null) { + if (other.cipherText != null) + return false; + } else if (!cipherText.equals(other.cipherText)) + return false; + if (ec == null) { + if (other.ec != null) + return false; + } else if (!ec.equals(other.ec)) + return false; + return true; + } + + @Override + public String toString() { + return "DecryptMapKey [cipherText=" + cipherText + ", ec=" + ec + "]"; + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java new file mode 100644 index 0000000000..fe293a0d02 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java @@ -0,0 +1,175 @@ +/* + * 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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.URI; + +import com.amazonaws.services.dynamodbv2.local.main.ServerRunner; +import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer; + +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.*; + +/** + * Wrapper for a local DynamoDb server used in testing. Each instance of this class will find a new port to run on, + * so multiple instances can be safely run simultaneously. Each instance of this service uses memory as a storage medium + * and is thus completely ephemeral; no data will be persisted between stops and starts. + * + * LocalDynamoDb localDynamoDb = new LocalDynamoDb(); + * localDynamoDb.start(); // Start the service running locally on host + * DynamoDbClient dynamoDbClient = localDynamoDb.createClient(); + * ... // Do your testing with the client + * localDynamoDb.stop(); // Stop the service and free up resources + * + * If possible it's recommended to keep a single running instance for all your tests, as it can be slow to teardown + * and create new servers for every test, but there have been observed problems when dropping tables between tests for + * this scenario, so it's best to write your tests to be resilient to tables that already have data in them. + */ +public class LocalDynamoDb { + private static DynamoDBProxyServer server; + private static int port; + + /** + * Start the local DynamoDb service and run in background + */ + public void start() { + port = getFreePort(); + String portString = Integer.toString(port); + + try { + server = createServer(portString); + server.start(); + } catch (Exception e) { + throw propagate(e); + } + } + + /** + * Create a standard AWS v2 SDK client pointing to the local DynamoDb instance + * @return A DynamoDbClient pointing to the local DynamoDb instance + */ + public DynamoDbClient createClient() { + start(); + String endpoint = String.format("http://localhost:%d", port); + return DynamoDbClient.builder() + .endpointOverride(URI.create(endpoint)) + // The region is meaningless for local DynamoDb but required for client builder validation + .region(Region.US_EAST_1) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("dummy-key", "dummy-secret"))) + .build(); + } + + /** + * If you require a client object that can be mocked or spied using standard mocking frameworks, then you must call + * this method to create the client instead. Only some methods are supported by this client, but it is easy to add + * new ones. + * @return A mockable/spyable DynamoDbClient pointing to the Local DynamoDB service. + */ + public DynamoDbClient createLimitedWrappedClient() { + return new WrappedDynamoDbClient(createClient()); + } + + /** + * Stops the local DynamoDb service and frees up resources it is using. + */ + public void stop() { + try { + server.stop(); + } catch (Exception e) { + throw propagate(e); + } + } + + private static DynamoDBProxyServer createServer(String portString) throws Exception { + return ServerRunner.createServerFromCommandLineArgs( + new String[]{ + "-inMemory", + "-port", portString + }); + } + + private static int getFreePort() { + try { + ServerSocket socket = new ServerSocket(0); + int port = socket.getLocalPort(); + socket.close(); + return port; + } catch (IOException ioe) { + throw propagate(ioe); + } + } + + private static RuntimeException propagate(Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } + throw new RuntimeException(e); + } + + /** + * This class can wrap any other implementation of a DynamoDbClient. The default implementation of the real + * DynamoDbClient is a final class, therefore it cannot be easily spied upon unless you first wrap it in a class + * like this. If there's a method you need it to support, just add it to the wrapper here. + */ + private static class WrappedDynamoDbClient implements DynamoDbClient { + private final DynamoDbClient wrappedClient; + + private WrappedDynamoDbClient(DynamoDbClient wrappedClient) { + this.wrappedClient = wrappedClient; + } + + @Override + public String serviceName() { + return wrappedClient.serviceName(); + } + + @Override + public void close() { + wrappedClient.close(); + } + + @Override + public PutItemResponse putItem(PutItemRequest putItemRequest) { + return wrappedClient.putItem(putItemRequest); + } + + @Override + public GetItemResponse getItem(GetItemRequest getItemRequest) { + return wrappedClient.getItem(getItemRequest); + } + + @Override + public QueryResponse query(QueryRequest queryRequest) { + return wrappedClient.query(queryRequest); + } + + @Override + public ListTablesResponse listTables(ListTablesRequest listTablesRequest) { return wrappedClient.listTables(listTablesRequest); } + + @Override + public ScanResponse scan(ScanRequest scanRequest) { return wrappedClient.scan(scanRequest); } + + @Override + public CreateTableResponse createTable(CreateTableRequest createTableRequest) { + return wrappedClient.createTable(createTableRequest); + } + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java new file mode 100644 index 0000000000..14c84d8605 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/ScenarioManifest.java @@ -0,0 +1,77 @@ +package software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ScenarioManifest { + + public static final String MOST_RECENT_PROVIDER_NAME = "most_recent"; + public static final String WRAPPED_PROVIDER_NAME = "wrapped"; + public static final String STATIC_PROVIDER_NAME = "static"; + public static final String AWS_KMS_PROVIDER_NAME = "awskms"; + public static final String SYMMETRIC_KEY_TYPE = "symmetric"; + + public List scenarios; + + @JsonProperty("keys") + public String keyDataPath; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Scenario { + @JsonProperty("ciphertext") + public String ciphertextPath; + + @JsonProperty("provider") + public String providerName; + + public String version; + + @JsonProperty("material_name") + public String materialName; + + public Metastore metastore; + public Keys keys; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Metastore { + @JsonProperty("ciphertext") + public String path; + + @JsonProperty("table_name") + public String tableName; + + @JsonProperty("provider") + public String providerName; + + public Keys keys; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Keys { + @JsonProperty("encrypt") + public String encryptName; + + @JsonProperty("sign") + public String signName; + + @JsonProperty("decrypt") + public String decryptName; + + @JsonProperty("verify") + public String verifyName; + } + + public static class KeyData { + public String material; + public String algorithm; + public String encoding; + + @JsonProperty("type") + public String keyType; + + public String keyId; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java new file mode 100644 index 0000000000..c19c5565b3 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/TestDelegatedKey.java @@ -0,0 +1,128 @@ +/* + * Copyright 2014-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 software.amazon.cryptools.dynamodbencryptionclientsdk2.testing; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.IvParameterSpec; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DelegatedKey; + +public class TestDelegatedKey implements DelegatedKey { + private static final long serialVersionUID = 1L; + + private final Key realKey; + + public TestDelegatedKey(Key key) { + this.realKey = key; + } + + @Override + public String getAlgorithm() { + return "DELEGATED:" + realKey.getAlgorithm(); + } + + @Override + public byte[] getEncoded() { + return realKey.getEncoded(); + } + + @Override + public String getFormat() { + return realKey.getFormat(); + } + + @Override + public byte[] encrypt(byte[] plainText, byte[] additionalAssociatedData, String algorithm) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, + NoSuchPaddingException { + Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); + cipher.init(Cipher.ENCRYPT_MODE, realKey); + byte[] iv = cipher.getIV(); + byte[] result = new byte[cipher.getOutputSize(plainText.length) + iv.length + 1]; + result[0] = (byte) iv.length; + System.arraycopy(iv, 0, result, 1, iv.length); + try { + cipher.doFinal(plainText, 0, plainText.length, result, iv.length + 1); + } catch (ShortBufferException e) { + throw new RuntimeException(e); + } + return result; + } + + @Override + public byte[] decrypt(byte[] cipherText, byte[] additionalAssociatedData, String algorithm) + throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, + NoSuchPaddingException, InvalidAlgorithmParameterException { + final byte ivLength = cipherText[0]; + IvParameterSpec iv = new IvParameterSpec(cipherText, 1, ivLength); + Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); + cipher.init(Cipher.DECRYPT_MODE, realKey, iv); + return cipher.doFinal(cipherText, ivLength + 1, cipherText.length - ivLength - 1); + } + + @Override + public byte[] wrap(Key key, byte[] additionalAssociatedData, String algorithm) throws InvalidKeyException, + NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException { + Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); + cipher.init(Cipher.WRAP_MODE, realKey); + return cipher.wrap(key); + } + + @Override + public Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType, + byte[] additionalAssociatedData, String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException, + InvalidKeyException { + Cipher cipher = Cipher.getInstance(extractAlgorithm(algorithm)); + cipher.init(Cipher.UNWRAP_MODE, realKey); + return cipher.unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType); + } + + @Override + public byte[] sign(byte[] dataToSign, String algorithm) throws NoSuchAlgorithmException, InvalidKeyException { + Mac mac = Mac.getInstance(extractAlgorithm(algorithm)); + mac.init(realKey); + return mac.doFinal(dataToSign); + } + + @Override + public boolean verify(byte[] dataToSign, byte[] signature, String algorithm) { + try { + byte[] expected = sign(dataToSign, extractAlgorithm(algorithm)); + return MessageDigest.isEqual(expected, signature); + } catch (GeneralSecurityException ex) { + return false; + } + } + + private String extractAlgorithm(String alg) { + if (alg.startsWith(getAlgorithm())) { + return alg.substring(10); + } else { + return alg; + } + } +} From 75255c0422d96704cb0030595b3b657995f7d6db Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 14:20:24 -0800 Subject: [PATCH 12/33] m --- DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json diff --git a/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json b/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json deleted file mode 100644 index b041173a8c..0000000000 --- a/DynamoDbEncryption/runtimes/java/dynamodb-local-metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"installationId":"64477908-a49f-45a3-b4b3-e44905cf8ec6","telemetryEnabled":"true"} \ No newline at end of file From 294abefba070a6930a32b90d650e4ce2cd375b3e Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 15:00:45 -0800 Subject: [PATCH 13/33] m --- .../dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java index fe293a0d02..561c9204cb 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/testing/LocalDynamoDb.java @@ -73,7 +73,7 @@ public DynamoDbClient createClient() { // The region is meaningless for local DynamoDb but required for client builder validation .region(Region.US_EAST_1) .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create("dummy-key", "dummy-secret"))) + AwsBasicCredentials.create("dummykey", "dummysecret"))) .build(); } From 5a57429e22f4d91a9e979758ad22bd119cbb5423 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Tue, 3 Feb 2026 13:14:54 -0800 Subject: [PATCH 14/33] adapter --- .../legacy/InternalLegacyOverride.java | 108 +++++++++++------- .../legacy/LegacyEncryptorAdapter.java | 18 +++ .../legacy/V1EncryptorAdapter.java | 50 ++++++++ .../legacy/V2EncryptorAdapter.java | 43 +++++++ ...bEncryptor.java => DynamoDBEncryptor.java} | 15 +-- .../encryption/EncryptionContext.java | 2 +- .../encryption/providers/store/MetaStore.java | 12 +- .../utils/EncryptionContextOperators.java | 2 +- .../HolisticIT.java | 12 +- .../encryption/DelegatedEncryptionTest.java | 10 +- .../DelegatedEnvelopeEncryptionTest.java | 10 +- .../encryption/DynamoDbEncryptorTest.java | 30 ++--- .../CachingMostRecentProviderTests.java | 4 +- .../providers/store/MetaStoreTests.java | 6 +- 14 files changed, 227 insertions(+), 95 deletions(-) create mode 100644 DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java create mode 100644 DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java rename DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/{DynamoDbEncryptor.java => DynamoDBEncryptor.java} (98%) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index b7ee420dcc..d399976fe1 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -34,33 +34,27 @@ public class InternalLegacyOverride extends _ExternBase_InternalLegacyOverride { - private DynamoDBEncryptor encryptor; - private Map> actions; - private EncryptionContext encryptionContext; - private LegacyPolicy _policy; - private DafnySequence materialDescriptionFieldName; - private DafnySequence signatureFieldName; + private final LegacyEncryptorAdapter _adapter; + private final LegacyPolicy _policy; + private final DafnySequence materialDescriptionFieldNameDafnyType; + private final DafnySequence signatureFieldNameDafnyType; private InternalLegacyOverride( - DynamoDBEncryptor encryptor, - Map> actions, - EncryptionContext encryptionContext, + LegacyEncryptorAdapter adapter, LegacyPolicy policy ) { - this.encryptor = encryptor; - this.actions = actions; - this.encryptionContext = encryptionContext; + this._adapter = adapter; this._policy = policy; // It is possible that these values // have been customized by the customer. - this.materialDescriptionFieldName = - software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - encryptor.getMaterialDescriptionFieldName() - ); - this.signatureFieldName = - software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - encryptor.getSignatureFieldName() - ); + this.materialDescriptionFieldNameDafnyType = + software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( + adapter.getMaterialDescriptionFieldName() + ); + this.signatureFieldNameDafnyType = + software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( + adapter.getSignatureFieldName() + ); } public static TypeDescriptor _typeDescriptor() { @@ -78,8 +72,8 @@ public boolean IsLegacyInput( //# attributes for the material description and the signature. return ( input.is_DecryptItemInput() && - input._encryptedItem.contains(materialDescriptionFieldName) && - input._encryptedItem.contains(signatureFieldName) + input._encryptedItem.contains(materialDescriptionFieldNameDafnyType) && + input._encryptedItem.contains(signatureFieldNameDafnyType) ); } @@ -109,19 +103,12 @@ > EncryptItem( .EncryptItemInput(input) .plaintextItem(); - final Map< - String, - com.amazonaws.services.dynamodbv2.model.AttributeValue - > encryptedItem = encryptor.encryptRecord( - V2MapToV1Map(plaintextItem), - actions, - encryptionContext - ); + Map encryptedItem = _adapter.encryptRecord(plaintextItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput .builder() - .encryptedItem(V1MapToV2Map(encryptedItem)) + .encryptedItem(encryptedItem) .build(); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.EncryptItemOutput dafnyOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.ToDafny.EncryptItemOutput( @@ -162,19 +149,12 @@ > DecryptItem( .DecryptItemInput(input) .encryptedItem(); - final Map< - String, - com.amazonaws.services.dynamodbv2.model.AttributeValue - > plaintextItem = encryptor.decryptRecord( - V2MapToV1Map(encryptedItem), - actions, - encryptionContext - ); + Map plaintextItem = _adapter.decryptRecord(encryptedItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput .builder() - .plaintextItem(V1MapToV2Map(plaintextItem)) + .plaintextItem(plaintextItem) .build(); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.DecryptItemOutput dafnyOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.ToDafny.DecryptItemOutput( @@ -224,11 +204,26 @@ public static Result, Error> Build( return CreateBuildFailure(maybeEncryptionContext.error()); } + final LegacyEncryptorAdapter adapter; + if (maybeEncryptor instanceof DynamoDBEncryptor) { + adapter = new V1EncryptorAdapter( + (DynamoDBEncryptor) maybeEncryptor, + maybeActions.value(), + maybeEncryptionContext.value() + ); + } else if (maybeEncryptor instanceof software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor) { + adapter = new V2EncryptorAdapter( + (software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor) maybeEncryptor, + convertActionsV1ToV2(maybeActions.value()), + convertEncryptionContextV1ToV2(maybeEncryptionContext.value()) + ); + } else { + return CreateBuildFailure(createError("Unsupported encryptor type")); + } + final InternalLegacyOverride internalLegacyOverride = new InternalLegacyOverride( - (DynamoDBEncryptor) maybeEncryptor, - maybeActions.value(), - maybeEncryptionContext.value(), + adapter, legacyOverride.dtor_policy() ); @@ -250,7 +245,32 @@ public static Error createError(String message) { public static boolean isDynamoDBEncryptor( software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor maybe ) { - return maybe instanceof DynamoDBEncryptor; + return maybe instanceof DynamoDBEncryptor || + maybe instanceof software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; + } + + // Convert SDK V1 EncryptionFlags to SDK V2 + private static Map> + convertActionsV1ToV2(Map> v1Actions) { + Map> v2Actions = new HashMap<>(); + for (Map.Entry> entry : v1Actions.entrySet()) { + Set v2Flags = new HashSet<>(); + for (EncryptionFlags v1Flag : entry.getValue()) { + v2Flags.add(software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags.valueOf(v1Flag.name())); + } + v2Actions.put(entry.getKey(), v2Flags); + } + return v2Actions; + } + + // Convert SDK V1 EncryptionContext to SDK V2 + private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext + convertEncryptionContextV1ToV2(EncryptionContext v1Context) { + return software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.builder() + .tableName(v1Context.getTableName()) + .hashKeyName(v1Context.getHashKeyName()) + .rangeKeyName(v1Context.getRangeKeyName()) + .build(); } public static String ToNativeString(DafnySequence s) { diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java new file mode 100644 index 0000000000..93a6cd6b5a --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java @@ -0,0 +1,18 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; + +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.security.GeneralSecurityException; +import java.util.Map; + +public interface LegacyEncryptorAdapter { + + Map + encryptRecord(Map item) throws GeneralSecurityException; + + Map + decryptRecord(Map item) throws GeneralSecurityException; + + String getMaterialDescriptionFieldName(); + String getSignatureFieldName(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java new file mode 100644 index 0000000000..1231caaed5 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java @@ -0,0 +1,50 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; + +import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor; +import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext; +import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionFlags; + +import java.security.GeneralSecurityException; +import java.util.Map; +import java.util.Set; + +import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V1MapToV2Map; +import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V2MapToV1Map; + +public class V1EncryptorAdapter implements LegacyEncryptorAdapter { + private final DynamoDBEncryptor encryptor; + private final Map> actions; + private final EncryptionContext encryptionContext; + + V1EncryptorAdapter(DynamoDBEncryptor encryptor, Map> actions,EncryptionContext encryptionContext) { + this.encryptor = encryptor; + this.actions = actions; + this.encryptionContext = encryptionContext; + } + + @Override + public Map + encryptRecord(Map item) throws GeneralSecurityException { + return V1MapToV2Map( + encryptor.encryptRecord(V2MapToV1Map(item), actions, encryptionContext) + ); + } + + @Override + public Map + decryptRecord(Map item) throws GeneralSecurityException { + return V1MapToV2Map( + encryptor.decryptRecord(V2MapToV1Map(item), actions, encryptionContext) + ); + } + + @Override + public String getMaterialDescriptionFieldName() { + return encryptor.getMaterialDescriptionFieldName(); + } + + @Override + public String getSignatureFieldName() { + return encryptor.getSignatureFieldName(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java new file mode 100644 index 0000000000..effa15e4d5 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java @@ -0,0 +1,43 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; + +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; + +import java.security.GeneralSecurityException; +import java.util.Map; +import java.util.Set; + +public class V2EncryptorAdapter implements LegacyEncryptorAdapter { + private final DynamoDBEncryptor encryptor; + private final Map> actions; + private final EncryptionContext encryptionContext; + + V2EncryptorAdapter(DynamoDBEncryptor encryptor, Map> actions, EncryptionContext encryptionContext) { + this.encryptor = encryptor; + this.actions = actions; + this.encryptionContext = encryptionContext; + } + + @Override + public Map + encryptRecord(Map item) throws GeneralSecurityException { + return encryptor.encryptRecord(item, actions, encryptionContext); + } + + @Override + public Map + decryptRecord(Map item) throws GeneralSecurityException { + return encryptor.decryptRecord(item, actions, encryptionContext); + } + + @Override + public String getMaterialDescriptionFieldName() { + return encryptor.getMaterialDescriptionFieldName(); + } + + @Override + public String getSignatureFieldName() { + return encryptor.getSignatureFieldName(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDBEncryptor.java similarity index 98% rename from DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java rename to DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDBEncryptor.java index 95e6ec73c7..b52494e76e 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDBEncryptor.java @@ -34,6 +34,7 @@ import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; @@ -48,7 +49,7 @@ * * @author Greg Rubin */ -public class DynamoDbEncryptor { +public class DynamoDBEncryptor implements ILegacyDynamoDbEncryptor { private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA"; private static final String DEFAULT_METADATA_FIELD = "*amzn-ddb-map-desc*"; private static final String DEFAULT_SIGNATURE_FIELD = "*amzn-ddb-map-sig*"; @@ -79,19 +80,19 @@ public class DynamoDbEncryptor { private Function encryptionContextOverrideOperator; - protected DynamoDbEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { + protected DynamoDBEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { this.encryptionMaterialsProvider = provider; this.descriptionBase = descriptionBase; symmetricEncryptionModeHeader = this.descriptionBase + "sym-mode"; signingAlgorithmHeader = this.descriptionBase + "signingAlg"; } - public static DynamoDbEncryptor getInstance( + public static DynamoDBEncryptor getInstance( EncryptionMaterialsProvider provider, String descriptionbase) { - return new DynamoDbEncryptor(provider, descriptionbase); + return new DynamoDBEncryptor(provider, descriptionbase); } - public static DynamoDbEncryptor getInstance(EncryptionMaterialsProvider provider) { + public static DynamoDBEncryptor getInstance(EncryptionMaterialsProvider provider) { return getInstance(provider, DEFAULT_DESCRIPTION_BASE); } @@ -454,7 +455,7 @@ private void actualEncryption(Map itemAttributes, * * @return the name of the DynamoDB field used to store the signature */ - String getSignatureFieldName() { + public String getSignatureFieldName() { return signatureFieldName; } @@ -474,7 +475,7 @@ void setSignatureFieldName(final String signatureFieldName) { * @return the name of the DynamoDB field used to store metadata used by the * DynamoDBEncryptedMapper */ - String getMaterialDescriptionFieldName() { + public String getMaterialDescriptionFieldName() { return materialDescriptionFieldName; } diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java index 9a78ad9b04..f0ae27e26c 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java @@ -83,7 +83,7 @@ public Map getAttributeValues() { /** * This object has no meaning (and will not be set or examined) by any core libraries. * It exists to allow custom object mappers and data access layers to pass - * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDbEncryptor}. + * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDBEncryptor}. */ public Object getDeveloperContext() { return developerContext; diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java index c0fbe5e06f..9744061094 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java @@ -44,7 +44,7 @@ import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; import software.amazon.awssdk.services.dynamodb.model.QueryRequest; import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; @@ -95,7 +95,7 @@ public class MetaStore extends ProviderStore { // private final DynamoDbEncryptionConfiguration encryptionConfiguration; private final String tableName; private final DynamoDbClient ddb; - private final DynamoDbEncryptor encryptor; + private final DynamoDBEncryptor encryptor; private final EncryptionContext ddbCtx; private final ExtraDataSupplier extraDataSupplier; @@ -129,7 +129,7 @@ public interface ExtraDataSupplier { * @param encryptor used to perform crypto operations on the record attributes. */ public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor) { + final DynamoDBEncryptor encryptor) { this(ddb, tableName, encryptor, EMPTY_EXTRA_DATA_SUPPLIER); } @@ -142,7 +142,7 @@ public MetaStore(final DynamoDbClient ddb, final String tableName, * @param extraDataSupplier provides extra data that should be stored along with the material. */ public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { + final DynamoDBEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { this.ddb = checkNotNull(ddb, "ddb must not be null"); this.tableName = checkNotNull(tableName, "tableName must not be null"); this.encryptor = checkNotNull(encryptor, "encryptor must not be null"); @@ -389,7 +389,7 @@ private EncryptionMaterialsProvider decryptProvider(final Map getPlainText(final Map encryptedRecord; Map> actions; EncryptionContext encryptionContext = @@ -690,7 +690,7 @@ private Map getItems(Map map, St private void assertVersionCompatibility(EncryptionMaterialsProvider provider, String tableName) throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(provider); Map response; Map decryptedRecord; EncryptionContext encryptionContext = @@ -805,7 +805,7 @@ private void assertVersionCompatibility(EncryptionMaterialsProvider provider, St private void assertVersionCompatibility_2(EncryptionMaterialsProvider provider, String tableName) throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(provider); Map response; Map decryptedRecord; EncryptionContext encryptionContext = diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java index fd3bf37ace..b2f76bd374 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java @@ -54,7 +54,7 @@ public class DelegatedEncryptionTest { private static DelegatedKey macKey; private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; + private DynamoDBEncryptor encryptor; private Map attribs; private EncryptionContext context; @@ -71,7 +71,7 @@ public static void setupClass() { public void setUp() { prov = new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); attribs = new HashMap<>(); attribs.put("intValue", AttributeValue.builder().n("123").build()); @@ -189,7 +189,7 @@ public void signedOnly() throws GeneralSecurityException { @Test public void signedOnlyNullCryptoKey() throws GeneralSecurityException { prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); @@ -234,7 +234,7 @@ public void RsaSignedOnly() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-" @@ -262,7 +262,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-" diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java index ce22c396fa..c052c6d1a8 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java @@ -55,7 +55,7 @@ public class DelegatedEnvelopeEncryptionTest { private static DelegatedKey macKey; private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; + private DynamoDBEncryptor encryptor; private Map attribs; private EncryptionContext context; @@ -73,7 +73,7 @@ public void setUp() throws Exception { prov = new WrappedMaterialsProvider( encryptionKey, encryptionKey, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); attribs = new HashMap(); attribs.put("intValue", AttributeValue.builder().n("123").build()); @@ -174,7 +174,7 @@ public void badVersionNumber() throws GeneralSecurityException { @Test public void signedOnlyNullCryptoKey() throws GeneralSecurityException { prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); @@ -218,7 +218,7 @@ public void RsaSignedOnly() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); @@ -246,7 +246,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java index 87fb8353bb..b332e9c2d6 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java @@ -64,7 +64,7 @@ public class DynamoDbEncryptorTest { private static SecretKey macKey; private InstrumentedEncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; + private DynamoDBEncryptor encryptor; private Map attribs; private EncryptionContext context; private static final String OVERRIDDEN_TABLE_NAME = "TheBestTableName"; @@ -85,7 +85,7 @@ public void setUp() { prov = new InstrumentedEncryptionMaterialsProvider( new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "enryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "enryptor-"); attribs = new HashMap<>(); attribs.put("intValue", AttributeValue.builder().n("123").build()); @@ -255,7 +255,7 @@ public void signedOnlyNullCryptoKey() throws GeneralSecurityException { prov = new InstrumentedEncryptionMaterialsProvider( new SymmetricStaticProvider(null, macKey, Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); @@ -299,7 +299,7 @@ public void RsaSignedOnly() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); @@ -327,7 +327,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); @@ -347,7 +347,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { */ @Test public void testNullEncryptionContextOperator() throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator(null); encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); } @@ -359,7 +359,7 @@ public void testNullEncryptionContextOperator() throws GeneralSecurityException public void testTableNameOverriddenEncryptionContextOperator() throws GeneralSecurityException { // Ensure that the table name is different from what we override the table to. assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator( overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); Map encryptedItems = @@ -378,8 +378,8 @@ public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor( throws GeneralSecurityException { // Ensure that the table name is different from what we override the table to. assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); + DynamoDBEncryptor encryptorWithoutOverride = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator( overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); Map encryptedItems = @@ -402,8 +402,8 @@ public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor( throws GeneralSecurityException { // Ensure that the table name is different from what we override the table to. assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); + DynamoDBEncryptor encryptorWithoutOverride = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator( overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); Map encryptedItems = @@ -418,7 +418,7 @@ public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor( @Test public void EcdsaSignedOnly() throws GeneralSecurityException { - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + encryptor = DynamoDBEncryptor.getInstance(getMaterialProviderwithECDSA()); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); @@ -440,7 +440,7 @@ public void EcdsaSignedOnly() throws GeneralSecurityException { @Test(expectedExceptions = SignatureException.class) public void EcdsaSignedOnlyBadSignature() throws GeneralSecurityException { - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + encryptor = DynamoDBEncryptor.getInstance(getMaterialProviderwithECDSA()); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); @@ -500,7 +500,7 @@ public void testDecryptWithPlainTextItemAndAdditionNewAttributeHavingEncryptionF private void assertToByteArray( final String msg, final byte[] expected, final ByteBuffer testValue) throws ReflectiveOperationException { - Method m = DynamoDbEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); + Method m = DynamoDBEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); m.setAccessible(true); int oldPosition = testValue.position(); @@ -537,7 +537,7 @@ private EncryptionMaterialsProvider getMaterialProviderwithECDSA() g.initialize(ecSpec, Utils.getRng()); KeyPair keypair = g.generateKeyPair(); Map description = new HashMap<>(); - description.put(DynamoDbEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); + description.put(DynamoDBEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); return new SymmetricStaticProvider(null, keypair, description); } diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java index f286648332..e3b4078bc8 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java @@ -8,7 +8,7 @@ import static org.testng.AssertJUnit.assertTrue; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; @@ -40,7 +40,7 @@ public class CachingMostRecentProviderTests { new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); + private static final DynamoDBEncryptor ENCRYPTOR = DynamoDBEncryptor.getInstance(BASE_PROVIDER); private DynamoDbClient client; private Map methodCalls; diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java index 3449908a6d..f98f807fda 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java @@ -28,7 +28,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; @@ -56,8 +56,8 @@ public class MetaStoreTests { 2, 4, 6, 8, 10, 12, 14 }, "HmacSHA256"); private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); private static final EncryptionMaterialsProvider TARGET_BASE_PROVIDER = new SymmetricStaticProvider(TARGET_AES_KEY, TARGET_HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); - private static final DynamoDbEncryptor TARGET_ENCRYPTOR = DynamoDbEncryptor.getInstance(TARGET_BASE_PROVIDER); + private static final DynamoDBEncryptor ENCRYPTOR = DynamoDBEncryptor.getInstance(BASE_PROVIDER); + private static final DynamoDBEncryptor TARGET_ENCRYPTOR = DynamoDBEncryptor.getInstance(TARGET_BASE_PROVIDER); private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); private final LocalDynamoDb targetLocalDynamoDb = new LocalDynamoDb(); From 018c92be78ba06ce22405a022a745c218bc26a0a Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Tue, 3 Feb 2026 13:43:39 -0800 Subject: [PATCH 15/33] m --- .../internaldafny/legacy/InternalLegacyOverride.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index d399976fe1..6b8070f7e0 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -34,7 +34,7 @@ public class InternalLegacyOverride extends _ExternBase_InternalLegacyOverride { - private final LegacyEncryptorAdapter _adapter; + private final LegacyEncryptorAdapter _encryptorAdapter; private final LegacyPolicy _policy; private final DafnySequence materialDescriptionFieldNameDafnyType; private final DafnySequence signatureFieldNameDafnyType; @@ -43,7 +43,7 @@ private InternalLegacyOverride( LegacyEncryptorAdapter adapter, LegacyPolicy policy ) { - this._adapter = adapter; + this._encryptorAdapter = adapter; this._policy = policy; // It is possible that these values // have been customized by the customer. @@ -103,7 +103,7 @@ > EncryptItem( .EncryptItemInput(input) .plaintextItem(); - Map encryptedItem = _adapter.encryptRecord(plaintextItem); + Map encryptedItem = _encryptorAdapter.encryptRecord(plaintextItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput @@ -149,7 +149,7 @@ > DecryptItem( .DecryptItemInput(input) .encryptedItem(); - Map plaintextItem = _adapter.decryptRecord(encryptedItem); + Map plaintextItem = _encryptorAdapter.decryptRecord(encryptedItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput From eef8a56296d4ffef2adb577e54a8607bea489f55 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Tue, 3 Feb 2026 13:46:30 -0800 Subject: [PATCH 16/33] formatting --- .../legacy/InternalLegacyOverride.java | 94 +++++++++++------- .../legacy/LegacyEncryptorAdapter.java | 27 ++++-- .../legacy/V1EncryptorAdapter.java | 96 +++++++++++-------- .../legacy/V2EncryptorAdapter.java | 88 ++++++++++------- 4 files changed, 189 insertions(+), 116 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index 6b8070f7e0..059d6581d9 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -48,13 +48,13 @@ private InternalLegacyOverride( // It is possible that these values // have been customized by the customer. this.materialDescriptionFieldNameDafnyType = - software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - adapter.getMaterialDescriptionFieldName() - ); + software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( + adapter.getMaterialDescriptionFieldName() + ); this.signatureFieldNameDafnyType = - software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - adapter.getSignatureFieldName() - ); + software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( + adapter.getSignatureFieldName() + ); } public static TypeDescriptor _typeDescriptor() { @@ -103,7 +103,10 @@ > EncryptItem( .EncryptItemInput(input) .plaintextItem(); - Map encryptedItem = _encryptorAdapter.encryptRecord(plaintextItem); + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > encryptedItem = _encryptorAdapter.encryptRecord(plaintextItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput @@ -149,7 +152,10 @@ > DecryptItem( .DecryptItemInput(input) .encryptedItem(); - Map plaintextItem = _encryptorAdapter.decryptRecord(encryptedItem); + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > plaintextItem = _encryptorAdapter.decryptRecord(encryptedItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput @@ -206,26 +212,28 @@ public static Result, Error> Build( final LegacyEncryptorAdapter adapter; if (maybeEncryptor instanceof DynamoDBEncryptor) { - adapter = new V1EncryptorAdapter( - (DynamoDBEncryptor) maybeEncryptor, - maybeActions.value(), - maybeEncryptionContext.value() - ); - } else if (maybeEncryptor instanceof software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor) { - adapter = new V2EncryptorAdapter( - (software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor) maybeEncryptor, - convertActionsV1ToV2(maybeActions.value()), - convertEncryptionContextV1ToV2(maybeEncryptionContext.value()) - ); + adapter = + new V1EncryptorAdapter( + (DynamoDBEncryptor) maybeEncryptor, + maybeActions.value(), + maybeEncryptionContext.value() + ); + } else if ( + maybeEncryptor instanceof + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor + ) { + adapter = + new V2EncryptorAdapter( + (software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor) maybeEncryptor, + convertActionsV1ToV2(maybeActions.value()), + convertEncryptionContextV1ToV2(maybeEncryptionContext.value()) + ); } else { return CreateBuildFailure(createError("Unsupported encryptor type")); } final InternalLegacyOverride internalLegacyOverride = - new InternalLegacyOverride( - adapter, - legacyOverride.dtor_policy() - ); + new InternalLegacyOverride(adapter, legacyOverride.dtor_policy()); return CreateBuildSuccess( CreateInternalLegacyOverrideSome(internalLegacyOverride) @@ -245,18 +253,36 @@ public static Error createError(String message) { public static boolean isDynamoDBEncryptor( software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor maybe ) { - return maybe instanceof DynamoDBEncryptor || - maybe instanceof software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; + return ( + maybe instanceof DynamoDBEncryptor || + maybe instanceof + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor + ); } // Convert SDK V1 EncryptionFlags to SDK V2 - private static Map> - convertActionsV1ToV2(Map> v1Actions) { - Map> v2Actions = new HashMap<>(); + private static Map< + String, + Set< + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags + > + > convertActionsV1ToV2(Map> v1Actions) { + Map< + String, + Set< + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags + > + > v2Actions = new HashMap<>(); for (Map.Entry> entry : v1Actions.entrySet()) { - Set v2Flags = new HashSet<>(); + Set< + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags + > v2Flags = new HashSet<>(); for (EncryptionFlags v1Flag : entry.getValue()) { - v2Flags.add(software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags.valueOf(v1Flag.name())); + v2Flags.add( + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags.valueOf( + v1Flag.name() + ) + ); } v2Actions.put(entry.getKey(), v2Flags); } @@ -264,9 +290,11 @@ public static boolean isDynamoDBEncryptor( } // Convert SDK V1 EncryptionContext to SDK V2 - private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext - convertEncryptionContextV1ToV2(EncryptionContext v1Context) { - return software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.builder() + private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext convertEncryptionContextV1ToV2( + EncryptionContext v1Context + ) { + return software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext + .builder() .tableName(v1Context.getTableName()) .hashKeyName(v1Context.getHashKeyName()) .rangeKeyName(v1Context.getRangeKeyName()) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java index 93a6cd6b5a..284f8d675f 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java @@ -1,18 +1,27 @@ package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; -import software.amazon.awssdk.services.dynamodb.model.AttributeValue; - import java.security.GeneralSecurityException; import java.util.Map; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; public interface LegacyEncryptorAdapter { + Map encryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException; - Map - encryptRecord(Map item) throws GeneralSecurityException; - - Map - decryptRecord(Map item) throws GeneralSecurityException; + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > decryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException; - String getMaterialDescriptionFieldName(); - String getSignatureFieldName(); + String getMaterialDescriptionFieldName(); + String getSignatureFieldName(); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java index 1231caaed5..f563281495 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java @@ -1,50 +1,68 @@ package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; +import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V1MapToV2Map; +import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V2MapToV1Map; + import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor; import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext; import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionFlags; - import java.security.GeneralSecurityException; import java.util.Map; import java.util.Set; -import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V1MapToV2Map; -import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V2MapToV1Map; - public class V1EncryptorAdapter implements LegacyEncryptorAdapter { - private final DynamoDBEncryptor encryptor; - private final Map> actions; - private final EncryptionContext encryptionContext; - - V1EncryptorAdapter(DynamoDBEncryptor encryptor, Map> actions,EncryptionContext encryptionContext) { - this.encryptor = encryptor; - this.actions = actions; - this.encryptionContext = encryptionContext; - } - - @Override - public Map - encryptRecord(Map item) throws GeneralSecurityException { - return V1MapToV2Map( - encryptor.encryptRecord(V2MapToV1Map(item), actions, encryptionContext) - ); - } - - @Override - public Map - decryptRecord(Map item) throws GeneralSecurityException { - return V1MapToV2Map( - encryptor.decryptRecord(V2MapToV1Map(item), actions, encryptionContext) - ); - } - - @Override - public String getMaterialDescriptionFieldName() { - return encryptor.getMaterialDescriptionFieldName(); - } - - @Override - public String getSignatureFieldName() { - return encryptor.getSignatureFieldName(); - } + + private final DynamoDBEncryptor encryptor; + private final Map> actions; + private final EncryptionContext encryptionContext; + + V1EncryptorAdapter( + DynamoDBEncryptor encryptor, + Map> actions, + EncryptionContext encryptionContext + ) { + this.encryptor = encryptor; + this.actions = actions; + this.encryptionContext = encryptionContext; + } + + @Override + public Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > encryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException { + return V1MapToV2Map( + encryptor.encryptRecord(V2MapToV1Map(item), actions, encryptionContext) + ); + } + + @Override + public Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > decryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException { + return V1MapToV2Map( + encryptor.decryptRecord(V2MapToV1Map(item), actions, encryptionContext) + ); + } + + @Override + public String getMaterialDescriptionFieldName() { + return encryptor.getMaterialDescriptionFieldName(); + } + + @Override + public String getSignatureFieldName() { + return encryptor.getSignatureFieldName(); + } } diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java index effa15e4d5..41622bd9f4 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java @@ -1,43 +1,61 @@ package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; - import java.security.GeneralSecurityException; import java.util.Map; import java.util.Set; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; public class V2EncryptorAdapter implements LegacyEncryptorAdapter { - private final DynamoDBEncryptor encryptor; - private final Map> actions; - private final EncryptionContext encryptionContext; - - V2EncryptorAdapter(DynamoDBEncryptor encryptor, Map> actions, EncryptionContext encryptionContext) { - this.encryptor = encryptor; - this.actions = actions; - this.encryptionContext = encryptionContext; - } - - @Override - public Map - encryptRecord(Map item) throws GeneralSecurityException { - return encryptor.encryptRecord(item, actions, encryptionContext); - } - - @Override - public Map - decryptRecord(Map item) throws GeneralSecurityException { - return encryptor.decryptRecord(item, actions, encryptionContext); - } - - @Override - public String getMaterialDescriptionFieldName() { - return encryptor.getMaterialDescriptionFieldName(); - } - - @Override - public String getSignatureFieldName() { - return encryptor.getSignatureFieldName(); - } + + private final DynamoDBEncryptor encryptor; + private final Map> actions; + private final EncryptionContext encryptionContext; + + V2EncryptorAdapter( + DynamoDBEncryptor encryptor, + Map> actions, + EncryptionContext encryptionContext + ) { + this.encryptor = encryptor; + this.actions = actions; + this.encryptionContext = encryptionContext; + } + + @Override + public Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > encryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException { + return encryptor.encryptRecord(item, actions, encryptionContext); + } + + @Override + public Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > decryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException { + return encryptor.decryptRecord(item, actions, encryptionContext); + } + + @Override + public String getMaterialDescriptionFieldName() { + return encryptor.getMaterialDescriptionFieldName(); + } + + @Override + public String getSignatureFieldName() { + return encryptor.getSignatureFieldName(); + } } From ce476735a7bbccca6a3a923a50ae9acfa4b83924 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Tue, 3 Feb 2026 14:30:57 -0800 Subject: [PATCH 17/33] m --- .../legacy/InternalLegacyOverride.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index 059d6581d9..3a84222f3c 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -40,20 +40,20 @@ public class InternalLegacyOverride extends _ExternBase_InternalLegacyOverride { private final DafnySequence signatureFieldNameDafnyType; private InternalLegacyOverride( - LegacyEncryptorAdapter adapter, + LegacyEncryptorAdapter encryptorAdapter, LegacyPolicy policy ) { - this._encryptorAdapter = adapter; + this._encryptorAdapter = encryptorAdapter; this._policy = policy; // It is possible that these values // have been customized by the customer. this.materialDescriptionFieldNameDafnyType = software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - adapter.getMaterialDescriptionFieldName() + encryptorAdapter.getMaterialDescriptionFieldName() ); this.signatureFieldNameDafnyType = software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - adapter.getSignatureFieldName() + encryptorAdapter.getSignatureFieldName() ); } @@ -210,9 +210,9 @@ public static Result, Error> Build( return CreateBuildFailure(maybeEncryptionContext.error()); } - final LegacyEncryptorAdapter adapter; + final LegacyEncryptorAdapter encryptorAdapter; if (maybeEncryptor instanceof DynamoDBEncryptor) { - adapter = + encryptorAdapter = new V1EncryptorAdapter( (DynamoDBEncryptor) maybeEncryptor, maybeActions.value(), @@ -222,7 +222,7 @@ public static Result, Error> Build( maybeEncryptor instanceof software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor ) { - adapter = + encryptorAdapter = new V2EncryptorAdapter( (software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor) maybeEncryptor, convertActionsV1ToV2(maybeActions.value()), @@ -233,7 +233,7 @@ public static Result, Error> Build( } final InternalLegacyOverride internalLegacyOverride = - new InternalLegacyOverride(adapter, legacyOverride.dtor_policy()); + new InternalLegacyOverride(encryptorAdapter, legacyOverride.dtor_policy()); return CreateBuildSuccess( CreateInternalLegacyOverrideSome(internalLegacyOverride) From befb6bd9f2d4b17fd2b715dc896cd8144344b777 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Tue, 3 Feb 2026 16:52:01 -0800 Subject: [PATCH 18/33] m --- .../internaldafny/legacy/InternalLegacyOverride.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index 3a84222f3c..56ddfd07b6 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -229,7 +229,7 @@ public static Result, Error> Build( convertEncryptionContextV1ToV2(maybeEncryptionContext.value()) ); } else { - return CreateBuildFailure(createError("Unsupported encryptor type")); + return CreateBuildFailure(createError("Unsupported encryptor type: " + maybeEncryptor.getClass().getName())); } final InternalLegacyOverride internalLegacyOverride = @@ -298,6 +298,9 @@ private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption .tableName(v1Context.getTableName()) .hashKeyName(v1Context.getHashKeyName()) .rangeKeyName(v1Context.getRangeKeyName()) + .attributeValues(V1MapToV2Map(v1Context.getAttributeValues())) + .developerContext(v1Context.getDeveloperContext()) + .materialDescription(v1Context.getMaterialDescription()) .build(); } From 651d34364a0a7b1752233656015101d308755bd1 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Tue, 3 Feb 2026 17:08:29 -0800 Subject: [PATCH 19/33] m --- .../internaldafny/legacy/InternalLegacyOverride.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index 56ddfd07b6..6197b7c314 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -428,10 +428,10 @@ public static com.amazonaws.services.dynamodbv2.model.AttributeValue V2Attribute case SS: return attribute.withSS(value.ss()); case UNKNOWN_TO_SDK_VERSION: - throw new IllegalArgumentException("omfg"); + throw new IllegalArgumentException(("Unsupported AttributeValue type: UNKNOWN_TO_SDK_VERSION. This may indicate a newer DynamoDB attribute type that is not supported by this SDK version."); } - throw new IllegalArgumentException("omfg"); + throw new IllegalArgumentException("Unexpected AttributeValue type: " + value.type() + ". Unable to convert from SDK v2 to SDK v1 format."); } public static Map< From 4b157e2c61301a6a6d70da738a2edcbb5e372084 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 4 Feb 2026 09:17:39 -0800 Subject: [PATCH 20/33] m --- .../internaldafny/legacy/InternalLegacyOverride.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index 6197b7c314..11c46bf3ec 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -428,7 +428,7 @@ public static com.amazonaws.services.dynamodbv2.model.AttributeValue V2Attribute case SS: return attribute.withSS(value.ss()); case UNKNOWN_TO_SDK_VERSION: - throw new IllegalArgumentException(("Unsupported AttributeValue type: UNKNOWN_TO_SDK_VERSION. This may indicate a newer DynamoDB attribute type that is not supported by this SDK version."); + throw new IllegalArgumentException("Unsupported AttributeValue type: UNKNOWN_TO_SDK_VERSION. This may indicate a newer DynamoDB attribute type that is not supported by this SDK version."); } throw new IllegalArgumentException("Unexpected AttributeValue type: " + value.type() + ". Unable to convert from SDK v2 to SDK v1 format."); From 07d9e4dc5bb774d3ce4a0be9c86ef71309a12024 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 4 Feb 2026 10:09:58 -0800 Subject: [PATCH 21/33] m --- .../internaldafny/legacy/InternalLegacyOverride.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index 11c46bf3ec..ae2dafc75e 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -103,7 +103,7 @@ > EncryptItem( .EncryptItemInput(input) .plaintextItem(); - Map< + final Map< String, software.amazon.awssdk.services.dynamodb.model.AttributeValue > encryptedItem = _encryptorAdapter.encryptRecord(plaintextItem); From 3298a2d0dee6e74304984fb13a8fa9e35b9bc2ee Mon Sep 17 00:00:00 2001 From: Rishav karanjit Date: Wed, 4 Feb 2026 15:00:29 -0800 Subject: [PATCH 22/33] Update permissions in pull request workflow Added permissions for contents and id-token in PR CI workflow. --- .github/workflows/pull.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml index 3f237ee0d0..7038076c45 100644 --- a/.github/workflows/pull.yml +++ b/.github/workflows/pull.yml @@ -1,6 +1,10 @@ # This workflow runs for every pull request name: PR CI +permissions: + contents: read + id-token: write + on: pull_request: From e319647e9ea3e1bcf36ede38c80ca05960f87f11 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 4 Feb 2026 22:54:40 -0800 Subject: [PATCH 23/33] m --- .../legacy/InternalLegacyOverride.java | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index ae2dafc75e..74ce475374 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -291,17 +291,23 @@ > convertActionsV1ToV2(Map> v1Actions) { // Convert SDK V1 EncryptionContext to SDK V2 private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext convertEncryptionContextV1ToV2( - EncryptionContext v1Context + final EncryptionContext v1Context ) { - return software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext - .builder() - .tableName(v1Context.getTableName()) - .hashKeyName(v1Context.getHashKeyName()) - .rangeKeyName(v1Context.getRangeKeyName()) - .attributeValues(V1MapToV2Map(v1Context.getAttributeValues())) - .developerContext(v1Context.getDeveloperContext()) - .materialDescription(v1Context.getMaterialDescription()) - .build(); + + final software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.Builder builder = software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext + .builder() + .tableName(v1Context.getTableName()) + .hashKeyName(v1Context.getHashKeyName()) + .rangeKeyName(v1Context.getRangeKeyName()) + .developerContext(v1Context.getDeveloperContext()); + + if (v1Context.getMaterialDescription() != null) { + builder.materialDescription(v1Context.getMaterialDescription()); + } + if (v1Context.getAttributeValues() != null) { + builder.attributeValues(V1MapToV2Map(v1Context.getAttributeValues())); + } + return builder.build(); } public static String ToNativeString(DafnySequence s) { @@ -443,6 +449,9 @@ > V2MapToV1Map( software.amazon.awssdk.services.dynamodb.model.AttributeValue > input ) { + if (input == null) { + return null; + } return input .entrySet() .stream() @@ -510,6 +519,9 @@ public static software.amazon.awssdk.services.dynamodb.model.AttributeValue V1At > V1MapToV2Map( Map input ) { + if (input == null) { + return null; + } return input .entrySet() .stream() From bfb73dced6e579757d8f45ad964fdb26eefbabf6 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 4 Feb 2026 15:28:32 -0800 Subject: [PATCH 24/33] m --- .github/workflows/ci_examples_java.yml | 1 + .../providers/DirectKmsMaterialsProvider.java | 13 +- .../awsdbe/MigrationExampleStep3.java | 10 +- .../Migration/DDBECv2ToAWSDBE/.gitattributes | 9 + .../java/Migration/DDBECv2ToAWSDBE/.gitignore | 5 + .../DDBECv2ToAWSDBE/build.gradle.kts | 121 +++++++++ .../java/Migration/DDBECv2ToAWSDBE/gradlew | 244 ++++++++++++++++++ .../Migration/DDBECv2ToAWSDBE/gradlew.bat | 92 +++++++ .../DDBECv2ToAWSDBE/settings.gradle.kts | 10 + .../awsdbe/MigrationExampleStep1.java | 197 ++++++++++++++ .../awsdbe/MigrationExampleStep2.java | 176 +++++++++++++ .../awsdbe/MigrationExampleStep3.java | 148 +++++++++++ .../migration/awsdbe/SimpleClass.java | 70 +++++ .../ddbec/MigrationExampleStep0.java | 137 ++++++++++ .../awsdbe/TestMigrationExampleStep1.java | 57 ++++ .../awsdbe/TestMigrationExampleStep2.java | 57 ++++ .../awsdbe/TestMigrationExampleStep3.java | 70 +++++ .../examples/migration/awsdbe/TestUtils.java | 12 + .../ddbec/TestMigrationExampleStep0.java | 72 ++++++ .../examples/migration/ddbec/TestUtils.java | 12 + 20 files changed, 1507 insertions(+), 6 deletions(-) create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitattributes create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitignore create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/build.gradle.kts create mode 100755 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew.bat create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/settings.gradle.kts create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/SimpleClass.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestUtils.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestUtils.java diff --git a/.github/workflows/ci_examples_java.yml b/.github/workflows/ci_examples_java.yml index c5efc2f03c..7da10401cd 100644 --- a/.github/workflows/ci_examples_java.yml +++ b/.github/workflows/ci_examples_java.yml @@ -106,3 +106,4 @@ jobs: # Run migration examples gradle -p runtimes/java/Migration/PlaintextToAWSDBE test gradle -p runtimes/java/Migration/DDBECToAWSDBE test + gradle -p runtimes/java/Migration/DDBECv2ToAWSDBE test diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java index 425a4119f2..83501d2ee4 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java @@ -109,13 +109,24 @@ public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { final String providedEncAlg = materialDescription.get(CONTENT_KEY_ALGORITHM); final String providedSigAlg = materialDescription.get(SIGNING_KEY_ALGORITHM); + final String envelopeKey = materialDescription.get(ENVELOPE_KEY); + + // DDBEC with SDK v1 does not do this check and returns NPE + // DDBEC with SDK v2 does not return NPE but return DynamoDbEncryptionException + if (envelopeKey == null) { + throw new DynamoDbEncryptionException( + "Missing " + ENVELOPE_KEY + " in material description. " + + "This item may have been encrypted with a different encryption format." + ); + } + ec.put("*" + CONTENT_KEY_ALGORITHM + "*", providedEncAlg); ec.put("*" + SIGNING_KEY_ALGORITHM + "*", providedSigAlg); populateKmsEcFromEc(context, ec); DecryptRequest.Builder request = DecryptRequest.builder(); - request.ciphertextBlob(SdkBytes.fromByteArray(Base64.decode(materialDescription.get(ENVELOPE_KEY)))); + request.ciphertextBlob(SdkBytes.fromByteArray(Base64.decode(envelopeKey))); request.encryptionContext(ec); final DecryptResponse decryptResponse = decrypt(request.build(), context); validateEncryptionKeyId(decryptResponse.keyId(), context); diff --git a/Examples/runtimes/java/Migration/DDBECToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java b/Examples/runtimes/java/Migration/DDBECToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java index c2df453287..ec19b43ad4 100644 --- a/Examples/runtimes/java/Migration/DDBECToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java +++ b/Examples/runtimes/java/Migration/DDBECToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java @@ -63,7 +63,7 @@ public static void MigrationStep3( final List allowedUnsignedAttributes = Arrays.asList("attribute3"); - // 3. Create the DynamoDb Encryption Interceptor with the above configuration. + // 2. Create the DynamoDb Encryption Interceptor with the above configuration. // Do not configure any legacy behavior. final Map tableConfigs = new HashMap<>(); @@ -85,7 +85,7 @@ public static void MigrationStep3( .build() ); - // 4. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + // 3. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above final DynamoDbClient ddb = DynamoDbClient .builder() .overrideConfiguration( @@ -96,7 +96,7 @@ public static void MigrationStep3( ) .build(); - // 5. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // 4. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, // and create a Table with your modelled class final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient .builder() @@ -107,7 +107,7 @@ public static void MigrationStep3( schemaOnEncrypt ); - // 6. Put an item into your table using the DynamoDb Enhanced Client. + // 5. Put an item into your table using the DynamoDb Enhanced Client. // This item will be encrypted in the latest format, using the // configuration from your modelled class to decide // which attribute to encrypt and/or sign. @@ -120,7 +120,7 @@ public static void MigrationStep3( table.putItem(item); - // 7. Get an item back from the table using the DynamoDb Enhanced Client. + // 6. Get an item back from the table using the DynamoDb Enhanced Client. // If this is an item written in the old format (e.g. any item written // during Step 0 or 1), then we fail to return the item. // If this is an item written in the new format (e.g. any item written diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitattributes b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitattributes new file mode 100644 index 0000000000..097f9f98d9 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitignore b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitignore new file mode 100644 index 0000000000..1b6985c009 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitignore @@ -0,0 +1,5 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/build.gradle.kts b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/build.gradle.kts new file mode 100644 index 0000000000..d548fe6f58 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/build.gradle.kts @@ -0,0 +1,121 @@ +import java.io.File +import java.io.FileInputStream +import java.util.Properties +import java.net.URI +import javax.annotation.Nullable +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent + +plugins { + `java` + `java-library` +} + +var props = Properties().apply { + load(FileInputStream(File(rootProject.rootDir, "../../../../../project.properties"))) +} + +group = "software.amazon.cryptography" +version = "1.0-SNAPSHOT" +description = "AWSDatabaseEncryptionSDKMigrationExamples" + +var mplVersion = props.getProperty("mplDependencyJavaVersion") +var ddbecVersion = props.getProperty("projectJavaVersion") + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(8)) + sourceSets["main"].java { + srcDir("src/main/java") + } + sourceSets["test"].java { + srcDir("src/test/java") + } +} + +var caUrl: URI? = null +@Nullable +val caUrlStr: String? = System.getenv("CODEARTIFACT_REPO_URL") +if (!caUrlStr.isNullOrBlank()) { + caUrl = URI.create(caUrlStr) +} + +var caPassword: String? = null +@Nullable +val caPasswordString: String? = System.getenv("CODEARTIFACT_TOKEN") +if (!caPasswordString.isNullOrBlank()) { + caPassword = caPasswordString +} + +repositories { + mavenLocal() + maven { + name = "DynamoDB Local Release Repository - US West (Oregon) Region" + url = URI.create("https://s3-us-west-2.amazonaws.com/dynamodb-local/release") + } + mavenCentral() + if (caUrl != null && caPassword != null) { + maven { + name = "CodeArtifact" + url = caUrl!! + credentials { + username = "aws" + password = caPassword!! + } + } + } +} + +dependencies { + implementation("software.amazon.cryptography:aws-database-encryption-sdk-dynamodb:${ddbecVersion}") + implementation("software.amazon.cryptography:aws-cryptographic-material-providers:${mplVersion}") + + implementation(platform("software.amazon.awssdk:bom:2.19.1")) + implementation("software.amazon.awssdk:dynamodb") + implementation("software.amazon.awssdk:dynamodb-enhanced") + implementation("software.amazon.awssdk:kms") + + // https://mvnrepository.com/artifact/org.testng/testng + testImplementation("org.testng:testng:7.5") +} + +tasks.withType() { + options.encoding = "UTF-8" +} + +tasks.test { + useTestNG() + + // This will show System.out.println statements + testLogging.showStandardStreams = true + + testLogging { + lifecycle { + events = mutableSetOf(TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED) + exceptionFormat = TestExceptionFormat.FULL + showExceptions = true + showCauses = true + showStackTraces = true + showStandardStreams = true + } + info.events = lifecycle.events + info.exceptionFormat = lifecycle.exceptionFormat + } + + // See https://github.com/gradle/kotlin-dsl/issues/836 + addTestListener(object : TestListener { + override fun beforeSuite(suite: TestDescriptor) {} + override fun beforeTest(testDescriptor: TestDescriptor) {} + override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) {} + + override fun afterSuite(suite: TestDescriptor, result: TestResult) { + if (suite.parent == null) { // root suite + logger.lifecycle("----") + logger.lifecycle("Test result: ${result.resultType}") + logger.lifecycle("Test summary: ${result.testCount} tests, " + + "${result.successfulTestCount} succeeded, " + + "${result.failedTestCount} failed, " + + "${result.skippedTestCount} skipped") + } + } + }) +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew new file mode 100755 index 0000000000..65dcd68d65 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew.bat b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew.bat new file mode 100644 index 0000000000..93e3f59f13 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/settings.gradle.kts b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/settings.gradle.kts new file mode 100644 index 0000000000..f4b7d8d7d9 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/settings.gradle.kts @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/7.6/userguide/multi_project_builds.html + */ + +rootProject.name = "AWSDatabaseEncryptionSDKMigrationExamples" diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java new file mode 100644 index 0000000000..9df2e898d5 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java @@ -0,0 +1,197 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.CreateDynamoDbEncryptionInterceptorInput; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedClientEncryption; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedTableEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyOverride; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyPolicy; +import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; + +/* + Migration Step 1: This is an example demonstrating how to start using the + AWS Database Encryption SDK with a pre-existing table used with DynamoDB Encryption Client (SDK V2). + In this example, you configure a DynamoDb Encryption Interceptor to do the following: + - Read items encrypted in the old format + - Continue to encrypt items in the old format on write + - Read items encrypted in the new format + While this step configures your client to be ready to start reading items encrypted, + we do not yet expect to be reading any items in the new format. + Before you move on to step 2, ensure that these changes have successfully been deployed + to all of your readers. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) + */ +public class MigrationExampleStep1 { + + public static void MigrationStep1( + final String kmsKeyId, + final String ddbTableName, + final int sortReadValue + ) { + // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + // For this migration, we will continue to encrypt and decrypt data using the kmsKeyId + // used in Step 0 by configuring an AWS KMS Keyring with that same kmsKeyId. + // We will use the `CreateMrkMultiKeyring` method to create this keyring, + // as it will correctly handle both single region and Multi-Region KMS Keys. + // Note that we are now using the AWS SDK for Java v2 KMS client. + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMrkMultiKeyringInput keyringInput = + CreateAwsKmsMrkMultiKeyringInput.builder().generator(kmsKeyId).build(); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput); + + // 2. Create a Table Schema over your annotated class. + // See SimpleClass.java in this directory for the update to use the new DynamoDb Enhanced Client annotations. + // All primary key attributes will be signed but not encrypted (SIGN_ONLY) + // and by default all non-primary key attributes will be encrypted and signed (ENCRYPT_AND_SIGN). + // If you want a particular non-primary key attribute to be signed but not encrypted, + // use the `DynamoDbEncryptionSignOnly` annotation. + // If you want a particular attribute to be neither signed nor encrypted (DO_NOTHING), + // use the `DynamoDbEncryptionDoNothing` annotation. + final TableSchema schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); + + // 3. Configure which attributes we expect to be excluded in the signature + // when reading items. This value represents all unsigned attributes + // across our entire dataset. If you ever want to add new unsigned attributes + // in the future, you must make an update to this field to all your readers + // before deploying any change to start writing that new data. It is not safe + // to remove attributes from this field. + final List allowedUnsignedAttributes = Arrays.asList("attribute3"); + + // 4. Configure the attributeActionsOnEncrypt that are configured on our modelled class in Step 0 + // in an explicit map. + final Map legacyActions = new HashMap<>(); + legacyActions.put("partition_key", CryptoAction.SIGN_ONLY); + legacyActions.put("sort_key", CryptoAction.SIGN_ONLY); + legacyActions.put("attribute1", CryptoAction.ENCRYPT_AND_SIGN); + legacyActions.put("attribute2", CryptoAction.SIGN_ONLY); + legacyActions.put("attribute3", CryptoAction.DO_NOTHING); + + // 5. Configure the same DynamoDBEncryptor that we did in Step 0. + final KmsClient kmsClient = KmsClient.create(); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider(kmsClient, kmsKeyId); + final DynamoDBEncryptor oldEncryptor = DynamoDBEncryptor.getInstance(cmp); + + // 6. Configure legacy behavior with FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT. + // This policy continues to read and write items using the old format, + // but can read new format items as soon as they appear. + final LegacyOverride legacyOverride = LegacyOverride + .builder() + .encryptor(oldEncryptor) + .policy(LegacyPolicy.FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT) + .attributeActionsOnEncrypt(legacyActions) + .build(); + + // 7. Create the DynamoDb Encryption Interceptor. + final Map tableConfigs = new HashMap<>(); + tableConfigs.put( + ddbTableName, + DynamoDbEnhancedTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .keyring(kmsKeyring) + .allowedUnsignedAttributes(allowedUnsignedAttributes) + .schemaOnEncrypt(schemaOnEncrypt) + .legacyOverride(legacyOverride) + .build() + ); + final DynamoDbEncryptionInterceptor interceptor = + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ); + + // 8. Create DynamoDB client with the encryption interceptor. + final DynamoDbClient ddb = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration.builder() + .addExecutionInterceptor(interceptor) + .build() + ) + .build(); + + // 9. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // and create a Table with your modelled class + final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient + .builder() + .dynamoDbClient(ddb) + .build(); + final DynamoDbTable table = enhancedClient.table(ddbTableName, schemaOnEncrypt); + + // 10. Put an item into your table using the DynamoDb Enhanced Client. + // This item will be encrypted in the legacy format, using the + // old DynamoDBEncryptor and the legacy attribute actions. + // In this step, we do not expect items to be encrypted any differently + // from before. + final SimpleClass item = new SimpleClass(); + item.setPartitionKey("MigrationExample"); + item.setSortKey(1); + item.setAttribute1("encrypt and sign me!"); + item.setAttribute2("sign me!"); + item.setAttribute3("ignore me!"); + + table.putItem(item); + + // 11. Get an item back from the table using the DynamoDb Enhanced Client. + // If this is an item written in the old format (e.g. any item written + // during Step 0 or 1), then we will attempt to decrypt the item + // using the legacy behavior. + // If this is an item written in the new format (e.g. any item written + // during Step 2 or after), then we will attempt to decrypt the item using + // the non-legacy behavior. + final Key key = Key.builder() + .partitionValue("MigrationExample") + .sortValue(sortReadValue) + .build(); + + final SimpleClass decryptedItem = table.getItem( + (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key) + ); + + // Demonstrate we get the expected item back + assert decryptedItem.getPartitionKey().equals("MigrationExample"); + assert decryptedItem.getAttribute1().equals("encrypt and sign me!"); + assert decryptedItem.getAttribute2().equals("sign me!"); + assert decryptedItem.getAttribute3().equals("ignore me!"); + } + + public static void main(final String[] args) { + if (args.length < 3) { + throw new IllegalArgumentException( + "To run this example, include the kmsKeyId, ddbTableName, and sortReadValue as args." + ); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + final int sortReadValue = Integer.parseInt(args[2]); + MigrationStep1(kmsKeyId, ddbTableName, sortReadValue); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java new file mode 100644 index 0000000000..eeba7ec89f --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java @@ -0,0 +1,176 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.CreateDynamoDbEncryptionInterceptorInput; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedClientEncryption; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedTableEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyOverride; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyPolicy; +import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; + +/* + Migration Step 2: This is an example demonstrating how to update your configuration + to start writing items using the latest encryption format, but still continue + to read any items written using the old encryption format. + + Once you deploy this change to your system, you will have a dataset + containing items in both the old and new format. + Because the changes in Step 1 have been deployed to all our readers, + we can be sure that our entire system is ready to read this new data. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) + */ +public class MigrationExampleStep2 { + + public static void MigrationStep2( + final String kmsKeyId, + final String ddbTableName, + final int sortReadValue + ) { + // 1. Continue to configure your Keyring, Table Schema, legacy attribute actions, + // and allowedUnsignedAttributes, and old DynamoDBEncryptor as you did in Step 1. + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMrkMultiKeyringInput keyringInput = + CreateAwsKmsMrkMultiKeyringInput.builder().generator(kmsKeyId).build(); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput); + + final TableSchema schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); + + final List allowedUnsignedAttributes = Arrays.asList("attribute3"); + + final Map legacyActions = new HashMap<>(); + legacyActions.put("partition_key", CryptoAction.SIGN_ONLY); + legacyActions.put("sort_key", CryptoAction.SIGN_ONLY); + legacyActions.put("attribute1", CryptoAction.ENCRYPT_AND_SIGN); + legacyActions.put("attribute2", CryptoAction.SIGN_ONLY); + legacyActions.put("attribute3", CryptoAction.DO_NOTHING); + + // Configure the DynamoDBEncryptor using DDBEC SDK V2. + final KmsClient kmsClient = KmsClient.create(); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider(kmsClient, kmsKeyId); + final DynamoDBEncryptor oldEncryptor = DynamoDBEncryptor.getInstance(cmp); + + // 2. When configuring our legacy behavior, use `FORBID_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT`. + // With this policy, you will continue to read items in both formats, + // but will only write new items using the new format. + final LegacyOverride legacyOverride = LegacyOverride + .builder() + .encryptor(oldEncryptor) + .policy(LegacyPolicy.FORBID_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT) + .attributeActionsOnEncrypt(legacyActions) + .build(); + + // 3. Create the DynamoDb Encryption Interceptor with the above configuration. + final Map tableConfigs = new HashMap<>(); + tableConfigs.put( + ddbTableName, + DynamoDbEnhancedTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .keyring(kmsKeyring) + .allowedUnsignedAttributes(allowedUnsignedAttributes) + .schemaOnEncrypt(schemaOnEncrypt) + .legacyOverride(legacyOverride) + .build() + ); + final DynamoDbEncryptionInterceptor interceptor = + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ); + + // 4. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(interceptor) + .build() + ) + .build(); + + // 5. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // and create a Table with your modelled class + final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient + .builder() + .dynamoDbClient(ddb) + .build(); + final DynamoDbTable table = enhancedClient.table(ddbTableName, schemaOnEncrypt); + + // 6. Put an item into your table using the DynamoDb Enhanced Client. + // This item will be encrypted in the latest format, using the + // configuration from your modelled class to decide + // which attribute to encrypt and/or sign. + final SimpleClass item = new SimpleClass(); + item.setPartitionKey("MigrationExample"); + item.setSortKey(2); + item.setAttribute1("encrypt and sign me!"); + item.setAttribute2("sign me!"); + item.setAttribute3("ignore me!"); + + table.putItem(item); + + // 7. Get an item back from the table using the DynamoDb Enhanced Client. + // If this is an item written in the old format (e.g. any item written + // during Step 0 or 1), then we will attempt to decrypt the item + // using the legacy behavior. + // If this is an item written in the new format (e.g. any item written + // during Step 2 or after), then we will attempt to decrypt the item using + // the non-legacy behavior. + final Key key = Key + .builder() + .partitionValue("MigrationExample") + .sortValue(sortReadValue) + .build(); + + final SimpleClass decryptedItem = table.getItem( + (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key) + ); + + // Demonstrate we get the expected item back + assert decryptedItem.getPartitionKey().equals("MigrationExample"); + assert decryptedItem.getAttribute1().equals("encrypt and sign me!"); + } + + public static void main(final String[] args) { + if (args.length < 3) { + throw new IllegalArgumentException( + "To run this example, include the kmsKeyId, ddbTableName, and sortReadValue as args." + ); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + // You can manipulate this value to demonstrate reading records written in other steps + final int sortReadValue = Integer.parseInt(args[2]); + MigrationStep2(kmsKeyId, ddbTableName, sortReadValue); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java new file mode 100644 index 0000000000..4db5212321 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java @@ -0,0 +1,148 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.CreateDynamoDbEncryptionInterceptorInput; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedClientEncryption; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedTableEncryptionConfig; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +/* + Migration Step 3: This is an example demonstrating how to update your configuration + to stop accepting reading items encrypted using the old format. + In order to proceed with this step, you will need to re-encrypt all + old items in your table. + + Once you complete Step 3, you can be sure that all items being read by your system + ensure the security properties configured for the new format. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) + */ +public class MigrationExampleStep3 { + + public static void MigrationStep3( + final String kmsKeyId, + final String ddbTableName, + final int sortReadValue + ) { + // 1. Continue to configure your Keyring, Table Schema, + // and allowedUnsignedAttributes as you did in Step 1. + // However, now you can remove the configuration for the old DynamoDBEncryptor + // and the legacy attribute actions. + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMrkMultiKeyringInput keyringInput = + CreateAwsKmsMrkMultiKeyringInput.builder().generator(kmsKeyId).build(); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput); + + final TableSchema schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); + + final List allowedUnsignedAttributes = Arrays.asList("attribute3"); + + // 2. Create the DynamoDb Encryption Interceptor with the above configuration. + // Do not configure any legacy behavior. + final Map tableConfigs = new HashMap<>(); + tableConfigs.put( + ddbTableName, + DynamoDbEnhancedTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .keyring(kmsKeyring) + .allowedUnsignedAttributes(allowedUnsignedAttributes) + .schemaOnEncrypt(schemaOnEncrypt) + .build() + ); + final DynamoDbEncryptionInterceptor interceptor = + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ); + + // 3. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(interceptor) + .build() + ) + .build(); + + // 4. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // and create a Table with your modelled class + final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient + .builder() + .dynamoDbClient(ddb) + .build(); + final DynamoDbTable table = enhancedClient.table(ddbTableName, schemaOnEncrypt); + + // 5. Put an item into your table using the DynamoDb Enhanced Client. + // This item will be encrypted in the latest format, using the + // configuration from your modelled class to decide + // which attribute to encrypt and/or sign. + final SimpleClass item = new SimpleClass(); + item.setPartitionKey("MigrationExample"); + item.setSortKey(3); + item.setAttribute1("encrypt and sign me!"); + item.setAttribute2("sign me!"); + item.setAttribute3("ignore me!"); + + table.putItem(item); + + // 6. Get an item back from the table using the DynamoDb Enhanced Client. + // If this is an item written in the old format (e.g. any item written + // during Step 0 or 1), then we fail to return the item. + // If this is an item written in the new format (e.g. any item written + // during Step 2 or after), then we will attempt to decrypt the item using + // the non-legacy behavior. + final Key key = Key + .builder() + .partitionValue("MigrationExample") + .sortValue(sortReadValue) + .build(); + + final SimpleClass decryptedItem = table.getItem( + (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key) + ); + + // Demonstrate we get the expected item back + assert decryptedItem.getPartitionKey().equals("MigrationExample"); + assert decryptedItem.getAttribute1().equals("encrypt and sign me!"); + } + + public static void main(final String[] args) { + if (args.length < 3) { + throw new IllegalArgumentException( + "To run this example, include the kmsKeyId, ddbTableName, and sortReadValue as args." + ); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + // You can manipulate this value to demonstrate reading records written in other steps + final int sortReadValue = Integer.parseInt(args[2]); + MigrationStep3(kmsKeyId, ddbTableName, sortReadValue); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/SimpleClass.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/SimpleClass.java new file mode 100644 index 0000000000..c03d2506a3 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/SimpleClass.java @@ -0,0 +1,70 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; + +/** + * This class is used by the Migration Examples with DynamoDB Enhanced Client. + */ +@DynamoDbBean +public class SimpleClass { + + private String partitionKey; + private int sortKey; + private String attribute1; + private String attribute2; + private String attribute3; + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + @DynamoDbAttribute(value = "attribute1") + public String getAttribute1() { + return this.attribute1; + } + + public void setAttribute1(String attribute1) { + this.attribute1 = attribute1; + } + + @DynamoDbEncryptionSignOnly + @DynamoDbAttribute(value = "attribute2") + public String getAttribute2() { + return this.attribute2; + } + + public void setAttribute2(String attribute2) { + this.attribute2 = attribute2; + } + + @DynamoDbEncryptionDoNothing + @DynamoDbAttribute(value = "attribute3") + public String getAttribute3() { + return this.attribute3; + } + + public void setAttribute3(String attribute3) { + this.attribute3 = attribute3; + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java new file mode 100644 index 0000000000..7d6199ca12 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java @@ -0,0 +1,137 @@ +package software.amazon.cryptography.examples.migration.ddbec; + +import java.security.GeneralSecurityException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; + +/* + Migration Step 0: This is an example demonstrating use with the DynamoDb Encryption Client + with AWS SDK for Java V2, and is the starting state for our migration to the + AWS Database Encryption SDK for DynamoDb. + + In this example we configure a DynamoDBEncryptor to encrypt and decrypt items. + The encryption and decryption of data is configured to use a KMS Key as the root of trust. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +*/ +public class MigrationExampleStep0 { + + public static void MigrationStep0( + final String kmsKeyId, + final String ddbTableName, + final int sortReadValue + ) throws GeneralSecurityException { + // 1. Create the DirectKmsMaterialsProvider that protects your data keys. + // This uses the AWS SDK for Java V2 KmsClient. + final KmsClient kmsClient = KmsClient.create(); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider( + kmsClient, + kmsKeyId + ); + + // 2. Create the DynamoDBEncryptor using the Material Provider created above + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // 3. Configure attribute flags for encryption. + // For each attribute, specify whether to ENCRYPT and/or SIGN. + final Map> attributeFlags = new HashMap<>(); + // Keys are signed but never encrypted + attributeFlags.put("partition_key", EnumSet.of(EncryptionFlags.SIGN)); + attributeFlags.put("sort_key", EnumSet.of(EncryptionFlags.SIGN)); + // attribute1 - encrypt and sign + attributeFlags.put("attribute1", EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); + // attribute2 - sign only + attributeFlags.put("attribute2", EnumSet.of(EncryptionFlags.SIGN)); + // attribute3 - no flags (do nothing) + + // 4. Create a DynamoDB client + final DynamoDbClient ddbClient = DynamoDbClient.create(); + + // 5. Create the item to encrypt + final Map item = new HashMap<>(); + item.put("partition_key", AttributeValue.builder().s("MigrationExample").build()); + item.put("sort_key", AttributeValue.builder().n("0").build()); + item.put("attribute1", AttributeValue.builder().s("encrypt and sign me!").build()); + item.put("attribute2", AttributeValue.builder().s("sign me!").build()); + item.put("attribute3", AttributeValue.builder().s("ignore me!").build()); + + // 6. Create the EncryptionContext for this operation + final EncryptionContext encryptionContext = EncryptionContext.builder() + .tableName(ddbTableName) + .hashKeyName("partition_key") + .rangeKeyName("sort_key") + .build(); + + // 7. Encrypt the item using encryptRecord + final Map encryptedItem = encryptor.encryptRecord( + item, + attributeFlags, + encryptionContext + ); + + // Verify attribute1 is encrypted (original attribute name no longer exists) + assert encryptedItem.get("attribute1").b() != null; + // Verify attribute2 and attribute3 are not encrypted (sign-only and do-nothing) + assert encryptedItem.get("attribute2").s().equals("sign me!"); + assert encryptedItem.get("attribute3").s().equals("ignore me!"); + + // 8. Put the encrypted item into DynamoDB + ddbClient.putItem(PutItemRequest.builder() + .tableName(ddbTableName) + .item(encryptedItem) + .build()); + + // 9. Get the encrypted item back from DynamoDB + final Map key = new HashMap<>(); + key.put("partition_key", AttributeValue.builder().s("MigrationExample").build()); + key.put("sort_key", AttributeValue.builder().n(String.valueOf(sortReadValue)).build()); + + final GetItemResponse response = ddbClient.getItem(GetItemRequest.builder() + .tableName(ddbTableName) + .key(key) + .build()); + + // 10. Decrypt the item using decryptRecord + final Map decryptedItem = encryptor.decryptRecord( + response.item(), + attributeFlags, + encryptionContext + ); + + // Demonstrate we get the expected item back + assert decryptedItem.get("partition_key").s().equals("MigrationExample"); + assert decryptedItem.get("attribute1").s().equals("encrypt and sign me!"); + assert decryptedItem.get("attribute2").s().equals("sign me!"); + assert decryptedItem.get("attribute3").s().equals("ignore me!"); + } + + public static void main(final String[] args) throws GeneralSecurityException { + if (args.length < 3) { + throw new IllegalArgumentException( + "To run this example, include the kmsKeyId, ddbTableName, and sortReadValue as args." + ); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + // You can manipulate this value to demonstrate reading records written in other steps + final int sortReadValue = Integer.parseInt(args[2]); + MigrationStep0(kmsKeyId, ddbTableName, sortReadValue); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java new file mode 100644 index 0000000000..0a4e2f7543 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java @@ -0,0 +1,57 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.security.GeneralSecurityException; +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.migration.ddbec.MigrationExampleStep0; + +public class TestMigrationExampleStep1 { + + @Test + public void TestMigrationStep1() throws GeneralSecurityException { + // Successfully executes step 1 + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 1 + ); + + // Given: Step 0 has succeeded + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 0 + ); + // When: Execute Step 1 with sortReadValue=0, Then: Success (i.e. can read values in old format) + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 0 + ); + + // Given: Step 2 has succeeded + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 2 + ); + // When: Execute Step 1 with sortReadValue=2, Then: Success (i.e. can read values in new format) + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 2 + ); + + // Given: Step 3 has succeeded + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 3 + ); + // When: Execute Step 1 with sortReadValue=3, Then: Success (i.e. can read values in new format) + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 3 + ); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java new file mode 100644 index 0000000000..6b96d64242 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java @@ -0,0 +1,57 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.security.GeneralSecurityException; +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.migration.ddbec.MigrationExampleStep0; + +public class TestMigrationExampleStep2 { + + @Test + public void TestMigrationStep2() throws GeneralSecurityException { + // Successfully executes step 2 + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 2 + ); + + // Given: Step 0 has succeeded + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 0 + ); + // When: Execute Step 2 with sortReadValue=0, Then: Success (i.e. can read values in old format) + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 0 + ); + + // Given: Step 1 has succeeded + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 1 + ); + // When: Execute Step 2 with sortReadValue=1, Then: Success (i.e. can read values in old format) + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 1 + ); + + // Given: Step 3 has succeeded + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 3 + ); + // When: Execute Step 2 with sortReadValue=3, Then: Success (i.e. can read values in new format) + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 3 + ); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java new file mode 100644 index 0000000000..e420a9807f --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java @@ -0,0 +1,70 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import static org.testng.Assert.assertThrows; + +import java.security.GeneralSecurityException; +import org.testng.annotations.Test; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.cryptography.examples.migration.ddbec.MigrationExampleStep0; + +public class TestMigrationExampleStep3 { + + @Test + public void TestMigrationStep3() throws GeneralSecurityException { + // Successfully executes Step 3 + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 3 + ); + + // Given: Step 0 has succeeded + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 0 + ); + // When: Execute Step 3 with sortReadValue=0, Then: throws SdkClientException (i.e. cannot read values in old format) + assertThrows( + SdkClientException.class, + () -> { + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 0 + ); + } + ); + + // Given: Step 1 has succeeded + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 1 + ); + // When: Execute Step 3 with sortReadValue=1, Then: throws SdkClientException (i.e. cannot read values in old format) + assertThrows( + SdkClientException.class, + () -> { + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 1 + ); + } + ); + + // Given: Step 2 has succeeded + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 2 + ); + // When: Execute Step 3 with sortReadValue=2, Then: Success (i.e. can read values in new format) + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 2 + ); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestUtils.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestUtils.java new file mode 100644 index 0000000000..53c987bfc0 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestUtils.java @@ -0,0 +1,12 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +public class TestUtils { + + // This is a public KMS Key that MUST only be used for testing, and MUST NOT be used for any production data + public static String TEST_KMS_KEY_ID = + "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"; + + // Our tests require access to DDB Table with this name + public static final String TEST_DDB_TABLE_NAME = + "DynamoDbEncryptionInterceptorTestTable"; +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java new file mode 100644 index 0000000000..a7900b1e73 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java @@ -0,0 +1,72 @@ +package software.amazon.cryptography.examples.migration.ddbec; + +import static org.testng.Assert.assertThrows; + +import java.security.GeneralSecurityException; +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.migration.awsdbe.MigrationExampleStep1; +import software.amazon.cryptography.examples.migration.awsdbe.MigrationExampleStep2; +import software.amazon.cryptography.examples.migration.awsdbe.MigrationExampleStep3; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; + +public class TestMigrationExampleStep0 { + + @Test + public void TestMigrationStep0() throws GeneralSecurityException { + // Successfully executes Step 0 + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 0 + ); + + // Given: Step 1 has succeeded + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 1 + ); + // When: Execute Step 0 with sortReadValue=1, Then: Success (i.e. can read values in old format) + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 1 + ); + + // Given: Step 2 has succeeded + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 2 + ); + // When: Execute Step 0 with sortReadValue=2, Then: throws exception (i.e. cannot read values in new format) + assertThrows( + DynamoDbEncryptionException.class, + () -> { + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 2 + ); + } + ); + + // Given: Step 3 has succeeded + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 3 + ); + // When: Execute Step 0 with sortReadValue=3, Then: throws exception (i.e. cannot read values in new format) + assertThrows( + DynamoDbEncryptionException.class, + () -> { + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + 3 + ); + } + ); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestUtils.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestUtils.java new file mode 100644 index 0000000000..76d9f5c88d --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestUtils.java @@ -0,0 +1,12 @@ +package software.amazon.cryptography.examples.migration.ddbec; + +public class TestUtils { + + // This is a public KMS Key that MUST only be used for testing, and MUST NOT be used for any production data + public static String TEST_KMS_KEY_ID = + "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"; + + // Our tests require access to DDB Table with this name + public static final String TEST_DDB_TABLE_NAME = + "DynamoDbEncryptionInterceptorTestTable"; +} From a107274efccde71ac1f1bb533ae0c69482e788f4 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 4 Feb 2026 22:36:25 -0800 Subject: [PATCH 25/33] m --- .../legacy/InternalLegacyOverride.java | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index 74ce475374..a0420aaf14 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -291,23 +291,17 @@ > convertActionsV1ToV2(Map> v1Actions) { // Convert SDK V1 EncryptionContext to SDK V2 private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext convertEncryptionContextV1ToV2( - final EncryptionContext v1Context + EncryptionContext v1Context ) { - - final software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.Builder builder = software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext - .builder() - .tableName(v1Context.getTableName()) - .hashKeyName(v1Context.getHashKeyName()) - .rangeKeyName(v1Context.getRangeKeyName()) - .developerContext(v1Context.getDeveloperContext()); - - if (v1Context.getMaterialDescription() != null) { - builder.materialDescription(v1Context.getMaterialDescription()); - } - if (v1Context.getAttributeValues() != null) { - builder.attributeValues(V1MapToV2Map(v1Context.getAttributeValues())); - } - return builder.build(); + return software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext + .builder() + .tableName(v1Context.getTableName()) + .hashKeyName(v1Context.getHashKeyName()) + .rangeKeyName(v1Context.getRangeKeyName()) + .attributeValues(V1MapToV2Map(v1Context.getAttributeValues())) + .developerContext(v1Context.getDeveloperContext()) + .materialDescription(v1Context.getMaterialDescription()) + .build(); } public static String ToNativeString(DafnySequence s) { @@ -449,9 +443,6 @@ > V2MapToV1Map( software.amazon.awssdk.services.dynamodb.model.AttributeValue > input ) { - if (input == null) { - return null; - } return input .entrySet() .stream() From 5214abee48e65286e8a02ee76e9a7894f0bb0767 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 4 Feb 2026 22:55:26 -0800 Subject: [PATCH 26/33] Revert "m" This reverts commit 9c947152979c96c09c608cea8452039c74e5733d. --- .../internaldafny/legacy/InternalLegacyOverride.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index a0420aaf14..ae2dafc75e 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -510,9 +510,6 @@ public static software.amazon.awssdk.services.dynamodb.model.AttributeValue V1At > V1MapToV2Map( Map input ) { - if (input == null) { - return null; - } return input .entrySet() .stream() From fa2cd7fd56badb5407f3ec2dd2121854f68a2b72 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 4 Feb 2026 16:55:35 -0800 Subject: [PATCH 27/33] m --- .../internaldafny/legacy/InternalLegacyOverride.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index ae2dafc75e..a0420aaf14 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -510,6 +510,9 @@ public static software.amazon.awssdk.services.dynamodb.model.AttributeValue V1At > V1MapToV2Map( Map input ) { + if (input == null) { + return null; + } return input .entrySet() .stream() From 1485d5781021bacb0cade4a3723d08538ae8058b Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 4 Feb 2026 22:36:25 -0800 Subject: [PATCH 28/33] m --- .../legacy/InternalLegacyOverride.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index a0420aaf14..7bfc16e25e 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -293,15 +293,21 @@ > convertActionsV1ToV2(Map> v1Actions) { private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext convertEncryptionContextV1ToV2( EncryptionContext v1Context ) { - return software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext + final Map v2AttributeValues = V1MapToV2Map(v1Context.getAttributeValues()); + final software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.Builder builder = software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext .builder() .tableName(v1Context.getTableName()) .hashKeyName(v1Context.getHashKeyName()) .rangeKeyName(v1Context.getRangeKeyName()) - .attributeValues(V1MapToV2Map(v1Context.getAttributeValues())) - .developerContext(v1Context.getDeveloperContext()) - .materialDescription(v1Context.getMaterialDescription()) - .build(); + .developerContext(v1Context.getDeveloperContext()); + + if (v1Context.getMaterialDescription() != null) { + builder.materialDescription(v1Context.getMaterialDescription()); + } + if (v2AttributeValues != null) { + builder.attributeValues(v2AttributeValues); + } + return builder.build(); } public static String ToNativeString(DafnySequence s) { From 1b87db7d2c5931d6e4679b740160f21f76ac618e Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Wed, 4 Feb 2026 23:02:31 -0800 Subject: [PATCH 29/33] m --- .../legacy/InternalLegacyOverride.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index 7bfc16e25e..74ce475374 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -291,21 +291,21 @@ > convertActionsV1ToV2(Map> v1Actions) { // Convert SDK V1 EncryptionContext to SDK V2 private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext convertEncryptionContextV1ToV2( - EncryptionContext v1Context + final EncryptionContext v1Context ) { - final Map v2AttributeValues = V1MapToV2Map(v1Context.getAttributeValues()); + final software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.Builder builder = software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext - .builder() - .tableName(v1Context.getTableName()) - .hashKeyName(v1Context.getHashKeyName()) - .rangeKeyName(v1Context.getRangeKeyName()) - .developerContext(v1Context.getDeveloperContext()); + .builder() + .tableName(v1Context.getTableName()) + .hashKeyName(v1Context.getHashKeyName()) + .rangeKeyName(v1Context.getRangeKeyName()) + .developerContext(v1Context.getDeveloperContext()); if (v1Context.getMaterialDescription() != null) { builder.materialDescription(v1Context.getMaterialDescription()); } - if (v2AttributeValues != null) { - builder.attributeValues(v2AttributeValues); + if (v1Context.getAttributeValues() != null) { + builder.attributeValues(V1MapToV2Map(v1Context.getAttributeValues())); } return builder.build(); } @@ -449,6 +449,9 @@ > V2MapToV1Map( software.amazon.awssdk.services.dynamodb.model.AttributeValue > input ) { + if (input == null) { + return null; + } return input .entrySet() .stream() From 92f4fc15ac3333ff03d6450f4676c38ca48246f2 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Thu, 5 Feb 2026 10:14:54 -0800 Subject: [PATCH 30/33] formatting --- .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61574 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 +++ .../awsdbe/MigrationExampleStep1.java | 37 +++++++++----- .../awsdbe/MigrationExampleStep2.java | 28 ++++++++--- .../awsdbe/MigrationExampleStep3.java | 23 ++++++--- .../ddbec/MigrationExampleStep0.java | 46 ++++++++++++------ .../ddbec/TestMigrationExampleStep0.java | 2 +- 7 files changed, 101 insertions(+), 41 deletions(-) create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.jar create mode 100644 Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.properties diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.jar b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..943f0cbfa754578e88a3dae77fce6e3dea56edbf GIT binary patch literal 61574 zcmb6AV{~QRwml9f72CFLyJFk6ZKq;e729@pY}>YNR8p1vbMJH7ubt# zZR`2@zJD1Ad^Oa6Hk1{VlN1wGR-u;_dyt)+kddaNpM#U8qn@6eX;fldWZ6BspQIa= zoRXcQk)#ENJ`XiXJuK3q0$`Ap92QXrW00Yv7NOrc-8ljOOOIcj{J&cR{W`aIGXJ-` z`ez%Mf7qBi8JgIb{-35Oe>Zh^GIVe-b^5nULQhxRDZa)^4+98@`hUJe{J%R>|LYHA z4K3~Hjcp8_owGF{d~lZVKJ;kc48^OQ+`_2migWY?JqgW&))70RgSB6KY9+&wm<*8 z_{<;(c;5H|u}3{Y>y_<0Z59a)MIGK7wRMX0Nvo>feeJs+U?bt-++E8bu7 zh#_cwz0(4#RaT@xy14c7d<92q-Dd}Dt<*RS+$r0a^=LGCM{ny?rMFjhgxIG4>Hc~r zC$L?-FW0FZ((8@dsowXlQq}ja%DM{z&0kia*w7B*PQ`gLvPGS7M}$T&EPl8mew3In z0U$u}+bk?Vei{E$6dAYI8Tsze6A5wah?d(+fyP_5t4ytRXNktK&*JB!hRl07G62m_ zAt1nj(37{1p~L|m(Bsz3vE*usD`78QTgYIk zQ6BF14KLzsJTCqx&E!h>XP4)bya|{*G7&T$^hR0(bOWjUs2p0uw7xEjbz1FNSBCDb@^NIA z$qaq^0it^(#pFEmuGVS4&-r4(7HLmtT%_~Xhr-k8yp0`$N|y>#$Ao#zibzGi*UKzi zhaV#@e1{2@1Vn2iq}4J{1-ox;7K(-;Sk{3G2_EtV-D<)^Pk-G<6-vP{W}Yd>GLL zuOVrmN@KlD4f5sVMTs7c{ATcIGrv4@2umVI$r!xI8a?GN(R;?32n0NS(g@B8S00-=zzLn z%^Agl9eV(q&8UrK^~&$}{S(6-nEXnI8%|hoQ47P?I0Kd=woZ-pH==;jEg+QOfMSq~ zOu>&DkHsc{?o&M5`jyJBWbfoPBv9Y#70qvoHbZXOj*qRM(CQV=uX5KN+b>SQf-~a8 ziZg}@&XHHXkAUqr)Q{y`jNd7`1F8nm6}n}+_She>KO`VNlnu(&??!(i#$mKOpWpi1 z#WfWxi3L)bNRodhPM~~?!5{TrrBY_+nD?CIUupkwAPGz-P;QYc-DcUoCe`w(7)}|S zRvN)9ru8b)MoullmASwsgKQo1U6nsVAvo8iKnbaWydto4y?#-|kP^%e6m@L`88KyDrLH`=EDx*6>?r5~7Iv~I zr__%SximG(izLKSnbTlXa-ksH@R6rvBrBavt4)>o3$dgztLt4W=!3=O(*w7I+pHY2(P0QbTma+g#dXoD7N#?FaXNQ^I0*;jzvjM}%=+km`YtC%O#Alm| zqgORKSqk!#^~6whtLQASqiJ7*nq?38OJ3$u=Tp%Y`x^eYJtOqTzVkJ60b2t>TzdQ{I}!lEBxm}JSy7sy8DpDb zIqdT%PKf&Zy--T^c-;%mbDCxLrMWTVLW}c=DP2>Td74)-mLl|70)8hU??(2)I@Zyo z2i`q5oyA!!(2xV~gahuKl&L(@_3SP012#x(7P!1}6vNFFK5f*A1xF({JwxSFwA|TM z&1z}!*mZKcUA-v4QzLz&5wS$7=5{M@RAlx@RkJaA4nWVqsuuaW(eDh^LNPPkmM~Al zwxCe@*-^4!ky#iNv2NIIU$CS+UW%ziW0q@6HN3{eCYOUe;2P)C*M`Bt{~-mC%T3%# zEaf)lATO1;uF33x>Hr~YD0Ju*Syi!Jz+x3myVvU^-O>C*lFCKS&=Tuz@>&o?68aF& zBv<^ziPywPu#;WSlTkzdZ9`GWe7D8h<1-v0M*R@oYgS5jlPbgHcx)n2*+!+VcGlYh?;9Ngkg% z=MPD+`pXryN1T|%I7c?ZPLb3bqWr7 zU4bfG1y+?!bw)5Iq#8IqWN@G=Ru%Thxf)#=yL>^wZXSCC8we@>$hu=yrU;2=7>h;5 zvj_pYgKg2lKvNggl1ALnsz2IlcvL;q79buN5T3IhXuJvy@^crqWpB-5NOm{7UVfxmPJ>`?;Tn@qHzF+W!5W{8Z&ZAnDOquw6r4$bv*jM#5lc%3v|c~^ zdqo4LuxzkKhK4Q+JTK8tR_|i6O(x#N2N0Fy5)!_trK&cn9odQu#Vlh1K~7q|rE z61#!ZPZ+G&Y7hqmY;`{XeDbQexC2@oFWY)Nzg@lL3GeEVRxWQlx@0?Zt`PcP0iq@6 zLgc)p&s$;*K_;q0L(mQ8mKqOJSrq$aQYO-Hbssf3P=wC6CvTVHudzJH-Jgm&foBSy zx0=qu$w477lIHk);XhaUR!R-tQOZ;tjLXFH6;%0)8^IAc*MO>Q;J={We(0OHaogG0 zE_C@bXic&m?F7slFAB~x|n#>a^@u8lu;=!sqE*?vq zu4`(x!Jb4F#&3+jQ|ygldPjyYn#uCjNWR)%M3(L!?3C`miKT;~iv_)dll>Q6b+I&c zrlB04k&>mSYLR7-k{Od+lARt~3}Bv!LWY4>igJl!L5@;V21H6dNHIGr+qV551e@yL z`*SdKGPE^yF?FJ|`#L)RQ?LJ;8+={+|Cl<$*ZF@j^?$H%V;jqVqt#2B0yVr}Nry5R z5D?S9n+qB_yEqvdy9nFc+8WxK$XME$3ftSceLb+L(_id5MMc*hSrC;E1SaZYow%jh zPgo#1PKjE+1QB`Of|aNmX?}3TP;y6~0iN}TKi3b+yvGk;)X&i3mTnf9M zuv3qvhErosfZ%Pb-Q>|BEm5(j-RV6Zf^$icM=sC-5^6MnAvcE9xzH@FwnDeG0YU{J zi~Fq?=bi0;Ir=hfOJu8PxC)qjYW~cv^+74Hs#GmU%Cw6?3LUUHh|Yab`spoqh8F@_ zm4bCyiXPx-Cp4!JpI~w!ShPfJOXsy>f*|$@P8L8(oeh#~w z-2a4IOeckn6}_TQ+rgl_gLArS3|Ml(i<`*Lqv6rWh$(Z5ycTYD#Z*&-5mpa}a_zHt z6E`Ty-^L9RK-M*mN5AasoBhc|XWZ7=YRQSvG)3$v zgr&U_X`Ny0)IOZtX}e$wNUzTpD%iF7Rgf?nWoG2J@PsS-qK4OD!kJ?UfO+1|F*|Bo z1KU`qDA^;$0*4mUJ#{EPOm7)t#EdX=Yx1R2T&xlzzThfRC7eq@pX&%MO&2AZVO%zw zS;A{HtJiL=rfXDigS=NcWL-s>Rbv|=)7eDoOVnVI>DI_8x>{E>msC$kXsS}z?R6*x zi(yO`$WN)_F1$=18cbA^5|f`pZA+9DG_Zu8uW?rA9IxUXx^QCAp3Gk1MSdq zBZv;_$W>*-zLL)F>Vn`}ti1k!%6{Q=g!g1J*`KONL#)M{ZC*%QzsNRaL|uJcGB7jD zTbUe%T(_x`UtlM!Ntp&-qu!v|mPZGcJw$mdnanY3Uo>5{oiFOjDr!ZznKz}iWT#x& z?*#;H$`M0VC|a~1u_<(}WD>ogx(EvF6A6S8l0%9U<( zH||OBbh8Tnzz*#bV8&$d#AZNF$xF9F2{_B`^(zWNC}af(V~J+EZAbeC2%hjKz3V1C zj#%d%Gf(uyQ@0Y6CcP^CWkq`n+YR^W0`_qkDw333O<0FoO9()vP^!tZ{`0zsNQx~E zb&BcBU>GTP2svE2Tmd;~73mj!_*V8uL?ZLbx}{^l9+yvR5fas+w&0EpA?_g?i9@A$j*?LnmctPDQG|zJ`=EF}Vx8aMD^LrtMvpNIR*|RHA`ctK*sbG= zjN7Q)(|dGpC}$+nt~bupuKSyaiU}Ws{?Tha@$q}cJ;tvH>+MuPih+B4d$Zbq9$Y*U z)iA(-dK?Ov@uCDq48Zm%%t5uw1GrnxDm7*ITGCEF!2UjA`BqPRiUR`yNq^zz|A3wU zG(8DAnY-GW+PR2&7@In{Sla(XnMz5Rk^*5u4UvCiDQs@hvZXoiziv{6*i?fihVI|( zPrY8SOcOIh9-AzyJ*wF4hq%ojB&Abrf;4kX@^-p$mmhr}xxn#fVU?ydmD=21&S)s*v*^3E96(K1}J$6bi8pyUr-IU)p zcwa$&EAF$0Aj?4OYPcOwb-#qB=kCEDIV8%^0oa567_u6`9+XRhKaBup z2gwj*m#(}=5m24fBB#9cC?A$4CCBj7kanaYM&v754(b%Vl!gg&N)ZN_gO0mv(jM0# z>FC|FHi=FGlEt6Hk6H3!Yc|7+q{&t%(>3n#>#yx@*aS+bw)(2!WK#M0AUD~wID>yG z?&{p66jLvP1;!T7^^*_9F322wJB*O%TY2oek=sA%AUQT75VQ_iY9`H;ZNKFQELpZd z$~M`wm^Y>lZ8+F0_WCJ0T2td`bM+b`)h3YOV%&@o{C#|t&7haQfq#uJJP;81|2e+$ z|K#e~YTE87s+e0zCE2X$df`o$`8tQhmO?nqO?lOuTJ%GDv&-m_kP9X<5GCo1=?+LY z?!O^AUrRb~3F!k=H7Aae5W0V1{KlgH379eAPTwq=2+MlNcJ6NM+4ztXFTwI)g+)&Q7G4H%KH_(}1rq%+eIJ*3$?WwnZxPZ;EC=@`QS@|-I zyl+NYh&G>k%}GL}1;ap8buvF>x^yfR*d+4Vkg7S!aQ++_oNx6hLz6kKWi>pjWGO5k zlUZ45MbA=v(xf>Oeqhg8ctl56y{;uDG?A9Ga5aEzZB80BW6vo2Bz&O-}WAq>(PaV;*SX0=xXgI_SJ< zYR&5HyeY%IW}I>yKu^?W2$~S!pw?)wd4(#6;V|dVoa}13Oiz5Hs6zA zgICc;aoUt$>AjDmr0nCzeCReTuvdD1{NzD1wr*q@QqVW*Wi1zn;Yw1dSwLvTUwg#7 zpp~Czra7U~nSZZTjieZxiu~=}!xgV68(!UmQz@#w9#$0Vf@y%!{uN~w^~U_d_Aa&r zt2l>)H8-+gA;3xBk?ZV2Cq!L71;-tb%7A0FWziYwMT|#s_Ze_B>orZQWqDOZuT{|@ zX04D%y&8u@>bur&*<2??1KnaA7M%%gXV@C3YjipS4|cQH68OSYxC`P#ncvtB%gnEI z%fxRuH=d{L70?vHMi>~_lhJ@MC^u#H66=tx?8{HG;G2j$9@}ZDYUuTetwpvuqy}vW)kDmj^a|A%z(xs7yY2mU0#X2$un&MCirr|7 z%m?8+9aekm0x5hvBQ2J+>XeAdel$cy>J<6R3}*O^j{ObSk_Ucv$8a3_WPTd5I4HRT z(PKP5!{l*{lk_19@&{5C>TRV8_D~v*StN~Pm*(qRP+`1N12y{#w_fsXrtSt={0hJw zQ(PyWgA;;tBBDql#^2J(pnuv;fPn(H>^d<6BlI%00ylJZ?Evkh%=j2n+|VqTM~EUh zTx|IY)W;3{%x(O{X|$PS&x0?z#S2q-kW&G}7#D?p7!Q4V&NtA_DbF~v?cz6_l+t8e zoh1`dk;P-%$m(Ud?wnoZn0R=Ka$`tnZ|yQ-FN!?!9Wmb^b(R!s#b)oj9hs3$p%XX9DgQcZJE7B_dz0OEF6C zx|%jlqj0WG5K4`cVw!19doNY+(;SrR_txAlXxf#C`uz5H6#0D>SzG*t9!Fn|^8Z8; z1w$uiQzufUzvPCHXhGma>+O327SitsB1?Rn6|^F198AOx}! zfXg22Lm0x%=gRvXXx%WU2&R!p_{_1H^R`+fRO2LT%;He@yiekCz3%coJ=8+Xbc$mN zJ;J7*ED|yKWDK3CrD?v#VFj|l-cTgtn&lL`@;sMYaM1;d)VUHa1KSB5(I54sBErYp z>~4Jz41?Vt{`o7T`j=Se{-kgJBJG^MTJ}hT00H%U)pY-dy!M|6$v+-d(CkZH5wmo1 zc2RaU`p3_IJ^hf{g&c|^;)k3zXC0kF1>rUljSxd}Af$!@@R1fJWa4g5vF?S?8rg=Z z4_I!$dap>3l+o|fyYy(sX}f@Br4~%&&#Z~bEca!nMKV zgQSCVC!zw^j<61!7#T!RxC6KdoMNONcM5^Q;<#~K!Q?-#6SE16F*dZ;qv=`5 z(kF|n!QIVd*6BqRR8b8H>d~N@ab+1+{3dDVPVAo>{mAB#m&jX{usKkCg^a9Fef`tR z?M79j7hH*;iC$XM)#IVm&tUoDv!(#f=XsTA$)(ZE37!iu3Gkih5~^Vlx#<(M25gr@ zOkSw4{l}6xI(b0Gy#ywglot$GnF)P<FQt~9ge1>qp8Q^k;_Dm1X@Tc^{CwYb4v_ld}k5I$&u}avIDQ-D(_EP zhgdc{)5r_iTFiZ;Q)5Uq=U73lW%uYN=JLo#OS;B0B=;j>APk?|!t{f3grv0nv}Z%` zM%XJk^#R69iNm&*^0SV0s9&>cl1BroIw*t3R0()^ldAsq)kWcI=>~4!6fM#0!K%TS ziZH=H%7-f=#-2G_XmF$~Wl~Um%^9%AeNSk)*`RDl##y+s)$V`oDlnK@{y+#LNUJp1^(e89sed@BB z^W)sHm;A^9*RgQ;f(~MHK~bJRvzezWGr#@jYAlXIrCk_iiUfC_FBWyvKj2mBF=FI;9|?0_~=E<)qnjLg9k*Qd!_ zl}VuSJB%#M>`iZm*1U^SP1}rkkI};91IRpZw%Hb$tKmr6&H5~m?A7?+uFOSnf)j14 zJCYLOYdaRu>zO%5d+VeXa-Ai7{7Z}iTn%yyz7hsmo7E|{ z@+g9cBcI-MT~2f@WrY0dpaC=v{*lDPBDX}OXtJ|niu$xyit;tyX5N&3pgmCxq>7TP zcOb9%(TyvOSxtw%Y2+O&jg39&YuOtgzn`uk{INC}^Na_-V;63b#+*@NOBnU{lG5TS zbC+N-qt)u26lggGPcdrTn@m+m>bcrh?sG4b(BrtdIKq3W<%?WuQtEW0Z)#?c_Lzqj*DlZ zVUpEV3~mG#DN$I#JJp3xc8`9ex)1%Il7xKwrpJt)qtpq}DXqI=5~~N}N?0g*YwETZ z(NKJO5kzh?Os`BQ7HYaTl>sXVr!b8>(Wd&PU*3ivSn{;q`|@n*J~-3tbm;4WK>j3&}AEZ*`_!gJ3F4w~4{{PyLZklDqWo|X}D zbZU_{2E6^VTCg#+6yJt{QUhu}uMITs@sRwH0z5OqM>taO^(_+w1c ztQ?gvVPj<_F_=(ISaB~qML59HT;#c9x(;0vkCi2#Zp`;_r@+8QOV1Ey2RWm6{*J&9 zG(Dt$zF^7qYpo9Ne}ce5re^j|rvDo*DQ&1Be#Fvo#?m4mfFrNZb1#D4f`Lf(t_Fib zwxL3lx(Zp(XVRjo_ocElY#yS$LHb6yl;9;Ycm1|5y_praEcGUZxLhS%7?b&es2skI z9l!O)b%D=cXBa@v9;64f^Q9IV$xOkl;%cG6WLQ`_a7I`woHbEX&?6NJ9Yn&z+#^#! zc8;5=jt~Unn7!cQa$=a7xSp}zuz#Lc#Q3-e7*i`Xk5tx_+^M~!DlyBOwVEq3c(?`@ zZ_3qlTN{eHOwvNTCLOHjwg0%niFYm({LEfAieI+k;U2&uTD4J;Zg#s`k?lxyJN<$mK6>j?J4eOM@T*o?&l@LFG$Gs5f4R*p*V1RkTdCfv9KUfa< z{k;#JfA3XA5NQJziGd%DchDR*Dkld&t;6i9e2t7{hQPIG_uDXN1q0T;IFCmCcua-e z`o#=uS2_en206(TuB4g-!#=rziBTs%(-b1N%(Bl}ea#xKK9zzZGCo@<*i1ZoETjeC zJ)ll{$mpX7Eldxnjb1&cB6S=7v@EDCsmIOBWc$p^W*;C0i^Hc{q(_iaWtE{0qbLjxWlqBe%Y|A z>I|4)(5mx3VtwRBrano|P))JWybOHUyOY67zRst259tx;l(hbY@%Z`v8Pz^0Sw$?= zwSd^HLyL+$l&R+TDnbV_u+h{Z>n$)PMf*YGQ}1Df@Nr{#Gr+@|gKlnv?`s1rm^$1+ zic`WeKSH?{+E}0^#T<&@P;dFf;P5zCbuCOijADb}n^{k=>mBehDD6PtCrn5ZBhh2L zjF$TbzvnwT#AzGEG_Rg>W1NS{PxmL9Mf69*?YDeB*pK!&2PQ7!u6eJEHk5e(H~cnG zZQ?X_rtws!;Tod88j=aMaylLNJbgDoyzlBv0g{2VYRXObL=pn!n8+s1s2uTwtZc

YH!Z*ZaR%>WTVy8-(^h5J^1%NZ$@&_ZQ)3AeHlhL~=X9=fKPzFbZ;~cS**=W-LF1 z5F82SZ zG8QZAet|10U*jK*GVOA(iULStsUDMjhT$g5MRIc4b8)5q_a?ma-G+@xyNDk{pR*YH zjCXynm-fV`*;}%3=+zMj**wlCo6a{}*?;`*j%fU`t+3Korws%dsCXAANKkmVby*eJ z6`2%GB{+&`g2;snG`LM9S~>#^G|nZ|JMnWLgSmJ4!kB->uAEF0sVn6km@s=#_=d)y zzld%;gJY>ypQuE z!wgqqTSPxaUPoG%FQ()1hz(VHN@5sfnE68of>9BgGsQP|9$7j zGqN{nxZx4CD6ICwmXSv6&RD<-etQmbyTHIXn!Q+0{18=!p))>To8df$nCjycnW07Q zsma_}$tY#Xc&?#OK}-N`wPm)+2|&)9=9>YOXQYfaCI*cV1=TUl5({a@1wn#V?y0Yn z(3;3-@(QF|0PA}|w4hBWQbTItc$(^snj$36kz{pOx*f`l7V8`rZK}82pPRuy zxwE=~MlCwOLRC`y%q8SMh>3BUCjxLa;v{pFSdAc7m*7!}dtH`MuMLB)QC4B^Uh2_? zApl6z_VHU}=MAA9*g4v-P=7~3?Lu#ig)cRe90>@B?>})@X*+v&yT6FvUsO=p#n8p{ zFA6xNarPy0qJDO1BPBYk4~~LP0ykPV ztoz$i+QC%Ch%t}|i^(Rb9?$(@ijUc@w=3F1AM}OgFo1b89KzF6qJO~W52U_;R_MsB zfAC29BNUXpl!w&!dT^Zq<__Hr#w6q%qS1CJ#5Wrb*)2P1%h*DmZ?br)*)~$^TExX1 zL&{>xnM*sh=@IY)i?u5@;;k6+MLjx%m(qwDF3?K3p>-4c2fe(cIpKq#Lc~;#I#Wwz zywZ!^&|9#G7PM6tpgwA@3ev@Ev_w`ZZRs#VS4}<^>tfP*(uqLL65uSi9H!Gqd59C&=LSDo{;#@Isg3caF1X+4T}sL2B+Q zK*kO0?4F7%8mx3di$B~b&*t7y|{x%2BUg4kLFXt`FK;Vi(FIJ+!H zW;mjBrfZdNT>&dDfc4m$^f@k)mum{DioeYYJ|XKQynXl-IDs~1c(`w{*ih0-y_=t$ zaMDwAz>^CC;p*Iw+Hm}%6$GN49<(rembdFvb!ZyayLoqR*KBLc^OIA*t8CXur+_e0 z3`|y|!T>7+jdny7x@JHtV0CP1jI^)9){!s#{C>BcNc5#*hioZ>OfDv)&PAM!PTjS+ zy1gRZirf>YoGpgprd?M1k<;=SShCMn406J>>iRVnw9QxsR|_j5U{Ixr;X5n$ih+-=X0fo(Oga zB=uer9jc=mYY=tV-tAe@_d-{aj`oYS%CP@V3m6Y{)mZ5}b1wV<9{~$`qR9 zEzXo|ok?1fS?zneLA@_C(BAjE_Bv7Dl2s?=_?E9zO5R^TBg8Be~fpG?$9I; zDWLH9R9##?>ISN8s2^wj3B?qJxrSSlC6YB}Yee{D3Ex8@QFLZ&zPx-?0>;Cafcb-! zlGLr)wisd=C(F#4-0@~P-C&s%C}GvBhb^tTiL4Y_dsv@O;S56@?@t<)AXpqHx9V;3 zgB!NXwp`=%h9!L9dBn6R0M<~;(g*nvI`A@&K!B`CU3^FpRWvRi@Iom>LK!hEh8VjX z_dSw5nh-f#zIUDkKMq|BL+IO}HYJjMo=#_srx8cRAbu9bvr&WxggWvxbS_Ix|B}DE zk!*;&k#1BcinaD-w#E+PR_k8I_YOYNkoxw5!g&3WKx4{_Y6T&EV>NrnN9W*@OH+niSC0nd z#x*dm=f2Zm?6qhY3}Kurxl@}d(~ z<}?Mw+>%y3T{!i3d1%ig*`oIYK|Vi@8Z~*vxY%Od-N0+xqtJ*KGrqo*9GQ14WluUn z+%c+og=f0s6Mcf%r1Be#e}&>1n!!ZxnWZ`7@F9ymfVkuFL;m6M5t%6OrnK#*lofS{ z=2;WPobvGCu{(gy8|Mn(9}NV99Feps6r*6s&bg(5aNw$eE ztbYsrm0yS`UIJ?Kv-EpZT#76g76*hVNg)L#Hr7Q@L4sqHI;+q5P&H{GBo1$PYkr@z zFeVdcS?N1klRoBt4>fMnygNrDL!3e)k3`TXoa3#F#0SFP(Xx^cc)#e2+&z9F=6{qk z%33-*f6=+W@baq){!d_;ouVthV1PREX^ykCjD|%WUMnNA2GbA#329aEihLk~0!!}k z)SIEXz(;0lemIO{|JdO{6d|-9LePs~$}6vZ>`xYCD(ODG;OuwOe3jeN;|G$~ml%r* z%{@<9qDf8Vsw581v9y+)I4&te!6ZDJMYrQ*g4_xj!~pUu#er`@_bJ34Ioez)^055M$)LfC|i*2*3E zLB<`5*H#&~R*VLYlNMCXl~=9%o0IYJ$bY+|m-0OJ-}6c@3m<~C;;S~#@j-p?DBdr<><3Y92rW-kc2C$zhqwyq09;dc5;BAR#PPpZxqo-@e_s9*O`?w5 zMnLUs(2c-zw9Pl!2c#+9lFpmTR>P;SA#Id;+fo|g{*n&gLi}7`K)(=tcK|?qR4qNT z%aEsSCL0j9DN$j8g(a+{Z-qPMG&O)H0Y9!c*d?aN0tC&GqC+`%(IFY$ll~!_%<2pX zuD`w_l)*LTG%Qq3ZSDE)#dt-xp<+n=3&lPPzo}r2u~>f8)mbcdN6*r)_AaTYq%Scv zEdwzZw&6Ls8S~RTvMEfX{t@L4PtDi{o;|LyG>rc~Um3;x)rOOGL^Bmp0$TbvPgnwE zJEmZ>ktIfiJzdW5i{OSWZuQWd13tz#czek~&*?iZkVlLkgxyiy^M~|JH(?IB-*o6% zZT8+svJzcVjcE0UEkL_5$kNmdrkOl3-`eO#TwpTnj?xB}AlV2`ks_Ua9(sJ+ok|%b z=2n2rgF}hvVRHJLA@9TK4h#pLzw?A8u31&qbr~KA9;CS7aRf$^f1BZ5fsH2W8z}FU zC}Yq76IR%%g|4aNF9BLx6!^RMhv|JYtoZW&!7uOskGSGL+}_>L$@Jg2Vzugq-NJW7 zzD$7QK7cftU1z*Fxd@}wcK$n6mje}=C|W)tm?*V<<{;?8V9hdoi2NRm#~v^#bhwlc z5J5{cSRAUztxc6NH>Nwm4yR{(T>0x9%%VeU&<&n6^vFvZ{>V3RYJ_kC9zN(M(` zp?1PHN>f!-aLgvsbIp*oTZv4yWsXM2Q=C}>t7V(iX*N8{aoWphUJ^(n3k`pncUt&` ze+sYjo)>>=I?>X}1B*ZrxYu`|WD0J&RIb~ zPA_~u)?&`}JPwc1tu=OlKlJ3f!9HXa)KMb|2%^~;)fL>ZtycHQg`j1Vd^nu^XexYkcae@su zOhxk8ws&Eid_KAm_<}65zbgGNzwshR#yv&rQ8Ae<9;S^S}Dsk zubzo?l{0koX8~q*{uA%)wqy*Vqh4>_Os7PPh-maB1|eT-4 zK>*v3q}TBk1QlOF!113XOn(Kzzb5o4Dz@?q3aEb9%X5m{xV6yT{;*rnLCoI~BO&SM zXf=CHLI>kaSsRP2B{z_MgbD;R_yLnd>^1g`l;uXBw7|)+Q_<_rO!!VaU-O+j`u%zO z1>-N8OlHDJlAqi2#z@2yM|Dsc$(nc>%ZpuR&>}r(i^+qO+sKfg(Ggj9vL%hB6 zJ$8an-DbmKBK6u6oG7&-c0&QD#?JuDYKvL5pWXG{ztpq3BWF)e|7aF-(91xvKt047 zvR{G@KVKz$0qPNXK*gt*%qL-boz-*E;7LJXSyj3f$7;%5wj)2p8gvX}9o_u}A*Q|7 z)hjs?k`8EOxv1zahjg2PQDz5pYF3*Cr{%iUW3J+JU3P+l?n%CwV;`noa#3l@vd#6N zc#KD2J;5(Wd1BP)`!IM;L|(d9m*L8QP|M7W#S7SUF3O$GFnWvSZOwC_Aq~5!=1X+s z6;_M++j0F|x;HU6kufX-Ciy|du;T%2@hASD9(Z)OSVMsJg+=7SNTAjV<8MYN-zX5U zVp~|N&{|#Z)c6p?BEBBexg4Q((kcFwE`_U>ZQotiVrS-BAHKQLr87lpmwMCF_Co1M z`tQI{{7xotiN%Q~q{=Mj5*$!{aE4vi6aE$cyHJC@VvmemE4l_v1`b{)H4v7=l5+lm^ ztGs>1gnN(Vl+%VuwB+|4{bvdhCBRxGj3ady^ zLxL@AIA>h@eP|H41@b}u4R`s4yf9a2K!wGcGkzUe?!21Dk)%N6l+#MP&}B0%1Ar*~ zE^88}(mff~iKMPaF+UEp5xn(gavK(^9pvsUQT8V;v!iJt|7@&w+_va`(s_57#t?i6 zh$p!4?BzS9fZm+ui`276|I307lA-rKW$-y^lK#=>N|<-#?WPPNs86Iugsa&n{x%*2 zzL_%$#TmshCw&Yo$Ol?^|hy{=LYEUb|bMMY`n@#(~oegs-nF){0ppwee|b{ca)OXzS~01a%cg&^ zp;}mI0ir3zapNB)5%nF>Sd~gR1dBI!tDL z&m24z9sE%CEv*SZh1PT6+O`%|SG>x74(!d!2xNOt#C5@I6MnY%ij6rK3Y+%d7tr3&<^4XU-Npx{^`_e z9$-|@$t`}A`UqS&T?cd@-+-#V7n7tiZU!)tD8cFo4Sz=u65?f#7Yj}MDFu#RH_GUQ z{_-pKVEMAQ7ljrJ5Wxg4*0;h~vPUI+Ce(?={CTI&(RyX&GVY4XHs>Asxcp%B+Y9rK z5L$q94t+r3=M*~seA3BO$<0%^iaEb2K=c7((dIW$ggxdvnC$_gq~UWy?wljgA0Dwd`ZsyqOC>)UCn-qU5@~!f znAWKSZeKRaq#L$3W21fDCMXS;$X(C*YgL7zi8E|grQg%Jq8>YTqC#2~ys%Wnxu&;ZG<`uZ1L<53jf2yxYR3f0>a;%=$SYI@zUE*g7f)a{QH^<3F?%({Gg)yx^zsdJ3^J2 z#(!C3qmwx77*3#3asBA(jsL`86|OLB)j?`0hQIh>v;c2A@|$Yg>*f+iMatg8w#SmM z<;Y?!$L--h9vH+DL|Wr3lnfggMk*kyGH^8P48or4m%K^H-v~`cBteWvnN9port02u zF;120HE2WUDi@8?&Oha6$sB20(XPd3LhaT~dRR2_+)INDTPUQ9(-370t6a!rLKHkIA`#d-#WUcqK%pMcTs6iS2nD?hln+F-cQPUtTz2bZ zq+K`wtc1;ex_iz9?S4)>Fkb~bj0^VV?|`qe7W02H)BiibE9=_N8=(5hQK7;(`v7E5Mi3o? z>J_)L`z(m(27_&+89P?DU|6f9J*~Ih#6FWawk`HU1bPWfdF?02aY!YSo_!v$`&W znzH~kY)ll^F07=UNo|h;ZG2aJ<5W~o7?*${(XZ9zP0tTCg5h-dNPIM=*x@KO>a|Bk zO13Cbnbn7+_Kj=EEMJh4{DW<))H!3)vcn?_%WgRy=FpIkVW>NuV`knP`VjT78dqzT z>~ay~f!F?`key$EWbp$+w$8gR1RHR}>wA8|l9rl7jsT+>sQLqs{aITUW{US&p{Y)O zRojdm|7yoA_U+`FkQkS?$4$uf&S52kOuUaJT9lP@LEqjKDM)iqp9aKNlkpMyJ76eb zAa%9G{YUTXa4c|UE>?CCv(x1X3ebjXuL&9Dun1WTlw@Wltn3zTareM)uOKs$5>0tR zDA~&tM~J~-YXA<)&H(ud)JyFm+d<97d8WBr+H?6Jn&^Ib0<{6ov- ze@q`#Y%KpD?(k{if5-M(fO3PpK{Wjqh)7h+ojH ztb=h&vmy0tn$eA8_368TlF^DKg>BeFtU%3|k~3lZAp(C$&Qjo9lR<#rK{nVn$)r*y z#58_+t=UJm7tp|@#7}6M*o;vn7wM?8Srtc z3ZFlKRDYc^HqI!O9Z*OZZ8yo-3ie9i8C%KDYCfE?`rjrf(b&xBXub!54yaZY2hFi2w2asEOiO8;Hru4~KsqQZMrs+OhO8WMX zFN0=EvME`WfQ85bmsnPFp|RU;GP^&Ik#HV(iR1B}8apb9W9)Nv#LwpED~%w67o;r! zVzm@zGjsl)loBy6p>F(G+#*b|7BzZbV#E0Pi`02uAC}D%6d12TzOD19-9bhZZT*GS zqY|zxCTWn+8*JlL3QH&eLZ}incJzgX>>i1dhff}DJ=qL{d?yv@k33UhC!}#hC#31H zOTNv5e*ozksj`4q5H+75O70w4PoA3B5Ea*iGSqA=v)}LifPOuD$ss*^W}=9kq4qqd z6dqHmy_IGzq?j;UzFJ*gI5)6qLqdUL;G&E*;lnAS+ZV1nO%OdoXqw(I+*2-nuWjwM-<|XD541^5&!u2 z1XflFJp(`^D|ZUECbaoqT5$#MJ=c23KYpBjGknPZ7boYRxpuaO`!D6C_Al?T$<47T zFd@QT%860pwLnUwer$BspTO9l1H`fknMR|GC?@1Wn`HscOe4mf{KbVio zahne0&hJd0UL#{Xyz=&h@oc>E4r*T|PHuNtK6D279q!2amh%r#@HjaN_LT4j>{&2I z?07K#*aaZ?lNT6<8o85cjZoT~?=J&Xd35I%JJom{P=jj?HQ5yfvIR8bd~#7P^m%B-szS{v<)7i?#at=WA+}?r zwMlc-iZv$GT};AP4k2nL70=Q-(+L_CYUN{V?dnvG-Av+%)JxfwF4-r^Z$BTwbT!Jh zG0YXK4e8t`3~){5Qf6U(Ha0WKCKl^zlqhqHj~F}DoPV#yHqLu+ZWlv2zH29J6}4amZ3+-WZkR7(m{qEG%%57G!Yf&!Gu~FDeSYmNEkhi5nw@#6=Bt& zOKT!UWVY-FFyq1u2c~BJ4F`39K7Vw!1U;aKZw)2U8hAb&7ho|FyEyP~D<31{_L>RrCU>eEk-0)TBt5sS5?;NwAdRzRj5qRSD?J6 ze9ueq%TA*pgwYflmo`=FnGj2r_u2!HkhE5ZbR_Xf=F2QW@QTLD5n4h(?xrbOwNp5` zXMEtm`m52{0^27@=9VLt&GI;nR9S)p(4e+bAO=e4E;qprIhhclMO&7^ThphY9HEko z#WfDFKKCcf%Bi^umN({q(avHrnTyPH{o=sXBOIltHE?Q65y_At<9DsN*xWP|Q=<|R z{JfV?B5dM9gsXTN%%j;xCp{UuHuYF;5=k|>Q=;q zU<3AEYawUG;=%!Igjp!FIAtJvoo!*J^+!oT%VI4{P=XlbYZl;Dc467Nr*3j zJtyn|g{onj!_vl)yv)Xv#}(r)@25OHW#|eN&q7_S4i2xPA<*uY9vU_R7f};uqRgVb zM%<_N3ys%M;#TU_tQa#6I1<+7Bc+f%mqHQ}A@(y^+Up5Q*W~bvS9(21FGQRCosvIX zhmsjD^OyOpae*TKs=O?(_YFjSkO`=CJIb*yJ)Pts1egl@dX6-YI1qb?AqGtIOir&u zyn>qxbJhhJi9SjK+$knTBy-A)$@EfzOj~@>s$M$|cT5V!#+|X`aLR_gGYmNuLMVH4 z(K_Tn;i+fR28M~qv4XWqRg~+18Xb?!sQ=Dy)oRa)Jkl{?pa?66h$YxD)C{F%EfZt| z^qWFB2S_M=Ryrj$a?D<|>-Qa5Y6RzJ$6Yp`FOy6p2lZSjk%$9guVsv$OOT*6V$%TH zMO}a=JR(1*u`MN8jTn|OD!84_h${A)_eFRoH7WTCCue9X73nbD282V`VzTH$ckVaC zalu%ek#pHxAx=0migDNXwcfbK3TwB7@T7wx2 zGV7rS+2g9eIT9>uWfao+lW2Qi9L^EBu#IZSYl0Q~A^KYbQKwNU(YO4Xa1XH_>ml1v z#qS;P!3Lt%2|U^=++T`A!;V-!I%upi?<#h~h!X`p7eP!{+2{7DM0$yxi9gBfm^W?M zD1c)%I7N>CG6250NW54T%HoCo^ud#`;flZg_4ciWuj4a884oWUYV(#VW`zO1T~m(_ zkayymAJI)NU9_0b6tX)GU+pQ3K9x=pZ-&{?07oeb1R7T4RjYYbfG^>3Y>=?dryJq& zw9VpqkvgVB?&aK}4@m78NQhTqZeF=zUtBkJoz8;6LO<4>wP7{UPEs1tP69;v919I5 zzCqXUhfi~FoK5niVU~hQqAksPsD@_|nwH4avOw67#fb@Z5_OS=$eP%*TrPU%HG<-A z`9)Y3*SAdfiqNTJ2eKj8B;ntdqa@U46)B+odlH)jW;U{A*0sg@z>-?;nN}I=z3nEE@Bf3kh1B zdqT{TWJvb#AT&01hNsBz8v(OwBJSu#9}A6Y!lv|`J#Z3uVK1G`0$J&OH{R?3YVfk% z9P3HGpo<1uy~VRCAe&|c4L!SR{~^0*TbVtqej3ARx(Okl5c>m~|H9ZwKVHc_tCe$hsqA`l&h7qPP5xBgtwu!; zzQyUD<6J!M5fsV-9P?C9P49qnXR+iXt#G_AS2N<6!HZ(eS`|-ndb|y!(0Y({2 z4aF~GO8bHM7s+wnhPz>sa!Z%|!qWk*DGr)azB}j6bLe#FQXV4aO>Eo7{v`0x=%5SY zy&{kY+VLXni6pPJYG_Sa*9hLy-s$79$zAhkF)r?9&?UaNGmY9F$uf>iJ~u@Q;sydU zQaN7B>4B*V;rtl^^pa3nFh$q*c&sx^Um}I)Z)R&oLEoWi3;Yv6za?;7m?fZe>#_mS z-EGInS^#UHdOzCaMRSLh7Mr0}&)WCuw$4&K^lx{;O+?Q1p5PD8znQ~srGrygJ?b~Q5hIPt?Wf2)N?&Dae4%GRcRKL(a-2koctrcvxSslXn-k9cYS|<-KJ#+$Wo>}yKKh*3Q zHsK(4-Jv!9R3*FKmN$Z#^aZcACGrlGjOe^#Z&DfPyS-1bT9OIX~-I-5lN6Y>M}dvivbs2BcbPcaNH%25-xMkT$>*soDJ) z27;};8oCYHSLF0VawZFn8^H;hIN=J457@eoI6s2P87QN6O`q8coa;PN$mRZ>2Vv+! zQj1}Tvp8?>yyd_U>dnhx%q~k*JR`HO=43mB?~xKAW9Z}Vh2b0<(T89%eZ z57kGs@{NUHM>|!+QtqI@vE8hp`IIGc`A9Y{p?c;@a!zJFmdaCJ;JmzOJ8)B1x{yZp zi!U{Wh-h+u6vj`2F+(F6gTv*cRX7MR z9@?>is`MSS1L#?PaW6BWEd#EX4+O1x6WdU~LZaQ^Quow~ybz*aAu{ZMrQ;yQ8g)-qh>x z^}@eFu1u7+3C0|hRMD1{MEn(JOmJ|wYHqGyn*xt-Y~J3j@nY56i)sgNjS4n@Q&p@@^>HQjzNaw#C9=TbwzDtiMr2a^}bX< zZE%HU^|CnS`WYVcs}D)+fP#bW0+Q#l#JC+!`OlhffKUCN8M-*CqS;VQX`If78$as0 z=$@^NFcDpTh~45heE63=x5nmP@4hBaFn(rmTY2Yj{S&k;{4W!0Nu9O5pK30}oxM7{ z>l4cKb~9D?N#u_AleD<~8XD@23sY^rt&fN%Q0L=Ti2bV#px`RhM$}h*Yg-iC4A+rI zV~@yY7!1}-@onsZ)@0tUM23cN-rXrZYWF#!V-&>vds8rP+w0t{?~Q zT^LN*lW==+_ifPb+-yMh9JhfcYiXo_zWa`ObRP9_En3P))Qyu0qPJ3*hiFSu>Vt-j z<*HWbiP2#BK@nt<g|pe3 zfBKS@i;ISkorx@cOIx9}p^d8Gis%$)))%ByVYU^KG#eE+j1p;^(Y1ndHnV&YuQZm~ zj;f+mf>0ru!N`)_p@Ls<& z`t+JDx7}R568Q|8`4A}G@t8Wc?SOXunyW5C-AWoB@P>r}uwFY*=?=!K@J(!t@#xOuPXhFS@FTf6-7|%k;nw2%Z+iHl219Ho1!bv(Ee0|ao!Rs%Jl0@3suGrOsb_@VM;(xzrf^Cbd;CK3b%a|ih-fG)`Rd00O74=sQYW~Ve z#fl!*(fo~SIQ5-Sl?1@o7-E*|SK|hoVEKzxeg!$KmQLSTN=5N`rYeh$AH&x}JMR+5dq|~FUy&Oj%QIy;HNr;V*7cQC+ka>LAwdU)?ubI@W z={eg%A&7D**SIj$cu=CN%vN^(_JeIHMUyejCrO%C3MhOcVL~Niu;8WYoN}YVhb+=- zR}M3p|H0`E2Id99y#03r`8$s0t*iD>`^7EPm1~guC)L~uW#O~>I85Q3Nj8(sG<@T| zL^e~XQt9O0AXQ^zkMdgzk5bdYttP~nf-<831zulL>>ghTFii$lg3^80t8Gb*x1w5| zN{kZuv`^8Fj=t(T*46M=S$6xY@0~AvWaGOYOBTl0?}KTkplmGn-*P(X=o-v^48OY} zi11-+Y}y)fdy_tI;*W(>#qzvgQZ52t!nrGsJEy!c86TKIN(n|!&ucCduG$XaIapI z{(Z9gZANsI={A=5Aorgq2H25Dd}H5@-5=j=s{f`%^>6b5qkm_2|3g>r-^amf=B_xV zXg*>aqxXZ6=VUI4$})ypDMy$IKkgJ;V>077T9o#OhpFhKtHP_4mnjS5QCgGe<;~Xe zt<2ZhL7?JL6Mi|U_w?;?@4OD@=4EB2op_s)N-ehm#7`zSU#7itU$#%^ncqjc`9HCG zfj;O1T+*oTkzRi-6NN`oS3w3$7ZB37L>PcN$C$L^qqHfiYO4_>0_qCw0r@FEMj=>}}%q_`d#pUT;c?=gI zqTGpiY4Z;Q(B~#hXIVBFbi#dO=cOdmOqD0|An?7nMdrm2^C>yw*dQ=#lf8)@DvXK; z$MXp}QZgnE!&L73x0LZX_bCdD4lRY$$^?9dt1RwCng{lIpbb%Ej%yOh{@76yEyb}K zXZy%^656Sk3BLKbalcc>Dt5iDzo^tj2!wnDL(X;urJfpkWrab!frFSC6Q7m zuoqN!(t=L&+Ov&~9mz(yEB`MK%RPXS>26Ww5(F;aZ zR@tPAw~=q2ioOiynxgBqE&3-R-@6yCo0*mE;#I^c!=g~HyyjGA6}|<(0EseKDTM4w z94YnCO^VYIUY@}x8kr;;El-cFHVO<$6;-UdmUB|J8R*Wf$a37gVgYT|w5^KkYe=(i zMkA$%7;^a*$V+}e%S~&*^^O;AX9NLt@cIPc*v!lKZ)(zahAsUj%PJot19ErFU=Uk( z9Hw;Lb`V+BzVpMu;TGB9}y~ff)^mbEmF?g{{7_0SR zPgp*n)l{?>7-Ji;eWG{ln$)Bro+UJAQo6W2-23d@SI=HiFV3hR2OUcAq_9q~ye)o@ zq8WZvhg`H(?1AUZ-NM%_Cuj}eb{4wOCnqs^E1G9U4HKjqaw@4dsXWP#$wx^}XPZ0F zywsJ0aJHA>AHc^q#nhQjD3!KDFT6FaDioJ#HsZU7Wo?8WH19TJ%OMDz$XH5J4Cjdt z@crE;#JNG`&1H8ekB(R4?QiiZ55kztsx}pQti}gG0&8`dP=d(8aCLOExd*Sw^WL`Q zHvZ(u`5A58h?+G&GVsA;pQNNPFI)U@O`#~RjaG(6Y<=gKT2?1 z*pCUGU)f??VlyP64P@uT`qh?L03ZQyLOBn?EKwH+IG{XvTh5|NldaSV_n~DK&F1aa znq~C_lCQHMfW6xib%a2m!h&%J)aXb{%-0!HCcW|kzaoSwPMhJ6$KL|F~Sx(tctbwfkgV;#KZlEmJN5&l5XF9eD;Kqb<| z>os)CqC^qF8$be|v;)LY{Gh@c0?a??k7M7&9CH+-B)t&T$xeSzCs30sf8O-+I#rq} z&kZj5&i>UyK9lDjI<*TLZ3USVwwpiE5x8<|{Db z3`HX3+Tt>1hg?+uY{^wC$|Tb7ud@3*Ub?=2xgztgv6OOz0G z-4VRyIChHfegUak^-)-P;VZY@FT64#xyo=+jG<48n2%wcx`ze6yd51(!NclmN=$*kY=#uu#>=yAU-u4I9Bt0n_6ta?&9jN+tM_5_3RH);I zxTN4n$EhvKH%TmOh5mq|?Cx$m>$Ed?H7hUEiRW^lnW+}ZoN#;}aAuy_n189qe1Juk z6;QeZ!gdMAEx4Na;{O*j$3F3e?FLAYuJ2iuMbWf8Ub6(nDo?zI5VNhN@ib6Yw_4P)GY^0M7TJwat z2S*2AcP}e0tibZ@k&htTD&yxT9QRG0CEq$;obfgV^&6YVX9B9|VJf`1aS_#Xk>DFo zwhk?~)>XlP5(u~UW0hP7dWZuCuN4QM24Td&j^7~)WQ6YeCg)njG*ri}tTcG-NxX}p zNB>kcxd5ipW@tN3=6r@Jgm#rgrK*dXA!gxy6fAvP7$)8)Vc~PPQ|`( zPy|bG1sUz958-!zW^j(8ILV%QC@x`~PDFczboZqWjvSU<9O3!TQ&xYi%?Y0AiVBLV z%R?#1L#G&xw*RZPsrwF?)B5+MSM(b$L;GLnRsSU!_$N;6pD97~H}`c>0F`&E_FCNE z_)Q*EA1%mOp`z>+h&aqlLKUD9*w?D>stDeBRdR*AS9)u;ABm7w1}eE|>YH>YtMyBR z^e%rPeZzBx_hj?zhJVNRM_PX(O9N#^ngmIJ0W@A)PRUV7#2D!#3vyd}ADuLry;jdn zSsTsHfQ@6`lH z^GWQf?ANJS>bBO-_obBL$Apvakhr1e5}l3axEgcNWRN$4S6ByH+viK#CnC1|6Xqj& z*_i7cullAJKy9GBAkIxUIzsmN=M|(4*WfBhePPHp?55xfF}yjeBld7+A7cQPX8PE-|Pe_xqboE;2AJb5ifrEfr86k&F0+y!r`-urW}OXSkfz2;E``UTrGSt^B)7&#RSLTQitk=mmPKUKP`uGQ4)vp_^$^U`2Jjq zeul!ptEpa%aJo0S(504oXPGdWM7dAA9=o9s4-{>z*pP zJ31L#|L?YR;^%+>YRJrLrFC=5vc;0{hcxDKF z!ntmgO>rVDaGmRpMI7-+mv(j~;s_LARvcpkXj|{GHu1c<1 zKI)#7RE~Dizu1lG>p-PcY2jX#)!oJlBA$LHnTUWX=lu``E)vhf9h4tYL-juZ`e|Kb z=F?C;Ou)h^cxB;M-8@$ZSH0jkVD>x-XS$ePV1vlU8&CG))4NgU(=XFH=Jb1IB7dBysS+94}Y>sjS(&YcJwhn zifzA|g$D5rW89vkJSv()I+Th4R&C$g-!CB30xkh%aw4po3$@DK2fW>}enE2YPt&{C~j}`>RYICK{ zYAPfZ&%`R}u6MYo<>d`^O#Q(dM{3>T^%J{Vu;lr#Utg4x9!Z9J%iXs(j+dn&SS1_2 zzxGtMnu^`d%K4Xq4Ms-ErG3_7n?c(3T!?rvyW=G<7_XKDv*ox`zN*^BVwUoqh{D7o zdEiq;Zp6}k_mCIAVTUcMdH|fo%L#qkN19X$%b1#Oko|u4!M*oRqdBa3z98{H#g=d%5X&D#NXhLh`nUjxi8@3oo(AgeItdJ zIrt9ieHI1GiwHiU4Cba-*nK@eHI4uj^LVmVIntU@Gwf^t6i3{;SfLMCs#L;s;P4s5oqd^}8Uil!NssP>?!K z07nAH>819U=^4H6l-Dhy`^Q6DV^}B9^aR0B%4AH=D&+dowt9N}zCK+xHnXb-tsKaV6kjf;Wdp#uIZ_QsI4ralE>MWP@%_5eN=MApv92( z09SSB#%eE|2atm9P~X2W2F-zJD+#{q9@1}L2fF|Lzu@1CAJq*d6gA8*Jjb;<+Asih zctE|7hdr5&b-hRhVe}PN z$0G{~;pz1yhkbwuLkfbvnX=<7?b(1PhxAmefKn$VS6Sv)t-UypwhEs3?*E=(pc%Dlul1V~OdWvdf z{WBX?lhfO_g$$X~hm^Bhl@U0t<|beYgT)2L_C(z@B^-63c9Ak2*Aa)iOMylfl|qyNQdO#yoJ?m2FOkhZ1ou@G%+^m z#!#(gTv8nx^34(HddDp|dcFl@&eh+&FFJc@^FL3fV2?u&9Wt|Yp3&MS)e+ez0g~Ys zY7d0n^)+ z0@K^GJTLN?XAV(0F6e>o>HCGJU5(8WsSFErs0FsO=O1u$=T~xx7HYK{7C>-IGB8U+ z&G^Vy>uY}Bq7HX-X`U^nNh+11GjG-)N1l_tG<^4Tu4+4X9KO9IrdH+eXGk|G6Tc(U zU~g7BoO!{elBk>;uN-`rGQP-7qIf9lQhj-=_~0Qyszu>s$s0FrJatSylv!ol&{29~ z7S4fv&-UBOF&cR@xpuW*{x9$R;c_ALt?{+dI&HoBKG-!EY{yE=>aWhlmNhHlCXc(B zuA-zI*?Z9ohO$i8s*SEIHzVvyEF$65b5m=H*fQ)hi*rX8 zKlPqjD*Ix1tPzfR_Z3bO^n32iQ#vhjWDwj6g@4S?_2GyjiGdZZRs3MLM zTfl0_Dsn=CvL`zRey?yi)&4TpF&skAi|)+`N-wrB_%I_Osi~)9`X+`Z^03whrnP7f z?T`*4Id`J@1x#T~L(h5^5z%Cok~U|&g&GpCF%E4sB#i3xAe>6>24%Kuu=)=HRS;Pu2wghgTFa zHqm#sa{7-~{w_039gH0vrOm&KPMiPmuPRpAQTm5fkPTZVT&9eKuu%Riu%-oMQl2X6 z{Bnx`3ro^Z$}rVzvUZsk9T)pX|4%sY+j0i)If_z-9;a^vr1YN>=D(I7PX){_JTJ&T zPS6~9iDT{TFPn}%H=QS!Tc$I9FPgI<0R7?Mu`{FTP~rRq(0ITmP1yrJdy|m;nWmDelF-V^y7*UEVvbxNv0sHR?Q=PVYRuZinR(;RjVAG zm&qlSYvaiIbVEqBwyDaJ8LVmiCi{6ESF4pO?U&7pk&CASm6vuB;n-RauPFzdr!C%1 z8pjdSUts7EbA4Kg(01zK!ZU<-|d zU&jWswHnSLIg&mTR;!=-=~z(#!UsXt%NJR|^teM8kG@8Qg_0^6Jqfn&(eENtP8D7K zvnll3Y%7yh1Ai~0+l6dAG|lEGe~Oa+3hO>K2}{ulO?Vf*R{o2feaRBolc;SJg)HXHn4qtzomq^EM zb)JygZ=_4@I_T=Xu$_;!Q`pv6l)4E%bV%37)RAba{sa4T*cs%C!zK?T8(cPTqE`bJ zrBWY`04q&+On`qH^KrAQT7SD2j@C>aH7E8=9U*VZPN-(x>2a++w7R$!sHH+wlze2X)<<=zC_JJvTdY7h&Jum?s?VRV)JU`T;vjdi7N-V)_QCBzI zcWqZT{RI4(lYU~W0N}tdOY@dYO8Rx5d7DF1Ba5*U7l$_Er$cO)R4dV zE#ss{Dl`s#!*MdLfGP>?q2@GSNboVP!9ZcHBZhQZ>TJ85(=-_i4jdX5A-|^UT}~W{CO^Lt4r;<1ps@s|K7A z90@6x1583&fobrg9-@p&`Gh+*&61N!$v2He2fi9pk9W2?6|)ng7Y~pJT3=g~DjTcYWjY9gtZ5hk*1Qf!y2$ot@0St$@r8|9^GMWEE>iB~etL zXYxn#Rvc`DV&y93@U$Z91md1qVtGY*M(=uCc}@STDOry@58JNx`bUH}EIb(n6I}i? zSYJOZ2>B6&Payu+@V!gxb;)_zh-{~qtgVwQ-V;vK7e0^Ag_$3+g+{xSVudVOY_p-R z$sXhpFSk7je2lk5)7Y2;Z847E1<;5?;z(I)55YFtgF!J;NT|eVi}q^*2sM}zyM{+s zD0phl+J>k1E7cZEGmP?1-3~RE;R$q(I5}m?MX8xi?6@0f#rD8Cjkpv1GmL5HVbTnM zAQ&4-rbkpdaoLp~?ZoW>^+t0t1t%GO2B;ZD4?{qeP+qsjOm{1%!oy1OfmX?_POQJ4 zGwvChl|uE;{zGoO?9B_m{c8p(-;_yq?b^jA({}iQG35?7H7`1cm`BGyfuq7z1s~T| zm88HpS{z54T{jxC=>kZ=Z#8G@uya3tt0$xST5V$-V<;6MA66VFg}`LLU8L=q3DmkU z)P^X8pg`ndMY*>gr{6~ur^Q@Z8LNQf*6wkP03K<|M*+cDc#XKZ`Z0$1FkI-IDRw#| za52W4MyHlDABs~AQu7Duebjgc}02W;1jgBx&I@TMDXU`LJutQ?@r%1z`W zlB8G-U$q37G1ob>Er8j0$q@OU3IwG#8HsvJM#)j=Y%~#zY`jaG%5;!(kY3*a^t>(qf6>I zpAJpF%;FQ?BhDSsVG27tQEG*CmWhl4)Ngp%}D?U0!nb1=)1M==^B)^$8Li$boCY$S4U;G^A!?24nSYHra{< zSNapX#G+0BTac|xh`w&}K!);$sA3ay%^a2f?+^*9Ev8ONilfwYUaDTMvhqz2Ue2<81uuB71 zAl|VEOy%GQ7zxAJ&;V^h6HOrAzF=q!s4x)Mdlmp{WWI=gZRk(;4)saI0cpWJw$2TJcyc2hWG=|v^1CAkKYp;s_QmU?A;Yj!VQ1m-ugzkaJA(wQ_ zah00eSuJg<5Nd#OWWE?|GrmWr+{-PpE_Dbqs&2`BI=<%ggbwK^8VcGiwC-6x`x|ZY z1&{Vj*XIF2$-2Lx?KC3UNRT z&=j7p1B(akO5G)SjxXOjEzujDS{s?%o*k{Ntu4*X z;2D|UsC@9Wwk5%)wzTrR`qJX!c1zDZXG>-Q<3Z)7@=8Y?HAlj_ZgbvOJ4hPlcH#Iw z!M-f`OSHF~R5U`p(3*JY=kgBZ{Gk;0;bqEu%A;P6uvlZ0;BAry`VUoN(*M9NJ z%CU2_w<0(mSOqG;LS4@`p(3*Z7jC|Khm5-i>FcYr87};_J9)XKlE}(|HSfnA(I3)I zfxNYZhs#E6k5W(z9TI2)qGY&++K@Z?bd;H%B@^!>e2Wi@gLk)wC)T93gTxdRPU7uh z)`$-m(G2I5AuK52aj!fMJR|d^H?0X~+4xSpw zqNRtq5r8hic*{eAwUT<=gI5uXLg)o5mg4XnO^T+Rd+{l)<$Aqp{+RxhNYuX^45W0k z5$t%+7R;dX$`s6CYQYcims>5bNt+k&l_t%C9D-6sYVm%Y8SRC#kgRh*%2kqMg2ewb zp_X*$NFU%#$PuQ@ULP>h9Xw`cJ>J-ma8lU`n*9PcWFpE%x0^}(DvOVe2jz@ z0^2QOi0~t!ov?jI{#bw~`Aj5ymQW@eruRg`ZNJ5IT5_5AHbQ?|C>_7rwREf2e2x&L zlV8xdOkp_*+wdaqE?6bmdrFfaGepcj=0AI<+c=Tg^WB9BhFx?SvwoVdTEm&zPy@Vs zPs2mVPiw1n_h?Xi6!+w)ypsFXXuM>gIY(J+1N6r!sJ{+r1%BzRF20!D;bN>L^?O8n z(5|x2p^Q6X`!pm3!MMFET5`nJXn>tK`fFAj5Eo&t6;F>TU_4G93YGyzvF2_fB& zfE8(dq?R@@&Wh8~%G~rDt1+e)96O5)by_%;G~Zv`TpmZ)vY@BkAan*zEy(s`*{-@U z;$WPjoNx~m?`6Z;^O=K3SBL3LrIxfU{&g)edERkPQZK!mVYU-zHuV0ENDq^e<-?^U zGyRcrPDZZw*wxK(1SPUR$0t0Wc^*u_gb*>qEOP102FX|`^U%n*7z=wM@pOmYa6Z=-)T%!{tAFELY2`dTl3$&w! z7sgKXCTU(h3+8)H#Qov19%85Xo+oQh?C-q0zaM_X2twSCz|j_u!te3J2zLV#Ut_q7 zl+5LGx#{I`(9FzE$0==km|?%m?g~HB#BSz2vHynf1x14mEX^~pej*dhzD|6gMgOJ_ z8F_<>&OIz;`NSqrel?HI-K(|ypxwz}NtX!CF3&T(CkuYOnKS&%lUSU44KsgS`L>!w zl{MoT4`t=+p8>@88)Ea%*hOIkxt#b4RfrwRMr91UF_Ic~kV;|+dRW0a8Vl725+gsvtHr5 z>?3fai&9NmU|3;-nAu8OB|<(-2Kfub4MX&1i}dDd=R~Dk=U-Vr=@&lfEIYU~xtHHO z4TKt=wze`qm=69lD)sOOkZ;$9=0B#*g@X6xPM-%zG*rCXkN%eRDEUp$gAaEd29t&T zRTAg##Sk+TAYaa(LyTD__zL3?Z+45^+1o}(&f<~lQ*-z7`Um^>v@PKqOunTE#OyKFY^q&L^fqZgplhXQ>P3?BMaq6%rO5hfsiln7TppJ z>nG9|2MmL|lShn4-yz0qH>+o;Fe`V!-e*R0M|q~31B=EC$(bQZTW^!PrHCPE4i|>e zyAFK!@P}u>@hqwf%<#uv*jen5xEL|v!VQEK!F`SIz_H8emZfn#Hg}}@SuqPv+gJ@- zf3a`DT_Q#)DnHv+XVXX`H}At zmQwW2K`t@(k%ULJrBe6ln9|W8+3B*pJ#-^9P?21%mOk(W1{t#h?|j0ZrRi_dwGh#*eBd?fy(UBXWqAt5I@L3=@QdaiK`B_NQ$ zLXzm{0#6zh2^M zfu>HFK^d`&v|x&xxa&M|pr))A4)gFw<_X@eN`B1X%C^a{$39fq`(mOG!~22h)DYut z(?MONP1>xp4@dIN^rxtMp&a^yeGc8gmcajyuXhgaB;3}vFCQFa!pTDht9ld9`&ql`2&(dwNl5FZqedD^BP zf5K1`(_&i7x-&rD=^zkFD87idQrk(Y?E;-j^DMCht`A8Qa5J-46@G_*Y3J+&l{$}*QCATEc9zuzaQGHR8B;y*>eWuv)E##?Ba3w= zZ|v(l{EB`XzD#|ncVm#Wy?#Nzm3bS1!FJ70e{DGe$EgNDg7<_ic^mJSh&Xc|aTwCrTv;XkW~UlS&G%KyLklCn}F^i(YP(f z{cqH%5q9ND_S;l$HRP$Q@`D=F*_1$CXIA5X@|V&Vir$NQ$vCx!b&LGCR<-2y)m%HI zxeeyQIjiWcf4uD9+FP+EJ`&$oJ%$R(#w~GjqP|aTQj#d(;l#rq$vcM&Y4ZQ_i{Kpx z?k2BtoKb?+1-EVmG^ne-W%8+y?i#J5N5g8f^qpH5(ZZp7$u+?I9GB+&MREX?TmVV$ zA}Ps=^CkD^sD9N;tNtN!a>@D^&940cTETu*DUZlJO*z7BBy`Rl;$-D@8$6PFq@tz0 z=_2JMmq-JRSvx`;!XM|kO!|DENI-5ke8WR*Zj#vy#Nf1;mW-{6>_sCO8?sVWOKDM| zR(iaZrBrzlRatUzp_Y|2nOXnY2G%WLGXCo9*)th_RnXvXV=q;WNAimI98!A54|$&OCCG%$4m{%E&o?S|Qx<4K~YGmM1CS!vZAzLN%d znbZsw6ql=XkiwSbNofNeA42q8#LH6Rk(u@z172O#6K>Sb{#`t#GUgpd{2;D(9@I_9 zwsY(6Go7RmOThs2rM3|Z#Vbs}CHPLgBK6gE8;XkJQDx~p5wJ?XkE(0<^hwnt6;$~R zXCAzMfK@`myzdkkpv*ZbarVwCi&{-O#rswrb-#x4zRkxfVCq;mJLic|*C92T?0CYv z)FCqY$xA(QZmggPocZqQj0Rc?=Afna`@fpSn)&nSqtI}?;cLphqEF3F9^OZfW9@HDunc^2{_H)1D9(O}4e zJMi_4(&$CD{Jf5&u|7#Iq*F~)l!8pAzNrX^<&wfEu~}Ipslzx=g^ff2?B9SnV=!$ zv&K0`hMN6BVIusHNX-lr`#K?OG1S*S4rCQaI3ea(!gCl7YjxJ3YQ)7-b&N*D8k><*x|47s3; z4f~WTWuk|Qd*d*DICV}Vb0YSzFZp5|%s4}@jvtTfm&`|(jNpajge zD}@CMaUBs+b?Yu6&c#18=TxzMCLE76#Dy=DLiq_a_knQX4Uxk$&@3ORoBFK_&a>`QKaWu^)Hzrqz{5)?h3B_`4AOn{fG9k zEwnjQb>8XRq!k?rmCd6E**1cY#b9yczN4mD%GLCeRk}{TmR1*!dTNzY;(f!B0yVuk zSjRyf;9i@2>bdGSZJ=FNrnxOExb075;gB z*7&YR|4ZraFO#45-4h%8z8U}jdt?83AmU3)Ln#m3GT!@hYdzqqDrkeHW zU#R`Z8RHq996HR=mC}SRGtsz07;-C-!n*ALpwwBe~loM)YqMH)Um$sH0RbTTzxFd)h1=-w5Yl3k|3nQ zZG>=_yZ7Lsn=b8_MZI+LSHLGYSSCc?ht~7cv#39>Moz6AS}5 zus?xge0PGdFd2FpXgIscWOyG}oxATgd$yl0Ugf_&J_vwt`)XWx!p*gE_cWU(tUTnz zQS}!bMxJyi3KWh^W9m zxLcy``V@EfJzYjK@$e7Yk=q!kL8cd3E-zpc*wwvGJ62O!V;N zFG7Y?sJ+^a%H1;rdDZRu2JmGn6<&ERKes=Pwx)GG-nt73&M78+>SOy!^#=gvLB)2H zjv!J0O`-zft|0Jv$3k5wScY)XB+9leZgR5%3~HtZA=bCg7=Dn+F}>2lf;!*1+vBtf z9jhmqlH=t5XW{0MC7Y~O7jaju&2`p!ZDLGlgnd~%+EJ%A#pIByi-+EOmoLVoK&ow8 zTDjB%0hxhiRv+O3c2*y00rMA=)s|3-ev7emcbT43#izku7dvaDXy1IMV0ahjB9yzi z9C9fN+I2Mzt1*{`a6B?+PdWHiJ5fH}rb2t>q)~3RfCxmyK^y5jN7Pn(9DFh61GO%p zuBErj=m|bDn_L8SINU)Z&@K*AgGz+SUYO_RUeJt=E0M+eh&kqK;%Y1psBNU<4-s9# ziHFr7QP6Ew=-2CdfA#Bf|EsctH;<&=Hsd>)Ma8NvHB$cpVY@}TV!UN}3?9o@CS5kw zx%nXo%y|r5`YOWoZi#hE(3+rNKLZ2g5^(%Z99nSVt$2TeU2zD%$Q(=$Y;%@QyT5Rq zRI#b><}zztscQaTiFbsu2+%O~sd`L+oKYy5nkF4Co6p88i0pmJN9In`zg*Q;&u#uK zj#>lsuWWH14-2iG z&4w{6QN8h$(MWPNu84w1m{Qg0I31ra?jdyea*I~Xk(+A5bz{x%7+IL}vFDUI-Rf{! zE^&Dau9QxA2~)M98b42(D6Q}2PUum0%g>B?JS?o~VrP+Go2&c-7hIf7(@o1*7k$zS zy@o5MEe8DoX$Ie(%SZByyf9Xf9n8xkoX}s6RiO1sg*kAV^6EAAz$>*x^OmIy!*?1k zG+UQ|aIWDEl%)#;k{>-(w9UE7oKM#2AvQud}sby=D7$l6{$}SE8O9WgHM_+ zJ?tHeu@Pi93{AuwVF^)N(B~0?#V*6z;zY)wtgqF7Nx7?YQdD^s+f8T0_;mFV9r<+C z4^NloIJIir%}ptEpDk!z`l+B z5h(k$0bO$VV(i$E@(ngVG^YAjdieHWwMrz6DvNGM*ydHGU#ZG{HG5YGTT&SIqub@) z=U)hR_)Q@#!jck+V`$X5itp9&PGiENo(yT5>4erS<|Rh#mbCA^aO2rw+~zR&2N6XP z5qAf^((HYO2QQQu2j9fSF)#rRAwpbp+o=X>au|J5^|S@(vqun`du;1_h-jxJU-%v| z_#Q!izX;$3%BBE8Exh3ojXC?$Rr6>dqXlxIGF?_uY^Z#INySnWam=5dV`v_un`=G*{f$51(G`PfGDBJNJfg1NRT2&6E^sG%z8wZyv|Yuj z%#)h~7jGEI^U&-1KvyxIbHt2%zb|fa(H0~Qwk7ED&KqA~VpFtQETD^AmmBo54RUhi z=^Xv>^3L^O8~HO`J_!mg4l1g?lLNL$*oc}}QDeh!w@;zex zHglJ-w>6cqx3_lvZ_R#`^19smw-*WwsavG~LZUP@suUGz;~@Cj9E@nbfdH{iqCg>! zD7hy1?>dr^ynOw|2(VHK-*e%fvU0AoKxsmReM7Uy{qqUVvrYc5Z#FK&Z*XwMNJ$TJ zW1T**U1Vfvq1411ol1R?nE)y%NpR?4lVjqZL`J}EWT0m7r>U{2BYRVVzAQamN#wiT zu*A`FGaD=fz|{ahqurK^jCapFS^2e>!6hSQTh87V=OjzVZ}ShM3vHX+5IY{f^_uFp zIpKBGq)ildb_?#fzJWy)MLn#ov|SvVOA&2|y;{s;Ym4#as?M^K}L_g zDkd`3GR+CuH0_$s*Lm6j)6@N;L7Vo@R=W3~a<#VxAmM&W33LiEioyyVpsrtMBbON+ zX^#%iKHM;ueExK@|t3fX`R+vO(C zucU#Xf>OjSH0Kd%521=Sz%5Y!O(ug(?gRH@K>IUayFU~ntx`Wdm27dB-2s@)J=jf_ zjI-o;hKnjQ|Lg~GKX!*OHB69xvuDU zuG-H48~inKa)^r539a{F)OS`*4GShX>%BR)LU~a-|6+sx&FYsrS1}_b)xSNOzH|Kv zq>+1-cSc0`99EsUz(XWcoRO)|shn>TqKoQBHE)w8i8K`*Xy6(ls%WN_#d}YC^)NJ; zzl8!Zduz^Gg8*f0tCWnLEzw6k5Fv!QWC1x4)3r}+x~@#O8_)0>lP-@3(kFwLl%%Mz(TpATVnL5Pl2Gahw45QXI~>Hrw))CcEs@PP?}4^zkM$ z@(?H6^`Jl?A=(&Ue;W0`*a8&fR7vde@^q^AzX^H#gd~96`Ay^_A%?;?@q@t7l7iGn zWms#2J|To4;o1?3g3L!K_chdtmbEg~>U>$5{WO@Ip~YE&H($(^X6y_OBuNHkd0wu= z4rXGy#-@vZ?>M<_gpE8+W-{#ZJeAfgE#yIDSS?M?K(oY@A|FaS3P;OjMNOG% zGWyZWS(}LJCPaGi9=5b%sq$i!6x@o(G}wwfpI5|yJe24d_V}cT1{^(Qe$KEMZ;>I@ zuE6ee%FLgem>CKEN8SeY)fpK#>*lGcH~71)T4p|9jWT;vwM@N!gL}nCW=Oi6+_>K2 zl4sWXeM1U}RETA~hp=o3tCk+?Zwl#*QA>Wwd|FlUF0)U;rEGPD1s0Syluo zfW9L(F>q9li8YKwKXZrp*t)N9E;?&Hdbm-AZp2BcDTHO6q=tzVkZsozEIXjIH`tm} zo2-UleNm*Lj7zgvhBph_|1IggkSuW~S(9ueZEfao8BuzqlF(a+pRivTv(Zb zXFaHwcuovdM#d+!rjV7F<^VW&@}=5|xj!OUF)s0zh|8yzC)7!9CZB+TLnycoGBsDF z$u&j={5c(4A$iik;x6_S96Krw8--+9pGY+*oSVTIuq;$z8*)W8B~rMX_(U6uM}!Gc`T;WfEKwI84%)-e7j}>NA(O_)3Vn9 zjXxY1Fnx3Fx%CFpUHVu0xjvxgZv}F9@!vC!lD|05#ew3eJ}@!V&urwRKH`1f{0e^o zWvM1S@NbI6pHdzm33pza_q;#?s%J*$4>10uYi4l%5qi|j5qh+D=oqSJR=7QwkQh>>c$|uJ#Z@lK6PMHs@ zyvnnoOSkGQkYz#g>||xN&1fV)aJb*y--Y`UQV~lt!u8yTUG59ns1l7u>CX2F>9fl; zB)zH3z^XHmSU{F_jlvESvaNL&nj^;j)29~1LcTYw>(6}>bt0hiRooqm0@qTj%A&P9 zKmexPwyXG@Rs1i+8>AJ;=?&7RHC7Mn%nO>@+l?Qj~+lD376O2rp)>tlVHn8MKq zwop1KRLhUjZ|+6ecGIAftSPT*3i94=QzYCi_ay+5J&O(%^IsqZ!$w-^bmd7ds$^!q z;AkC;5mTAU>l0S$6NSyG30Ej?KPq@#T)^x#x?@U~fl2m$Ffk)s6u|iPr!)-j0BlA7p3E*A|My8S#KH;8i-IQq7Q*F4*ZVPe<{^SWz_ zr?!6cS+@|C#-P~d#=W1n7acn8_pg#W-lcyf+41zwR+BU6`jUkP^`*wgX)FxEaXzoi z8)?FE*97Yqz|b@fR1(r{QD363t260rQ(F||dt9^xABi+{C*_HL9Zt5T;fq|#*b}=K zo5yj_cZB(oydMAL&X(W6yKf>ui?!%(HhiHJ83EA|#k0hQ!gpVd( zVSqRR&ado+v4BP9mzamKtSsV<|0U-Fe2HP5{{x&K>NxWLIT+D^7md{%>D1Z-5lwS~ z6Q<1`Hfc+0G{4-84o-6dr@)>5;oTt|P6jt9%a43^wGCslQtONH)7QXJEYa!c~39 zWJpTL@bMYhtem1de>svLvOUa*DL7+Ah0(_~2|ng`!Z!qiN}6xL;F}<%M8qWv&52-Y zG*1A&ZKlp~{UFV%Hb_*Re({93f7W*jJZMV-Yn|<+l3SPN+%GuPl=+tSZxxr%?6SEc zntb0~hcK691wwxlQz_jSY+V_h+0o`X!Vm{;qYK$n?6ib1G{q>a%UejzOfk6q<=8oM z6Izkn2%JA2E)aRZbel(M#gI45(Fo^O=F=W26RA8Qb0X;m(IPD{^Wd|Q;#jgBg}e( z+zY(c!4nxoIWAE4H*_ReTm|0crMv8#RLSDwAv<+|fsaqT)3}g=|0_CJgxKZo7MhUiYc8Dy7B~kohCQ$O6~l#1*#v4iWZ=7AoNuXkkVVrnARx?ZW^4-%1I8 zEdG1%?@|KmyQ}tploH>5@&8Cp{`)CxVQOss&x|Z7@gGL3=tCVNDG!N9`&;N$gu^MDk|`rRm=lhnXAJ5v1T)WTz)qvz|Dw zR?{}W4VB(O6#9%o9Z^kFZZV*PDTAWqkQ8TH!rti8QIcR&>zcg3qG}&A( zwH^K8=`1C1lRfhrX{IvNn9R9!$UMC%k(;;VH%`S0h_on|Gh6qDSH&#}*m-u{;p~WB zF$_I~xx!RxVrxNQdr@3T>{F#^D{@N9OYC9LsV62F_Z1KYQ5yk*C5WQ4&q}Kz(I{9UWWf?LIcCZicB1EO_FUH*a9QKS(4IR%#D5DTi_@M}Q_-4)J4d zz@!vR0}5MPAOK(#uL+$7XOcP$5SS#*EK9Rt6XN%}HB7@`8S^gNRk!HLv(CvCjX4o= z>9scPwWbE!F8T=@x9^;s-OF2!eO(!gL9$-AmzUiDnu&QS4If5ea2T070n1-IyNhck z9$J8b!he3@q5qB-cQ;5ymVIXXn46kK0sqKZV+3s3^mac=3~BrCW})WNrrRs1KtMmg zLzwXYC?@_H#s3W4D$W0rh%WL|G<1$$uYdptPbxy0ke!c%v#x9I=2?S)YVkg1X$W^cB!i>B{e9wXlm8AcCT8|verIZQngj>{%W%~W0J%N`Q($h z^u3}p|HyHk?(ls7?R`a&&-q@R<94fI30;ImG3jARzFz<(!K|o9@lqB@Va+on`X2G) zegCM8$vvJ$kUwXlM8df|r^GQXr~2q*Zepf&Mc%kgWGTf;=Wx%7e{&KId-{G}r22lI zmq%L6Y-M*T$xf8 z#kWOBg2TF1cwcd{<$B)AZmD%h-a6>j z%I=|#ir#iEkj3t4UhHy)cRB$3-K12y!qH^1Z%g*-t;RK z6%Mjb*?GGROZSHSRVY1Ip=U_V%(GNfjnUkhk>q%&h!xjFvh69W8Mzg)7?UM=8VHS* zx|)6Ew!>6-`!L+uS+f0xLQC^brt2b(8Y9|5j=2pxHHlbdSN*J1pz(#O%z*W-5WSf# z6EW5Nh&r<;$<3o1b013?U$#Y!jXY)*QiGFt|M58sO45TBGPiHl4PKqZhJ|VRX=AOO zsFz-=3$~g#t4Ji9c;GFS9L~}~bzgCqnYuJ-60AMDdN7HZt8_$~Of{oXaD3HVn9zkH z`>#xQNe=YpWTq_LcOoy}R`L<_4il7w4)QH4rl?AUk%?fH##I>`1_mnp&=$-%SutYT zs}sSNMWo;(a&D()U$~PG0MvZ#1lmsF&^P4l_oN#_NORD-GSmR{h_NbJ^ZdY#R9#qW zKAC%V*?y~}V1Zh#d|-z1Z8sy5A+}*cOq$xk@Pn&{QffzG-9ReyPeEhqF%~Z3@|r(s z3(wA&)dV~fELW*&*=!~l9M=7wq8xE(<@)BjjN8bUiS8@N9E{wi+Dd!V1AtT;Nl}9> zTz`2ge2Jn#Dlg1kC%oFlOe<>?jYC`Asr^%i4hH;S`*qZTPRan2a9Kjj=0aq{iVi2Z z87PZt$d(LAm_{92kl+2Z%k3KGV;~gsp;C>k?gMYZrVIzaI|0D+fka9G_4v>N96*8T zI(C8bj?A7l%V&U?H_IpSeCvf7@y1e?b>G7cN382GVO0qAMQ93(T*<*9c_;%P1}x2l zi8S$s<=e_8ww%DaBAf4oIQ7}U7_48$eYpo}Fb+F|K|43IAPR1y9xbqPPg6er{I7xj|=>-c%pGBRLn1~=5KbAb1mJAx=z(loN!w{49VkEthF>*OX z)=gqXyZB5%5lIWYPWh~{!5pSt43-)-@L@x=pmiuKP-3Cwq8qSxGNwaTT4->BWEjxk zUjr)z7WrBZB5u3iV>Y_>*i~*!vRYL)iAh5hMqNzVq1eeq=&d9Ye!26jks{f~6Ru&c zg$D;^4ui#kC`rSxx`fP!zZ^6&qSneQzZRq0F*V4QvKYKB<9FC%t#)Tik%Zq*G*IOW z3*`2!4d)!3oH>GxVcXlorJDt+JnH)p{~olYBPq|>_V@8=l#(f*diW=L+%>rfWCcPQ z#H^ksQt15Z5Uc4ODq8_JwD5^H&OGqyH6E@MabJQO>s`?bqgA6}J_QpytW{2jH#eCN z8k7y*TFZ2lj2B|1CB(@QZedFfPhX|IQbKMI;$YK>9Zla0fsU7}an6(kP;sXpBWLR` zJ#z_kk!`JJC7h(1J!+G)gL2WB2&0*~Q!%s??}GH?=`hU@03xOwU} z6s7?tGySLz!%(MwxQRiF)2(vR2wQX`YB}u&I-S+RR)LQcyH407#-{*pWLJJR?X|5 zsAl2k{&0N-?JArn@)9YTo-5+gl}R~XkbZM*5AOjPrcikpE3P?p0oN^?H+5+n)}Qxe z*RQ!-eu0RxPyF8B=}xnseNpQMXFU$d^=(G%kUd&|!BHSm7bXoGR$WA+%yjuA{|S>u z?9N6JDhS+ui~rd?wY_t7`p)|qKIMM>6jz%$jv4hc_YUDjF6-%5muq|SNuoji2)|qK zNY5+oWMe+5vu{I*grk6xlVk;(J)uuy13G`VDbj(~Vz9lA)_;$aj?=-cmd#h~N0mn{ z9EIS_d4C=L3H;Pl^;vcpb&-B+)8vt%#?gn5z>#;G{1L&8u8cXJYADMUsm9>%*%)&F zsi&I{Y=VUsV82+)hdNgDWh^M7^hMs|TA0M269^|RIGfdX1MetV2z`Ycb&_Mn4iRI! zeI6O}O9mOhN6pzfs5IfMz#Gxl`C{(111okA8M4gijgb~5s7QTyh84zUiZZ^sr1^ps z1GO`$eOS@k@XP^OVH|8)n}Wx)fKHoGwL&5;W?qEf5Jdsd!3hf7L`%QNwN0gGBm^2= z@WI+qJMJG1w2AS9d@Dt$sj_P$+S2kh7+M72^SfcdBjQEtWQ5?PT&a~G9hOo6CtS>h zoghqoR;sk{X)`ZK-M|lu{M}0>Mrs^ZW@ngC?c$26_vYKDBK^n7sFiod_xV#XcPL!^ zRPyqD{w^9u{oA3y73IW0 zH;%xop$r(Q=bq=JaLT%myEKD_2&?L@s6TzsUwE#g^OkiU6{lN)(7I?%a;_%r5_^@d zS-Z)Q-2o|~?F~f`sHlhNhiZk;!CW;3Ma6{xPlBjJx8PXc!Oq{uTo$p*tyH~ka`g<` z;3?wLhLg5pfL)2bYZTd)jP%f+N7|vIi?c491#Kv57sE3fQh(ScM?+ucH2M>9Rqj?H zY^d!KezBk6rQ|p{^RNn2dRt(9)VN_j#O!3TV`AGl-@jbbBAW$!3S$LXS0xNMr}S%f z%K9x%MRp(D2uO90(0||EOzFc6DaLm((mCe9Hy2 z-59y8V)5(K^{B0>YZUyNaQD5$3q41j-eX))x+REv|TIckJ+g#DstadNn_l~%*RBSss_jV3XS&>yNBc8H2jo(lwcLz-PuYp< z7>)~}zl$Ts0+RFxnYj7-UMpmFcw_H zYrsXM>8icD)@Iauiu_(Y#~Iyl)|pj@kHkWvg2N$kGG(W>Y)nfNn%z2xvTLwk1O2GQ zb^5KAW?c%5;VM4RWBy}`JVCBFOGQWoA9|+bgn7^fY3tSk1MSZccs9&Fy6{8F>_K@? zK(z=zgmq1R#jGE^eGV`<`>SP9SEBx!_-Ao|VZq6)-rUpd^<2GgVN&uHiM{0zA9kI( z<1^1%*uE$?4mXV@?W8}fvnBOpfwCo^?(a0E402!pZi&Kd5pp$oV%2Ofx<}YC-1mynB3X|BzWC_ufrmaH1F&VrU&Gs+5>uixj*OJ*f=gs9VR8k^7HRR$Ns|DYBc*Slz>hGK5B1}U+}#j0{ohGC zE80>WClD5FP+nUS?1qa}ENOPb2`P4ccI<9j;k?hqEe|^#jE4gguHYz-$_BCovNqIb zMUrsU;Fq%n$Ku_wB{Ny>%(B&x9$pr=Anti@#U%DgKX|HzC^=21<5Fn6EKc#~g!Mcj zJrI(gW+aK+3BWVFPWEF*ntHX5;aabHqRgU-Nr2t++%JRPP7-6$XS|M8o&YSgf3a9A zLW*tSJxoe1?#T4EocApa*+1kUIgy7oA%Ig9n@)AdY%)p_FWgF-Kxx{6vta)2X1O5y z#+%KQlxETmcIz@64y`mrSk2Z17~}k1n{=>d#$AVMbp>_60Jc&$ILCg-DTN~kM8)#o$M#Fk~<10{bQ>_@gU2uZE z*eN~mqqQC*wh{CI(!xvRQ^{jyUcvE~8N)S0bMA^SK@v;b7|xUOi63X~3Qc>2UNSD1) z7moi9K3QN_iW5KmKH>1ijU41PO>BvA6f1;kL)6io%^r>?YQ#+bB;)Rzad5;{XAJGeAT#FnDV0$w2>v|JeFIB zZ>8vmz?WVs78PuCDiHfb@D0Yi;2#%){*#?bY4dpta6dSjquGLcOw?Z{nxg98mN^4* zj&^!WMUQ_zFp+}B|G0vcNsk8(2u9(LAPk5ogKt%zgQ4^1#UCd;`-W#X8v{YyQ_m9g z8`jydw>>@1J{Q*q#5^cHVA~xR9LR3Hl@^bx)`IBKmj+Gmye36;xwL0>sS|mV+$~%b zC;2wEm&Ht3#6P|2Y0XQ+5t-aI)jn{o%&ZHWvjzEtSojFgXxNKO^e(RmM`gsJ4GrR8 zKhBtBoRjnH`mD$kT;-8ttq|iw?*`7iTF_AX<^Qe3=h8L^tqz$w$#Z@Z$`C579Jeeu ztr0z~HEazU&htfG@`HW!201!N(70hCd{%~@Wv)G*uKnJZ8>hFx`9LnYs;T>8p!`5T zx#aXXU?}B{QTV_Ux(EMzDhl-a^y^f5tRU;xnOQoN)pThr4M>-HU)As8nQ34-0*sab&z<2ye-D_3m&Q`KJJ|ZEZbaDrE%j>yQ(LM#N845j zNYrP)@)md;&r5|;JA?<~l^<=F1VRGFM93c=6@MJ`tDO_7E7Ru zW{ShCijJ?yHl63Go)-YlOW2n3W*x%w||iw(Cy>@dBJHdQl){bBVg{wmRt{#oXb9kaWqe{bJPmGE$$ z_0=cmD9dVzh<8&oyM8rK9F^bufW$Bj2cFhw&f*oKKyu$H{PI=Aqe^NL6B=dkMEAk& zE3y&F=x;e|!7kMn%(UX>G!OE$Y$@UyME#d;#d+WLmm@W@y!sboiIox^DZPB|EN<>7 z57xm5YWlFUGyF|{<*;b&Cqm+|DC8{rB9R@2EFHGL^NX*l#AcDpw6}bCmhY7!(Gv{s zm^eYNvzyJLQA#GhmL*oSt^Uulb5&ZYBuGJTC>Vm9yGaZ=Vd--pMUoDRaV_^3hE9b*Pby#Ubl65U!VBm7sV}coY)m zn1Ag^jPPLT93J{wpK%>8TnkNp;=a@;`sA7{Q}JmmS1bEK5=d@hQEWl;k$9M-PYX~S zayGm;P(Wwk23}JR7XM~kNqba`6!Z+Wt2|5K>g_j3ajhR>+;HF?88GBN!P; zr6sQ8YYpn%r^gbi8yYK7qx6U5^Tf<|VfcR$jCo`$VMVh_&(9w@O?|o3eRHq*e*#P z8-==G)D?vB3Zo~b-dkx8lg0^=gn`9FUy?ZzAfWQd>>@cyqF!sHQ_S&@$r&tTB~Lxq zAjAZTK~?J{A|L3)8K>S{`Qf%131B>?<~t=w!D{;olQ>#31R#{go`a9DOy+H*q5t+; z^*Ka!r@#8tk?~tQbylaG-$n#wP2VzIm3vjrZjcmTL zl`{6mhBhMKbSWoGqi;g3z1@G0q!ib`(Zz_o8HG_*vr8U5G|vhZn26h`f~bO&)RY0; zw(CWk*a_{ji_=O9U}66lI` zCm32)SEcAo5)5k>{<8DLI@Zz)*R29BB!^wF;WZRF9sAi39BGObmZzg?$lUn6w1rYPHSB^L4^AN zLObEaUh7TXpt6)hWck#6AZV(2`lze<`urGFre|>LUF+j5;9z%=K@&BPXCM)P$>;Xc z!tRA4j0grcS%E!urO^lsH-Ey*XY4m&9lK(;gJOyKk*#l!y7$BaBC)xHc|3i~e^bpR zz5E-=BX_5n8|<6hLj(W67{mWk@Bfc){NGAX z5-O3SP^38wjh6dCEDLB#0((3`g4rl}@I(&E8V2yDB=wYhSxlxB4&!sRy>NTh#cVvv z=HyRrf9dVK&3lyXel+#=R6^hf`;lF$COPUYG)Bq4`#>p z@u%=$28dn8+?|u94l6)-ay7Z!8l*6?m}*!>#KuZ1rF??R@Zd zrRXSfn3}tyD+Z0WOeFnKEZi^!az>x zDgDtgv>Hk-xS~pZRq`cTQD(f=kMx3Mfm2AVxtR(u^#Ndd6xli@n1(c6QUgznNTseV z_AV-qpfQ0#ZIFIccG-|a+&{gSAgtYJ{5g!ane(6mLAs5z?>ajC?=-`a5p8%b*r*mOk}?)zMfus$+W~k z{Tmz9p5$wsX1@q`aNMukq-jREu;;A6?LA(kpRut+jX?Tt?}4HGQr}7>+8z4miohO2 zU4fQ?Y8ggl%cj&>+M+)TTjn8(?^%`~!oAt#ri8gIbzIig$y#d7o##077fM9sCu%N9 zOIsq4vyox6`itu*j{eOD<$gTZd-$JuyM^cM>{?v<8# zS1yN%R0zRy&>+D*Gv-&S80?JF+Y|c^^IJWDnfy06MI2{NFO-x4JXsb@3Qp;EnL!a{ zJwKwV@mO zYVGvNmeJ!;+ce+@j@oo-+`DaPJX|h@7@4BD`QEdP?NKkYzdIa3KrZt%VUSsR+{b+| zk?dSd#9NnVl?&Y$A{-OtZ>wk%mWVF5)bf`)AA2{EFapIS4jil69Xan>*J^6Juou&`oJx|7-&|@8z?$ z2V#jm!UHstCE*qM{OGtqYY8q+x%SL6&aGY!a>@d=_G~^0;+7dY9P`oJ*)67*9Kx*O zKitC5V3g5;&L-fa37?eN=;V_c^L-ph_uKv5)Q`&!Z!RPlDWA2{J%a2q@_*?-cn@bH zIt)+mA@HaJj2RV+-MNc#y#Vji*N~m!ZyrYyg-7UK4PYK4F7Y$3Y%@Lk6iPp=I96N> z!;ih(KtZMB23*v{`5cJ}^4D*P!k1&OfU&1%borv_q|7jfaV7fL+wwx8Zp*b}B_O>NRSeJeM zpvw3M`=vSYjFYQ11kx1xqOnJ@degPh&SyXnWz-l719EiW17Yo?c~Bh~;R$MOl+jzV zM1yTq-1**x-=AVR;p0;IPi`#=E!G5qIT>EFE`Bn<7o*8!aVd7?(CZT=U9^Gi3rmWUQG z0|GaP9s$^4t_oLCs!fInyCoB(d?=tZ%%Bb2Y+X&7gvQ6~C4kU%e$W_H;-%XSM;&*HYYnLI z>%{5x_RtSUC~PI4C0H^>O%FixKYVubA>#72wexd}Cgwuw5ZYTvcN2ywVP(dO=5975 zCjo)mOa2Bo&ucEsaq8wi1{h*brT(H=XrTOy*P>?0%VV1QDr09X+Je!T)JT`02?gjX zT@B8}h|;4lH35Guq2gKZT?ags-~Ts~S=poPnQ_T1*?U|{$jaur_PjQ6WmF_(XLFG)d#|iiBC=&B zp}1eOQvQ!3UpL?K`=8hAzMkv#a^COr`J8i}d!BPX&*xp-LL#qse~mOtxI-}{yPRNV zJNTL1{7A55F~K>0e&Os%MwQ~?n1>QV=j!8o_`^-&*E|Q-L9DNr%#6sw8kQVE3E|*}$aAoO$@27ei1w=+zU%?AA!;mf#!%IV*w_D=u516!Kz1F0-WnyVB`I6F1Pc3r1=0iT<_(pCyk>@22z1$w$@M>7AIuk6+ zRG&MFVQ_7>5DLoR5HeOa$?2SA(v2u!#8;5I(ss%=x9U#R zU62n~&)22RTTsp${}6C&$+l&0skFVX%ACgc$(iQ#DVRRz!`Y+b>E?;ib(TH#6Wa=} zs(q_;SA|fhyEo7Ix%rAY9j=Ul^Rzd`3ABf+yO@~h@Rh=wo`?;8PdHE1AUo34r7izy znAr`;VavQueSu7bD5r^nXTERcW(P-{2SOSfF1x0cW1Nczvj0}@!!upORN1%_-b2bh zGt#zokJz&SveJRzlUK4DruxR(YuHEAmB%F}buU`*pAzJ7Mbgs4sg;H@&6x*wxvGm6 z>KH@ilsvvdl@CGfm4T+$agodrB=md8ygG!|O=r@FY>S_zX%*)mqf?XBX*chhQ9uPP z-(T(24)})vWD*{bQM5_hy3CD8C>anuNtCXMkG7T?Yew^>=PK!~Hlr0{-0h0cNAJ8> zRMzLFz7aJv)Yh)_s)^L&L*nDV@qfeg>_<`z1z(?s}}3tE4h|7_taB> zPfmmOCFZ8%>`gyf1@|7t3;e~mwBRCDDw(Rrt>@O}obs#1?!W((+9>d$b7t!{&wR!P ziQbn0@j=&sw={`s##Uc@uS^(tbShjtsk=qrU1LW0lu}BplIfzv{fwxNsSaG~b|ryo zTQ}YXfp6o?^sSHW>s~m;l@h6wFbIPw{Z(IqO1u){{hEZgrTdF0o$n;hYIm`h5ejym zWt^w~#8p1J)FtfY6LvGmNQ~#n>4#mN4B^ zjrQk)Zt%k}GBRD>l`<~og6N_{6HYKDtsAtd%y?KbXCQR(sW8O(v_)kwYMz|(OW zsFz6A1^abSklOl`wLC-KYI8x=oMD^qZBs}}JVW@YY|3&k&IZ_n2Ia@5WiK>buV!E- zOsYcS4dFPE7vzj%_?5i2!XY`TiPd*jy>#C`i^XG8h?f35`=)s`0EhQBN!+YrXbpt( z-bwg_Jen`w<+6&B`hldU%rr&Xdgtze>rKuJ61AI12ja-eDZZX-+u1H>Sa|7pCine9 z&MEhmT7nq`P!pPK>l?I8cjuPpN<7(hqH~beChC*YMR+p;;@6#0j2k$=onUM`IXW3> z`dtX8`|@P|Ep-_0>)@&7@aLeg$jOd4G`eIW=^dQQ*^cgKeWAsSHOY?WEOsrtnG|^yeQ3lSd`pKAR}kzgIiEk@OvQb>DS*pGidh`E=BHYepHXbV)SV6pE2dx6 zkND~nK}2qjDVX3Z`H;2~lUvar>zT7u%x8LZa&rp7YH@n@GqQ65Cv+pkxI1OU6(g`b z?>)NcE7>j@p>V0mFk-5Rpi`W}oQ!tUU&Yn8m0OWYFj|~`?aVFOx;e`M)Q!YSokY)3 zV6l-;hK6?j=mp2#1e5cCn7P6n_7)n^+MdRw@5pvkOA>|&B8`QZ32|ynqaf}Kcdro= zzQchCYM0^)7$;m2iZnMbE$!}hwk&AVvN`iX3A9mB&`*BDmLV-m`OMvd`sJ?;%U`p~ zmwow{y6sPbcZNQPZ#GQS0&mzy?s%>_p>ZM|sCXVAUlST;rQ-3#Iu!-bpFSV4g7?-l zGfX>Z#hR+i;9B};^CO@7<<#MGFeY)SC&;a{!` zf;yaQo%{bjSa8KT~@?O$cK z(DGnm7w>cG1hH#*J%X}%Y%~+nLT*{aP08@l&Nu}>!-j|!8lSqt_xUNF+Y}SQmupyb zPua2PI;@1YaIsRF*knA^rJv84Tc=7?J2}!1kMfHSO$d$+PK*u?OI%=P7;`PHxMB0k zau~T0Wk)rPEGJ$NiXW~kfPA#m%Sr|7=$tHelF9A6rFLa$^g{6)8GSW*6}#~Zb^qk% zg=pLwC!SkY+&Gne((9`TCy`i`a#eCS{A2yMi>J>p*NS*!V~aAgK;wnSOHPULqzyj- z-q4BPXqXn))iRnMF*WZj17wUYjC!h43tI7uScHLf1|WJfA7^5O9`%lH>ga`cmpiz( zs|I8nTUD4?d{CQ-vwD!2uwGU_Ts&{1_mvqY`@A{j^b?n&WbPhb418NY1*Otz19`1w zc9rn?0e_*En&8?OWii89x+jaqRVzlL!QUCg^qU&+WERycV&1+fcsJ%ExEPjiQWRTU zCJpu*1dXyvrJJcH`+OKn7;q`X#@Gmy3U?5ZAV~mXjQhBJOCMw>o@2kznF>*?qOW;D z6!GTcM)P-OY-R`Yd>FeX%UyL%dY%~#^Yl!c42;**WqdGtGwTfB9{2mf2h@#M8YyY+!Q(4}X^+V#r zcZXYE$-hJyYzq%>$)k8vSQU` zIpxU*yy~naYp=IocRp5no^PeFROluibl( zmaKkWgSWZHn(`V_&?hM{%xl3TBWCcr59WlX6Q{j45)`A^-kUv4!qM=OdcwpsGB)l} z&-_U+8S8bQ!RDc&Y3~?w5NwLNstoUYqPYs(y+lj!HFqIZ7FA>WsxAE7vB=20K zn_&y{2)Uaw4b^NCFNhJXd&XrhA4E~zD7Ue7X^f98=&5!wn_r=6qAwDkd>g#2+*ahd zaV|_P_8e%jiHh7W;cl(d=&-r-C}_Ov?bts8s^rKUWQ|XkuW!ToSwe}Z{4|kl+q&&W zn%iW48c5*ft#*m)+xSps+j(B5bPh&u0&m6=@WgwBf_QfJJzg2Qdz89HwcV`5kZ#5z zw;W&H8>5R(>KRwvd0gh30wJHA>|2N(im;~wy1HTv_}Ue%qb)>5qL^$hIyPvoT(nk_<`7F;#nS8;q!cqKspvBc<%xMsQj*h|>`Z)F6LDxue@to))OIbs2X+zY2L9#2UNrR^)?c8&PFc?j*&Q-r|C%7a$)ZRQ->#|?rEj&M4spQfNt;J^ntwf(d+q;tt)C`d{*|t)czD4x-qw{Chm0vuKp8axqy5`Yz z1756|;JX1q(lEieR=uT;%havqflgv+`5i!Z`R}(JNV~&`x}I9Lmm;aB7Bnc^UC?>W zu)(J7@fs}pL=Y-4aLq&Z*lO$e^0(bOW z3gWbcvb^gjEfhV=6Lgu2aX{(zjq|NH*fSgm&kBj?6dFqD2MWk5@eHt@_&^ZTX$b?o}S<9BGaCZIm6Hz)Qkruacn!qv*>La|#%j*XFp(*;&v3h4 zcjPbZWzv|cOypb@XDnd}g%(@f7A>w2Nseo|{KdeVQu)mN=W=Q`N?ID%J_SXUr0Rl# z3X;tO*^?41^%c!H;ia@hX``kWS3TR|CJ4_9j-?l6RjC=n?}r&sr>m%58&~?$JJV6{ zDq5h#m4S_BPiibQQaPGg6LIHVCc`9w3^3ZVWP$n>p7 z5dIEH-W9e;$Id8>9?wh%WnWf>4^1U<%vn=<4oNFhVl9zVk+jn;WtQUQ)ZeEjKYy8C z3g#tIb28thR1nZdKrN}(r zJdy-Y3Rvr5D3D|msZbmE;FLePbiM0ZjwTIQQHk)8G+sB$iwmEa2kQv&9Vs9m#$_8j zNKz}(x$Wc(M)a9H-Pn?5(Lk-CmOS(&+EVLOfsiq>e3ru6P?Lp>FOwPt>0o=j8UyF^ zO{(vf#MGx^y~WaOKnt%I78s}60(O#jFx0^47^Ikh$QTar(Dg$c=0KR|rRD|6s zz?tEX0_=(Hm0jWl;QOu!-k)mV?^i(Etl=Lg-{ z0G}CBprLX60zgAUz-fS^&m#o;erEC5TU+mn_Wj(zL$zqMo!e`D>s7X&;E zFz}}}puI+c%xq0uTpWS3RBlIS2jH0)W(9FU1>6PLcj|6O>=y)l`*%P`6K4}U2p}a0 zvInj%$AmqzkNLy%azH|_f7x$lYxSG=-;7BViUN(&0HPUobDixM1RVBzWhv8LokKI2 zjDwvWu=S~8We)+K{oMd-_cuXNO&+{eUaA8Ope3MxME0?PD+0a)99N>WZ66*;sn(N++hjPyz5z0RC{- z$pcSs{|)~a_h?w)y}42A6fg|nRnYUjMaBqg=68&_K%h3eboQ=%i083nfIVZZ04qOp%d*)*hNJA_foPjiW z$1r8ZZiRSvJT3zhK>iR@8_+TTJ!tlNLdL`e0=yjzv3Ie80h#wSfS3$>DB!!@JHxNd z0Mvd0Vqq!zfDy$?goY+|h!e(n3{J2;Ag=b)eLq{F0W*O?j&@|882U5?hUVIw_v3aV8tMn`8jPa5pSxzaZe{z}z|}$zM$o=3-mQ0Zgd?ZtaI> zQVHP1W3v1lbw>|?z@2MO(Ex!5KybKQ@+JRAg1>nzpP-!@3!th3rV=o?eiZ~fQRWy_ zfA!U9^bUL+z_$VJI=ic;{epla<&J@W-QMPZm^kTQ8a^2TX^TDpza*^tOu!WZ=T!PT z+0lJ*HuRnNGobNk0PbPT?i;^h{&0u+-fejISNv#9&j~Ep2;dYspntgzwR6<$@0dTQ z!qLe3Ztc=Ozy!btCcx!G$U7FlBRe}-L(E|RpH%_gt4m_LJllX3!iRYJEPvxcJ>C76 zfBy0_zKaYn{3yG6@;}S&+BeJk5X}$Kchp<Ea-=>VDg&zi*8xM0-ya!{ zcDN@>%H#vMwugU&1KN9pqA6-?Q8N@Dz?VlJ3IDfz#i#_RxgQS*>K+|Q@bek+s7#Qk z(5NZ-4xs&$j)X=@(1(hLn)vPj&pP>Nyu)emQ1MW6)g0hqXa5oJ_slh@(5MMS4xnG= z{0aK#F@_p=e}FdAa3tEl!|+j?h8h`t0CvCmNU%dOwEq<+jmm-=n|r|G^7QX4N4o(v zPU!%%w(Cet)Zev3QA?;TMm_aEK!5(~Nc6pJlp|sQP@z%JI}f0_`u+rc`1Df^j0G&s ScNgau(U?ep-K_E5zy1%ZQTdPn literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.properties b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..f398c33c4b --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java index 9df2e898d5..93527d2a5a 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java @@ -64,7 +64,9 @@ public static void MigrationStep1( .build(); final CreateAwsKmsMrkMultiKeyringInput keyringInput = CreateAwsKmsMrkMultiKeyringInput.builder().generator(kmsKeyId).build(); - final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring( + keyringInput + ); // 2. Create a Table Schema over your annotated class. // See SimpleClass.java in this directory for the update to use the new DynamoDb Enhanced Client annotations. @@ -74,7 +76,9 @@ public static void MigrationStep1( // use the `DynamoDbEncryptionSignOnly` annotation. // If you want a particular attribute to be neither signed nor encrypted (DO_NOTHING), // use the `DynamoDbEncryptionDoNothing` annotation. - final TableSchema schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); + final TableSchema schemaOnEncrypt = TableSchema.fromBean( + SimpleClass.class + ); // 3. Configure which attributes we expect to be excluded in the signature // when reading items. This value represents all unsigned attributes @@ -95,7 +99,10 @@ public static void MigrationStep1( // 5. Configure the same DynamoDBEncryptor that we did in Step 0. final KmsClient kmsClient = KmsClient.create(); - final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider(kmsClient, kmsKeyId); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider( + kmsClient, + kmsKeyId + ); final DynamoDBEncryptor oldEncryptor = DynamoDBEncryptor.getInstance(cmp); // 6. Configure legacy behavior with FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT. @@ -109,7 +116,8 @@ public static void MigrationStep1( .build(); // 7. Create the DynamoDb Encryption Interceptor. - final Map tableConfigs = new HashMap<>(); + final Map tableConfigs = + new HashMap<>(); tableConfigs.put( ddbTableName, DynamoDbEnhancedTableEncryptionConfig @@ -123,7 +131,8 @@ public static void MigrationStep1( ); final DynamoDbEncryptionInterceptor interceptor = DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( - CreateDynamoDbEncryptionInterceptorInput.builder() + CreateDynamoDbEncryptionInterceptorInput + .builder() .tableEncryptionConfigs(tableConfigs) .build() ); @@ -132,7 +141,8 @@ public static void MigrationStep1( final DynamoDbClient ddb = DynamoDbClient .builder() .overrideConfiguration( - ClientOverrideConfiguration.builder() + ClientOverrideConfiguration + .builder() .addExecutionInterceptor(interceptor) .build() ) @@ -144,7 +154,10 @@ public static void MigrationStep1( .builder() .dynamoDbClient(ddb) .build(); - final DynamoDbTable table = enhancedClient.table(ddbTableName, schemaOnEncrypt); + final DynamoDbTable table = enhancedClient.table( + ddbTableName, + schemaOnEncrypt + ); // 10. Put an item into your table using the DynamoDb Enhanced Client. // This item will be encrypted in the legacy format, using the @@ -167,14 +180,16 @@ public static void MigrationStep1( // If this is an item written in the new format (e.g. any item written // during Step 2 or after), then we will attempt to decrypt the item using // the non-legacy behavior. - final Key key = Key.builder() + final Key key = Key + .builder() .partitionValue("MigrationExample") .sortValue(sortReadValue) .build(); - final SimpleClass decryptedItem = table.getItem( - (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key) - ); + final SimpleClass decryptedItem = + table.getItem((GetItemEnhancedRequest.Builder requestBuilder) -> + requestBuilder.key(key) + ); // Demonstrate we get the expected item back assert decryptedItem.getPartitionKey().equals("MigrationExample"); diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java index eeba7ec89f..991dcf46c0 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java @@ -58,9 +58,13 @@ public static void MigrationStep2( .build(); final CreateAwsKmsMrkMultiKeyringInput keyringInput = CreateAwsKmsMrkMultiKeyringInput.builder().generator(kmsKeyId).build(); - final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring( + keyringInput + ); - final TableSchema schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); + final TableSchema schemaOnEncrypt = TableSchema.fromBean( + SimpleClass.class + ); final List allowedUnsignedAttributes = Arrays.asList("attribute3"); @@ -73,7 +77,10 @@ public static void MigrationStep2( // Configure the DynamoDBEncryptor using DDBEC SDK V2. final KmsClient kmsClient = KmsClient.create(); - final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider(kmsClient, kmsKeyId); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider( + kmsClient, + kmsKeyId + ); final DynamoDBEncryptor oldEncryptor = DynamoDBEncryptor.getInstance(cmp); // 2. When configuring our legacy behavior, use `FORBID_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT`. @@ -87,7 +94,8 @@ public static void MigrationStep2( .build(); // 3. Create the DynamoDb Encryption Interceptor with the above configuration. - final Map tableConfigs = new HashMap<>(); + final Map tableConfigs = + new HashMap<>(); tableConfigs.put( ddbTableName, DynamoDbEnhancedTableEncryptionConfig @@ -124,7 +132,10 @@ public static void MigrationStep2( .builder() .dynamoDbClient(ddb) .build(); - final DynamoDbTable table = enhancedClient.table(ddbTableName, schemaOnEncrypt); + final DynamoDbTable table = enhancedClient.table( + ddbTableName, + schemaOnEncrypt + ); // 6. Put an item into your table using the DynamoDb Enhanced Client. // This item will be encrypted in the latest format, using the @@ -152,9 +163,10 @@ public static void MigrationStep2( .sortValue(sortReadValue) .build(); - final SimpleClass decryptedItem = table.getItem( - (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key) - ); + final SimpleClass decryptedItem = + table.getItem((GetItemEnhancedRequest.Builder requestBuilder) -> + requestBuilder.key(key) + ); // Demonstrate we get the expected item back assert decryptedItem.getPartitionKey().equals("MigrationExample"); diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java index 4db5212321..494ce45929 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java @@ -53,15 +53,20 @@ public static void MigrationStep3( .build(); final CreateAwsKmsMrkMultiKeyringInput keyringInput = CreateAwsKmsMrkMultiKeyringInput.builder().generator(kmsKeyId).build(); - final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring( + keyringInput + ); - final TableSchema schemaOnEncrypt = TableSchema.fromBean(SimpleClass.class); + final TableSchema schemaOnEncrypt = TableSchema.fromBean( + SimpleClass.class + ); final List allowedUnsignedAttributes = Arrays.asList("attribute3"); // 2. Create the DynamoDb Encryption Interceptor with the above configuration. // Do not configure any legacy behavior. - final Map tableConfigs = new HashMap<>(); + final Map tableConfigs = + new HashMap<>(); tableConfigs.put( ddbTableName, DynamoDbEnhancedTableEncryptionConfig @@ -97,7 +102,10 @@ public static void MigrationStep3( .builder() .dynamoDbClient(ddb) .build(); - final DynamoDbTable table = enhancedClient.table(ddbTableName, schemaOnEncrypt); + final DynamoDbTable table = enhancedClient.table( + ddbTableName, + schemaOnEncrypt + ); // 5. Put an item into your table using the DynamoDb Enhanced Client. // This item will be encrypted in the latest format, using the @@ -124,9 +132,10 @@ public static void MigrationStep3( .sortValue(sortReadValue) .build(); - final SimpleClass decryptedItem = table.getItem( - (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key) - ); + final SimpleClass decryptedItem = + table.getItem((GetItemEnhancedRequest.Builder requestBuilder) -> + requestBuilder.key(key) + ); // Demonstrate we get the expected item back assert decryptedItem.getPartitionKey().equals("MigrationExample"); diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java index 7d6199ca12..ed2ff93fa9 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java @@ -56,7 +56,10 @@ public static void MigrationStep0( attributeFlags.put("partition_key", EnumSet.of(EncryptionFlags.SIGN)); attributeFlags.put("sort_key", EnumSet.of(EncryptionFlags.SIGN)); // attribute1 - encrypt and sign - attributeFlags.put("attribute1", EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN)); + attributeFlags.put( + "attribute1", + EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN) + ); // attribute2 - sign only attributeFlags.put("attribute2", EnumSet.of(EncryptionFlags.SIGN)); // attribute3 - no flags (do nothing) @@ -66,14 +69,21 @@ public static void MigrationStep0( // 5. Create the item to encrypt final Map item = new HashMap<>(); - item.put("partition_key", AttributeValue.builder().s("MigrationExample").build()); + item.put( + "partition_key", + AttributeValue.builder().s("MigrationExample").build() + ); item.put("sort_key", AttributeValue.builder().n("0").build()); - item.put("attribute1", AttributeValue.builder().s("encrypt and sign me!").build()); + item.put( + "attribute1", + AttributeValue.builder().s("encrypt and sign me!").build() + ); item.put("attribute2", AttributeValue.builder().s("sign me!").build()); item.put("attribute3", AttributeValue.builder().s("ignore me!").build()); // 6. Create the EncryptionContext for this operation - final EncryptionContext encryptionContext = EncryptionContext.builder() + final EncryptionContext encryptionContext = EncryptionContext + .builder() .tableName(ddbTableName) .hashKeyName("partition_key") .rangeKeyName("sort_key") @@ -93,20 +103,28 @@ public static void MigrationStep0( assert encryptedItem.get("attribute3").s().equals("ignore me!"); // 8. Put the encrypted item into DynamoDB - ddbClient.putItem(PutItemRequest.builder() - .tableName(ddbTableName) - .item(encryptedItem) - .build()); + ddbClient.putItem( + PutItemRequest + .builder() + .tableName(ddbTableName) + .item(encryptedItem) + .build() + ); // 9. Get the encrypted item back from DynamoDB final Map key = new HashMap<>(); - key.put("partition_key", AttributeValue.builder().s("MigrationExample").build()); - key.put("sort_key", AttributeValue.builder().n(String.valueOf(sortReadValue)).build()); + key.put( + "partition_key", + AttributeValue.builder().s("MigrationExample").build() + ); + key.put( + "sort_key", + AttributeValue.builder().n(String.valueOf(sortReadValue)).build() + ); - final GetItemResponse response = ddbClient.getItem(GetItemRequest.builder() - .tableName(ddbTableName) - .key(key) - .build()); + final GetItemResponse response = ddbClient.getItem( + GetItemRequest.builder().tableName(ddbTableName).key(key).build() + ); // 10. Decrypt the item using decryptRecord final Map decryptedItem = encryptor.decryptRecord( diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java index a7900b1e73..76e0e4fe38 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java @@ -59,7 +59,7 @@ public void TestMigrationStep0() throws GeneralSecurityException { ); // When: Execute Step 0 with sortReadValue=3, Then: throws exception (i.e. cannot read values in new format) assertThrows( - DynamoDbEncryptionException.class, + DynamoDbEncryptionException.class, () -> { MigrationExampleStep0.MigrationStep0( TestUtils.TEST_KMS_KEY_ID, From 1c31c91a084a5c8cc1d8d2f314f57d1065981453 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Thu, 5 Feb 2026 10:27:36 -0800 Subject: [PATCH 31/33] m --- .../migration/awsdbe/MigrationExampleStep1.java | 12 +++++++----- .../migration/ddbec/MigrationExampleStep0.java | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java index 93527d2a5a..7d7cfd0f30 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java @@ -105,9 +105,10 @@ public static void MigrationStep1( ); final DynamoDBEncryptor oldEncryptor = DynamoDBEncryptor.getInstance(cmp); - // 6. Configure legacy behavior with FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT. - // This policy continues to read and write items using the old format, - // but can read new format items as soon as they appear. + // 6. Configure our legacy behavior, inputting the DynamoDBEncryptor and attribute actions + // created above. For Legacy Policy, use `FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT`. + // With this policy, you will continue to read and write items using the old format, + // but will be able to start reading new items in the new format as soon as they appear. final LegacyOverride legacyOverride = LegacyOverride .builder() .encryptor(oldEncryptor) @@ -115,7 +116,7 @@ public static void MigrationStep1( .attributeActionsOnEncrypt(legacyActions) .build(); - // 7. Create the DynamoDb Encryption Interceptor. + // 7. Create the DynamoDb Encryption Interceptor with the above configuration. final Map tableConfigs = new HashMap<>(); tableConfigs.put( @@ -137,7 +138,7 @@ public static void MigrationStep1( .build() ); - // 8. Create DynamoDB client with the encryption interceptor. + // 8. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above final DynamoDbClient ddb = DynamoDbClient .builder() .overrideConfiguration( @@ -206,6 +207,7 @@ public static void main(final String[] args) { } final String kmsKeyId = args[0]; final String ddbTableName = args[1]; + // You can manipulate this value to demonstrate reading records written in other steps final int sortReadValue = Integer.parseInt(args[2]); MigrationStep1(kmsKeyId, ddbTableName, sortReadValue); } diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java index ed2ff93fa9..26a9b06190 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java @@ -96,7 +96,7 @@ public static void MigrationStep0( encryptionContext ); - // Verify attribute1 is encrypted (original attribute name no longer exists) + // Verify attribute1 is encrypted assert encryptedItem.get("attribute1").b() != null; // Verify attribute2 and attribute3 are not encrypted (sign-only and do-nothing) assert encryptedItem.get("attribute2").s().equals("sign me!"); From cc410a6cbb7db1a4fed286abb470a7923cb36c7f Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 15:02:18 -0800 Subject: [PATCH 32/33] m --- .../migration/awsdbe/MigrationExampleStep1.java | 14 ++++++++------ .../migration/awsdbe/MigrationExampleStep2.java | 12 +++++++----- .../migration/awsdbe/MigrationExampleStep3.java | 16 +++++++++------- .../migration/ddbec/MigrationExampleStep0.java | 16 +++++++++------- .../awsdbe/TestMigrationExampleStep1.java | 10 ++++++++++ .../awsdbe/TestMigrationExampleStep2.java | 10 ++++++++++ .../awsdbe/TestMigrationExampleStep3.java | 10 ++++++++++ .../ddbec/TestMigrationExampleStep0.java | 10 ++++++++++ 8 files changed, 73 insertions(+), 25 deletions(-) diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java index 7d7cfd0f30..9b34dfe042 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java @@ -50,6 +50,7 @@ public class MigrationExampleStep1 { public static void MigrationStep1( final String kmsKeyId, final String ddbTableName, + final String partitionKeyValue, final int sortReadValue ) { // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. @@ -166,7 +167,7 @@ public static void MigrationStep1( // In this step, we do not expect items to be encrypted any differently // from before. final SimpleClass item = new SimpleClass(); - item.setPartitionKey("MigrationExample"); + item.setPartitionKey(partitionKeyValue); item.setSortKey(1); item.setAttribute1("encrypt and sign me!"); item.setAttribute2("sign me!"); @@ -183,7 +184,7 @@ public static void MigrationStep1( // the non-legacy behavior. final Key key = Key .builder() - .partitionValue("MigrationExample") + .partitionValue(partitionKeyValue) .sortValue(sortReadValue) .build(); @@ -193,22 +194,23 @@ public static void MigrationStep1( ); // Demonstrate we get the expected item back - assert decryptedItem.getPartitionKey().equals("MigrationExample"); + assert decryptedItem.getPartitionKey().equals(partitionKeyValue); assert decryptedItem.getAttribute1().equals("encrypt and sign me!"); assert decryptedItem.getAttribute2().equals("sign me!"); assert decryptedItem.getAttribute3().equals("ignore me!"); } public static void main(final String[] args) { - if (args.length < 3) { + if (args.length < 4) { throw new IllegalArgumentException( "To run this example, include the kmsKeyId, ddbTableName, and sortReadValue as args." ); } final String kmsKeyId = args[0]; final String ddbTableName = args[1]; + final String partitionKeyValue = args[2]; // You can manipulate this value to demonstrate reading records written in other steps - final int sortReadValue = Integer.parseInt(args[2]); - MigrationStep1(kmsKeyId, ddbTableName, sortReadValue); + final int sortReadValue = Integer.parseInt(args[3]); + MigrationStep1(kmsKeyId, ddbTableName, partitionKeyValue, sortReadValue); } } diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java index 991dcf46c0..2f50a57be1 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java @@ -48,6 +48,7 @@ public class MigrationExampleStep2 { public static void MigrationStep2( final String kmsKeyId, final String ddbTableName, + final String partitionKeyValue, final int sortReadValue ) { // 1. Continue to configure your Keyring, Table Schema, legacy attribute actions, @@ -142,7 +143,7 @@ public static void MigrationStep2( // configuration from your modelled class to decide // which attribute to encrypt and/or sign. final SimpleClass item = new SimpleClass(); - item.setPartitionKey("MigrationExample"); + item.setPartitionKey(partitionKeyValue); item.setSortKey(2); item.setAttribute1("encrypt and sign me!"); item.setAttribute2("sign me!"); @@ -159,7 +160,7 @@ public static void MigrationStep2( // the non-legacy behavior. final Key key = Key .builder() - .partitionValue("MigrationExample") + .partitionValue(partitionKeyValue) .sortValue(sortReadValue) .build(); @@ -169,7 +170,7 @@ public static void MigrationStep2( ); // Demonstrate we get the expected item back - assert decryptedItem.getPartitionKey().equals("MigrationExample"); + assert decryptedItem.getPartitionKey().equals(partitionKeyValue); assert decryptedItem.getAttribute1().equals("encrypt and sign me!"); } @@ -181,8 +182,9 @@ public static void main(final String[] args) { } final String kmsKeyId = args[0]; final String ddbTableName = args[1]; + final String partitionKeyValue = args[2]; // You can manipulate this value to demonstrate reading records written in other steps - final int sortReadValue = Integer.parseInt(args[2]); - MigrationStep2(kmsKeyId, ddbTableName, sortReadValue); + final int sortReadValue = Integer.parseInt(args[3]); + MigrationStep2(kmsKeyId, ddbTableName, partitionKeyValue, sortReadValue); } } diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java index 494ce45929..3aa1f799c9 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java @@ -41,6 +41,7 @@ public class MigrationExampleStep3 { public static void MigrationStep3( final String kmsKeyId, final String ddbTableName, + final String partitionKeyValue, final int sortReadValue ) { // 1. Continue to configure your Keyring, Table Schema, @@ -112,7 +113,7 @@ public static void MigrationStep3( // configuration from your modelled class to decide // which attribute to encrypt and/or sign. final SimpleClass item = new SimpleClass(); - item.setPartitionKey("MigrationExample"); + item.setPartitionKey(partitionKeyValue); item.setSortKey(3); item.setAttribute1("encrypt and sign me!"); item.setAttribute2("sign me!"); @@ -128,7 +129,7 @@ public static void MigrationStep3( // the non-legacy behavior. final Key key = Key .builder() - .partitionValue("MigrationExample") + .partitionValue(partitionKeyValue) .sortValue(sortReadValue) .build(); @@ -138,20 +139,21 @@ public static void MigrationStep3( ); // Demonstrate we get the expected item back - assert decryptedItem.getPartitionKey().equals("MigrationExample"); + assert decryptedItem.getPartitionKey().equals(partitionKeyValue); assert decryptedItem.getAttribute1().equals("encrypt and sign me!"); } public static void main(final String[] args) { - if (args.length < 3) { + if (args.length < 4) { throw new IllegalArgumentException( - "To run this example, include the kmsKeyId, ddbTableName, and sortReadValue as args." + "To run this example, include the kmsKeyId, ddbTableName, partitionKeyValue and sortReadValue as args." ); } final String kmsKeyId = args[0]; final String ddbTableName = args[1]; + final String partitionKeyValue = args[2]; // You can manipulate this value to demonstrate reading records written in other steps - final int sortReadValue = Integer.parseInt(args[2]); - MigrationStep3(kmsKeyId, ddbTableName, sortReadValue); + final int sortReadValue = Integer.parseInt(args[3]); + MigrationStep3(kmsKeyId, ddbTableName, partitionKeyValue, sortReadValue); } } diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java index 26a9b06190..fd0ad3f32a 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java @@ -36,6 +36,7 @@ public class MigrationExampleStep0 { public static void MigrationStep0( final String kmsKeyId, final String ddbTableName, + final String partitionKeyValue, final int sortReadValue ) throws GeneralSecurityException { // 1. Create the DirectKmsMaterialsProvider that protects your data keys. @@ -71,7 +72,7 @@ public static void MigrationStep0( final Map item = new HashMap<>(); item.put( "partition_key", - AttributeValue.builder().s("MigrationExample").build() + AttributeValue.builder().s(partitionKeyValue).build() ); item.put("sort_key", AttributeValue.builder().n("0").build()); item.put( @@ -115,7 +116,7 @@ public static void MigrationStep0( final Map key = new HashMap<>(); key.put( "partition_key", - AttributeValue.builder().s("MigrationExample").build() + AttributeValue.builder().s(partitionKeyValue).build() ); key.put( "sort_key", @@ -134,22 +135,23 @@ public static void MigrationStep0( ); // Demonstrate we get the expected item back - assert decryptedItem.get("partition_key").s().equals("MigrationExample"); + assert decryptedItem.get("partition_key").s().equals(partitionKeyValue); assert decryptedItem.get("attribute1").s().equals("encrypt and sign me!"); assert decryptedItem.get("attribute2").s().equals("sign me!"); assert decryptedItem.get("attribute3").s().equals("ignore me!"); } public static void main(final String[] args) throws GeneralSecurityException { - if (args.length < 3) { + if (args.length < 4) { throw new IllegalArgumentException( - "To run this example, include the kmsKeyId, ddbTableName, and sortReadValue as args." + "To run this example, include the kmsKeyId, ddbTableName, partitionKeyValue and sortReadValue as args." ); } final String kmsKeyId = args[0]; final String ddbTableName = args[1]; + final String partitionKeyValue = args[2]; // You can manipulate this value to demonstrate reading records written in other steps - final int sortReadValue = Integer.parseInt(args[2]); - MigrationStep0(kmsKeyId, ddbTableName, sortReadValue); + final int sortReadValue = Integer.parseInt(args[3]); + MigrationStep0(kmsKeyId, ddbTableName, partitionKeyValue, sortReadValue); } } diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java index 0a4e2f7543..3acb5f6aff 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java @@ -1,6 +1,8 @@ package software.amazon.cryptography.examples.migration.awsdbe; import java.security.GeneralSecurityException; +import java.util.UUID; + import org.testng.annotations.Test; import software.amazon.cryptography.examples.migration.ddbec.MigrationExampleStep0; @@ -8,10 +10,12 @@ public class TestMigrationExampleStep1 { @Test public void TestMigrationStep1() throws GeneralSecurityException { + final String partitionKeyValue = "TestMigrationStep1-DDBEC-with-sdk-v2" + UUID.randomUUID(); // Successfully executes step 1 MigrationExampleStep1.MigrationStep1( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 1 ); @@ -19,12 +23,14 @@ public void TestMigrationStep1() throws GeneralSecurityException { MigrationExampleStep0.MigrationStep0( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 0 ); // When: Execute Step 1 with sortReadValue=0, Then: Success (i.e. can read values in old format) MigrationExampleStep1.MigrationStep1( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 0 ); @@ -32,12 +38,14 @@ public void TestMigrationStep1() throws GeneralSecurityException { MigrationExampleStep2.MigrationStep2( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 2 ); // When: Execute Step 1 with sortReadValue=2, Then: Success (i.e. can read values in new format) MigrationExampleStep1.MigrationStep1( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 2 ); @@ -45,12 +53,14 @@ public void TestMigrationStep1() throws GeneralSecurityException { MigrationExampleStep3.MigrationStep3( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 3 ); // When: Execute Step 1 with sortReadValue=3, Then: Success (i.e. can read values in new format) MigrationExampleStep1.MigrationStep1( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 3 ); } diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java index 6b96d64242..4c819c4c7a 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java @@ -1,6 +1,8 @@ package software.amazon.cryptography.examples.migration.awsdbe; import java.security.GeneralSecurityException; +import java.util.UUID; + import org.testng.annotations.Test; import software.amazon.cryptography.examples.migration.ddbec.MigrationExampleStep0; @@ -8,10 +10,12 @@ public class TestMigrationExampleStep2 { @Test public void TestMigrationStep2() throws GeneralSecurityException { + final String partitionKeyValue = "TestMigrationStep2-DDBEC-with-sdk-v2" + UUID.randomUUID(); // Successfully executes step 2 MigrationExampleStep2.MigrationStep2( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 2 ); @@ -19,12 +23,14 @@ public void TestMigrationStep2() throws GeneralSecurityException { MigrationExampleStep0.MigrationStep0( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 0 ); // When: Execute Step 2 with sortReadValue=0, Then: Success (i.e. can read values in old format) MigrationExampleStep2.MigrationStep2( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 0 ); @@ -32,12 +38,14 @@ public void TestMigrationStep2() throws GeneralSecurityException { MigrationExampleStep1.MigrationStep1( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 1 ); // When: Execute Step 2 with sortReadValue=1, Then: Success (i.e. can read values in old format) MigrationExampleStep2.MigrationStep2( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 1 ); @@ -45,12 +53,14 @@ public void TestMigrationStep2() throws GeneralSecurityException { MigrationExampleStep3.MigrationStep3( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 3 ); // When: Execute Step 2 with sortReadValue=3, Then: Success (i.e. can read values in new format) MigrationExampleStep2.MigrationStep2( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 3 ); } diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java index e420a9807f..97e675cc74 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java @@ -3,6 +3,8 @@ import static org.testng.Assert.assertThrows; import java.security.GeneralSecurityException; +import java.util.UUID; + import org.testng.annotations.Test; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.cryptography.examples.migration.ddbec.MigrationExampleStep0; @@ -11,10 +13,12 @@ public class TestMigrationExampleStep3 { @Test public void TestMigrationStep3() throws GeneralSecurityException { + final String partitionKeyValue = "TestMigrationStep3-DDBEC-with-sdk-v2" + UUID.randomUUID(); // Successfully executes Step 3 MigrationExampleStep3.MigrationStep3( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 3 ); @@ -22,6 +26,7 @@ public void TestMigrationStep3() throws GeneralSecurityException { MigrationExampleStep0.MigrationStep0( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 0 ); // When: Execute Step 3 with sortReadValue=0, Then: throws SdkClientException (i.e. cannot read values in old format) @@ -31,6 +36,7 @@ public void TestMigrationStep3() throws GeneralSecurityException { MigrationExampleStep3.MigrationStep3( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 0 ); } @@ -40,6 +46,7 @@ public void TestMigrationStep3() throws GeneralSecurityException { MigrationExampleStep1.MigrationStep1( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 1 ); // When: Execute Step 3 with sortReadValue=1, Then: throws SdkClientException (i.e. cannot read values in old format) @@ -49,6 +56,7 @@ public void TestMigrationStep3() throws GeneralSecurityException { MigrationExampleStep3.MigrationStep3( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 1 ); } @@ -58,12 +66,14 @@ public void TestMigrationStep3() throws GeneralSecurityException { MigrationExampleStep2.MigrationStep2( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 2 ); // When: Execute Step 3 with sortReadValue=2, Then: Success (i.e. can read values in new format) MigrationExampleStep3.MigrationStep3( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 2 ); } diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java index 76e0e4fe38..0a6ff84215 100644 --- a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java @@ -3,6 +3,8 @@ import static org.testng.Assert.assertThrows; import java.security.GeneralSecurityException; +import java.util.UUID; + import org.testng.annotations.Test; import software.amazon.cryptography.examples.migration.awsdbe.MigrationExampleStep1; import software.amazon.cryptography.examples.migration.awsdbe.MigrationExampleStep2; @@ -13,10 +15,12 @@ public class TestMigrationExampleStep0 { @Test public void TestMigrationStep0() throws GeneralSecurityException { + final String partitionKeyValue = "TestMigrationStep0-DDBEC-with-sdk-v2" + UUID.randomUUID(); // Successfully executes Step 0 MigrationExampleStep0.MigrationStep0( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 0 ); @@ -24,12 +28,14 @@ public void TestMigrationStep0() throws GeneralSecurityException { MigrationExampleStep1.MigrationStep1( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 1 ); // When: Execute Step 0 with sortReadValue=1, Then: Success (i.e. can read values in old format) MigrationExampleStep0.MigrationStep0( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 1 ); @@ -37,6 +43,7 @@ public void TestMigrationStep0() throws GeneralSecurityException { MigrationExampleStep2.MigrationStep2( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 2 ); // When: Execute Step 0 with sortReadValue=2, Then: throws exception (i.e. cannot read values in new format) @@ -46,6 +53,7 @@ public void TestMigrationStep0() throws GeneralSecurityException { MigrationExampleStep0.MigrationStep0( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 2 ); } @@ -55,6 +63,7 @@ public void TestMigrationStep0() throws GeneralSecurityException { MigrationExampleStep3.MigrationStep3( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 3 ); // When: Execute Step 0 with sortReadValue=3, Then: throws exception (i.e. cannot read values in new format) @@ -64,6 +73,7 @@ public void TestMigrationStep0() throws GeneralSecurityException { MigrationExampleStep0.MigrationStep0( TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, 3 ); } From b2d24144460a43ef2650cbee6cca770df7c9ea7b Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Mon, 9 Feb 2026 13:48:39 -0800 Subject: [PATCH 33/33] Tony's comment --- .../legacy/InternalLegacyOverride.java | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index 74ce475374..a0c3eb05e9 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -16,7 +16,6 @@ import StandardLibraryInternal.InternalResult; import Wrappers_Compile.Option; import Wrappers_Compile.Result; -import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor; import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext; import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionFlags; import dafny.DafnyMap; @@ -28,7 +27,6 @@ import software.amazon.awssdk.core.SdkBytes; import software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor; import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.LegacyPolicy; -import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.ToNative; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction; @@ -36,8 +34,8 @@ public class InternalLegacyOverride extends _ExternBase_InternalLegacyOverride { private final LegacyEncryptorAdapter _encryptorAdapter; private final LegacyPolicy _policy; - private final DafnySequence materialDescriptionFieldNameDafnyType; - private final DafnySequence signatureFieldNameDafnyType; + private final DafnySequence materialDescriptionFieldNameDafny; + private final DafnySequence signatureFieldNameDafny; private InternalLegacyOverride( LegacyEncryptorAdapter encryptorAdapter, @@ -47,11 +45,11 @@ private InternalLegacyOverride( this._policy = policy; // It is possible that these values // have been customized by the customer. - this.materialDescriptionFieldNameDafnyType = + this.materialDescriptionFieldNameDafny = software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( encryptorAdapter.getMaterialDescriptionFieldName() ); - this.signatureFieldNameDafnyType = + this.signatureFieldNameDafny = software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( encryptorAdapter.getSignatureFieldName() ); @@ -72,8 +70,8 @@ public boolean IsLegacyInput( //# attributes for the material description and the signature. return ( input.is_DecryptItemInput() && - input._encryptedItem.contains(materialDescriptionFieldNameDafnyType) && - input._encryptedItem.contains(signatureFieldNameDafnyType) + input._encryptedItem.contains(materialDescriptionFieldNameDafny) && + input._encryptedItem.contains(signatureFieldNameDafny) ); } @@ -211,10 +209,10 @@ public static Result, Error> Build( } final LegacyEncryptorAdapter encryptorAdapter; - if (maybeEncryptor instanceof DynamoDBEncryptor) { + if (maybeEncryptor instanceof com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor) { encryptorAdapter = new V1EncryptorAdapter( - (DynamoDBEncryptor) maybeEncryptor, + (com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor) maybeEncryptor, maybeActions.value(), maybeEncryptionContext.value() ); @@ -229,11 +227,18 @@ public static Result, Error> Build( convertEncryptionContextV1ToV2(maybeEncryptionContext.value()) ); } else { - return CreateBuildFailure(createError("Unsupported encryptor type: " + maybeEncryptor.getClass().getName())); + return CreateBuildFailure( + createError( + "Unsupported encryptor type: " + maybeEncryptor.getClass().getName() + ) + ); } final InternalLegacyOverride internalLegacyOverride = - new InternalLegacyOverride(encryptorAdapter, legacyOverride.dtor_policy()); + new InternalLegacyOverride( + encryptorAdapter, + legacyOverride.dtor_policy() + ); return CreateBuildSuccess( CreateInternalLegacyOverrideSome(internalLegacyOverride) @@ -254,7 +259,7 @@ public static boolean isDynamoDBEncryptor( software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor maybe ) { return ( - maybe instanceof DynamoDBEncryptor || + maybe instanceof com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor || maybe instanceof software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor ); @@ -293,13 +298,13 @@ > convertActionsV1ToV2(Map> v1Actions) { private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext convertEncryptionContextV1ToV2( final EncryptionContext v1Context ) { - - final software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.Builder builder = software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext - .builder() - .tableName(v1Context.getTableName()) - .hashKeyName(v1Context.getHashKeyName()) - .rangeKeyName(v1Context.getRangeKeyName()) - .developerContext(v1Context.getDeveloperContext()); + final software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.Builder builder = + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext + .builder() + .tableName(v1Context.getTableName()) + .hashKeyName(v1Context.getHashKeyName()) + .rangeKeyName(v1Context.getRangeKeyName()) + .developerContext(v1Context.getDeveloperContext()); if (v1Context.getMaterialDescription() != null) { builder.materialDescription(v1Context.getMaterialDescription()); @@ -434,10 +439,16 @@ public static com.amazonaws.services.dynamodbv2.model.AttributeValue V2Attribute case SS: return attribute.withSS(value.ss()); case UNKNOWN_TO_SDK_VERSION: - throw new IllegalArgumentException("Unsupported AttributeValue type: UNKNOWN_TO_SDK_VERSION. This may indicate a newer DynamoDB attribute type that is not supported by this SDK version."); + throw new IllegalArgumentException( + "Unsupported AttributeValue type: UNKNOWN_TO_SDK_VERSION. This may indicate a newer DynamoDB attribute type that is not supported by this SDK version." + ); } - throw new IllegalArgumentException("Unexpected AttributeValue type: " + value.type() + ". Unable to convert from SDK v2 to SDK v1 format."); + throw new IllegalArgumentException( + "Unexpected AttributeValue type: " + + value.type() + + ". Unable to convert from SDK v2 to SDK v1 format." + ); } public static Map<