From ec8077cdc031bca3d22d6d785fd39e0d5713b214 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 30 Jan 2026 11:10:04 -0800 Subject: [PATCH 01/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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 84f790e28d0e9634b8d161352ed855dc7e5e21e9 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Thu, 5 Feb 2026 11:33:56 -0800 Subject: [PATCH 24/38] m --- Examples/runtimes/java/DDBEC/build.gradle.kts | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Examples/runtimes/java/DDBEC/build.gradle.kts diff --git a/Examples/runtimes/java/DDBEC/build.gradle.kts b/Examples/runtimes/java/DDBEC/build.gradle.kts new file mode 100644 index 0000000000..b3e20a4f25 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build.gradle.kts @@ -0,0 +1,120 @@ +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 = "DDBECExamples" + +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") + + 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") + } + } + }) +} From 8c0f08bf2d69aeb924f121403eb965e7ada811ca Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 10:53:34 -0800 Subject: [PATCH 25/38] m --- .../.gradle/7.6/checksums/checksums.lock | Bin 0 -> 17 bytes .../.gradle/7.6/checksums/md5-checksums.bin | Bin 0 -> 22097 bytes .../.gradle/7.6/checksums/sha1-checksums.bin | Bin 0 -> 22385 bytes .../dependencies-accessors.lock | Bin 0 -> 17 bytes .../7.6/dependencies-accessors/gc.properties | 0 .../7.6/executionHistory/executionHistory.bin | Bin 0 -> 206193 bytes .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .../.gradle/7.6/fileChanges/last-build.bin | Bin 0 -> 1 bytes .../.gradle/7.6/fileHashes/fileHashes.bin | Bin 0 -> 23197 bytes .../.gradle/7.6/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../7.6/fileHashes/resourceHashesCache.bin | Bin 0 -> 22271 bytes .../java/DDBEC/.gradle/7.6/gc.properties | 0 .../.gradle/8.11.1/checksums/checksums.lock | Bin 0 -> 17 bytes .../8.11.1/checksums/md5-checksums.bin | Bin 0 -> 22097 bytes .../8.11.1/checksums/sha1-checksums.bin | Bin 0 -> 22385 bytes .../executionHistory/executionHistory.bin | Bin 0 -> 162066 bytes .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .../.gradle/8.11.1/fileChanges/last-build.bin | Bin 0 -> 1 bytes .../.gradle/8.11.1/fileHashes/fileHashes.bin | Bin 0 -> 23197 bytes .../.gradle/8.11.1/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../8.11.1/fileHashes/resourceHashesCache.bin | Bin 0 -> 21047 bytes .../java/DDBEC/.gradle/8.11.1/gc.properties | 0 .../buildOutputCleanup.lock | Bin 0 -> 17 bytes .../buildOutputCleanup/cache.properties | 2 + .../buildOutputCleanup/outputFiles.bin | Bin 0 -> 18749 bytes .../java/DDBEC/.gradle/file-system.probe | Bin 0 -> 8 bytes .../java/DDBEC/.gradle/vcs-1/gc.properties | 0 .../bin/main/AsymmetricEncryptedItem.class | Bin 0 -> 9173 bytes .../DDBEC/bin/main/AwsKmsEncryptedItem.class | Bin 0 -> 9040 bytes .../DDBEC/bin/main/AwsKmsMultiRegionKey.class | Bin 0 -> 8806 bytes .../bin/main/MostRecentEncryptedItem.class | Bin 0 -> 10143 bytes .../bin/main/SymmetricEncryptedItem.class | Bin 0 -> 7122 bytes .../test/AsymmetricEncryptedItemTest.class | Bin 0 -> 1723 bytes .../bin/test/AwsKmsEncryptedItemTest.class | Bin 0 -> 1804 bytes .../bin/test/AwsKmsMultiRegionKeyTest.class | Bin 0 -> 1294 bytes .../test/MostRecentEncryptedItemTest.class | Bin 0 -> 2016 bytes .../bin/test/SymmetricEncryptedItemTest.class | Bin 0 -> 1708 bytes .../java/DDBEC/bin/test/TestUtils.class | Bin 0 -> 2704 bytes .../java/main/AsymmetricEncryptedItem.class | Bin 0 -> 9147 bytes .../java/main/AwsKmsEncryptedItem.class | Bin 0 -> 9029 bytes .../java/main/AwsKmsMultiRegionKey.class | Bin 0 -> 8712 bytes .../java/main/MostRecentEncryptedItem.class | Bin 0 -> 10069 bytes .../java/main/SymmetricEncryptedItem.class | Bin 0 -> 7028 bytes .../test/AsymmetricEncryptedItemTest.class | Bin 0 -> 1842 bytes .../java/test/AwsKmsEncryptedItemTest.class | Bin 0 -> 1919 bytes .../java/test/AwsKmsMultiRegionKeyTest.class | Bin 0 -> 1290 bytes .../test/MostRecentEncryptedItemTest.class | Bin 0 -> 2123 bytes .../test/SymmetricEncryptedItemTest.class | Bin 0 -> 1836 bytes .../build/classes/java/test/TestUtils.class | Bin 0 -> 2700 bytes .../DDBEC/build/libs/DDBEC-1.0-SNAPSHOT.jar | Bin 0 -> 10748 bytes .../classes/AsymmetricEncryptedItemTest.html | 96 +++++++ .../test/classes/AwsKmsEncryptedItemTest.html | 96 +++++++ .../classes/AwsKmsMultiRegionKeyTest.html | 96 +++++++ .../classes/MostRecentEncryptedItemTest.html | 96 +++++++ .../classes/SymmetricEncryptedItemTest.html | 96 +++++++ .../reports/tests/test/css/base-style.css | 179 +++++++++++++ .../build/reports/tests/test/css/style.css | 84 ++++++ .../DDBEC/build/reports/tests/test/index.html | 173 +++++++++++++ .../build/reports/tests/test/js/report.js | 194 ++++++++++++++ .../tests/test/packages/default-package.html | 143 ++++++++++ .../test/TEST-AsymmetricEncryptedItemTest.xml | 7 + .../test/TEST-AwsKmsEncryptedItemTest.xml | 7 + .../test/TEST-AwsKmsMultiRegionKeyTest.xml | 7 + .../test/TEST-MostRecentEncryptedItemTest.xml | 7 + .../test/TEST-SymmetricEncryptedItemTest.xml | 7 + .../build/test-results/test/binary/output.bin | 0 .../test-results/test/binary/output.bin.idx | Bin 0 -> 1 bytes .../test-results/test/binary/results.bin | Bin 0 -> 622 bytes .../AwsKmsMultiRegionKey.class.uniqueId0 | Bin 0 -> 8729 bytes .../compileJava/previous-compilation-data.bin | Bin 0 -> 73542 bytes .../AwsKmsMultiRegionKeyTest.class.uniqueId0 | Bin 0 -> 1304 bytes .../previous-compilation-data.bin | Bin 0 -> 51731 bytes .../java/DDBEC/build/tmp/jar/MANIFEST.MF | 2 + .../DDBEC/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61574 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + Examples/runtimes/java/DDBEC/gradlew | 244 ++++++++++++++++++ .../main/java/AsymmetricEncryptedItem.java | 136 ++++++++++ .../src/main/java/AwsKmsEncryptedItem.java | 129 +++++++++ .../src/main/java/AwsKmsMultiRegionKey.java | 129 +++++++++ .../main/java/MostRecentEncryptedItem.java | 152 +++++++++++ .../src/main/java/SymmetricEncryptedItem.java | 153 +++++++++++ .../java/AsymmetricEncryptedItemTest.java | 23 ++ .../test/java/AwsKmsEncryptedItemTest.java | 23 ++ .../test/java/AwsKmsMultiRegionKeyTest.java | 26 ++ .../java/MostRecentEncryptedItemTest.java | 28 ++ .../test/java/SymmetricEncryptedItemTest.java | 25 ++ .../java/DDBEC/src/test/java/TestUtils.java | 44 ++++ 87 files changed, 2410 insertions(+) create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/checksums.lock create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/md5-checksums.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/sha1-checksums.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/dependencies-accessors/dependencies-accessors.lock create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/dependencies-accessors/gc.properties create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/executionHistory/executionHistory.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/executionHistory/executionHistory.lock create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/fileChanges/last-build.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.lock create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/resourceHashesCache.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/gc.properties create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/checksums.lock create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/md5-checksums.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/sha1-checksums.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.lock create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileChanges/last-build.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.lock create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/resourceHashesCache.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/gc.properties create mode 100644 Examples/runtimes/java/DDBEC/.gradle/buildOutputCleanup/buildOutputCleanup.lock create mode 100644 Examples/runtimes/java/DDBEC/.gradle/buildOutputCleanup/cache.properties create mode 100644 Examples/runtimes/java/DDBEC/.gradle/buildOutputCleanup/outputFiles.bin create mode 100644 Examples/runtimes/java/DDBEC/.gradle/file-system.probe create mode 100644 Examples/runtimes/java/DDBEC/.gradle/vcs-1/gc.properties create mode 100644 Examples/runtimes/java/DDBEC/bin/main/AsymmetricEncryptedItem.class create mode 100644 Examples/runtimes/java/DDBEC/bin/main/AwsKmsEncryptedItem.class create mode 100644 Examples/runtimes/java/DDBEC/bin/main/AwsKmsMultiRegionKey.class create mode 100644 Examples/runtimes/java/DDBEC/bin/main/MostRecentEncryptedItem.class create mode 100644 Examples/runtimes/java/DDBEC/bin/main/SymmetricEncryptedItem.class create mode 100644 Examples/runtimes/java/DDBEC/bin/test/AsymmetricEncryptedItemTest.class create mode 100644 Examples/runtimes/java/DDBEC/bin/test/AwsKmsEncryptedItemTest.class create mode 100644 Examples/runtimes/java/DDBEC/bin/test/AwsKmsMultiRegionKeyTest.class create mode 100644 Examples/runtimes/java/DDBEC/bin/test/MostRecentEncryptedItemTest.class create mode 100644 Examples/runtimes/java/DDBEC/bin/test/SymmetricEncryptedItemTest.class create mode 100644 Examples/runtimes/java/DDBEC/bin/test/TestUtils.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/AsymmetricEncryptedItem.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/AwsKmsEncryptedItem.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/AwsKmsMultiRegionKey.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/MostRecentEncryptedItem.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/SymmetricEncryptedItem.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/AsymmetricEncryptedItemTest.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/AwsKmsEncryptedItemTest.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/AwsKmsMultiRegionKeyTest.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/MostRecentEncryptedItemTest.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/SymmetricEncryptedItemTest.class create mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/TestUtils.class create mode 100644 Examples/runtimes/java/DDBEC/build/libs/DDBEC-1.0-SNAPSHOT.jar create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AsymmetricEncryptedItemTest.html create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsEncryptedItemTest.html create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsMultiRegionKeyTest.html create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/MostRecentEncryptedItemTest.html create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/SymmetricEncryptedItemTest.html create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/css/base-style.css create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/css/style.css create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/index.html create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/js/report.js create mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/packages/default-package.html create mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AsymmetricEncryptedItemTest.xml create mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsEncryptedItemTest.xml create mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsMultiRegionKeyTest.xml create mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-MostRecentEncryptedItemTest.xml create mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-SymmetricEncryptedItemTest.xml create mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin create mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin.idx create mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/binary/results.bin create mode 100644 Examples/runtimes/java/DDBEC/build/tmp/compileJava/compileTransaction/stash-dir/AwsKmsMultiRegionKey.class.uniqueId0 create mode 100644 Examples/runtimes/java/DDBEC/build/tmp/compileJava/previous-compilation-data.bin create mode 100644 Examples/runtimes/java/DDBEC/build/tmp/compileTestJava/compileTransaction/stash-dir/AwsKmsMultiRegionKeyTest.class.uniqueId0 create mode 100644 Examples/runtimes/java/DDBEC/build/tmp/compileTestJava/previous-compilation-data.bin create mode 100644 Examples/runtimes/java/DDBEC/build/tmp/jar/MANIFEST.MF create mode 100644 Examples/runtimes/java/DDBEC/gradle/wrapper/gradle-wrapper.jar create mode 100644 Examples/runtimes/java/DDBEC/gradle/wrapper/gradle-wrapper.properties create mode 100755 Examples/runtimes/java/DDBEC/gradlew create mode 100644 Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java create mode 100644 Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java create mode 100644 Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java create mode 100644 Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java create mode 100644 Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java create mode 100644 Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java create mode 100644 Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java create mode 100644 Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java create mode 100644 Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java create mode 100644 Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java create mode 100644 Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/checksums.lock b/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..ba021712dcba69358172340e0c71afefc067f396 GIT binary patch literal 17 UcmZRM=kvRlV0`c#0|aaW04%!%aR2}S literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/md5-checksums.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/md5-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..5a685a8a9914b64d0fed86d2f4c17d07d7fb7306 GIT binary patch literal 22097 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 E1qm4sn*aa+ literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/sha1-checksums.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/sha1-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..934ecb7207c555f19ed4ff4dc63ac29abb761355 GIT binary patch literal 22385 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 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/executionHistory/executionHistory.lock b/Examples/runtimes/java/DDBEC/.gradle/7.6/executionHistory/executionHistory.lock new file mode 100644 index 0000000000000000000000000000000000000000..ccd9ee529fe95be0b9fe68f5a133bb992a4470b0 GIT binary patch literal 17 UcmZQp;X3`|dCzwr1_)RW05{bIN&o-= literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/fileChanges/last-build.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/fileChanges/last-build.bin new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 IcmZPo000310RR91 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.bin new file mode 100644 index 0000000000000000000000000000000000000000..f495da3fcef624f6bdac8af1997ed2ab9fb0d733 GIT binary patch literal 23197 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 Xgt42)%}{2XrBR4x2*4WD)0pvJh8!j` literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.lock b/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.lock new file mode 100644 index 0000000000000000000000000000000000000000..e72f807afc6ee55c85b494b602908b233d5a8a74 GIT binary patch literal 17 VcmZSn(mz}4-UbN|1~6a>0suHi1dadz literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/resourceHashesCache.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000000000000000000000000000000000000..b982bd1bd4f57644858b06a4dab7d9f204c13dc1 GIT binary patch literal 22271 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 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/gc.properties b/Examples/runtimes/java/DDBEC/.gradle/7.6/gc.properties new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/checksums.lock b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..7561b220f3406420162548103f605a5718c0ac79 GIT binary patch literal 17 UcmZSfb>x@Qv|0U*0Rm3%=?-*4nC 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 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/sha1-checksums.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/sha1-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..0484a365e4841c38305c883cf7b55aa428663d9c GIT binary patch literal 22385 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 Yr!_?$s~Ua`&mY%KGIpj!&VP6K4}pB9n*aa+ literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.bin new file mode 100644 index 0000000000000000000000000000000000000000..5738d0ccfd623759a76be28f42daf98379604e52 GIT binary patch literal 162066 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 ZeVKaY|APcfu-byv&a%}8;xS{~{{ZdzNaz3n literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.lock b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.lock new file mode 100644 index 0000000000000000000000000000000000000000..4c6d0a87b250f67b8eed5b311ce5b27ba6bb09dd GIT binary patch literal 17 UcmZQBSiFQ|rSLuv1_*Em04*s4%m4rY literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileChanges/last-build.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileChanges/last-build.bin new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 IcmZPo000310RR91 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.bin new file mode 100644 index 0000000000000000000000000000000000000000..311401de42862285f1feb54041cdbbf4a43c2d87 GIT binary patch literal 23197 zcmeI3i8ock8KYEY5|T=TLL@^)-b6|f zLT?c%sVMc^=j^k+>%GTc@LOlEb-I8Mq4u%d~giob#qOh&=!J!w(dj!KM2c-FB`d`oB+8&HR2Igny;^x?PP)6 zSa*geZb=+J8L$#^lP!oxZgMNWwmI7vaw|l0FaFGfT$cm! zgb{VG9;=ILkQ?(Mp5&#mxAfGKaL8?Q5kFm0(p_$r*9Ey=6XNH5rM|0I1v)}*_5|?@ zIZQ`)r9b@!xsfsA=>pm+j;z{lkXwl&p3z|Au_Q{x404kK#IyKK{VE*SU54DI5b>OC z_KT(?oWXv@nt?sydEt~0z+a&dgqmiLgGok#qUiuCaFx`)L6(p5veiPh!tn7;5j*x%|O#Gl#M zE_7^L(hRxnJjA;#>kaPfPGQF~XgV)uc;SLwT!YC{kQ-Ve-qSw6cgcLgAjplv5FZZw z-6flyLab-o=ZKH3poa-;v`~lrt@UU4N$BzM`KNsSw6dFt(B)(0B z+_-0kZ`i)FA!yfK$W1yCm+$cs`6xX~<`;?e-zAq3S+8xwnEiUW|kh*R)-Es;P321NJv#L0s$0mxI^u_$xqe zS%LVrRq789tyVt*xt=oO+aH8&wamPC3UZ5h#Er|$)>%J&NgQ9+0*G6E)S@}{p4t-TuU8o%A+&@69e>)Y#?O5Jy>Un)g0QNV{p5bC0Lb-#2*d-S5HpG(X zREBu)cZ;IMb2*81YZHk0QRk2M$9@VA!|yS?gE)Gp(b)Uo->5B>0F?lh0F?lh0F?lh z0F?lh0F?lh0F?lh0F?lh0F?lh0F?lh0F?lh0F?lh0F?lh0F?lh0F?lh0F?lh0F?lh z0F?lh0F?lh!2fpwN?>}@;3bZ|77E>*;;`KGSm#oL{R5wCk%}}L{erwfjt-H5GkBN# zuhIQmd1*u6iglLt(v!JLee*G6_34^=+~6O=?%`%IG>hz==AZL2Zn+TBD7Pi?6K06N z(rm(w^$FPBj||4eP@KE{|p;|IK#npbR=c}NW23lWxT`?IgW zT_9MxbTc$kL-88)8WnPlRRImcH(Ne&ve&y`wv`tG_oHI-@Uy`UT68qo7+!g}Zsivl z^G9({S$M4eX@JI`AGpD!RYf)e(l*|d=9W3Jw%>;xQD2yy4C(s8oEaa_*@G#O4-JHVMOk0svG}dF)Q|h{*etnO-C%MKw z-_6SehDTLV|3j=RjkekiY#5BYtnHyM>?OI%0|p#dKYZQ|40r$jmP@!m^T+NIW-uCr zJ0%jb=@JE7{YxAwHiG9@z}d&dfH3ITedY{?pX=dU1I=SP@hXGaz4r4WFk?+%N*Z2+ zF1n3uj!*!v+TB?1V)l)>$Om5 zte9n=_i1!2^)*=Mo*qkI@8s-L4UA-_Agv#`!Q^wDT!SxSz(vbPBqzykXXo$I7vT91 z`suOcSh#lCj(i{+aWbxxtt}PazM~U-+)C{txKYRxir3)OQzaYf)-M)EU1j&*({ZDu z_)i5JZd~H%#SQjX;OfA*2S4Si(3U>Ux6J zSXS>Q8+~r?o~J2?iDj^w(7Rd}9D<{=eTo~jKJfZ_Y0HdPZh<}{3wYYdKS-o19hpL=tM%BPHm^UcjrxBLLalWko_XhS7CtyUDnR|kF&{$|~ zV9PM`tNFeOKAnB)0|PQEg>&i9ON7P@}`;)}Gj=6Wx`} z1#YdJ^SB6O7c}O~a=v=0U}DoDAW?jvGxn#h?|g8*z^-cM#8YK#3FI2_W!H*VsOQH; zA7fG!&b+YYzoS|lNj8`tTi;phx-o_C<67r|%e6X$u>+2ZbCzEjA9Pi2IaTLt%c<06 z^wc|oqZA+2GHB5K$u;(-@1k`_cI*-JoueA@Jj0eWWq(r$HrQ7{MA6Tgf$o>f+3*s_tb5 z=f3p5DHjE1jMo5b7;6&XY{a-qUNaS6&X9evjftOiS-P<-ybc;PVrK-<*k<|ls;{tP zw9+$Xr&&b#yej!OU^JkH#U|pJHD+I+F|0(*=J7pQEzad@Y^o=k^Nxd=0>dyIyYhp^ zlm<2oM)7IZP4YE%A2MY36mZ3T24{cFkORM(u*OshHVnr6&q7_XT5lb_el1ddSfm72 zIX3guwYWfI2iGh{BYfm`h)kdImEW5lYpR?-hF#S#LlYWo;CjMnI#K^Sfm5fwY zf%>(Y;9LkswPQQ>I~O#e6JWz&6`I4 z>!hz_z8x-B`SrZ|EgV$}F>|2Cn z;mnbexw`5ZJX`WOTTH;Y17Ew~%7Zl)POxDZRmi}Ifqs2tLd&%)sm*(xrU+vrF3{M( zI%71x7JbbMIQq&gGV*1`hQhlP~Dfd6W C#~bnh literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.lock b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.lock new file mode 100644 index 0000000000000000000000000000000000000000..cbe7280fe70f4fe54977d67ceb9d8c22c12e5c3c GIT binary patch literal 17 VcmZSXocH5?;`3iR3}C=$2LMF^1+M@A literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/resourceHashesCache.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000000000000000000000000000000000000..30fea0b10d7a600c7203e094f0b2776b8e131537 GIT binary patch literal 21047 zcmeI3do)yQAAl7yMXtFkw{i(lNfQZOFvb`Y-E%2LJ*jShFqdK!Z>Hv-mkMhueH8^zxAzm*1}$oXMTI{$DaLn5lM|oBUwr?* zr2pMSTc`jkfC``jr~oQ}3ZMe004jhApaQ4>Du4>00;m8gfC``jr~oSP|59MfKPNH| zI~h6M*}M%ge?C3~k$*PGq4(%bQs!<>*sm%1;Qt3_dMHna&O6`4IQ1Uj7hCtUCc@oI zaUKPD#Opo+w|Mj##%U3NM;fkeyq&3@iE$DQ@aRJ8vmX6~Zy2{L1w1A?C{k`&fEYgC z8}Qg(;fFE`cMst6e*k`2L+k$GaIMVpSVD)<+(2k1w!cwFA4{6zSo|bA^t`lm``a0d=ATL&QH~EF&HPc08aRx zF!D-fd_BJ3V8G`t<7Crw{cvt45BNM+^|_{Be7<15brs;EAzriZn^Y>`oH51g&3@_7e9Wx5Wo$r-i$Ai48(7jHgv#^eCId$pZ*M>-1l zIyED;c^9bXvHj9=0k@C~ulv!cFOG4W3cxoSBnf_NlB>p#(*kf(__7^A3Hl6-+qwWw zF?*osUlD-cuWa@JPTSK_Z%2(S!F)0m@GVLlmv~3bmH2+U0pBS*t0=bGryck21MX@{ z+{H|NAc%3w;VB;Y?DpF4+wu7{65xAVi|)EBbmM<_`!#?cv{!eZTr^C_=8(n!_gd%D z#Q7~$2IIDhfP0HLJylOl!tW=x<$xbC`*pBF_eCP^r%&-i-Q^wMuinOwb1vY%V{L^d z#xc(@Zj%Z4DVe<*ila@SPaNRjCm)}XJoX;+9Th+YPyti`6+i`00aO4LKm||%Q~(t~ z1yBK002M$5Pyti`6+i`00aO4LKm||%Q~(t~1yBK002M$5Pyti`6+i`0f&cviyv3os z#g7uaFYznlRuNY4-ZstHd0psH zsK7I&vb8G;kIHqvh7I{q>rea#eC3S^`uQuFb6jELR;!Woi{N`spuP?|0aUa?*2o;hDtP?6R>ipu;^LHYUBz{f<5_pBT38VagVIzk`i| z@Ld0NKd*aBepGU|m-+CXo|tyF+7CMhbC~=BN5?0f7p~#YhK<$@^yTR`WeRS?Z7Lqg z+5E8Ku)vFR(%*QwJhfdyVyO5BY{*ZrMJg;<2eX{>iobXgieMuxtX{h$pQ)`XGCyHc zW7rHmI)ooyC+J#8XqPk?{+CQqTVJ(rkBn~!qc|LYkl-6>R z4X$c$G%xr78&*o|_C-H{H7-AXS7jcuuKCf2Wz z6erHK30i|xi)@dZDwxjf(7pXrAtY^vD>--|v*4?5=D<4B3n^E%bzj0WO8J$#G}hZbUhbUsB=^V# z-Un&gUP3%r685)V3WQ7fY&aBeJi}G^-_NCa)tG*5FN6LmLol2>6vbO#K5Z`+K_26h z6xCs4M%0ho2+W)eBuXRoyEgv+OmJ2PNo*_cbJ>+98ZB9UDKMNhahT<N zS>1PMe_qQPtD_afX7g6TPdnRvlh^uZX;ofIa|@wyMeEnVhMt1SF714-`MLFs<=&c2 z1+WqRqMyawa_m|({m$)J^X4P4anUVh;(3;VK$G%`q^?Qs3{4k`ReFWxZMmJ8FY;t$ zFpCY(@SBsv@e9ow>AXrP)S1-AAH%E99p&rPQS-J38c~ zS#aMP&({&^bdzWmooyY-KcH3r(q+QJKy5ZGr`5B zww3B4uC2zfA+jAt|i1-fP-Q&B*aqqVtH>qjP_D^+a^}|u0GD83X z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~f&U^fr*EXi+>CR$$(Y#Vnl{>|Pi(uDrwqfx+}Gos|{GwX7@EqcW;N*i;>cxS|6A0$#2CT^U*uC zen`4+_TX^hw)~=cm-OIydda_d^PrwnkREZ3@7QxQ0oAXhN8c|C7tfhi)yvZ5s%e(6 zd5<#!2q1s}0tg_000IagfB*srAb!^_> zM~@^wvOX2OHgZ)A0YUuJy1a zs2h`?cC*0UzG|<)vo#g70x)p7hQ%bRc(|JM zBQXSWs~>_Zb+q6T?%x?tS_3wz!wRk9S1M{I_L}Lq>N|rC+#fx>%balf-i-cq>^FtSSAxY940f4 z#-mmy9Ggs<)h+=Y}?|9Uju*IRFQ?SzJa>k10((&wM zc!!m=(q>}F=@%CJu$CMi9O?<+YIJE>C%B?~=S|k+ZZn?F1IVV*I zr3BWuSDs~6eyBjgTtM9l<#jr)#|)M{`-rwmu!( z&`0|l&BYTjD=p|JITr8$1rZ8Do*6+6mKY{H18=(&BqyWG@u;%4(!wM z8vDpeALx!eF*9rW@LGXu!-fD3;Pn~~3YOP6H_hA%55*4mPG&8-CKIEi^tN<2R_d2mS@4jfTpRJU`#c;v_PoC{Evlin}A~Va3?n+V4 zA5+CK=sdGHsv)-^Tq=^!BqM7b&ngFZOmMJ@Tbn=e70?UX8Neh?YB)jPbn&9DiAKjA zc!R*7RoTISsj`iBI%wAMGUb#P?&u)^>@ z1?v5RYb&6Xq&zjDs&IEo#|Q8`0`FmKlBAK`*}!NgC>&y#_#i&4;X`Fn(LToS;Ui3` z<8&$}lP{*pWqWzEu*V7DA^d@chnWl)k2X82!ylI2L-NWWz$5smjz7kq z5Sh|Wm9pEZcfSuGBUKY7Q+<_P9^@g^>PK~)!6$eq>qw5?PH<@{e`X`}tv-B;5HqQC zR&7*Np4Bm}@_mhH21t-{_n+3$j&p*092hoVm=ricqS#$XEp=R7nKCGxN@SnW@i@+t z0nBolOIJHuiS(V*s_gP}D%O8q&|bx2or^+wpx5w4!6I8`b|O=_{`95OsdNBeP+98% zJgR{Ixk~1~JU6j&+*bfk;;S0|V*a`9?p#*gPzNbVD`UI z&p)HB4p6!CzgeJcwmJr?z*G2J9e;i<%d~e1`T*aP6wc*YQsp z{!wuKTotbgwfuUiKLez&*49H{1=|5Q=%tb2!;G5 zsBDG&o{s;)|5B&M^5S?oI#rHi{yJ6IU(l%+@c$bAPq6tV8%kcg=Ri`{{e!uoSh;@{ zkA9@%$0{B*#4I~a$ya}r3;wCPT+#*mV=9U9GcMI9-)juskDZ`&AVeZatPZCni`?BmxHQ%c}1nVfC5#ShBFrn|A77 zP5BLlh)ivaj`ON^TLlm1pWprJizVsuTls>V%7t!O0Hl>SD=LM9gXxZwBrFWBirWFt z<5PUTIhmkEx0LX9HY*i0I>huOV?$g@b2OuNnUCse&Q_)Jx0+^WsTDgoD=!~)V>m_K z&sXdQt-Kuxwp8%0rqWimAEmpFb(ElMEw98*PRv$gM@!B|E8Cdw+8K|Nzs#4O@`_yd zW)gI$1c}4IjTK<$ua`x!tI0CKvSQ}hpGa6^W}+uOmK$f;r&uDE<$~mJsx6&MwyBDFoAd0@*%oJs zC>OKZ3ODP{wo-Scw6c@$4+VEtK5e_N!o1yUGc#>wdMp!=t7IiBNb<@#7mL}u`d(&C zvPzc@Smbjf;6uAz+< z`89kfojRsU3c6e?*C}g^#fEYd6KRVt7j>Lo$U$r=WRG!{h!0u6UG#ZnW`t zfat&t%>;QBTdKE=|Lfq=x3C_nUSS+)VSW|7x{kA0t{}K<2#Rb4LG9M_3$PNeWY4c8 z#Z?__dpNUN5#{|54@pwpCqrwd5sr8bZ_tM+Tx)ozupy%5KfZI=G;$WNiuetG=QOq$ zo*8U+VFo){7evntcDu39@DsuoqizO!E2ly}&V@#*ZVNT4x&77m)pO4Km~X`686K&t z8=l7NW)Q6cKd4}gRe^sF@exBad}nbu5-gNN5Uw^ilE^tO8OJ zr@p)YZTwyy4SjJlzwJgS^rut!_$joU#+B@R((ZUqBV;rg%|=6>fu~^@&7q(@*JMvL zI-Di8DZ!}=Z#U`ayW_%B?!VB~_KUDcQdhsGQ z(JS6vL$%6Y~ZWUQ4Gl4EOydMPKYbNxHq{j%qP%(GhCythmI@ zcB|VGk8lJ_-3P?WmJci4w}{4b`th9W0r5+KF7RH`9DGk!dhklzLfqR#zKoJbUNp-Y`LbNXv1Xq2=W;1qL7wikw8~`&@qF)-MI37p zgGaeh$<4;Un15@K$8dQ8!{r4GONb#4mvO^^yO?nE&=xax(B+F44Hsk$LK?oV;gRy6 zt2J4wey~SFTti9&tCa7wfA)}whd};QX=g0j^xP z?nRup5B36ouUkh>UMX#J!JmoVOfH{d?oLX;lyWOBXS!0)Ae8q8aW!9r=?LYBgG zEM%E_rX>~+A?EldS(ndF9o9n&Uf>@E_%+#3u0@Y%(xrY_Nu$kl{R9oY{4_ND mn5S|{gt%SOEgSh=M)*6>Bb&UmD?Dp|pTd*&cM@6I1mhP8qs_Gd literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/bin/main/AwsKmsEncryptedItem.class b/Examples/runtimes/java/DDBEC/bin/main/AwsKmsEncryptedItem.class new file mode 100644 index 0000000000000000000000000000000000000000..cd6abe56ba95900148ac724bc89089ce265adacc GIT binary patch literal 9040 zcmcIq31Ae}8UEg6li6%0$!3F*^}vXrArRKY1K0qPK)_Upm;^AO#>wuGEZHOOZh&}H ztygQccdN#FRBK~vFR={?D5$m4Vr{FnwN+bt*jjCCYj3TUes6ZN2fJ&DL`gF5{O|vN z|NEc&-`=$cz^US76^cM*)3(@Ip;$}U7abai8~)a~5mI3jaBk7J>Ykt;?)9AAv&Hbm z1u8l^+gsaKb+2q~S-qmWt!Yh*fNOPXT}M0`2=_J$lrE2iV{ttkU#|xT4LgbjRAajy z8VDMM(%jnC)ZUc_#STrNTo3!JW08QjA)Dm zw61D9yS-&a#xj?HJ!t4#jp`6FR?gC6F(Voekg64dnBEgK`~qSl7cKS$1K~h?sX#%^ zob>{>|2AS-0$mOqYqg9qQxdr4isRvh7(Xl!s2}aA~<4o z4t+_98wmxr8@@=C%-vhFIu_X+-=;?mj~>!5j)Xn>wph&H@1Y>J27E@$;~xs^p@_f7 zv%+kw=vf{N7~yzhwnE>#5h-#<%XXhJU@Aw&Jb@EUna2#@U^EaP@~kq#MpO@WSpDQ; z74vB#=Gwt{Am}+$kM&W44%A_`1NAsXMT5YSLzV(XVFW!*aoTXtVB9b*SHr0|jken} z7zp}}s6cDYoI{suE{L<56QCMPumFoxEEH%tED|RGYVd+~AZR0-YO)nJm1Hl`uoPzq z6q;fYm`0(b{3H=9$X^qdt7xWargTL8XjlP;je>ze29$;Alv*3JLFI(L0IM{dX|f!) z<9&1xzaH1^SS_F|TI4_*)~YyLU{)UK(oGCchrhphC~nZL&p|sW-5iaC;~=#jC}C$J)~uyb>oBgD;F?|^|` z6`KV*4xa-nq&8c}Y9y+m4*`LaUL)RW9*sUq_~C524@X!QGKw`zT8-IuHhD~0Of@>t z4`vI&DUn%^V;4m+GY@mbI(0w&i{-c$Wy)uyO2bPztb}6DMmxSth_OgCE;mXlzpCMD zGLct_ri}#2;D5h{8tf3b+Ja%qg&B)=TBN#DX{A=E%PyG68or593V??O)8bsL zrD@;dR;IpVG7%XUsLA26)-5123sJFKV0tQtOWtc*qR~jyfn74CZo|D2_+FW5K74T2 zXN4~Z9>rrSzBT#8d+lIcUOn5XNh4;4{%^mlbUPDHGRT);AHJ*NJCjeha|4v&eE$@s z+PN{%1oq*38lJ@W={5AdkifYIn`ZWLk@uFDNFUz;*OC%Eg&(PST42G!CY~2+=G{@l zGx#yRY9OIZfh)5!oV=iN5tt)yIn@2J0p9iv#^-18GZjCbyhW~Js+MVy&%G;)%!HF> zkuTsEDt~;JuQdD`zhO{fB%KO_dplpQ^zJ_$d=$&@FVCYtYxs*ik4pW9nWiL4 zJ2C|SRo;9Vf{Xort7s#zC@Z(g>nNE%%c4P%FVx=@WvcB=p0H;n5<{km^4LuFD$*C2 zB@(k9XRffiMZB?@C8+YW{6_LbdU;(;+%*L}|Cip(yCp5o7a9=g%i1^j9PUu=my%o~ zQB#u{&$=lMa~hYOGXXYH(%U0mzR%7U&J19S+iOE7No{k$Vc1S;RDj{T*CN zr_+6AU5-YmPZY)ycNeF%)bMvt=-bYS&>5kHC$72eM#5VJmSxE{>7 zRc0Y|Xken4+LOi`?QU^ej$~yilcguKDA>4^1ofpMu>`m{3+&`AGo`k~?LwluE)7*4 zep1;s)Ajd;4oQhEqz7mfRW+NkWjbfQDvl7CnMxvCgF&NL4>m=62SY6KBn!88aiqZ3 z&Pa81FkCIG+SS(6z`W`J3qpf_qdIv>o>!d+P4lYL1CKPpd3-BK2ByquGY}=`&0e)0 ztJb5vF^8BY+^iZ2*7h^*3KQ4w^-O}qF)W1%-VPe`=g-N)l0Q!s-2ox^3Q&VN65{ci zm?^9Hg}z`UW;nzwaiS__(+^YP>gWk5baOmE75@R={kU?gu1v(|1tbQpxETD58%4@O_U z85QCv{uZ&PvZuh#z9X$)!hUI5zl?onTED_MT|SF&uZ??;Koub!$=))4<$o0}F^cKK zP;Dcy6+D0$_j9lS)%@!qI&ia-8;)m7_Ga?85Q?^pC0F@KVnOrpE8!K^?!+t!K`|jn zvLyt$TT3j!i8zV9VyPvrs%6{8nYogvqRnKolIZ=rQP;H-9&9@hyKo$*D;URO z7VuC%j%5Yd;8xu>Iaf4}mDzJHJLg=jIrq8B&E}2X(h1(n-0C47i=+7?u*a!&~ky@A6iR;&b;=fLP97W%#vC z*YkUn&-K_+eot~&xb7Ln$S^8La2z}LnVq5$xZGv#a<`U{;t1UCau*?SVYxX`YKfP~ zz9KMu;0CvYqAqinjN~52V;09mfw9Y?u(R z)9uV}T+aN)Mesi%_~!|pGWw+i|B?j%>^Od_z*|9iQJ$|PME083Wr{>HkS4M{ZdXP( zlY!)pG$Fpu9qHXXJJRLI9qC<)bvPwf>7_Q)OP^-V7j))g%;LY<3=JpZJbLMN`sl^< z(o5;3!))J4FMSa6@d!N3p&NMbJe?Q41$Z6{@gf%CB^D`O#%Xv3OYl0*z?-aLzJ+C? zkS|g*v0Pk$m0}B4i4a=FHa;I+#qk^Y26Qvd5yMy~cJU$SIc!igoU1IwCglvAr)=Q& zd~_?DaiMZ0bmcbqmD^z`qx|lMPk9Id!sK&YS|;Q}8bX2jVq!(M?Of5YF& zpKQgsOVR5jrr3=ytjf(*TFTZeJ1!@`IqN_ zR(62r(7VHXI-HZl+XNg}U;j29Rza3t<=^^x{=X3}QAz!0oIbQkl{y5jt=;1+x(j8y z#k8(UQMFS{-&6zbU%iml5GA!ST3VdF7|I>s^9hT3<6irKN9>O-0NjA&RII^ZA`gCtQGf;VCL+ T?c-VVyAO|=-vRUs58VF)ziG8i literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/bin/main/AwsKmsMultiRegionKey.class b/Examples/runtimes/java/DDBEC/bin/main/AwsKmsMultiRegionKey.class new file mode 100644 index 0000000000000000000000000000000000000000..da3bf5b90fe41295be4bd8b50768545132dc859b GIT binary patch literal 8806 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 T96x1$AIHP?_j+6_0r>t8)Z1yk literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/bin/main/MostRecentEncryptedItem.class b/Examples/runtimes/java/DDBEC/bin/main/MostRecentEncryptedItem.class new file mode 100644 index 0000000000000000000000000000000000000000..92d6d339aa5ab3fa0508dfa5ce773e4d8bd125c1 GIT binary patch literal 10143 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 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/bin/main/SymmetricEncryptedItem.class b/Examples/runtimes/java/DDBEC/bin/main/SymmetricEncryptedItem.class new file mode 100644 index 0000000000000000000000000000000000000000..155975ae7e2c3be3b3d43d0f70cf89e3dbbce982 GIT binary patch literal 7122 zcmcIp34Bxa9smB*G%rnGub^PWqfXEkfd~Rhg3PuQsJ2BLY%NUdYw{?89PP_1P&b`7 zD(=utQFMpL)-mU%xD_f+-Jwo)INast+;r19Hg&SY9Xh-3|K&9YO`S;PlP3S`cVGYC zpFM9s_%MKZ!mYp|FgY|3kL#up4L2sj#z3#BN19AMuD~f!xlY@l`D0q5$G@U`ogOv? zszNKQpkWS9eDfnlZqGt>?DXH@4*+tPoJGLy7))w{E1!K-03; z6>W`6@{V~0+%a9-pjXF9andv`mC_9}O0JefQ(AXSj|hlWJhUhrizcGxVu7NX+I9hF zLo%X!;KNh}N0O2+tQ#1R?43VrVfxxjUCgCu`H%AkC ztHtU{NmlAB8P;O$nh}+Ad$El0;q@(AuRV7yvY7Iq2-8&@kI7`rTpJ~UBbp1Fj7oRL zwP=F8)vO+C5-FOeaZ_0DwUnS>roai7Y$-k5Z$!-j|1v$H8(J)6&ohS=%oaE@|EzUL zZ==PMaSvu;y8N1>VlGY*C`ss>%=!USC%eTptLtmq70jnfB#KRb%gvJhRIeWP6OEyp zXXyhW&fGYa`m1XUc`y$PrPKj|DTFU1jT?(7(DJwz4lS>nJAXkb7UK*B9~3w>m0V+P z)C}FP#kGr)3BR^6m5QwQ)BYQxVLj!K3?#I8GScl|VhxsbH^icP!c8?-}h_MOZ5too3V-9B;k0T`Fq1iYDn& z#o<^oMNyra(L-BJD&RM?rYZfjRg-a~rlfP&b{9A*&5*SyJ&5!(yYxqwp>3>_PT*Kg z#VVXhdkK_oG_>AcCJGu}I&_|2WM!azQ_-Hp=(&KQjlPPBM;sJK&7FI~&h#mJClT#9|SiZ94@)!_AB&)FE1x%Nu}9q*6sgC+PfzM|kB zfpaEO=SyQ+5AWvp;;T%^AvOeb%n5M(tWhlj^qBjwL&5!dw}M7?A|ahl`vAVqB9L4o zFg@4Ua(}d@Sl0*}z|zH#ik*^arLnc4t@G@alESb=^`OA~F<5fS-ndw0A$&;1H}NoU zVC#9c)@9>1S~4vLxWvNoD88-WTY2@;65~7gF8iV=bFOA4Sx+bD!LmKH)`{S8d{4m> z>}awDcXU_9ld@tvWhbEE`+3b|+W9@$gCDB+5uPG3>7BCgvihecxwTit)A$J`(EIwC z`2`NoRfb)_f$qW-Yc+X0s{5_-x=7Cl<4VBOgiB$Cqty8l&%V+wwy9&ER6{c5Mz3TgXLY&>l(!O(%It zYI6>$apcJ+J!Z}KhmXZUMHDdsOHyO6uI!IP$$lfOFOAA~rKyGAg=R}&7W$?HUw5o0 zEZLDOc8`eT_)<~C2f<<-$v+oo3TFByr#a{40h-J%e41eEzbr z`S~mH70=j-X%d6O!XVj}7$mfsK7bQ&B4;J2K`l?s;MmEXlPsoQvzP?hW)#hB-i=wE zJK=AstbWMdD!&5GnX`s)@=TwT-@7rdbC!3(PMj7fmSdmO84t-L-;`BW@L3tA~l73#18XH$Dq2y+fcPGXxzm=JXq&?M;eKyN!0GLj@~x&O&JVbJE^o&!T;PoiV~qpDh_Pcq*D!jE(80YfEr5SFwsg*%Icq0A zCe5-7pA3+dn@<|TZQhO{eBRqRgu90D#X!jpY~O{i1w1?O4Qse7P&z8O>T?Zax1hw2 z2n5Opagncl7>`Nm_XNs(Wu1YFA^hMT+NFbkMO%-#Z8Lw5yFeMdgTJ#A3+@f_H|VR7 zB!9dO)q|MHNtHF3J&0pDAs^gv4Z`bFePupRTC0Pou%@N9l1cWb0^3F|_qly8?vxDS z7lE=JIA$A;_LX_v!}yg$+GGg7c>?7B1sd-6k7V`KGEe)hw$R92Vj&C zRauCa2$4Ir1j_J0hRf0n|1nT*F(kor7=FoDo;S@0&p_me@Ml8gPRD`Bm4XntQwp^$ zE0hdUC#w1VG=mWgCnxF|b`1=zB@Ed{zK$-1j%BzE%lSgngsT{icX7O(L3s}Y@?M7G zBOE`*KzoXTw->AM3@*U4^1_A-@gg+rV-x!d&c|!S^afvPMzBV(h6xX%;zXYl4Dvx~J1!MZ;&QQ%FGKrrm3R|Zi?^}aF%{Q1j>ol*S-8$|GPXEc zaHHcq+~8P+n;h5UX2-+W>UbTuSV`p0bYNTIPXQhK?Ia>cGEFeWawOA45uA=!@hZ~= z^R{Cjxpgs#R5_la)^qQjx|Da4Ic!S*i6aRuo2=O}p z%~7fFh?nsn!m5;VJ>DdaGO=AO$6H)0Bez@eUyjQ0HW^#Y-88Kcjx20~1tE=%BO4p_ zrrJ10GB`$vLzao>P(C82qC&x91$z|XkRJ}@r%%Ba1ve}j2aPcgmG2V5 z!t^-5SzJ`Ld<3NjO>6T_Sx01|yG690%k(4JX4iAw|z5!%(<&Y3yicg{ES=ik5n4&W*_4Gb{Mti&DP z7fQPJT42jgq=d7sgug9fWgx|H?lFJDEsqDg*8SFFVJn8rO*e4WEr!8Txyg{Kg^tK$ z5IGZRWEe*3ZXg%G`sv}%9@i8t@EqD}(z+tHLhw4_U+S{2F?;Y&P z5pyO!MTsG+!mg2F{3ux8mTDIB4D9j{7O`ZY!f-tfAF6#Wg~fgTV;ER`KaQO}D;DyJ zYm3-&Isx}Xr)8}sgVk2eb48#`Sh!4(Z7H}SRa7b38IPjUTw%!RD(z7A;w-K*%pHa6 zDVcP!9w;HTMnb856Xj)bjbS+AQn^|MKkSK47GF>v;!r9*%VQZg4UqPx`pVmXJ4}2D z(*LmNE^Z1tlvIPCN@wPCS||V4O1Y^kciY5@zME;=3uC&iHLMvRdG%eH?ME`)*Z0%J zU3^XKxZ`ZKqezN47Q}A4Ut!12q-TcF<80m&ohJ9%$(=tXeFjtK_{1mYI6ZAJ+&FXY z20(Y7LrM z)HCYS^DmHno|yC0YO`*Ks5QF|)DP2)?m4XwVuZ#inw>)-@uciiPe-yDvS(<#`y0l; zFHF9`^dV*raNz)-)C=Ve)?mx2Lo6((i|N8=h0hDu7YgM=+$g4B;aV~M3%+7_hQ)@S z=X>+Y%jHz%59Ere#xsmmim9KGqxJxIDleZCqggDGh=Cc@=>x&jB(4x-&}fWkOcMB6 z0)3GHU&1Kn2zH4qWqN)Vh|&@!@Q}PT;dvrP@Oy;Eu#Io9PE;rH0N>I`$D7AJij>Ai m;-7g#;WGx9fkyJz4A8%*7izCzu245L^cS7M@Ez?Kz=O94y~Wu8 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/bin/test/AwsKmsEncryptedItemTest.class b/Examples/runtimes/java/DDBEC/bin/test/AwsKmsEncryptedItemTest.class new file mode 100644 index 0000000000000000000000000000000000000000..5de0c99f585fc8b6c49b2fab632cc91785dc2972 GIT binary patch literal 1804 zcmcIk-BTM?6#rdFvPsxV2_J22t57Q@z;+`cA0)L(Ayp)#N+2^nIXAmEuqD~e>~2c& z|8eSTUnuH~eX}$ENBW?iy9pn$`ofHp%-wtM`TCu6f9L%6=TE-?Si!D>5r!LUN8VP) ztJsEn-1UXI>5I;;@O%X!hU-uHGp@I}y{|uMJ{5+~5P4wPmj94pG?i{Jgw`EX#4(DP ziZCJ!<5kNRwO*$w++E&m3x-71F?hSdT}z$^mm&Ya@)(lU{|H}ZnDPn6kl1o;hIr+< zA-X>8Jp~gClNT7zCFFRgXZu!1G%U}esB5G@5=~VTfmgRIKj@x)o!5uOUq&?qi z>w9~f8!Fz%ET!af+jKhehRT&n_kEmem1TsxDn7tHsz2{`g>5oumqYbmr#!F>9&8AA z;9_3IM@TV5eWx#^8lMHbWKxb|fq`Ylu!Q>xG=_rbwEQFP3Y~ZOcaE*|BhNDrbx*j@ zEJJv@d2I8JV>b1TV6)L&Z(G9lRp`hNWWyEQCnl6B+7(^Vvn(^jt{@TlbCsho2R&Sg1HlXSWTq2S&fxKr&uh7li@@*kxLX3tBZ;B zDay(4ugE9EKVpsH1(s^^93Pyu*QJp52C-zQ_5$NtGW0!Sw4Pu?d;OAfnni|44ctPF zejtD{jeGz(M$qDzp+sg0{9S^6k6_PZ0x1H&K+;8;ZyM=lFpUG!lxZ(edXxlN$+RcY zz?ayfG-pspm3A`b61FK?7*7KI$X^5@qJSy*D)?6v(0@r@RQ`avMxU{<-^mBV4q1$# F{x_oN*%bf) literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/bin/test/AwsKmsMultiRegionKeyTest.class b/Examples/runtimes/java/DDBEC/bin/test/AwsKmsMultiRegionKeyTest.class new file mode 100644 index 0000000000000000000000000000000000000000..b0d6b075d45b9d7c56936242d9b9ab1ecf92580e GIT binary patch literal 1294 zcmbVMT~8WO5Iq-ARz$R`^{chDt+i^4pdbP^O|8(xfHnc4PYt;&x3~(sWcO|@KdVnQ zedrJ9k7_y>j2Nql@#W6W-kCXb&dlzgzrTJ1NMbt(AH(eKnOkVP2VP5>CDAl(t01~Z z!j(Y;7$#2mHy&$os~P)LKM{sx7&|BxYL#*=zgs@4Z7}G?K}=aXrq$e`pgr4iCAVai zw>+U?grUG4E6dN^>}lK0dTvy3S4KCosjYNoE0v1J(}_g(RCHr)=QNs**BfbJG>k?v zk=*2hykvd5VPqOSnMw2Qi!Sn&+ULCLLPPi(fbgPcMB@Gc!*5N?lv!fVq_WF zp|ziw_|Z$z=~M-P04C`S_X9A6hvYF#Q?eg3y?gq75I{fv5m{AopKg6*$Mh#f{fVyW zqvu#0lvc^W1f4pSp6o$P_Y*E4{M7MsuLXuz1g1FkAy&vLi1)-%H5j4#tNLp_s>jZ; XaUElp>dX+=xhpgxmo&C0--pyc5JfzU literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/bin/test/MostRecentEncryptedItemTest.class b/Examples/runtimes/java/DDBEC/bin/test/MostRecentEncryptedItemTest.class new file mode 100644 index 0000000000000000000000000000000000000000..9588dc554d20f46f01560cf8ef73aed5854666e0 GIT binary patch literal 2016 zcmc&#TT|Oc6#f=$WE&B{kbpzdl1r%}2yrt8J56iAX(=|LV4RXZFl%c~6l}>O$q@Q~ zI(g0uq3ula*3NYLhuVj>XNAG0Oka5ESR?J(J=gDid;Hs9KfMPqkM9(8FpT7F*V__h zVR>1r>>SrUVXS$gRuHbIAi&V~iXU>l%B_9o^VW$j@klGU%P`b(dIs)t!>kf&A$&}QwdMYr z*XmWFMQ{iABog--ZpN>+X1o}|LyRf-gj(tQZg^%@-`QDPRq-j}RF=an!>-94iVz<^ z^Kqe-mJudYJi;XPg4gT9G8nY>P-j$0H!XekT*}>dF{MJsG(*U<&kh*|&x5s@q(hix zVAI`1+64s(hNNp(yd&-io!9vHwx#nU*EJ4wS2%}eS-84!Z1I|Hl=M}9v07TGn#6Dw zNi0&zWk(Q~7}6B&ny#odDTc7@(hi~LhVYnS>^xkPM#PIXi%2UuVmngTXI=yB_;L6{7;6a7LtEhka`MGt6{`E!qEmux~y3>!w1jSNfI2A8th_IUXq&+9&= z6nuMWRG;6c$hO@e`aLz}5FGuF-b-Rm$w7d7pqWbl4js6SAi79Wd?`e7q)qN74N3tw z=-o>iI(->^gA?!2_trOw(px&9O$^XmZLSA#lO%>)WZa1%S_eoPrT}O9BlIPgOhcIL zf!fcwvm5>B9Y#;_-~{6*&~nk4JlkNYz$xZZ!B{Ywj)tP2MZ2S)M>CJ2GpAUI1%JUp zEcheV7~WuVL&}k+tbIrYv|ka91vcKGSBnLHK$zYq$Y~$mQeC6Ok`)R(f-U-il9Z)9 z@#|EnlpadGm$dqEpH3dq&KL#}r?MvKWRgmnqH=V^FoQnwbsJUkB_WugQmG))mJkf! z1^Jf{-NJKh(@MskMgcnnS<%<;`kPYeQos~E_n*81dgkRu{!geG`gC{yK|UCE$)W=< F{sEPz6_fw~ literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/bin/test/SymmetricEncryptedItemTest.class b/Examples/runtimes/java/DDBEC/bin/test/SymmetricEncryptedItemTest.class new file mode 100644 index 0000000000000000000000000000000000000000..e72461cc55b5e98bc31fadfe1a83942dc13d2d2a GIT binary patch literal 1708 zcmbVNU31e$6g`{Rmh9LiF(HW{G|(1mHwkK!0Ko=Iz90dI&cvx_GCaxBHYnJVM^bRy znf9S?<$rW|=7rGGp>OR>|516MJ*ya}(B>s$&F(pigSb5n+qhJ?W2j`i3w8Tuu9xN7X8pZ`C zRERw@Cf7H^mb6uvKt}gur^=Cz4@u?1N;QKsIHx!~PuSdFIRsQ?fxPWuce9n%6A-yYZ17wfg=MKVye^8@7@ z>mdUZUugJTVC4O3A1E@58O#dg~kCme8AOyoS>cHXb^c?%Ks#Lh7^+XY1&U- zVt8vGBbC8(zv}C%xj#Ae9HW!dY+mBb)>QuNJ|;>DWl!fnQg%|5Qlg~2z$Yc$(DPG9 z!brZrB}4xmHw^t}%n3ZjY$>HCGTjLy^;=0ZQh#tlE2W-d$VeI5PaLs(j;maLM$lj4 zF1DCe2|N6O2o%m4qEZNTV#^U(p14MdX&ghm@p=9M40GoQ?%)*nkK#v+#hk3hhLz)fXL>e>1tX2BBeC g!)pAkY2Y^>e;3rs{A=VE+2wM7#Z|n=t$nEc1B|}6WdHyG literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/bin/test/TestUtils.class b/Examples/runtimes/java/DDBEC/bin/test/TestUtils.class new file mode 100644 index 0000000000000000000000000000000000000000..b96d92d3cf832fdd6d3ee51c5f7c1cbcf497f315 GIT binary patch literal 2704 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 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/build/classes/java/main/AwsKmsMultiRegionKey.class b/Examples/runtimes/java/DDBEC/build/classes/java/main/AwsKmsMultiRegionKey.class new file mode 100644 index 0000000000000000000000000000000000000000..32c11ea7ef3bb577f4508d82ac8b5ceae82551b5 GIT binary patch literal 8712 zcmcIq34Bxa9smAc(!4Zzz0y*Mh!g}Y<%pC!MWm%16)4yuP?^GO@+h%6N|GY=oSVbV z-Itrp9nLwtHWg=S#Di^abIx7vo^yvgbh^3e+;nWpzWSf!_>$~=*^WEyxXX^a?Kon`Jq~;Y_sWSu zJC52hq$1(Kblm5_G2HLK19;GZhj3gzzA7JIlaH^L;$eKlfp6klvh|2;eOqFA)QRul zF(heHxB$3zmpK2Dy4*eFOmI0#UCZJe^T+Z1M8EVKO;x}EV+7I?t4~B?O7GiIq(Cu4frv|O@y@8lO?Z)PdV;e%TP5r@mpu^}PmJP;06QPClK$!FO zJ2zgW@7Fy+J>28zj7J0Eo+cBU9_``&>H^aOrE!U=T@RVq^Y#mfoO`yyO`=SGV@eacmiIU~z_4$;HW<*k3rpHNocYi!ZR9O=Lnj{9!C;P32 zi7^|ulnDF&n$QU#v$Bj~>!5iG)4+#j%*b*F&xC5BQ9j z$3GC(LlJ+sr`2qj$QSBM)3G;?4pSyaDbkXCFC{X{!ph8=YsMSG0kCrCzsOxb)RYS0+p#L!@p}-QBo~w z{g$R|P|C{QYYc3StTV{Uu$vhEwzv@@nAEZ?Ftxb>V4!a}2y|!BNJCgrz@0{I+ca<^ z=xHAIvu%2?-)LG%miJ~MNeN=bIH)=!{*3coDF6)fz zzFyK`hFj(pW<;hw6@O>OQt^U{7ghX2#Y@!X3c64-kUKgf{ZXH>HXy^cyHI4FFX`8? z73XO%u!o7jtKpycmxh<|ii&@0ID!8NEFHDlazl~?^IsLOYIqGNH9{8xi{1!UPE=~? z4+QD=0`tbSjS@?TuU#~5=}L@U$!1kXY3udaUP_g83M-u_5UZ1UOcf=XunCpo)r4J? z3Mh*jHQ^8{*BA%%P@fd2)vw2O0WB5@8FlnXW=Li*W%+U@O+DD1Z$+RlAIgJq;;JwFexLe=Ha4j zO*9&bN?SWy8p|q8G~+yhnnKR2iWW_*7Bh2%zPbA%=0|~9ChVBu>yHNF1DaB96cs(?$2#8dB_N?RWK zNwS>Hh+a)K8LHdD^n%qvc>-dnZX1Qn{KK_&p?YLS2FaQtLd-APUk?y7B!%|u%f^wiqI|n5XljCG=8Pijnkwn^_qGhgrG(4&L?a>DZjMHECgFzqoh?N* znzNULif(~OQ9H?9T@>h;#nW08V;WSBA2nr3E$ck%>|rtq$Kra}$LxMQAL!VlYUT`- ziG=xwpnOS;9aCQXrZ#pJaF(${&QP9;;aM)UbEHj;BY1&**#GQG{<(-F-?rwi;1WL5$j;A2a(-4c@&bD>!OylD!vE)gM87NyfGLbi>0*`sH%A9u9 zHSMcAy0&Z;Se&;b%MD)#f{kYZ9@Kb>r}Q(0Lh>-3RSB7E?16OsP@A)SxUQhARjRn% zyaV8^886YSVY6eGnonM!alDP0kF_fADg-8EQ=SUEa^uyh@$@Z97J018n#NV|!#51s zGLT5#R8GvT#_{L~>Pr z9vBl)`o&1!9_{{EA1{23H>cs;gHaY|W7TkBK;xw0!obO~#k>Ic{}o&mf!dQiIldVM z=K>wOg46rc3LgDYBWbrIAdHNS$+(cYv1Z}L+j9$9<{`6CFw2U>>ElW6sU?+u1?mgr zz?4Df)^|Mr_=1rbOR0*?;m0iG`AxxZ#CR#}PamOqR~GM&VH&@boXc-ZqH>ZvxU*%2 zkXK!NZe!2Gx8S>^;V8u2Y$({yr_CHZ6+770k_*s<^VuuKPP~n)?0$j*7R#adYb`zbQU$u0$ z6s}g$&6Wi|bA^DPB=D9lNrJ7N^AiD!KF4Jlgw@vAJ~!3yT8B`QfQ^MGRBy=;?4+wS z0f)i?X9B#5JB~81jT3Gz&7V46inXnGJ)OvFri)yk3@cl96^-cIeb^UreV`JLDD zJ?%pE@Qr*o`&d;T4G;xJ!V;!)Eq_~`y_<@TC40=vR`NRZ6!-;Ce+-_}Nv4~wA2T~kckHN~_|Y%7TEEMlukV5P*y8?NJ6l|ZwCL9}=$ zxU1b0lH{%S)*eCYA$Vv#?gZ9IyIebnb?(}nE`@Rk&SO|l8*F7_$2r`Q-7T_X zN@m9f?#S*AVq+E#H{E6v@5*fnu-bL2=1V%X)r<{KVHw+V=!SDqjWg-i3$UCYcRR6| z^Gg^XmeNU=;c|{%$4|X?q6v>+1s=mnJjeG7l!*f^q86=U3f75fSkGJ2HZce1i22wc z77}?AF*jqgSc@%!0ZhcuA)etK;7iyhUd9e_0_Q6>T%eRaC{N=OiyN0(HsEqg2d=Pm;Y!PH zyxj~=FJcn$eiS_nKQ0+!%-}@U+1kr7hE28tY$+(U?7&5ASx{|hXI$J(S=CvZ5k!dL zZ@Hx&VMG{JJ1yntV@t($iv#<(&W@cH8=`EL!mqrF7~*hCW_-E%3Aiu!skjcmMqy;EXj_g#8I8p8SRebjrWlE zu&p#eX{ilK3WSz`%~1jrw;T;IsVv1bPFrv&r7bN<4|>oeOzeW4<>#dug?C z6vVIoS#RGl?|)zK&3yUgC!YeaLG1HlA0AclQ7;rc>O~hm=EKMF>$3TT4`=ZkviF-R zKIz7%-1xK`pK;^2+<453&*E{pcFv8@x$%S#zl~WH=e=mboEP(WQpE)?+VFW5zf(h8 z{P)d!ywLHK7Z>rg7r%?≶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 NS2_QKbtj&N|GyC|C?Ws= literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/build/classes/java/test/AsymmetricEncryptedItemTest.class b/Examples/runtimes/java/DDBEC/build/classes/java/test/AsymmetricEncryptedItemTest.class new file mode 100644 index 0000000000000000000000000000000000000000..42cb620a7e23242cda5e12297dc0a79b3de72482 GIT binary patch literal 1842 zcmb_cU3U{z6x}ypGfgK20;P?Jf+(hiFsLAs)M9Ci6w;zink5e`*3HbN9XgpAXKpC* z2k1ZWFYwGusk)ZD@!*fLo;#T){aE_YWmo5ZoO|}U`|NXa|N8s)KLOl8D~~g%Xt=>LABtX!yWN=bbN+&xF89{W266M&$SRj=c@{2Dm5vZ7vhDCji;$|ZFTzXS30zSz za1Y;VxNl$y57g$NfjX8M*mX+CzzQCb+=|YcbX=<1 z^-ITa>zLhffY_j<)cHIJgl98U%Jm_Eq*11hm=f)&g~npzfh~f@5Q-=gM0j$4SqBYSn`+@APSZQ8{!(D-r$*xh z?M#{(&DrTUV6WrjNg8vpF^3YZjlKik!#Ub9yiee?0yCHVbh7>O2U3sUAYIICBfEp# zHneT%_2S6#4)QCknyD0xVxc&8r8u&K6P0)Pp_F-t$;xjy$?ysfs@YPel-)-06=wI0 zH_4c-lKE{lM~F)Y5aU&i5OfJ4M*4_S?(J)2>A9c59eV69ph7>x{``a}ZotEjv~S`m z)IJTB=0`F8JS8+rsTK$|M!0eEDv;MWex?&8!89^B4~M=F2uTSiVw|Oz9}gzqLI~bNyEqS|0fzQgV8X0p9b~qQc1MK9s{rJF2za@jw$G;^kwL(GNx#y ODl|^Ze}hxkj%`QnTYfk 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 R#F=V`iqB`iqe*pz-$nF3D literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/build/classes/java/test/TestUtils.class b/Examples/runtimes/java/DDBEC/build/classes/java/test/TestUtils.class new file mode 100644 index 0000000000000000000000000000000000000000..60deaae79be54ec3e7648ddf625fb93ce5017433 GIT binary patch literal 2700 zcmb_e+g95~6y4*Sz!ro{lGf>+CX`DY5s0xdmj*&ZhXTXH2iEcyfe zp1w3VU8}24eds^6t7jwwPBE)yB`>2hXGZ(%+2_pVuYX?u4&V{gAcheQ!H3xpVu(wa zlaL6)k7y9{SO{SeOQMMj-K{s;WO%a<-}I&*vHXRt?>W*q^qTh1*>=&BF z5X$DcWpCSB)uQPAr7BnT?K%ZZ+q}l`Z0s`0cjXbBdu>5!Q>_urfx2qinyndnhM%~u zkY$+md2g_Nf};El2u39fP)VfGg*%{vAKQ>4_hL!H) zQBYo0TzDaI3xhy!znP_z`dOwbmpLqS$U)ajb}1~|RC2|J%^l_Dm&CGiwpu!(B|0VI z7Cu8TXEe+bU(-Y>^`BSE15vy(?%)F%8Du4F$k@aahFI4`-qfC9(Af{|T1)swpywpy zWo%Q+aup-A)1Ci;(Y&1R>ot>SUiqFg>sECx}Omrs3D-R>(c)1jL6pK$q<|c?Zw+&M;X>C}k*wCtFZc@38jdx#gXYg); zuecbL?4_>7>WIMLcla>eBsTvQiSQS@#_v>*3{s0KZSl0Ob91$-S{A1hD}9~Scf!7V zAwue{NS*;PFyi&J!%=ySJpB(-Z@odK7jKW@*5mwT@jK>&=?vNHOLys{I$JcQc>S8{ zU5Ul8-@dTcWq57zuEZy;$yO9 zcLF}ar_}4EP$T4_pV=LH`t*?R=oFqNybOQ9m+=1v|K2ME5`l1F@)S~}i5?G{=wnV7mYNv!U{_G|K~NlVLXV)2 z?uOgAi%HzW6h^UzaU5U*hvX6Tw_J)e>lXEhEX@`A2*Fb%j;NQkmV!PeT)&3 zpRl(HZIUE8KnV8{frJM%Q^?|uDKs`ko)FmI;wku0!Zg9okjq1F%`N&4k+nz<%Wng{ K^lTgu-+uwmFB$g$ literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/build/libs/DDBEC-1.0-SNAPSHOT.jar b/Examples/runtimes/java/DDBEC/build/libs/DDBEC-1.0-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..d2bfb94b39815397c420e955fb725e27c687a372 GIT binary patch literal 10748 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 P-$xe~1}2&5pRfN1m?|9& literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AsymmetricEncryptedItemTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AsymmetricEncryptedItemTest.html new file mode 100644 index 0000000000..b79a1799e6 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AsymmetricEncryptedItemTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class AsymmetricEncryptedItemTest + + + + + +
+

Class AsymmetricEncryptedItemTest

+
+
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.897s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + +
TestDurationResult
testAsymmetricEncryption0.897spassed
+
+
+ +
+ + diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsEncryptedItemTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsEncryptedItemTest.html new file mode 100644 index 0000000000..27f759fa09 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsEncryptedItemTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class AwsKmsEncryptedItemTest + + + + + +
+

Class AwsKmsEncryptedItemTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.252s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + +
TestDurationResult
testAwsKmsEncryption0.252spassed
+
+
+ +
+ + diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsMultiRegionKeyTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsMultiRegionKeyTest.html new file mode 100644 index 0000000000..332dbe8098 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsMultiRegionKeyTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class AwsKmsMultiRegionKeyTest + + + + + +
+

Class AwsKmsMultiRegionKeyTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.799s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + +
TestDurationResult
testMultiRegionEncryption0.799spassed
+
+
+ +
+ + diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/MostRecentEncryptedItemTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/MostRecentEncryptedItemTest.html new file mode 100644 index 0000000000..6dff9ac855 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/MostRecentEncryptedItemTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class MostRecentEncryptedItemTest + + + + + +
+

Class MostRecentEncryptedItemTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.202s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + +
TestDurationResult
testMostRecentEncryption0.202spassed
+
+
+ +
+ + diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/SymmetricEncryptedItemTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/SymmetricEncryptedItemTest.html new file mode 100644 index 0000000000..328990fed3 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/SymmetricEncryptedItemTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class SymmetricEncryptedItemTest + + + + + +
+

Class SymmetricEncryptedItemTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.007s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + +
TestDurationResult
testSymmetricEncryption0.007spassed
+
+
+ +
+ + diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/base-style.css b/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/base-style.css new file mode 100644 index 0000000000..4afa73e3dd --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/base-style.css @@ -0,0 +1,179 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding-left: 50px; + padding-right: 50px; + padding-top: 30px; + padding-bottom: 30px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +ul.tabLinks { + padding-left: 0; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; + min-width: 800px; + width: auto !important; + width: 800px; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + -moz-border-radius: 7px; + border-radius: 7px; + margin-right: 25px; + border: solid 1px #d4d4d4; + background-color: #f0f0f0; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #c5f0f5; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto !important; + width: 350px; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid #d0d0d0 1px; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0em; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto !important; + width: 700px; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} \ No newline at end of file diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/style.css b/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/style.css new file mode 100644 index 0000000000..3dc4913e7a --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/index.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/index.html new file mode 100644 index 0000000000..ca546ab166 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/index.html @@ -0,0 +1,173 @@ + + + + + +Test results - Test Summary + + + + + +
+

Test Summary

+
+ + + + + +
+
+ + + + + + + +
+
+
5
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
2.157s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Packages

+ + + + + + + + + + + + + + + + + + + + + +
PackageTestsFailuresIgnoredDurationSuccess rate
+default-package +5002.157s100%
+
+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+AsymmetricEncryptedItemTest +1000.897s100%
+AwsKmsEncryptedItemTest +1000.252s100%
+AwsKmsMultiRegionKeyTest +1000.799s100%
+MostRecentEncryptedItemTest +1000.202s100%
+SymmetricEncryptedItemTest +1000.007s100%
+
+
+ +
+ + diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/js/report.js b/Examples/runtimes/java/DDBEC/build/reports/tests/test/js/report.js new file mode 100644 index 0000000000..83bab4a19f --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/js/report.js @@ -0,0 +1,194 @@ +(function (window, document) { + "use strict"; + + var tabs = {}; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function initTabs() { + var container = document.getElementById("tabs"); + + tabs.tabs = findTabs(container); + tabs.titles = findTitles(tabs.tabs); + tabs.headers = findHeaders(container); + tabs.select = select; + tabs.deselectAll = deselectAll; + tabs.select(0); + + return true; + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + var spans = document.getElementById("tabs").getElementsByTagName("span"); + var codeBlocks = []; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + var codeBlocks = findCodeBlocks(); + + for (var i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + var checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + var checkBox = getCheckBox(); + var label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + function switchTab() { + var id = this.id.substr(1); + + for (var i = 0; i < tabs.tabs.length; i++) { + if (tabs.tabs[i].id === id) { + tabs.select(i); + break; + } + } + + return false; + } + + function select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var h2 = document.createElement("H2"); + + h2.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(h2); + } + + function deselectAll() { + for (var i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var a = document.createElement("A"); + + a.setAttribute("id", "ltab" + i); + a.setAttribute("href", "#tab" + i); + a.onclick = switchTab; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + var owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + var titles = []; + + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + var header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + var elements = []; + var children = container.childNodes; + + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); \ No newline at end of file diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/packages/default-package.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/packages/default-package.html new file mode 100644 index 0000000000..6bff1ae466 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/reports/tests/test/packages/default-package.html @@ -0,0 +1,143 @@ + + + + + +Test results - Default package + + + + + +
+

Default package

+ +
+ + + + + +
+
+ + + + + + + +
+
+
5
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
2.157s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+AsymmetricEncryptedItemTest +1000.897s100%
+AwsKmsEncryptedItemTest +1000.252s100%
+AwsKmsMultiRegionKeyTest +1000.799s100%
+MostRecentEncryptedItemTest +1000.202s100%
+SymmetricEncryptedItemTest +1000.007s100%
+
+
+ +
+ + diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AsymmetricEncryptedItemTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AsymmetricEncryptedItemTest.xml new file mode 100644 index 0000000000..adb9d25eb6 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AsymmetricEncryptedItemTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsEncryptedItemTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsEncryptedItemTest.xml new file mode 100644 index 0000000000..7df128206e --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsEncryptedItemTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsMultiRegionKeyTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsMultiRegionKeyTest.xml new file mode 100644 index 0000000000..766f6b6979 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsMultiRegionKeyTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-MostRecentEncryptedItemTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-MostRecentEncryptedItemTest.xml new file mode 100644 index 0000000000..d638bb7e29 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-MostRecentEncryptedItemTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-SymmetricEncryptedItemTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-SymmetricEncryptedItemTest.xml new file mode 100644 index 0000000000..b67bae9e2f --- /dev/null +++ b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-SymmetricEncryptedItemTest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin b/Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin.idx b/Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin.idx new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 IcmZPo000310RR91 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/binary/results.bin b/Examples/runtimes/java/DDBEC/build/test-results/test/binary/results.bin new file mode 100644 index 0000000000000000000000000000000000000000..0fad41ec9282d50dce068f0920c4d5c8b7ef30d6 GIT binary patch literal 622 zcmaKnI}UyWLF!Ulad6xHAOSF zk{X}oRiY7St%LTtiu_XQ*3!dF;N~cU`w)5}*l?P$kR?=G+{Uy6p?i8)_;-;Fi~t^b zeL04~WuC_|qcjR@`7KDrmt*V~Rs6q)OgG)Z)mjfaV#1B#ZA>{72gLWrn#*T=6M(%g HAbRiuB?;>B literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/build/tmp/compileJava/compileTransaction/stash-dir/AwsKmsMultiRegionKey.class.uniqueId0 b/Examples/runtimes/java/DDBEC/build/tmp/compileJava/compileTransaction/stash-dir/AwsKmsMultiRegionKey.class.uniqueId0 new file mode 100644 index 0000000000000000000000000000000000000000..4539330d0612e13342c4cc6db503e6a083a0e604 GIT binary patch literal 8729 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 K*?$U6^8G)5Az;)1 literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/build/tmp/compileJava/previous-compilation-data.bin b/Examples/runtimes/java/DDBEC/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000000000000000000000000000000000000..3ccb409440d3add1d45daecd704152c33bddb32d GIT binary patch literal 73542 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 ScNgau(U?ep-K_E5zy1%ZQTdPn literal 0 HcmV?d00001 diff --git a/Examples/runtimes/java/DDBEC/gradle/wrapper/gradle-wrapper.properties b/Examples/runtimes/java/DDBEC/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..f398c33c4b --- /dev/null +++ b/Examples/runtimes/java/DDBEC/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/DDBEC/gradlew b/Examples/runtimes/java/DDBEC/gradlew new file mode 100755 index 0000000000..65dcd68d65 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/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/DDBEC/src/main/java/AsymmetricEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java new file mode 100644 index 0000000000..2a74cc555d --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java @@ -0,0 +1,136 @@ +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.core.SdkBytes; +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.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; + +/** + * Example showing use of RSA keys for encryption and signing with DDBEC SDK v2. + */ +public class AsymmetricEncryptedItem { + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + + public static void encryptRecord( + DynamoDbClient ddbClient, + String tableName, + String partitionKeyName, + String sortKeyName, + String partitionKeyValue, + String sortKeyValue) + throws GeneralSecurityException { + + final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + // You should never use the same key for encryption and signing + final KeyPair wrappingKeys = keyGen.generateKeyPair(); + final KeyPair signingKeys = keyGen.generateKeyPair(); + + // Sample record to be encrypted + final Map record = new HashMap<>(); + record.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); + record.put( + BINARY_FIELD_NAME, + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); + + // Create AsymmetricStaticProvider with wrapping and signing keys + final AsymmetricStaticProvider cmp = new AsymmetricStaticProvider(wrappingKeys, signingKeys); + + // Create DynamoDBEncryptor + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // Create EncryptionContext + final EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + // Configure EncryptionFlags for each attribute + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = + EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final Map> actions = new HashMap<>(); + + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case "partition_key": + case "sort_key": + actions.put(attributeName, signOnly); + break; + default: + if (!attributeName.equals(IGNORED_FIELD_NAME)) { + actions.put(attributeName, encryptAndSign); + } + break; + } + } + + // Encrypt the record + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + + // For demo, verify encryption + assert encrypted_record.get(STRING_FIELD_NAME).b() != null; + assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; + assert !record.get(BINARY_FIELD_NAME).b().equals(encrypted_record.get(BINARY_FIELD_NAME).b()); + assert record.get(IGNORED_FIELD_NAME).s().equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); + + // For demo, the encrypted item is put to DynamoDB. You skip this as needed for their use case. + ddbClient.putItem( + PutItemRequest.builder().tableName(tableName).item(encrypted_record).build()); + + // Get the item back from DynamoDB + final Map keyToGet = new HashMap<>(); + keyToGet.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + + final GetItemResponse getResponse = + ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build()); + + // Decrypt the record retrieved from DynamoDB + final Map decrypted_record = + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + + // For demo, verify decryption + assert record.get(STRING_FIELD_NAME).s().equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record.get(NUMBER_FIELD_NAME).n().equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record.get(BINARY_FIELD_NAME).b().equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + } + + public static void main(final String[] args) throws GeneralSecurityException { + if (args.length < 5) { + throw new IllegalArgumentException( + "To run this example, include tableName, partitionKeyName, sortKeyName, " + + "partitionKeyValue, sortKeyValue as args"); + } + final String tableName = args[0]; + final String partitionKeyName = args[1]; + final String sortKeyName = args[2]; + final String partitionKeyValue = args[3]; + final String sortKeyValue = args[4]; + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord(ddbClient, tableName, partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue); + } + } +} diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java new file mode 100644 index 0000000000..804fca7a46 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java @@ -0,0 +1,129 @@ +import java.security.GeneralSecurityException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.core.SdkBytes; +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; + +/** Example showing use of AWS KMS with DDBEC SDK v2. */ +public class AwsKmsEncryptedItem { + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + + public static void encryptRecord( + final DynamoDbClient ddbClient, + final String tableName, + final String cmkArn, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue) + throws GeneralSecurityException { + // Sample record to be encrypted + final Map record = new HashMap<>(); + record.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); + record.put( + BINARY_FIELD_NAME, + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); + + // Create KMS client and DirectKmsMaterialsProvider + final KmsClient kmsClient = KmsClient.create(); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider(kmsClient, cmkArn); + + // Create DynamoDBEncryptor + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // Create EncryptionContext + final EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + // Configure EncryptionFlags for each attribute + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = + EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final Map> actions = new HashMap<>(); + + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case "partition_key": + case "sort_key": + actions.put(attributeName, signOnly); + break; + default: + if (!attributeName.equals(IGNORED_FIELD_NAME)) { + actions.put(attributeName, encryptAndSign); + } + break; + } + } + + // Encrypt the record + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + + // For demo, verify encryption + assert encrypted_record.get(STRING_FIELD_NAME).b() != null; + assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; + assert !record.get(BINARY_FIELD_NAME).b().equals(encrypted_record.get(BINARY_FIELD_NAME).b()); + assert record.get(IGNORED_FIELD_NAME).s().equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); + + // For demo, the encrypted item is put to DynamoDB. You skip this as needed for their use case. + ddbClient.putItem( + PutItemRequest.builder().tableName(tableName).item(encrypted_record).build()); + + // Get the item back from DynamoDB + final Map keyToGet = new HashMap<>(); + keyToGet.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + + final GetItemResponse getResponse = + ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build()); + + // Decrypt the record retrieved from DynamoDB + final Map decrypted_record = + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + + // For demo, verify decryption + assert record.get(STRING_FIELD_NAME).s().equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record.get(NUMBER_FIELD_NAME).n().equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record.get(BINARY_FIELD_NAME).b().equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + } + + public static void main(final String[] args) throws GeneralSecurityException { + if (args.length < 6) { + throw new IllegalArgumentException( + "To run this example, include tableName, cmkArn, partitionKeyName, sortKeyName, " + + "partitionKeyValue, sortKeyValue as args"); + } + final String tableName = args[0]; + final String cmkArn = args[1]; + final String partitionKeyName = args[2]; + final String sortKeyName = args[3]; + final String partitionKeyValue = args[4]; + final String sortKeyValue = args[5]; + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord(ddbClient, tableName, cmkArn, partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue); + } + } +} diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java new file mode 100644 index 0000000000..62cc98cd65 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java @@ -0,0 +1,129 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.security.GeneralSecurityException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.regions.Region; +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.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; + +/** + * Example showing use of AWS KMS CMP with an AWS KMS Multi-Region Key. We encrypt a record with a + * key in one region, then decrypt the ciphertext with the same key replicated to another region. + * + *

This example demonstrates that data encrypted with one MRK can be decrypted with its replica + * key from a different region, without requiring a Global Table. + */ +public class AwsKmsMultiRegionKey { + + public static void main(String[] args) throws GeneralSecurityException { + final String tableName = args[0]; + final String cmkArn1 = args[1]; + final String cmkArn2 = args[2]; + + encryptRecord(tableName, cmkArn1, cmkArn2); + } + + public static void encryptRecord( + final String tableName, final String cmkArnEncrypt, final String cmkArnDecrypt) + throws GeneralSecurityException { + + // Extract regions from ARNs + final String encryptRegion = cmkArnEncrypt.split(":")[3]; + final String decryptRegion = cmkArnDecrypt.split(":")[3]; + + // Use us-west-2 for DynamoDB since that's where the table exists + final DynamoDbClient ddbClient = + DynamoDbClient.builder().build(); + final KmsClient kmsEncrypt = KmsClient.builder().region(Region.of(encryptRegion)).build(); + final KmsClient kmsDecrypt = KmsClient.builder().region(Region.of(decryptRegion)).build(); + + // Sample record to be encrypted + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + final Map record = new HashMap<>(); + record.put(partitionKeyName, AttributeValue.builder().s("is this").build()); + record.put(sortKeyName, AttributeValue.builder().n("42").build()); + record.put("example", AttributeValue.builder().s("data").build()); + record.put("some numbers", AttributeValue.builder().n("99").build()); + record.put( + "and some binary", + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); + record.put("leave me", AttributeValue.builder().s("alone").build()); + + // Set up encryptor with first region's KMS key + final DirectKmsMaterialsProvider cmpEncrypt = + new DirectKmsMaterialsProvider(kmsEncrypt, cmkArnEncrypt); + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmpEncrypt); + + final EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = + EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final Map> actions = new HashMap<>(); + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case "partition_key": + case "sort_key": + actions.put(attributeName, signOnly); + break; + case "leave me": + break; + default: + actions.put(attributeName, encryptAndSign); + break; + } + } + + // Encrypt and save to table + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + ddbClient.putItem( + PutItemRequest.builder().tableName(tableName).item(encrypted_record).build()); + + // Set up decryptor using the replica key from second region + final DirectKmsMaterialsProvider cmpDecrypt = + new DirectKmsMaterialsProvider(kmsDecrypt, cmkArnDecrypt); + final DynamoDBEncryptor decryptor = DynamoDBEncryptor.getInstance(cmpDecrypt); + + // Retrieve from same table + final Map keyToGet = new HashMap<>(); + keyToGet.put(partitionKeyName, AttributeValue.builder().s("is this").build()); + keyToGet.put(sortKeyName, AttributeValue.builder().n("42").build()); + + final Map encryptedItem = + ddbClient + .getItem(GetItemRequest.builder().tableName(tableName).key(keyToGet).build()) + .item(); + + // Decrypt using replica key - demonstrates multi-region key capability + final Map decrypted_record = + decryptor.decryptRecord(encryptedItem, actions, encryptionContext); + + // Verify decryption + assert record.get("example").s().equals(decrypted_record.get("example").s()); + assert record.get("some numbers").n().equals(decrypted_record.get("some numbers").n()); + assert record.get("and some binary").b().equals(decrypted_record.get("and some binary").b()); + + ddbClient.close(); + kmsEncrypt.close(); + kmsDecrypt.close(); + } +} diff --git a/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java new file mode 100644 index 0000000000..1797119206 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java @@ -0,0 +1,152 @@ +import java.security.GeneralSecurityException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.core.SdkBytes; +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.CachingMostRecentProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; + +/** + * This demonstrates how to use the CachingMostRecentProvider backed by a MetaStore + * and the DirectKmsMaterialsProvider to encrypt your data. + */ +public class MostRecentEncryptedItem { + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + + public static void encryptRecord( + final DynamoDbClient ddbClient, + final String tableName, + final String keyTableName, + final String cmkArn, + final String materialName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue) + throws GeneralSecurityException { + // Sample record to be encrypted + final Map record = new HashMap<>(); + record.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); + record.put( + BINARY_FIELD_NAME, + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); + + // Provider Configuration to protect the data keys + final KmsClient kmsClient = KmsClient.create(); + final DirectKmsMaterialsProvider kmsProv = new DirectKmsMaterialsProvider(kmsClient, cmkArn); + final DynamoDBEncryptor keyEncryptor = DynamoDBEncryptor.getInstance(kmsProv); + + final MetaStore metaStore = new MetaStore(ddbClient, keyTableName, keyEncryptor); + + // Provider configuration to protect the data + final CachingMostRecentProvider cmp = + new CachingMostRecentProvider(metaStore, materialName, 60_000); + + // Encryptor creation + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // Information about the context of our data (normally just Table information) + final EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + // Describe what actions need to be taken for each attribute + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = + EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final Map> actions = new HashMap<>(); + + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case "partition_key": + case "sort_key": + actions.put(attributeName, signOnly); + break; + default: + if (!attributeName.equals(IGNORED_FIELD_NAME)) { + actions.put(attributeName, encryptAndSign); + } + break; + } + } + + // Encrypt the plaintext record directly + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + + // For demo, encrypted record fields change as expected + assert encrypted_record.get(STRING_FIELD_NAME).b() != null; + assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; + assert !record.get(BINARY_FIELD_NAME).b().equals(encrypted_record.get(BINARY_FIELD_NAME).b()); + assert record.get(IGNORED_FIELD_NAME).s().equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); + + // For demo, the encrypted item is put to DynamoDB. You can skip this as needed. + ddbClient.putItem(PutItemRequest.builder().tableName(tableName).item(encrypted_record).build()); + + // Get the item back from DynamoDB + final Map keyToGet = new HashMap<>(); + keyToGet.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + + final GetItemResponse getResponse = + ddbClient.getItem(GetItemRequest.builder().tableName(tableName).key(keyToGet).build()); + + // Decryption is identical + final Map decrypted_record = + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + + // For demo, the decrypted fields match the original fields before encryption + assert record.get(STRING_FIELD_NAME).s().equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record.get(NUMBER_FIELD_NAME).n().equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record.get(BINARY_FIELD_NAME).b().equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + } + + public static void main(final String[] args) throws GeneralSecurityException { + if (args.length < 8) { + throw new IllegalArgumentException( + "To run this example, include tableName, keyTableName, cmkArn, materialName, " + + "partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue as args"); + } + final String tableName = args[0]; + final String keyTableName = args[1]; + final String cmkArn = args[2]; + final String materialName = args[3]; + final String partitionKeyName = args[4]; + final String sortKeyName = args[5]; + final String partitionKeyValue = args[6]; + final String sortKeyValue = args[7]; + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord( + ddbClient, + tableName, + keyTableName, + cmkArn, + materialName, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue); + } + } +} diff --git a/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java new file mode 100644 index 0000000000..3c5efc5c73 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java @@ -0,0 +1,153 @@ +/* + * Copyright 2018 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. + */ + +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +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.WrappedMaterialsProvider; + +/** + * Example showing use of an AES key for encryption and an HmacSHA256 key for signing. For ease of + * the example, we create new random ones every time. + */ +public class SymmetricEncryptedItem { + + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + + public static void main(String[] args) throws GeneralSecurityException { + final String tableName = args[0]; + // Both AES and HMAC keys are just random bytes. + // You should never use the same keys for encryption and signing/integrity. + final SecureRandom secureRandom = new SecureRandom(); + byte[] rawAes = new byte[32]; + byte[] rawHmac = new byte[32]; + secureRandom.nextBytes(rawAes); + secureRandom.nextBytes(rawHmac); + final SecretKey wrappingKey = new SecretKeySpec(rawAes, "AES"); + final SecretKey signingKey = new SecretKeySpec(rawHmac, "HmacSHA256"); + + final DynamoDbClient ddbClient = DynamoDbClient.create(); + encryptRecord(ddbClient, tableName, wrappingKey, signingKey); + ddbClient.close(); + } + + public static void encryptRecord( + DynamoDbClient ddbClient, String tableName, SecretKey wrappingKey, SecretKey signingKey) + throws GeneralSecurityException { + // Sample record to be encrypted + final String partitionKeyName = "partition_attribute"; + final String sortKeyName = "sort_attribute"; + final Map record = new HashMap<>(); + record.put(partitionKeyName, AttributeValue.builder().s("is this").build()); + record.put(sortKeyName, AttributeValue.builder().n("55").build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); + record.put( + BINARY_FIELD_NAME, + AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); + record.put( + IGNORED_FIELD_NAME, + AttributeValue.builder().s("alone").build()); // We want to ignore this attribute + + // Set up our configuration and clients. All of this is thread-safe and can be reused across + // calls. + // Provider Configuration + final WrappedMaterialsProvider cmp = + new WrappedMaterialsProvider(wrappingKey, wrappingKey, signingKey); + // While the wrappedMaterialsProvider is better as it uses a unique encryption key per record, + // many existing systems use the SymmetricStaticProvider which always uses the same encryption + // key. + // final SymmetricStaticProvider cmp = new SymmetricStaticProvider(encryptionKey, + // signingKey); + // Encryptor creation + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // Information about the context of our data (normally just Table information) + final EncryptionContext encryptionContext = + EncryptionContext.builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + // Describe what actions need to be taken for each attribute + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = + EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final Map> actions = new HashMap<>(); + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case partitionKeyName: // fall through + case sortKeyName: + // Partition and sort keys must not be encrypted but should be signed + actions.put(attributeName, signOnly); + break; + case IGNORED_FIELD_NAME: + // For this example, we are neither signing nor encrypting this field + break; + default: + // We want to encrypt and sign everything else + actions.put(attributeName, encryptAndSign); + break; + } + } + // End set-up + + // Encrypt the plaintext record directly + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + + // Encrypted record fields change as expected + assert encrypted_record.get(STRING_FIELD_NAME).b() + != null; // the encrypted string is stored as bytes + assert encrypted_record.get(NUMBER_FIELD_NAME).b() + != null; // the encrypted number is stored as bytes + assert !record + .get(BINARY_FIELD_NAME) + .b() + .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); // the encrypted bytes have updated + assert record + .get(IGNORED_FIELD_NAME) + .s() + .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); // ignored field is left as is + + // We could now put the encrypted item to DynamoDB just as we would any other item. + // We're skipping it to to keep the example simpler. + + // Decryption is identical. We'll pretend that we retrieved the record from DynamoDB. + final Map decrypted_record = + encryptor.decryptRecord(encrypted_record, actions, encryptionContext); + + // The decrypted fields match the original fields before encryption + assert record.get(STRING_FIELD_NAME).s().equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record.get(NUMBER_FIELD_NAME).n().equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record.get(BINARY_FIELD_NAME).b().equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + } +} diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java new file mode 100644 index 0000000000..a3430f3131 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java @@ -0,0 +1,23 @@ +import java.util.UUID; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +public class AsymmetricEncryptedItemTest { + + @Test + public void testAsymmetricEncryption() throws Exception { + final String partitionKeyValue = "AsymmetricExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + AsymmetricEncryptedItem.encryptRecord( + ddbClient, + TestUtils.TEST_DDB_TABLE_NAME, + "partition_key", + "sort_key", + partitionKeyValue, + sortKeyValue); + } + } +} diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java new file mode 100644 index 0000000000..46f5e47352 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java @@ -0,0 +1,23 @@ +import java.util.UUID; +import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +public class AwsKmsEncryptedItemTest { + + @Test + public void testAwsKmsEncryption() throws Exception { + final String partitionKeyValue = "AwsKmsExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + AwsKmsEncryptedItem.encryptRecord( + ddbClient, + TestUtils.TEST_DDB_TABLE_NAME, + TestUtils.TEST_KMS_KEY_ID, + "partition_key", + "sort_key", + partitionKeyValue, + sortKeyValue); + } + } +} diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java new file mode 100644 index 0000000000..a4d438bec2 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java @@ -0,0 +1,26 @@ +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +public class AwsKmsMultiRegionKeyTest { + + // Multi-region KMS keys (MRKs) - same key replicated across regions + private static final String MRK_US_EAST_1 = + "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + private static final String MRK_EU_WEST_1 = + "arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + + @Test + public void testMultiRegionEncryption() throws Exception { + // Encrypt with us-east-1 MRK, decrypt with eu-west-1 replica MRK + // This demonstrates MRK capability without needing a Global Table + final String tableName = TestUtils.TEST_DDB_TABLE_NAME; + + AwsKmsMultiRegionKey.encryptRecord(tableName, MRK_US_EAST_1, MRK_EU_WEST_1); + } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, "partition_key", "sort_key", "is this", "42"); + } +} diff --git a/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java new file mode 100644 index 0000000000..ef8ec2dc59 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java @@ -0,0 +1,28 @@ +import java.util.UUID; +import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +public class MostRecentEncryptedItemTest { + + private static final String KEY_TABLE_NAME = "v2MostRecentKeyProviderPerfTestKeys"; + private static final String MATERIAL_NAME = "testMaterial"; + + @Test + public void testMostRecentEncryption() throws Exception { + final String partitionKeyValue = "MostRecentExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + MostRecentEncryptedItem.encryptRecord( + ddbClient, + TestUtils.TEST_DDB_TABLE_NAME, + KEY_TABLE_NAME, + TestUtils.TEST_KMS_KEY_ID, + MATERIAL_NAME, + "partition_key", + "sort_key", + partitionKeyValue, + sortKeyValue); + } + } +} diff --git a/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java new file mode 100644 index 0000000000..806998af5b --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java @@ -0,0 +1,25 @@ +import java.security.SecureRandom; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +public class SymmetricEncryptedItemTest { + + @Test + public void testSymmetricEncryption() throws Exception { + // Generate random keys + final SecureRandom secureRandom = new SecureRandom(); + byte[] rawAes = new byte[32]; + byte[] rawHmac = new byte[32]; + secureRandom.nextBytes(rawAes); + secureRandom.nextBytes(rawHmac); + final SecretKey wrappingKey = new SecretKeySpec(rawAes, "AES"); + final SecretKey signingKey = new SecretKeySpec(rawHmac, "HmacSHA256"); + + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + SymmetricEncryptedItem.encryptRecord( + ddbClient, TestUtils.TEST_DDB_TABLE_NAME, wrappingKey, signingKey); + } + } +} diff --git a/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java b/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java new file mode 100644 index 0000000000..79ec03b42a --- /dev/null +++ b/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java @@ -0,0 +1,44 @@ +import java.net.URI; +import java.util.HashMap; +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.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; + +public class TestUtils { + + // These are public KMS Keys that MUST only be used for testing, and MUST NOT be used for any production data + public static final 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"; + + /** + * Deletes an item from a DynamoDB table. + * + * @param tableName The name of the DynamoDB table + * @param partitionKeyName The name of partition key + * @param sortKeyName The name of sort key + * @param partitionKeyValue The value of the partition key + * @param sortKeyValue The value of the sort key (can be null if table doesn't have a sort key) + */ + public static void cleanUpDDBItem( + final String tableName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue) { + final DynamoDbClient ddb = DynamoDbClient.builder().build(); + final HashMap keyToDelete = new HashMap<>(); + keyToDelete.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + if (sortKeyValue != null) { + keyToDelete.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + } + final DeleteItemRequest deleteRequest = + DeleteItemRequest.builder().tableName(tableName).key(keyToDelete).build(); + ddb.deleteItem(deleteRequest); + } +} From 06d5ed48d6685feac9c2d29a3c160640aa7c9e85 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 10:54:41 -0800 Subject: [PATCH 26/38] m --- .../DDBEC/.gradle/7.6/checksums/checksums.lock | Bin 17 -> 0 bytes .../.gradle/7.6/checksums/md5-checksums.bin | Bin 22097 -> 0 bytes .../.gradle/7.6/checksums/sha1-checksums.bin | Bin 22385 -> 0 bytes .../dependencies-accessors.lock | Bin 17 -> 0 bytes .../7.6/dependencies-accessors/gc.properties | 0 .../7.6/executionHistory/executionHistory.bin | Bin 206193 -> 0 bytes .../7.6/executionHistory/executionHistory.lock | Bin 17 -> 0 bytes .../.gradle/7.6/fileChanges/last-build.bin | Bin 1 -> 0 bytes .../.gradle/7.6/fileHashes/fileHashes.bin | Bin 23197 -> 0 bytes .../.gradle/7.6/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes .../7.6/fileHashes/resourceHashesCache.bin | Bin 22271 -> 0 bytes .../java/DDBEC/.gradle/7.6/gc.properties | 0 .../.gradle/8.11.1/checksums/checksums.lock | Bin 17 -> 0 bytes .../.gradle/8.11.1/checksums/md5-checksums.bin | Bin 22097 -> 0 bytes .../8.11.1/checksums/sha1-checksums.bin | Bin 22385 -> 0 bytes .../executionHistory/executionHistory.bin | Bin 162066 -> 0 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 0 bytes .../.gradle/8.11.1/fileChanges/last-build.bin | Bin 1 -> 0 bytes .../.gradle/8.11.1/fileHashes/fileHashes.bin | Bin 23197 -> 0 bytes .../.gradle/8.11.1/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes .../8.11.1/fileHashes/resourceHashesCache.bin | Bin 21047 -> 0 bytes .../java/DDBEC/.gradle/8.11.1/gc.properties | 0 .../buildOutputCleanup/buildOutputCleanup.lock | Bin 17 -> 0 bytes .../buildOutputCleanup/cache.properties | 2 -- .../.gradle/buildOutputCleanup/outputFiles.bin | Bin 18749 -> 0 bytes .../java/DDBEC/.gradle/file-system.probe | Bin 8 -> 0 bytes .../java/DDBEC/.gradle/vcs-1/gc.properties | 0 27 files changed, 2 deletions(-) delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/checksums.lock delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/md5-checksums.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/sha1-checksums.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/dependencies-accessors/dependencies-accessors.lock delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/dependencies-accessors/gc.properties delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/executionHistory/executionHistory.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/executionHistory/executionHistory.lock delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/fileChanges/last-build.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.lock delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/resourceHashesCache.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/7.6/gc.properties delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/checksums.lock delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/md5-checksums.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/sha1-checksums.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.lock delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileChanges/last-build.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.lock delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/resourceHashesCache.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/8.11.1/gc.properties delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/buildOutputCleanup/buildOutputCleanup.lock delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/buildOutputCleanup/cache.properties delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/buildOutputCleanup/outputFiles.bin delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/file-system.probe delete mode 100644 Examples/runtimes/java/DDBEC/.gradle/vcs-1/gc.properties diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/checksums.lock b/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/checksums.lock deleted file mode 100644 index ba021712dcba69358172340e0c71afefc067f396..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZRM=kvRlV0`c#0|aaW04%!%aR2}S diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/md5-checksums.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/md5-checksums.bin deleted file mode 100644 index 5a685a8a9914b64d0fed86d2f4c17d07d7fb7306..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22097 zcmeI3i91zWAIHxTLZ%cQsU*o1DIpOWltZRtNL+K*lrBPs(-q2`S<*<7A%yFaM(W~L zhRa16$`BH%S1DBY@~(Z>Uft)O-Cyv&dp}S6oabYI)^D%x-fN%zJgv2dLZJyS;2Y(i zP4u6y{!8 z>>P7G3%O}2;;{h%3zd>uIXI^y9+$j0Y(h1+338)Nh^J+7E%%ocI0U(&JL2hnKltBk z>4;(}H{ZY_!Ud8;MV^>cTyzJQIMjrgUBH}MIg+a5t~bpY|p0_A1l zT^sr#HyK5|h*HsCUcT=u1|*1b;#-M-}nVr>6r6xuxP<%e&ri)x~e&v zaQp$}R&No%MfoD|Lb9|T^8K}l|7MYXtU6EcHRR?Uh}Vax@(i36&Bfc1LHv<<$%iH- z*AI}J?MD2Gu5=I=-MkQQUmo!$)jYEp+j1u4hKmqyK3r8h@wCSWat0UTtrlak&b~>U z`0;9pxBv7qf^!dVBi{b|Z@i^%bGWzVO32N65bv6{{_CZ+_Cv@Go*~|IvPVKs=)MT# zmiCDERyKUHU7TG5xyfq8`=>%qIz0^1f!urv;sae_qpAB6`tbI5BK|TzjI;8SY8d47 zBj5N`Lg%rCNq@*KlM#RA`r4Bx=pBAO=01pzy-`aK*0RCdH?~52+~JR)(b=B6u$}#O zh)*Sb$_gJn?FqR_CE_!DFMUtZXKvu!^c&Y7wJT&+--g`cGvaeIl&LIE<6V$jDkAZ41ZxK5j26WID$p_JG#S>$`31!1&wKYNo%GItoUVm9^;+c``_J6?d0Mz4zDUS^ z+m#&?@$h(y-x22t@>23`OB~1B_e5N{=jJ2U0YO{Nv_cmsdLr3=Q|fAM&?7q1)ljlVvz zIPGDo3*<)Wh_AQN+bAbLstP#+;RTuCk1ZA5{KJr&4xm5CW~Kg?YA}yKmyBZ&SGYD) z>leI89v*KNh`8ESo7ps-#65UjXT;UHYuabkT`oYrKM`@wwB#$F%9U*(r}HANB^Irq-DGbblfKpykQ<&ue4ie}%66k!CgjG_-*|fFYV!~YKgdm)h?~w%A9d)P z>%`mn8*$6Bg|P9}kDo$rR)Dy5!olqfapkLc`)?4ptsXTpJN6duFGB-y*I4ho)9Q+N zI~JP}ckh3?*Nc922(R0OxX0D=W95r>3PEl(i@0Z$`X!&pUNOimA0U1_qNHk0Y<@rF z<~tBapA-srJNz5DCleqOAQK=HAQK=HAQK=HAQK=HAQK=HAQK=HAQK=HAQK=HAQK=H zAQK=HAQK=HAQK=HAQK=HAQK=HAQK=HAQK=HAQK=HAQK=HAQSliNdTK9i%oEq!u||CT zkHERER4sd=A!V~6r7$`;-tPf#=eDuV5n!ay>f+(-f2zhwq9N+?^P;l1 zM&U?cWXy{D4}v4XSIu)^&vsTGG`lQ2X7llz9ihtd&bPqKYEa`rI9wS(4G1P0#+%Ml zIK-Zp40y)Osgpq`{OcE8>6!)epQ#t6di9#YkznJE20ONXFchB(PHeVz%(AJNN#Lxj!fQNX z36y1Dh=y>{N0)TdTKT!FO?I6+`@xzIa0NZLr$EEljA$f^<=zr_QMr5hkl0?m&ST(; zVb^?44jK$WqEXRsL%&9D;fct0zS86`oqEuaUkeRBY~C+hKLWb)#Mjqy+Usmny(qBz z<`!slzs3#hmSQu0olq&!7oE}3Z0R`lh<_`*uRN;ipt0VFXk_L$in}ab!>wMUBcNM# zW9`5D!7E8L!Z?cO0}~C+jb00d+Ps+5g~mt@I1+3;{$R&;R$ug`VH_O-`v>BRp zo`%K=VU|GQO(hzqzm9F8F*g^UUTY#w*%$)uJS^SZZO{;$A{uwbJBOn~_(P{gX;1$a zF5dlbV;MI4p6#q6)OPN8D=ELyAW1CxY?&rlzk}5%#o7iNUo6c;*<40atYKsK_0gZ(@mOxqco@f~I+^U@Xp{**~*BErlCf3Bh(&L-Ud2Hxm3*sv3ty+vKRy(BKe;H3Sb5 zjoU4HJp5l${|wgV5mhM`0ki3`8iO^^5H%qhX?m%7{pp9b)QiTRDaPFcbJQ_I2EXP4 zIYi^iNJNT4UU-7&-2@IT1G`XY_}jo5{Fy|fs#ZMev;NE*`Q(fHm^m-zp^+I5jisxI zM&zXSZfORscEs&W$)N+;U~w2YD^EieXz;odjq#`kv77+e#}XmB5kfR3J_et+K|^Re z(HPCxC9$r~lX2u6#pj^o4hvAj`(CjcZg9pB4S^e}eRjDMT50Dd9aZ*-JO+l3aMTbD z)=+y#G!F4_jSEi44$HJ8Em=@L_ljji^TliML7Z*uoY{~3p8!0^6p*<=F^&I00D<+#n3>IW)0 zmWOD+)vSs!1x7kYsQw@{xDtuRt4ggpZMSXOeSFkSZ6euVl@vAYNPCqBG`JrU4c~A_ z>8>p`F}wBT*Bs**#NGgsrWC5@puq`#v)Heo)S768_-ENdKME@R9^2@K-6ekG56|P* zd{qz8_}FlNXy;j}&jXerje9Nkf}T>t--f5<;5CjDjn~!ongSL@elJ~zYvd#MR|6xg zCtiaG8X5vbqee$elh-SXQme-QC~e`6Ljc6AVq$bzBEvuo{`yqB)8V#HbV10$*1m=+8T2kevWZueYT7S=8>rMD-D z&(Ytd75lAqt8eAU$6(BN#$dbt#I@N?DGDz4^!)}N<*EWBIfnOB9jqZ{NUULyEU0PV zFXa2V+JQUkV*?jveCQ50V;R&<*ve0~vx;_mo>#5vyLQA~)7(R2^%yV$-o0Dm#4;$l z;Q7UF+&;bJS}x;V;AY*n&BHh2Du8jS(n5I<8p@?aV`_fWF{VA?^mFPp8+(Mxf6|%X+7#Dex0&=TT+zIB-ViU zLTXAut(Pz~^s%wX*7JuMhPLs$Owv0h7+!m%!bEZQ?~>$7TpQ<(G0% zn;+6`CKBWui~j^h##ABx8%1^e&a-6VPGFvNRG7k&*#3GhVfs;)QRJM1RRF7`wt^j7 zjbBS%$}(lodKT+*wW=}?jk1h_D7+s$0YsyvaMD0X-9I#NZq}>5vXAu^K9%}D2OJ4D z9&4~;tFh44%qMPBf1ugURw~CTABR zW+-lh2DcK?aJ$gmv9~4idGCq99ap07Jb*o4;Q@`M-x;;%zp7ZR)&65x+a#_#yzE05 z%P2(e3?eeb8t>f#w|3NK@HCcb9^E0?-NiD}lgnX^<+4O$biVq!m6E;7Tz>E%*UF!< z_bY9>gd28ufKB~7zvr?Vkt#-}a_RCLb*WAQvT4B3w{2cLh#gO%^nPd5nww=mNNTq1 zoyt*}D^%$RXG7h$_%<3JM%+MSyB^%v?&cfrnQnSkzV*4A{h0+;Kdw_Bu}V-PSnt&T E1qm4sn*aa+ diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/sha1-checksums.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/checksums/sha1-checksums.bin deleted file mode 100644 index 934ecb7207c555f19ed4ff4dc63ac29abb761355..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22385 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 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/executionHistory/executionHistory.lock b/Examples/runtimes/java/DDBEC/.gradle/7.6/executionHistory/executionHistory.lock deleted file mode 100644 index ccd9ee529fe95be0b9fe68f5a133bb992a4470b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZQp;X3`|dCzwr1_)RW05{bIN&o-= diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/fileChanges/last-build.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/fileChanges/last-build.bin deleted file mode 100644 index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1 IcmZPo000310RR91 diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.bin deleted file mode 100644 index f495da3fcef624f6bdac8af1997ed2ab9fb0d733..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23197 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 Xgt42)%}{2XrBR4x2*4WD)0pvJh8!j` diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.lock b/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/fileHashes.lock deleted file mode 100644 index e72f807afc6ee55c85b494b602908b233d5a8a74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZSn(mz}4-UbN|1~6a>0suHi1dadz diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/resourceHashesCache.bin b/Examples/runtimes/java/DDBEC/.gradle/7.6/fileHashes/resourceHashesCache.bin deleted file mode 100644 index b982bd1bd4f57644858b06a4dab7d9f204c13dc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22271 zcmeI(c`#Lf9|v%h5D~IgKYL{FN0$0oD_rZnt|erN@KY%yODQD#zLaDSm0gH>ED2@F z+G6bqS;DmzNw%ju=X~!ok8@|5XXg3qnR92VJFnjJ`QG#Hp5x5)JtrwBIQMSBFN%M^ z*#7+%eTO!HHh?yOHh?yOHh?yOHh?yOHh?yOHh?yOHh?yOHh?yOHh?yOHh?yOHt_$_ zfbp*b*$o{Gr#Kby4U;DYg*3yjH<$gM1oW_~+F3$>tyc#A|GXj)%8=+Y08%%$MlnY^)a4#Jv0V#tw$u^Oy}QAPw;{q_YHW4 zv)+Wxi)bmxwR3=HRw(h02lVwpZV(MTtN*oY^2?G!$nj3Vv*jn6GtNzKz+MRaVY8;P zta8FIyR7J057h0d^gWM-~&0X7s?-MCWj*A#`z@AT^?(?v_OFKy(FK^3tYkBPeG0k2mb8) zQMJCY@rRIOUjlzlrQqW~T-gCR<__?BmqNaXkH>zUpTw83Bk)E?mo~F~_Rk^LvIE}I zdFio^+_PPfwB;j><(!aX(}91AU@&sZ#WKTloPiIqSXl)} z+Oa}zm`ZZXF?k*~E%>}JssTRiRGpxm)W!nkjl6-6MYi>ZGSVD{+~l9YzYv;gszVIn z>y@q&@aZt#j-L;Be4#w1lH{u&9*VaUR^a)jzzGZ=XyO~Lorc`-I`BE$AEHhJJ#haf zp1_v~EsIV3-EmM}-xB!umiK&zL_EJkuB}4yA>)15Gc>{RCIkPW7|}*ZA98~8XMwN1 z`QjhpfzyFpZv{9-=LKP3PdP`}KLDpbciubh!#sRkm@B~PBvG87LT*w8d{33ZN0X?d&XD7%firfid=nA(qla8)6*#keog1x74tzbw>;cYlT7kP` zCGjhi*AoNI9vLAzczsD0a;+BNTtbV?TBo&jA;(COd~;A~&goMn>^R{26&cLjLIsl` z*NF!%XdWlm7`rnSa$^DDLd%*ugCjHW^+ERwaA8BbukS+?GvItEaFLX_5UL%!%V3`e zF0Oj)`Q%KAJ>>enz$Fx$&4e|Z${{y+0$fhr0<)j%s0HNOQzUoY)8l#b0sQZRUj!~6 zJQ_yPV*>vi)D8fy5Uw#;6Ed3t&B1a2S1R#7<^Cfh1ajO3;L7}R{JTR8f*?0ZBKcyI z=~@L}FyvZ+z}3T_g_Wk#!`E%B1@IGsGqe7sf>ZDu8Q>?Slc>jgI9uU4YQQn!l4d@4 zr2-+>wFa&eME8NE&a@bEgLlAnQ*-)h*-{R{z6>09cA{I~AnqmPItIY;`A=Kr>0IFJ zxd}6HlNU5GFIt(weTzKs(-OjLI>)ZS*ApFA;AV;RGMF&IgYdeCfmMBQpNZtLT=y zwW-lJQ*kRdWa3p0&(O_#hN=Vo^7ofm?!7`L%%q<8H-sn*wB&CHRg~RuKqe~NA3J=_ zKe)J$mR--i>D2#9GyV zQXE095>$7=CSRE8;3$_>-sbmn%E*L0^Xut!T~!{R-eas27Yr{T6MmnERo!gL z`dc?Y5qB4FJC*^uYaLDo_eT8fsAmS2niG(Tz}z(_$!`~1&r)HJ89cyiA`^akE_WTr zX|9!z3>988Tl7aJ)aTf0XZz}C=|`K~y#@B-kcrY+Z)c9_5c58cl8>7MCy4tuw|&x* zh-JE4$>)LvJSrI&^B+qh6Y)`x`;yMbG73w|WEC8^oq3t@-Er3aco$BI%%GbOh~R-r`dGMcQHgwE>zWQ&LI<~$I_=ts7C%uU+cn^$K?@s z4R2fJ+*am1rATu`hMg~Nld)S7nULT`=7^|{hJdw4vOCbu@pbb64f`C?e9aY14kL(TGHE&(~iZC6o0ciZvW zmZa}nu10cM$V8ff4%UcQqkMh3t8{|jXaJd5+s&|Nzi7n}b@$AN%klJN&dS!bLH=M( zUeW$0%BZ$;&%2N_9+rMk`%;W+JwPRA?ywy50-4b3%1SoE7o`-PnojC)XD03i-`3bW zao%=E&bQBuCgpPC!p0sV6D$r3!|Z$2I@%cT_;he6#Uc~)_@Mq_=IreH#92(_@d)CM z=WVO}Lt{N9PXiaCn>0u;R9$33CT2ASFRT0+@Jh6K|G@x0KOSV_C0}`w#^Y87oZzE2 z1)NDdGSO>tYJJs|q?2{O^-9}!#C7%!%C z=z`}2y9k;459Px7@$-Sxu|u7XarJbzHpm&O5?L+@yQ3XydMX6Ra3dYkqAI5aku!eZ$M272?Lt6gj6K23eUa~KvbsMj zauw&wN0Ae?yf5Y7Kg7n3>5d>1CN{L*XMNa14T6lY42Y6Z`l=)QqK6 z*vWi9;tL|0sYL%yd24H~DIaxy4LKwC&XC{jlO~waldfazj)7#(TM^1OJ6n6b>w}Gt zmP|$aegvY_uhiL)iAN5n2C;7Ww>gL2%y*wHAwHq8?TlLVG*u9JKZ!qZw}`P_K8DOa z?t#k}uhii`9trerwKEtlCO&nwZN|RGQ5DvC=9HK>d}rj6MMaPa3DLi0bZ(Y@bxfw6 zm5eta^IaEQYt3$_5ff{?6`ZvaHhYN7i~y4zR$rxa3Et7eb(1FP$V7uy&TgI%U9|;! zPUaM~M^}&u#)KPeiJ#4GINp(xX7Wqgs|npq*Bt3osNWc_vce8WLBx2e14uw zVr-Q!kVmDUic20jqsEq%wyEOhgU}RVxsp$*X2^uW62HyfTS8P3{CkhOxh>!~pVmj=%QZ44z{TVrf+$#S9@?2wR diff --git a/Examples/runtimes/java/DDBEC/.gradle/7.6/gc.properties b/Examples/runtimes/java/DDBEC/.gradle/7.6/gc.properties deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/checksums.lock b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/checksums.lock deleted file mode 100644 index 7561b220f3406420162548103f605a5718c0ac79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZSfb>x@Qv|0U*0Rm3%=?-*4nC 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 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/sha1-checksums.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/checksums/sha1-checksums.bin deleted file mode 100644 index 0484a365e4841c38305c883cf7b55aa428663d9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22385 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 Yr!_?$s~Ua`&mY%KGIpj!&VP6K4}pB9n*aa+ diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.bin deleted file mode 100644 index 5738d0ccfd623759a76be28f42daf98379604e52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162066 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 ZeVKaY|APcfu-byv&a%}8;xS{~{{ZdzNaz3n diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.lock b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/executionHistory/executionHistory.lock deleted file mode 100644 index 4c6d0a87b250f67b8eed5b311ce5b27ba6bb09dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZQBSiFQ|rSLuv1_*Em04*s4%m4rY diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileChanges/last-build.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileChanges/last-build.bin deleted file mode 100644 index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1 IcmZPo000310RR91 diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.bin deleted file mode 100644 index 311401de42862285f1feb54041cdbbf4a43c2d87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23197 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 C#~bnh diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.lock b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/fileHashes.lock deleted file mode 100644 index cbe7280fe70f4fe54977d67ceb9d8c22c12e5c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZSXocH5?;`3iR3}C=$2LMF^1+M@A diff --git a/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/resourceHashesCache.bin b/Examples/runtimes/java/DDBEC/.gradle/8.11.1/fileHashes/resourceHashesCache.bin deleted file mode 100644 index 30fea0b10d7a600c7203e094f0b2776b8e131537..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21047 zcmeI3do)yQAAl7yMXtFkw{i(lNfQZOFvb`Y-E%2LJ*jShFqdK!Z>Hv-mkMhueH8^zxAzm*1}$oXMTI{$DaLn5lM|oBUwr?* zr2pMSTc`jkfC``jr~oQ}3ZMe004jhApaQ4>Du4>00;m8gfC``jr~oSP|59MfKPNH| zI~h6M*}M%ge?C3~k$*PGq4(%bQs!<>*sm%1;Qt3_dMHna&O6`4IQ1Uj7hCtUCc@oI zaUKPD#Opo+w|Mj##%U3NM;fkeyq&3@iE$DQ@aRJ8vmX6~Zy2{L1w1A?C{k`&fEYgC z8}Qg(;fFE`cMst6e*k`2L+k$GaIMVpSVD)<+(2k1w!cwFA4{6zSo|bA^t`lm``a0d=ATL&QH~EF&HPc08aRx zF!D-fd_BJ3V8G`t<7Crw{cvt45BNM+^|_{Be7<15brs;EAzriZn^Y>`oH51g&3@_7e9Wx5Wo$r-i$Ai48(7jHgv#^eCId$pZ*M>-1l zIyED;c^9bXvHj9=0k@C~ulv!cFOG4W3cxoSBnf_NlB>p#(*kf(__7^A3Hl6-+qwWw zF?*osUlD-cuWa@JPTSK_Z%2(S!F)0m@GVLlmv~3bmH2+U0pBS*t0=bGryck21MX@{ z+{H|NAc%3w;VB;Y?DpF4+wu7{65xAVi|)EBbmM<_`!#?cv{!eZTr^C_=8(n!_gd%D z#Q7~$2IIDhfP0HLJylOl!tW=x<$xbC`*pBF_eCP^r%&-i-Q^wMuinOwb1vY%V{L^d z#xc(@Zj%Z4DVe<*ila@SPaNRjCm)}XJoX;+9Th+YPyti`6+i`00aO4LKm||%Q~(t~ z1yBK002M$5Pyti`6+i`00aO4LKm||%Q~(t~1yBK002M$5Pyti`6+i`0f&cviyv3os z#g7uaFYznlRuNY4-ZstHd0psH zsK7I&vb8G;kIHqvh7I{q>rea#eC3S^`uQuFb6jELR;!Woi{N`spuP?|0aUa?*2o;hDtP?6R>ipu;^LHYUBz{f<5_pBT38VagVIzk`i| z@Ld0NKd*aBepGU|m-+CXo|tyF+7CMhbC~=BN5?0f7p~#YhK<$@^yTR`WeRS?Z7Lqg z+5E8Ku)vFR(%*QwJhfdyVyO5BY{*ZrMJg;<2eX{>iobXgieMuxtX{h$pQ)`XGCyHc zW7rHmI)ooyC+J#8XqPk?{+CQqTVJ(rkBn~!qc|LYkl-6>R z4X$c$G%xr78&*o|_C-H{H7-AXS7jcuuKCf2Wz z6erHK30i|xi)@dZDwxjf(7pXrAtY^vD>--|v*4?5=D<4B3n^E%bzj0WO8J$#G}hZbUhbUsB=^V# z-Un&gUP3%r685)V3WQ7fY&aBeJi}G^-_NCa)tG*5FN6LmLol2>6vbO#K5Z`+K_26h z6xCs4M%0ho2+W)eBuXRoyEgv+OmJ2PNo*_cbJ>+98ZB9UDKMNhahT<N zS>1PMe_qQPtD_afX7g6TPdnRvlh^uZX;ofIa|@wyMeEnVhMt1SF714-`MLFs<=&c2 z1+WqRqMyawa_m|({m$)J^X4P4anUVh;(3;VK$G%`q^?Qs3{4k`ReFWxZMmJ8FY;t$ zFpCY(@SBsv@e9ow>AXrP)S1-AAH%E99p&rPQS-J38c~ zS#aMP&({&^bdzWmooyY-KcH3r(q+QJKy5ZGr`5B zww3B4uC2zfA+jAt|i1-fP-Q&B*aqqVtH>qjP_D^+a^}|u0GD83X z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~f&U^fr*EXi+>CR$$(Y#Vnl{>|Pi(uDrwqfx+}Gos|{GwX7@EqcW;N*i;>cxS|6A0$#2CT^U*uC zen`4+_TX^hw)~=cm-OIydda_d^PrwnkREZ3@7QxQ0oAXhN8c|C7tfhi)yvZ5s%e(6 zd5<#!2q1s}0tg_000IagfB*srAb Date: Fri, 6 Feb 2026 10:56:04 -0800 Subject: [PATCH 27/38] m --- .../bin/main/AsymmetricEncryptedItem.class | Bin 9173 -> 0 bytes .../DDBEC/bin/main/AwsKmsEncryptedItem.class | Bin 9040 -> 0 bytes .../DDBEC/bin/main/AwsKmsMultiRegionKey.class | Bin 8806 -> 0 bytes .../bin/main/MostRecentEncryptedItem.class | Bin 10143 -> 0 bytes .../bin/main/SymmetricEncryptedItem.class | Bin 7122 -> 0 bytes .../test/AsymmetricEncryptedItemTest.class | Bin 1723 -> 0 bytes .../bin/test/AwsKmsEncryptedItemTest.class | Bin 1804 -> 0 bytes .../bin/test/AwsKmsMultiRegionKeyTest.class | Bin 1294 -> 0 bytes .../test/MostRecentEncryptedItemTest.class | Bin 2016 -> 0 bytes .../bin/test/SymmetricEncryptedItemTest.class | Bin 1708 -> 0 bytes .../java/DDBEC/bin/test/TestUtils.class | Bin 2704 -> 0 bytes .../java/main/AsymmetricEncryptedItem.class | Bin 9147 -> 0 bytes .../java/main/AwsKmsEncryptedItem.class | Bin 9029 -> 0 bytes .../java/main/AwsKmsMultiRegionKey.class | Bin 8712 -> 0 bytes .../java/main/MostRecentEncryptedItem.class | Bin 10069 -> 0 bytes .../java/main/SymmetricEncryptedItem.class | Bin 7028 -> 0 bytes .../test/AsymmetricEncryptedItemTest.class | Bin 1842 -> 0 bytes .../java/test/AwsKmsEncryptedItemTest.class | Bin 1919 -> 0 bytes .../java/test/AwsKmsMultiRegionKeyTest.class | Bin 1290 -> 0 bytes .../test/MostRecentEncryptedItemTest.class | Bin 2123 -> 0 bytes .../test/SymmetricEncryptedItemTest.class | Bin 1836 -> 0 bytes .../build/classes/java/test/TestUtils.class | Bin 2700 -> 0 bytes .../DDBEC/build/libs/DDBEC-1.0-SNAPSHOT.jar | Bin 10748 -> 0 bytes .../classes/AsymmetricEncryptedItemTest.html | 96 --------- .../test/classes/AwsKmsEncryptedItemTest.html | 96 --------- .../classes/AwsKmsMultiRegionKeyTest.html | 96 --------- .../classes/MostRecentEncryptedItemTest.html | 96 --------- .../classes/SymmetricEncryptedItemTest.html | 96 --------- .../reports/tests/test/css/base-style.css | 179 ---------------- .../build/reports/tests/test/css/style.css | 84 -------- .../DDBEC/build/reports/tests/test/index.html | 173 ---------------- .../build/reports/tests/test/js/report.js | 194 ------------------ .../tests/test/packages/default-package.html | 143 ------------- .../test/TEST-AsymmetricEncryptedItemTest.xml | 7 - .../test/TEST-AwsKmsEncryptedItemTest.xml | 7 - .../test/TEST-AwsKmsMultiRegionKeyTest.xml | 7 - .../test/TEST-MostRecentEncryptedItemTest.xml | 7 - .../test/TEST-SymmetricEncryptedItemTest.xml | 7 - .../build/test-results/test/binary/output.bin | 0 .../test-results/test/binary/output.bin.idx | Bin 1 -> 0 bytes .../test-results/test/binary/results.bin | Bin 622 -> 0 bytes .../AwsKmsMultiRegionKey.class.uniqueId0 | Bin 8729 -> 0 bytes .../compileJava/previous-compilation-data.bin | Bin 73542 -> 0 bytes .../AwsKmsMultiRegionKeyTest.class.uniqueId0 | Bin 1304 -> 0 bytes .../previous-compilation-data.bin | Bin 51731 -> 0 bytes .../java/DDBEC/build/tmp/jar/MANIFEST.MF | 2 - 46 files changed, 1290 deletions(-) delete mode 100644 Examples/runtimes/java/DDBEC/bin/main/AsymmetricEncryptedItem.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/main/AwsKmsEncryptedItem.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/main/AwsKmsMultiRegionKey.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/main/MostRecentEncryptedItem.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/main/SymmetricEncryptedItem.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/test/AsymmetricEncryptedItemTest.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/test/AwsKmsEncryptedItemTest.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/test/AwsKmsMultiRegionKeyTest.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/test/MostRecentEncryptedItemTest.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/test/SymmetricEncryptedItemTest.class delete mode 100644 Examples/runtimes/java/DDBEC/bin/test/TestUtils.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/AsymmetricEncryptedItem.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/AwsKmsEncryptedItem.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/AwsKmsMultiRegionKey.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/MostRecentEncryptedItem.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/main/SymmetricEncryptedItem.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/AsymmetricEncryptedItemTest.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/AwsKmsEncryptedItemTest.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/AwsKmsMultiRegionKeyTest.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/MostRecentEncryptedItemTest.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/SymmetricEncryptedItemTest.class delete mode 100644 Examples/runtimes/java/DDBEC/build/classes/java/test/TestUtils.class delete mode 100644 Examples/runtimes/java/DDBEC/build/libs/DDBEC-1.0-SNAPSHOT.jar delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AsymmetricEncryptedItemTest.html delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsEncryptedItemTest.html delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsMultiRegionKeyTest.html delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/MostRecentEncryptedItemTest.html delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/SymmetricEncryptedItemTest.html delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/css/base-style.css delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/css/style.css delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/index.html delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/js/report.js delete mode 100644 Examples/runtimes/java/DDBEC/build/reports/tests/test/packages/default-package.html delete mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AsymmetricEncryptedItemTest.xml delete mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsEncryptedItemTest.xml delete mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsMultiRegionKeyTest.xml delete mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-MostRecentEncryptedItemTest.xml delete mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/TEST-SymmetricEncryptedItemTest.xml delete mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin delete mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin.idx delete mode 100644 Examples/runtimes/java/DDBEC/build/test-results/test/binary/results.bin delete mode 100644 Examples/runtimes/java/DDBEC/build/tmp/compileJava/compileTransaction/stash-dir/AwsKmsMultiRegionKey.class.uniqueId0 delete mode 100644 Examples/runtimes/java/DDBEC/build/tmp/compileJava/previous-compilation-data.bin delete mode 100644 Examples/runtimes/java/DDBEC/build/tmp/compileTestJava/compileTransaction/stash-dir/AwsKmsMultiRegionKeyTest.class.uniqueId0 delete mode 100644 Examples/runtimes/java/DDBEC/build/tmp/compileTestJava/previous-compilation-data.bin delete mode 100644 Examples/runtimes/java/DDBEC/build/tmp/jar/MANIFEST.MF diff --git a/Examples/runtimes/java/DDBEC/bin/main/AsymmetricEncryptedItem.class b/Examples/runtimes/java/DDBEC/bin/main/AsymmetricEncryptedItem.class deleted file mode 100644 index 9c071868d6ff6302e77f60384fe06604b87557a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9173 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 diff --git a/Examples/runtimes/java/DDBEC/bin/main/AwsKmsEncryptedItem.class b/Examples/runtimes/java/DDBEC/bin/main/AwsKmsEncryptedItem.class deleted file mode 100644 index cd6abe56ba95900148ac724bc89089ce265adacc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9040 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 T?c-VVyAO|=-vRUs58VF)ziG8i diff --git a/Examples/runtimes/java/DDBEC/bin/main/AwsKmsMultiRegionKey.class b/Examples/runtimes/java/DDBEC/bin/main/AwsKmsMultiRegionKey.class deleted file mode 100644 index da3bf5b90fe41295be4bd8b50768545132dc859b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8806 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 T96x1$AIHP?_j+6_0r>t8)Z1yk diff --git a/Examples/runtimes/java/DDBEC/bin/main/MostRecentEncryptedItem.class b/Examples/runtimes/java/DDBEC/bin/main/MostRecentEncryptedItem.class deleted file mode 100644 index 92d6d339aa5ab3fa0508dfa5ce773e4d8bd125c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10143 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 diff --git a/Examples/runtimes/java/DDBEC/bin/main/SymmetricEncryptedItem.class b/Examples/runtimes/java/DDBEC/bin/main/SymmetricEncryptedItem.class deleted file mode 100644 index 155975ae7e2c3be3b3d43d0f70cf89e3dbbce982..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7122 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 diff --git a/Examples/runtimes/java/DDBEC/bin/test/AwsKmsEncryptedItemTest.class b/Examples/runtimes/java/DDBEC/bin/test/AwsKmsEncryptedItemTest.class deleted file mode 100644 index 5de0c99f585fc8b6c49b2fab632cc91785dc2972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1804 zcmcIk-BTM?6#rdFvPsxV2_J22t57Q@z;+`cA0)L(Ayp)#N+2^nIXAmEuqD~e>~2c& z|8eSTUnuH~eX}$ENBW?iy9pn$`ofHp%-wtM`TCu6f9L%6=TE-?Si!D>5r!LUN8VP) ztJsEn-1UXI>5I;;@O%X!hU-uHGp@I}y{|uMJ{5+~5P4wPmj94pG?i{Jgw`EX#4(DP ziZCJ!<5kNRwO*$w++E&m3x-71F?hSdT}z$^mm&Ya@)(lU{|H}ZnDPn6kl1o;hIr+< zA-X>8Jp~gClNT7zCFFRgXZu!1G%U}esB5G@5=~VTfmgRIKj@x)o!5uOUq&?qi z>w9~f8!Fz%ET!af+jKhehRT&n_kEmem1TsxDn7tHsz2{`g>5oumqYbmr#!F>9&8AA z;9_3IM@TV5eWx#^8lMHbWKxb|fq`Ylu!Q>xG=_rbwEQFP3Y~ZOcaE*|BhNDrbx*j@ zEJJv@d2I8JV>b1TV6)L&Z(G9lRp`hNWWyEQCnl6B+7(^Vvn(^jt{@TlbCsho2R&Sg1HlXSWTq2S&fxKr&uh7li@@*kxLX3tBZ;B zDay(4ugE9EKVpsH1(s^^93Pyu*QJp52C-zQ_5$NtGW0!Sw4Pu?d;OAfnni|44ctPF zejtD{jeGz(M$qDzp+sg0{9S^6k6_PZ0x1H&K+;8;ZyM=lFpUG!lxZ(edXxlN$+RcY zz?ayfG-pspm3A`b61FK?7*7KI$X^5@qJSy*D)?6v(0@r@RQ`avMxU{<-^mBV4q1$# F{x_oN*%bf) diff --git a/Examples/runtimes/java/DDBEC/bin/test/AwsKmsMultiRegionKeyTest.class b/Examples/runtimes/java/DDBEC/bin/test/AwsKmsMultiRegionKeyTest.class deleted file mode 100644 index b0d6b075d45b9d7c56936242d9b9ab1ecf92580e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1294 zcmbVMT~8WO5Iq-ARz$R`^{chDt+i^4pdbP^O|8(xfHnc4PYt;&x3~(sWcO|@KdVnQ zedrJ9k7_y>j2Nql@#W6W-kCXb&dlzgzrTJ1NMbt(AH(eKnOkVP2VP5>CDAl(t01~Z z!j(Y;7$#2mHy&$os~P)LKM{sx7&|BxYL#*=zgs@4Z7}G?K}=aXrq$e`pgr4iCAVai zw>+U?grUG4E6dN^>}lK0dTvy3S4KCosjYNoE0v1J(}_g(RCHr)=QNs**BfbJG>k?v zk=*2hykvd5VPqOSnMw2Qi!Sn&+ULCLLPPi(fbgPcMB@Gc!*5N?lv!fVq_WF zp|ziw_|Z$z=~M-P04C`S_X9A6hvYF#Q?eg3y?gq75I{fv5m{AopKg6*$Mh#f{fVyW zqvu#0lvc^W1f4pSp6o$P_Y*E4{M7MsuLXuz1g1FkAy&vLi1)-%H5j4#tNLp_s>jZ; XaUElp>dX+=xhpgxmo&C0--pyc5JfzU diff --git a/Examples/runtimes/java/DDBEC/bin/test/MostRecentEncryptedItemTest.class b/Examples/runtimes/java/DDBEC/bin/test/MostRecentEncryptedItemTest.class deleted file mode 100644 index 9588dc554d20f46f01560cf8ef73aed5854666e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2016 zcmc&#TT|Oc6#f=$WE&B{kbpzdl1r%}2yrt8J56iAX(=|LV4RXZFl%c~6l}>O$q@Q~ zI(g0uq3ula*3NYLhuVj>XNAG0Oka5ESR?J(J=gDid;Hs9KfMPqkM9(8FpT7F*V__h zVR>1r>>SrUVXS$gRuHbIAi&V~iXU>l%B_9o^VW$j@klGU%P`b(dIs)t!>kf&A$&}QwdMYr z*XmWFMQ{iABog--ZpN>+X1o}|LyRf-gj(tQZg^%@-`QDPRq-j}RF=an!>-94iVz<^ z^Kqe-mJudYJi;XPg4gT9G8nY>P-j$0H!XekT*}>dF{MJsG(*U<&kh*|&x5s@q(hix zVAI`1+64s(hNNp(yd&-io!9vHwx#nU*EJ4wS2%}eS-84!Z1I|Hl=M}9v07TGn#6Dw zNi0&zWk(Q~7}6B&ny#odDTc7@(hi~LhVYnS>^xkPM#PIXi%2UuVmngTXI=yB_;L6{7;6a7LtEhka`MGt6{`E!qEmux~y3>!w1jSNfI2A8th_IUXq&+9&= z6nuMWRG;6c$hO@e`aLz}5FGuF-b-Rm$w7d7pqWbl4js6SAi79Wd?`e7q)qN74N3tw z=-o>iI(->^gA?!2_trOw(px&9O$^XmZLSA#lO%>)WZa1%S_eoPrT}O9BlIPgOhcIL zf!fcwvm5>B9Y#;_-~{6*&~nk4JlkNYz$xZZ!B{Ywj)tP2MZ2S)M>CJ2GpAUI1%JUp zEcheV7~WuVL&}k+tbIrYv|ka91vcKGSBnLHK$zYq$Y~$mQeC6Ok`)R(f-U-il9Z)9 z@#|EnlpadGm$dqEpH3dq&KL#}r?MvKWRgmnqH=V^FoQnwbsJUkB_WugQmG))mJkf! z1^Jf{-NJKh(@MskMgcnnS<%<;`kPYeQos~E_n*81dgkRu{!geG`gC{yK|UCE$)W=< F{sEPz6_fw~ diff --git a/Examples/runtimes/java/DDBEC/bin/test/SymmetricEncryptedItemTest.class b/Examples/runtimes/java/DDBEC/bin/test/SymmetricEncryptedItemTest.class deleted file mode 100644 index e72461cc55b5e98bc31fadfe1a83942dc13d2d2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1708 zcmbVNU31e$6g`{Rmh9LiF(HW{G|(1mHwkK!0Ko=Iz90dI&cvx_GCaxBHYnJVM^bRy znf9S?<$rW|=7rGGp>OR>|516MJ*ya}(B>s$&F(pigSb5n+qhJ?W2j`i3w8Tuu9xN7X8pZ`C zRERw@Cf7H^mb6uvKt}gur^=Cz4@u?1N;QKsIHx!~PuSdFIRsQ?fxPWuce9n%6A-yYZ17wfg=MKVye^8@7@ z>mdUZUugJTVC4O3A1E@58O#dg~kCme8AOyoS>cHXb^c?%Ks#Lh7^+XY1&U- zVt8vGBbC8(zv}C%xj#Ae9HW!dY+mBb)>QuNJ|;>DWl!fnQg%|5Qlg~2z$Yc$(DPG9 z!brZrB}4xmHw^t}%n3ZjY$>HCGTjLy^;=0ZQh#tlE2W-d$VeI5PaLs(j;maLM$lj4 zF1DCe2|N6O2o%m4qEZNTV#^U(p14MdX&ghm@p=9M40GoQ?%)*nkK#v+#hk3hhLz)fXL>e>1tX2BBeC g!)pAkY2Y^>e;3rs{A=VE+2wM7#Z|n=t$nEc1B|}6WdHyG diff --git a/Examples/runtimes/java/DDBEC/bin/test/TestUtils.class b/Examples/runtimes/java/DDBEC/bin/test/TestUtils.class deleted file mode 100644 index b96d92d3cf832fdd6d3ee51c5f7c1cbcf497f315..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2704 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 diff --git a/Examples/runtimes/java/DDBEC/build/classes/java/main/AwsKmsMultiRegionKey.class b/Examples/runtimes/java/DDBEC/build/classes/java/main/AwsKmsMultiRegionKey.class deleted file mode 100644 index 32c11ea7ef3bb577f4508d82ac8b5ceae82551b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8712 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≶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 NS2_QKbtj&N|GyC|C?Ws= diff --git a/Examples/runtimes/java/DDBEC/build/classes/java/test/AsymmetricEncryptedItemTest.class b/Examples/runtimes/java/DDBEC/build/classes/java/test/AsymmetricEncryptedItemTest.class deleted file mode 100644 index 42cb620a7e23242cda5e12297dc0a79b3de72482..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1842 zcmb_cU3U{z6x}ypGfgK20;P?Jf+(hiFsLAs)M9Ci6w;zink5e`*3HbN9XgpAXKpC* z2k1ZWFYwGusk)ZD@!*fLo;#T){aE_YWmo5ZoO|}U`|NXa|N8s)KLOl8D~~g%Xt=>LABtX!yWN=bbN+&xF89{W266M&$SRj=c@{2Dm5vZ7vhDCji;$|ZFTzXS30zSz za1Y;VxNl$y57g$NfjX8M*mX+CzzQCb+=|YcbX=<1 z^-ITa>zLhffY_j<)cHIJgl98U%Jm_Eq*11hm=f)&g~npzfh~f@5Q-=gM0j$4SqBYSn`+@APSZQ8{!(D-r$*xh z?M#{(&DrTUV6WrjNg8vpF^3YZjlKik!#Ub9yiee?0yCHVbh7>O2U3sUAYIICBfEp# zHneT%_2S6#4)QCknyD0xVxc&8r8u&K6P0)Pp_F-t$;xjy$?ysfs@YPel-)-06=wI0 zH_4c-lKE{lM~F)Y5aU&i5OfJ4M*4_S?(J)2>A9c59eV69ph7>x{``a}ZotEjv~S`m z)IJTB=0`F8JS8+rsTK$|M!0eEDv;MWex?&8!89^B4~M=F2uTSiVw|Oz9}gzqLI~bNyEqS|0fzQgV8X0p9b~qQc1MK9s{rJF2za@jw$G;^kwL(GNx#y ODl|^Ze}hxkj%`QnTYfk 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 R#F=V`iqB`iqe*pz-$nF3D diff --git a/Examples/runtimes/java/DDBEC/build/classes/java/test/TestUtils.class b/Examples/runtimes/java/DDBEC/build/classes/java/test/TestUtils.class deleted file mode 100644 index 60deaae79be54ec3e7648ddf625fb93ce5017433..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2700 zcmb_e+g95~6y4*Sz!ro{lGf>+CX`DY5s0xdmj*&ZhXTXH2iEcyfe zp1w3VU8}24eds^6t7jwwPBE)yB`>2hXGZ(%+2_pVuYX?u4&V{gAcheQ!H3xpVu(wa zlaL6)k7y9{SO{SeOQMMj-K{s;WO%a<-}I&*vHXRt?>W*q^qTh1*>=&BF z5X$DcWpCSB)uQPAr7BnT?K%ZZ+q}l`Z0s`0cjXbBdu>5!Q>_urfx2qinyndnhM%~u zkY$+md2g_Nf};El2u39fP)VfGg*%{vAKQ>4_hL!H) zQBYo0TzDaI3xhy!znP_z`dOwbmpLqS$U)ajb}1~|RC2|J%^l_Dm&CGiwpu!(B|0VI z7Cu8TXEe+bU(-Y>^`BSE15vy(?%)F%8Du4F$k@aahFI4`-qfC9(Af{|T1)swpywpy zWo%Q+aup-A)1Ci;(Y&1R>ot>SUiqFg>sECx}Omrs3D-R>(c)1jL6pK$q<|c?Zw+&M;X>C}k*wCtFZc@38jdx#gXYg); zuecbL?4_>7>WIMLcla>eBsTvQiSQS@#_v>*3{s0KZSl0Ob91$-S{A1hD}9~Scf!7V zAwue{NS*;PFyi&J!%=ySJpB(-Z@odK7jKW@*5mwT@jK>&=?vNHOLys{I$JcQc>S8{ zU5Ul8-@dTcWq57zuEZy;$yO9 zcLF}ar_}4EP$T4_pV=LH`t*?R=oFqNybOQ9m+=1v|K2ME5`l1F@)S~}i5?G{=wnV7mYNv!U{_G|K~NlVLXV)2 z?uOgAi%HzW6h^UzaU5U*hvX6Tw_J)e>lXEhEX@`A2*Fb%j;NQkmV!PeT)&3 zpRl(HZIUE8KnV8{frJM%Q^?|uDKs`ko)FmI;wku0!Zg9okjq1F%`N&4k+nz<%Wng{ K^lTgu-+uwmFB$g$ diff --git a/Examples/runtimes/java/DDBEC/build/libs/DDBEC-1.0-SNAPSHOT.jar b/Examples/runtimes/java/DDBEC/build/libs/DDBEC-1.0-SNAPSHOT.jar deleted file mode 100644 index d2bfb94b39815397c420e955fb725e27c687a372..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10748 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 P-$xe~1}2&5pRfN1m?|9& diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AsymmetricEncryptedItemTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AsymmetricEncryptedItemTest.html deleted file mode 100644 index b79a1799e6..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AsymmetricEncryptedItemTest.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - -Test results - Class AsymmetricEncryptedItemTest - - - - - -

-

Class AsymmetricEncryptedItemTest

-
-
- - - - - -
-
- - - - - - - -
-
-
1
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
0.897s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Tests

- - - - - - - - - - - - - -
TestDurationResult
testAsymmetricEncryption0.897spassed
-
-
- -
- - diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsEncryptedItemTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsEncryptedItemTest.html deleted file mode 100644 index 27f759fa09..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsEncryptedItemTest.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - -Test results - Class AwsKmsEncryptedItemTest - - - - - -
-

Class AwsKmsEncryptedItemTest

- -
- - - - - -
-
- - - - - - - -
-
-
1
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
0.252s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Tests

- - - - - - - - - - - - - -
TestDurationResult
testAwsKmsEncryption0.252spassed
-
-
- -
- - diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsMultiRegionKeyTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsMultiRegionKeyTest.html deleted file mode 100644 index 332dbe8098..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/AwsKmsMultiRegionKeyTest.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - -Test results - Class AwsKmsMultiRegionKeyTest - - - - - -
-

Class AwsKmsMultiRegionKeyTest

- -
- - - - - -
-
- - - - - - - -
-
-
1
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
0.799s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Tests

- - - - - - - - - - - - - -
TestDurationResult
testMultiRegionEncryption0.799spassed
-
-
- -
- - diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/MostRecentEncryptedItemTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/MostRecentEncryptedItemTest.html deleted file mode 100644 index 6dff9ac855..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/MostRecentEncryptedItemTest.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - -Test results - Class MostRecentEncryptedItemTest - - - - - -
-

Class MostRecentEncryptedItemTest

- -
- - - - - -
-
- - - - - - - -
-
-
1
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
0.202s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Tests

- - - - - - - - - - - - - -
TestDurationResult
testMostRecentEncryption0.202spassed
-
-
- -
- - diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/SymmetricEncryptedItemTest.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/SymmetricEncryptedItemTest.html deleted file mode 100644 index 328990fed3..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/classes/SymmetricEncryptedItemTest.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - -Test results - Class SymmetricEncryptedItemTest - - - - - -
-

Class SymmetricEncryptedItemTest

- -
- - - - - -
-
- - - - - - - -
-
-
1
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
0.007s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Tests

- - - - - - - - - - - - - -
TestDurationResult
testSymmetricEncryption0.007spassed
-
-
- -
- - diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/base-style.css b/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/base-style.css deleted file mode 100644 index 4afa73e3dd..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/base-style.css +++ /dev/null @@ -1,179 +0,0 @@ - -body { - margin: 0; - padding: 0; - font-family: sans-serif; - font-size: 12pt; -} - -body, a, a:visited { - color: #303030; -} - -#content { - padding-left: 50px; - padding-right: 50px; - padding-top: 30px; - padding-bottom: 30px; -} - -#content h1 { - font-size: 160%; - margin-bottom: 10px; -} - -#footer { - margin-top: 100px; - font-size: 80%; - white-space: nowrap; -} - -#footer, #footer a { - color: #a0a0a0; -} - -#line-wrapping-toggle { - vertical-align: middle; -} - -#label-for-line-wrapping-toggle { - vertical-align: middle; -} - -ul { - margin-left: 0; -} - -h1, h2, h3 { - white-space: nowrap; -} - -h2 { - font-size: 120%; -} - -ul.tabLinks { - padding-left: 0; - padding-top: 10px; - padding-bottom: 10px; - overflow: auto; - min-width: 800px; - width: auto !important; - width: 800px; -} - -ul.tabLinks li { - float: left; - height: 100%; - list-style: none; - padding-left: 10px; - padding-right: 10px; - padding-top: 5px; - padding-bottom: 5px; - margin-bottom: 0; - -moz-border-radius: 7px; - border-radius: 7px; - margin-right: 25px; - border: solid 1px #d4d4d4; - background-color: #f0f0f0; -} - -ul.tabLinks li:hover { - background-color: #fafafa; -} - -ul.tabLinks li.selected { - background-color: #c5f0f5; - border-color: #c5f0f5; -} - -ul.tabLinks a { - font-size: 120%; - display: block; - outline: none; - text-decoration: none; - margin: 0; - padding: 0; -} - -ul.tabLinks li h2 { - margin: 0; - padding: 0; -} - -div.tab { -} - -div.selected { - display: block; -} - -div.deselected { - display: none; -} - -div.tab table { - min-width: 350px; - width: auto !important; - width: 350px; - border-collapse: collapse; -} - -div.tab th, div.tab table { - border-bottom: solid #d0d0d0 1px; -} - -div.tab th { - text-align: left; - white-space: nowrap; - padding-left: 6em; -} - -div.tab th:first-child { - padding-left: 0; -} - -div.tab td { - white-space: nowrap; - padding-left: 6em; - padding-top: 5px; - padding-bottom: 5px; -} - -div.tab td:first-child { - padding-left: 0; -} - -div.tab td.numeric, div.tab th.numeric { - text-align: right; -} - -span.code { - display: inline-block; - margin-top: 0em; - margin-bottom: 1em; -} - -span.code pre { - font-size: 11pt; - padding-top: 10px; - padding-bottom: 10px; - padding-left: 10px; - padding-right: 10px; - margin: 0; - background-color: #f7f7f7; - border: solid 1px #d0d0d0; - min-width: 700px; - width: auto !important; - width: 700px; -} - -span.wrapped pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: break-all; -} - -label.hidden { - display: none; -} \ No newline at end of file diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/style.css b/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/style.css deleted file mode 100644 index 3dc4913e7a..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/css/style.css +++ /dev/null @@ -1,84 +0,0 @@ - -#summary { - margin-top: 30px; - margin-bottom: 40px; -} - -#summary table { - border-collapse: collapse; -} - -#summary td { - vertical-align: top; -} - -.breadcrumbs, .breadcrumbs a { - color: #606060; -} - -.infoBox { - width: 110px; - padding-top: 15px; - padding-bottom: 15px; - text-align: center; -} - -.infoBox p { - margin: 0; -} - -.counter, .percent { - font-size: 120%; - font-weight: bold; - margin-bottom: 8px; -} - -#duration { - width: 125px; -} - -#successRate, .summaryGroup { - border: solid 2px #d0d0d0; - -moz-border-radius: 10px; - border-radius: 10px; -} - -#successRate { - width: 140px; - margin-left: 35px; -} - -#successRate .percent { - font-size: 180%; -} - -.success, .success a { - color: #008000; -} - -div.success, #successRate.success { - background-color: #bbd9bb; - border-color: #008000; -} - -.failures, .failures a { - color: #b60808; -} - -.skipped, .skipped a { - color: #c09853; -} - -div.failures, #successRate.failures { - background-color: #ecdada; - border-color: #b60808; -} - -ul.linkList { - padding-left: 0; -} - -ul.linkList li { - list-style: none; - margin-bottom: 5px; -} diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/index.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/index.html deleted file mode 100644 index ca546ab166..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/index.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - - -Test results - Test Summary - - - - - -
-

Test Summary

-
- - - - - -
-
- - - - - - - -
-
-
5
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
2.157s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Packages

- - - - - - - - - - - - - - - - - - - - - -
PackageTestsFailuresIgnoredDurationSuccess rate
-default-package -5002.157s100%
-
-
-

Classes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ClassTestsFailuresIgnoredDurationSuccess rate
-AsymmetricEncryptedItemTest -1000.897s100%
-AwsKmsEncryptedItemTest -1000.252s100%
-AwsKmsMultiRegionKeyTest -1000.799s100%
-MostRecentEncryptedItemTest -1000.202s100%
-SymmetricEncryptedItemTest -1000.007s100%
-
-
- -
- - diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/js/report.js b/Examples/runtimes/java/DDBEC/build/reports/tests/test/js/report.js deleted file mode 100644 index 83bab4a19f..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/js/report.js +++ /dev/null @@ -1,194 +0,0 @@ -(function (window, document) { - "use strict"; - - var tabs = {}; - - function changeElementClass(element, classValue) { - if (element.getAttribute("className")) { - element.setAttribute("className", classValue); - } else { - element.setAttribute("class", classValue); - } - } - - function getClassAttribute(element) { - if (element.getAttribute("className")) { - return element.getAttribute("className"); - } else { - return element.getAttribute("class"); - } - } - - function addClass(element, classValue) { - changeElementClass(element, getClassAttribute(element) + " " + classValue); - } - - function removeClass(element, classValue) { - changeElementClass(element, getClassAttribute(element).replace(classValue, "")); - } - - function initTabs() { - var container = document.getElementById("tabs"); - - tabs.tabs = findTabs(container); - tabs.titles = findTitles(tabs.tabs); - tabs.headers = findHeaders(container); - tabs.select = select; - tabs.deselectAll = deselectAll; - tabs.select(0); - - return true; - } - - function getCheckBox() { - return document.getElementById("line-wrapping-toggle"); - } - - function getLabelForCheckBox() { - return document.getElementById("label-for-line-wrapping-toggle"); - } - - function findCodeBlocks() { - var spans = document.getElementById("tabs").getElementsByTagName("span"); - var codeBlocks = []; - for (var i = 0; i < spans.length; ++i) { - if (spans[i].className.indexOf("code") >= 0) { - codeBlocks.push(spans[i]); - } - } - return codeBlocks; - } - - function forAllCodeBlocks(operation) { - var codeBlocks = findCodeBlocks(); - - for (var i = 0; i < codeBlocks.length; ++i) { - operation(codeBlocks[i], "wrapped"); - } - } - - function toggleLineWrapping() { - var checkBox = getCheckBox(); - - if (checkBox.checked) { - forAllCodeBlocks(addClass); - } else { - forAllCodeBlocks(removeClass); - } - } - - function initControls() { - if (findCodeBlocks().length > 0) { - var checkBox = getCheckBox(); - var label = getLabelForCheckBox(); - - checkBox.onclick = toggleLineWrapping; - checkBox.checked = false; - - removeClass(label, "hidden"); - } - } - - function switchTab() { - var id = this.id.substr(1); - - for (var i = 0; i < tabs.tabs.length; i++) { - if (tabs.tabs[i].id === id) { - tabs.select(i); - break; - } - } - - return false; - } - - function select(i) { - this.deselectAll(); - - changeElementClass(this.tabs[i], "tab selected"); - changeElementClass(this.headers[i], "selected"); - - while (this.headers[i].firstChild) { - this.headers[i].removeChild(this.headers[i].firstChild); - } - - var h2 = document.createElement("H2"); - - h2.appendChild(document.createTextNode(this.titles[i])); - this.headers[i].appendChild(h2); - } - - function deselectAll() { - for (var i = 0; i < this.tabs.length; i++) { - changeElementClass(this.tabs[i], "tab deselected"); - changeElementClass(this.headers[i], "deselected"); - - while (this.headers[i].firstChild) { - this.headers[i].removeChild(this.headers[i].firstChild); - } - - var a = document.createElement("A"); - - a.setAttribute("id", "ltab" + i); - a.setAttribute("href", "#tab" + i); - a.onclick = switchTab; - a.appendChild(document.createTextNode(this.titles[i])); - - this.headers[i].appendChild(a); - } - } - - function findTabs(container) { - return findChildElements(container, "DIV", "tab"); - } - - function findHeaders(container) { - var owner = findChildElements(container, "UL", "tabLinks"); - return findChildElements(owner[0], "LI", null); - } - - function findTitles(tabs) { - var titles = []; - - for (var i = 0; i < tabs.length; i++) { - var tab = tabs[i]; - var header = findChildElements(tab, "H2", null)[0]; - - header.parentNode.removeChild(header); - - if (header.innerText) { - titles.push(header.innerText); - } else { - titles.push(header.textContent); - } - } - - return titles; - } - - function findChildElements(container, name, targetClass) { - var elements = []; - var children = container.childNodes; - - for (var i = 0; i < children.length; i++) { - var child = children.item(i); - - if (child.nodeType === 1 && child.nodeName === name) { - if (targetClass && child.className.indexOf(targetClass) < 0) { - continue; - } - - elements.push(child); - } - } - - return elements; - } - - // Entry point. - - window.onload = function() { - initTabs(); - initControls(); - }; -} (window, window.document)); \ No newline at end of file diff --git a/Examples/runtimes/java/DDBEC/build/reports/tests/test/packages/default-package.html b/Examples/runtimes/java/DDBEC/build/reports/tests/test/packages/default-package.html deleted file mode 100644 index 6bff1ae466..0000000000 --- a/Examples/runtimes/java/DDBEC/build/reports/tests/test/packages/default-package.html +++ /dev/null @@ -1,143 +0,0 @@ - - - - - -Test results - Default package - - - - - -
-

Default package

- -
- - - - - -
-
- - - - - - - -
-
-
5
-

tests

-
-
-
-
0
-

failures

-
-
-
-
0
-

ignored

-
-
-
-
2.157s
-

duration

-
-
-
-
-
-
100%
-

successful

-
-
-
-
- -
-

Classes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ClassTestsFailuresIgnoredDurationSuccess rate
-AsymmetricEncryptedItemTest -1000.897s100%
-AwsKmsEncryptedItemTest -1000.252s100%
-AwsKmsMultiRegionKeyTest -1000.799s100%
-MostRecentEncryptedItemTest -1000.202s100%
-SymmetricEncryptedItemTest -1000.007s100%
-
-
- -
- - diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AsymmetricEncryptedItemTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AsymmetricEncryptedItemTest.xml deleted file mode 100644 index adb9d25eb6..0000000000 --- a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AsymmetricEncryptedItemTest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsEncryptedItemTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsEncryptedItemTest.xml deleted file mode 100644 index 7df128206e..0000000000 --- a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsEncryptedItemTest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsMultiRegionKeyTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsMultiRegionKeyTest.xml deleted file mode 100644 index 766f6b6979..0000000000 --- a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-AwsKmsMultiRegionKeyTest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-MostRecentEncryptedItemTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-MostRecentEncryptedItemTest.xml deleted file mode 100644 index d638bb7e29..0000000000 --- a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-MostRecentEncryptedItemTest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-SymmetricEncryptedItemTest.xml b/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-SymmetricEncryptedItemTest.xml deleted file mode 100644 index b67bae9e2f..0000000000 --- a/Examples/runtimes/java/DDBEC/build/test-results/test/TEST-SymmetricEncryptedItemTest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin b/Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin.idx b/Examples/runtimes/java/DDBEC/build/test-results/test/binary/output.bin.idx deleted file mode 100644 index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1 IcmZPo000310RR91 diff --git a/Examples/runtimes/java/DDBEC/build/test-results/test/binary/results.bin b/Examples/runtimes/java/DDBEC/build/test-results/test/binary/results.bin deleted file mode 100644 index 0fad41ec9282d50dce068f0920c4d5c8b7ef30d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 622 zcmaKnI}UyWLF!Ulad6xHAOSF zk{X}oRiY7St%LTtiu_XQ*3!dF;N~cU`w)5}*l?P$kR?=G+{Uy6p?i8)_;-;Fi~t^b zeL04~WuC_|qcjR@`7KDrmt*V~Rs6q)OgG)Z)mjfaV#1B#ZA>{72gLWrn#*T=6M(%g HAbRiuB?;>B diff --git a/Examples/runtimes/java/DDBEC/build/tmp/compileJava/compileTransaction/stash-dir/AwsKmsMultiRegionKey.class.uniqueId0 b/Examples/runtimes/java/DDBEC/build/tmp/compileJava/compileTransaction/stash-dir/AwsKmsMultiRegionKey.class.uniqueId0 deleted file mode 100644 index 4539330d0612e13342c4cc6db503e6a083a0e604..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8729 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 K*?$U6^8G)5Az;)1 diff --git a/Examples/runtimes/java/DDBEC/build/tmp/compileJava/previous-compilation-data.bin b/Examples/runtimes/java/DDBEC/build/tmp/compileJava/previous-compilation-data.bin deleted file mode 100644 index 3ccb409440d3add1d45daecd704152c33bddb32d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73542 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 Date: Fri, 6 Feb 2026 10:57:10 -0800 Subject: [PATCH 28/38] m --- .../DDBEC/gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Examples/runtimes/java/DDBEC/gradle/wrapper/gradle-wrapper.jar diff --git a/Examples/runtimes/java/DDBEC/gradle/wrapper/gradle-wrapper.jar b/Examples/runtimes/java/DDBEC/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61574 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 ScNgau(U?ep-K_E5zy1%ZQTdPn From a5625ea773aa1c9f8f6934400f0211593486eb9f Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 11:03:05 -0800 Subject: [PATCH 29/38] m --- Examples/runtimes/java/DDBEC/.gitignore | 30 +++++++++++++++++++ .../main/java/AsymmetricEncryptedItem.java | 3 ++ .../src/main/java/AwsKmsEncryptedItem.java | 3 ++ .../main/java/MostRecentEncryptedItem.java | 3 ++ .../src/main/java/SymmetricEncryptedItem.java | 17 ++--------- .../java/AsymmetricEncryptedItemTest.java | 4 ++- .../test/java/AwsKmsEncryptedItemTest.java | 3 ++ .../test/java/AwsKmsMultiRegionKeyTest.java | 3 ++ .../java/MostRecentEncryptedItemTest.java | 3 ++ .../test/java/SymmetricEncryptedItemTest.java | 3 ++ .../java/DDBEC/src/test/java/TestUtils.java | 7 ++--- 11 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 Examples/runtimes/java/DDBEC/.gitignore diff --git a/Examples/runtimes/java/DDBEC/.gitignore b/Examples/runtimes/java/DDBEC/.gitignore new file mode 100644 index 0000000000..221fe55b59 --- /dev/null +++ b/Examples/runtimes/java/DDBEC/.gitignore @@ -0,0 +1,30 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java index 2a74cc555d..7bad4fb73e 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java @@ -1,3 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java index 804fca7a46..802e4d6733 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java @@ -1,3 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import java.security.GeneralSecurityException; import java.util.EnumSet; import java.util.HashMap; diff --git a/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java index 1797119206..4006a4f087 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java @@ -1,3 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import java.security.GeneralSecurityException; import java.util.EnumSet; import java.util.HashMap; diff --git a/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java index 3c5efc5c73..86b93a31a1 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java @@ -1,19 +1,6 @@ -/* - * Copyright 2018 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. - */ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 -import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.EnumSet; diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java index a3430f3131..ce2c41c63c 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java @@ -1,5 +1,7 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import java.util.UUID; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java index 46f5e47352..0cbdfbd326 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java @@ -1,3 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import java.util.UUID; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java index a4d438bec2..a509718a62 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java @@ -1,3 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; diff --git a/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java index ef8ec2dc59..6f258bbdc9 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java @@ -1,3 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import java.util.UUID; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; diff --git a/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java index 806998af5b..56d630a76f 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java @@ -1,3 +1,6 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import java.security.SecureRandom; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; diff --git a/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java b/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java index 79ec03b42a..44599bd400 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java @@ -1,8 +1,7 @@ -import java.net.URI; +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + import java.util.HashMap; -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.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; From da134f3c01aeda2d15e7f0c046cb9a7ace15a1fa Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 11:33:52 -0800 Subject: [PATCH 30/38] m --- .../DDBEC/src/main/java/AsymmetricEncryptedItem.java | 3 ++- .../java/DDBEC/src/main/java/AwsKmsEncryptedItem.java | 8 ++++---- .../java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java | 10 +++++----- .../DDBEC/src/main/java/MostRecentEncryptedItem.java | 3 ++- .../DDBEC/src/test/java/AwsKmsEncryptedItemTest.java | 2 ++ .../DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java | 5 ++++- .../src/test/java/MostRecentEncryptedItemTest.java | 2 ++ 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java index 7bad4fb73e..9ba99446e3 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java @@ -20,7 +20,8 @@ import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.AsymmetricStaticProvider; /** - * Example showing use of RSA keys for encryption and signing with DDBEC SDK v2. + * Example showing use of RSA keys for encryption and signing. For ease of the example, we create + * new random ones every time. */ public class AsymmetricEncryptedItem { private static final String STRING_FIELD_NAME = "example"; diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java index 802e4d6733..f0afa62039 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java @@ -18,7 +18,7 @@ import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; -/** Example showing use of AWS KMS with DDBEC SDK v2. */ +/** Example showing use of AWS KMS CMP with record encryption functions directly. */ public class AwsKmsEncryptedItem { private static final String STRING_FIELD_NAME = "example"; private static final String BINARY_FIELD_NAME = "and some binary"; @@ -27,6 +27,7 @@ public class AwsKmsEncryptedItem { public static void encryptRecord( final DynamoDbClient ddbClient, + final KmsClient kmsClient, final String tableName, final String cmkArn, final String partitionKeyName, @@ -45,8 +46,7 @@ public static void encryptRecord( AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); - // Create KMS client and DirectKmsMaterialsProvider - final KmsClient kmsClient = KmsClient.create(); + // Create DirectKmsMaterialsProvider final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider(kmsClient, cmkArn); // Create DynamoDBEncryptor @@ -126,7 +126,7 @@ public static void main(final String[] args) throws GeneralSecurityException { final String partitionKeyValue = args[4]; final String sortKeyValue = args[5]; try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { - encryptRecord(ddbClient, tableName, cmkArn, partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue); + encryptRecord(ddbClient, KmsClient.create(), tableName, cmkArn, partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue); } } } diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java index 62cc98cd65..baa6cb076f 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java @@ -32,20 +32,20 @@ public static void main(String[] args) throws GeneralSecurityException { final String cmkArn1 = args[1]; final String cmkArn2 = args[2]; - encryptRecord(tableName, cmkArn1, cmkArn2); + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord(ddbClient, tableName, cmkArn1, cmkArn2); + } } public static void encryptRecord( - final String tableName, final String cmkArnEncrypt, final String cmkArnDecrypt) + final DynamoDbClient ddbClient, + final String tableName, final String cmkArnEncrypt, final String cmkArnDecrypt) throws GeneralSecurityException { // Extract regions from ARNs final String encryptRegion = cmkArnEncrypt.split(":")[3]; final String decryptRegion = cmkArnDecrypt.split(":")[3]; - // Use us-west-2 for DynamoDB since that's where the table exists - final DynamoDbClient ddbClient = - DynamoDbClient.builder().build(); final KmsClient kmsEncrypt = KmsClient.builder().region(Region.of(encryptRegion)).build(); final KmsClient kmsDecrypt = KmsClient.builder().region(Region.of(decryptRegion)).build(); diff --git a/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java index 4006a4f087..8d0be30007 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java @@ -32,6 +32,7 @@ public class MostRecentEncryptedItem { public static void encryptRecord( final DynamoDbClient ddbClient, + final KmsClient kmsClient, final String tableName, final String keyTableName, final String cmkArn, @@ -53,7 +54,6 @@ public static void encryptRecord( record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); // Provider Configuration to protect the data keys - final KmsClient kmsClient = KmsClient.create(); final DirectKmsMaterialsProvider kmsProv = new DirectKmsMaterialsProvider(kmsClient, cmkArn); final DynamoDBEncryptor keyEncryptor = DynamoDBEncryptor.getInstance(kmsProv); @@ -142,6 +142,7 @@ public static void main(final String[] args) throws GeneralSecurityException { try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { encryptRecord( ddbClient, + KmsClient.create(), tableName, keyTableName, cmkArn, diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java index 0cbdfbd326..f0c82f2e42 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java @@ -4,6 +4,7 @@ import java.util.UUID; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; public class AwsKmsEncryptedItemTest { @@ -15,6 +16,7 @@ public void testAwsKmsEncryption() throws Exception { try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { AwsKmsEncryptedItem.encryptRecord( ddbClient, + KmsClient.create(), TestUtils.TEST_DDB_TABLE_NAME, TestUtils.TEST_KMS_KEY_ID, "partition_key", diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java index a509718a62..aa3f17c07e 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java @@ -3,6 +3,7 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; public class AwsKmsMultiRegionKeyTest { @@ -17,8 +18,10 @@ public void testMultiRegionEncryption() throws Exception { // Encrypt with us-east-1 MRK, decrypt with eu-west-1 replica MRK // This demonstrates MRK capability without needing a Global Table final String tableName = TestUtils.TEST_DDB_TABLE_NAME; + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + AwsKmsMultiRegionKey.encryptRecord(ddbClient, tableName, MRK_US_EAST_1, MRK_EU_WEST_1); + } - AwsKmsMultiRegionKey.encryptRecord(tableName, MRK_US_EAST_1, MRK_EU_WEST_1); } @AfterMethod diff --git a/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java index 6f258bbdc9..e310bdeadb 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java @@ -4,6 +4,7 @@ import java.util.UUID; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; public class MostRecentEncryptedItemTest { @@ -18,6 +19,7 @@ public void testMostRecentEncryption() throws Exception { try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { MostRecentEncryptedItem.encryptRecord( ddbClient, + KmsClient.create(), TestUtils.TEST_DDB_TABLE_NAME, KEY_TABLE_NAME, TestUtils.TEST_KMS_KEY_ID, From 4a1bad55e62dcd819722fd311e5c6e3237dcc8d1 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 12:16:07 -0800 Subject: [PATCH 31/38] m --- .../main/java/AsymmetricEncryptedItem.java | 113 +++++++++---- .../src/main/java/AwsKmsEncryptedItem.java | 118 +++++++++----- .../src/main/java/AwsKmsMultiRegionKey.java | 99 ++++++++---- .../main/java/MostRecentEncryptedItem.java | 151 ++++++++++++------ .../src/main/java/SymmetricEncryptedItem.java | 151 +++++++++++++----- .../java/AsymmetricEncryptedItemTest.java | 13 +- .../test/java/AwsKmsEncryptedItemTest.java | 17 +- .../test/java/AwsKmsMultiRegionKeyTest.java | 19 ++- .../java/MostRecentEncryptedItemTest.java | 24 +-- .../test/java/SymmetricEncryptedItemTest.java | 13 +- .../java/DDBEC/src/test/java/TestUtils.java | 33 ++-- 11 files changed, 511 insertions(+), 240 deletions(-) diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java index 9ba99446e3..b7385c7d2b 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java @@ -24,20 +24,20 @@ * new random ones every time. */ public class AsymmetricEncryptedItem { + private static final String STRING_FIELD_NAME = "example"; private static final String BINARY_FIELD_NAME = "and some binary"; private static final String NUMBER_FIELD_NAME = "some numbers"; private static final String IGNORED_FIELD_NAME = "leave me"; public static void encryptRecord( - DynamoDbClient ddbClient, - String tableName, - String partitionKeyName, - String sortKeyName, - String partitionKeyValue, - String sortKeyValue) - throws GeneralSecurityException { - + final DynamoDbClient ddbClient, + final String tableName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue + ) throws GeneralSecurityException { final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); // You should never use the same key for encryption and signing @@ -46,33 +46,45 @@ public static void encryptRecord( // Sample record to be encrypted final Map record = new HashMap<>(); - record.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); record.put( - BINARY_FIELD_NAME, - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); + BINARY_FIELD_NAME, + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); // Create AsymmetricStaticProvider with wrapping and signing keys - final AsymmetricStaticProvider cmp = new AsymmetricStaticProvider(wrappingKeys, signingKeys); + final AsymmetricStaticProvider cmp = new AsymmetricStaticProvider( + wrappingKeys, + signingKeys + ); // Create DynamoDBEncryptor final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); // Create EncryptionContext - final EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName(partitionKeyName) - .rangeKeyName(sortKeyName) - .build(); + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); // Configure EncryptionFlags for each attribute final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); - final EnumSet encryptAndSign = - EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); final Map> actions = new HashMap<>(); for (final String attributeName : record.keySet()) { @@ -91,42 +103,66 @@ public static void encryptRecord( // Encrypt the record final Map encrypted_record = - encryptor.encryptRecord(record, actions, encryptionContext); + encryptor.encryptRecord(record, actions, encryptionContext); // For demo, verify encryption assert encrypted_record.get(STRING_FIELD_NAME).b() != null; assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; - assert !record.get(BINARY_FIELD_NAME).b().equals(encrypted_record.get(BINARY_FIELD_NAME).b()); - assert record.get(IGNORED_FIELD_NAME).s().equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); + assert !record + .get(BINARY_FIELD_NAME) + .b() + .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(IGNORED_FIELD_NAME) + .s() + .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); // For demo, the encrypted item is put to DynamoDB. You skip this as needed for their use case. ddbClient.putItem( - PutItemRequest.builder().tableName(tableName).item(encrypted_record).build()); + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); // Get the item back from DynamoDB final Map keyToGet = new HashMap<>(); - keyToGet.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); - final GetItemResponse getResponse = - ddbClient.getItem( - GetItemRequest.builder().tableName(tableName).key(keyToGet).build()); + final GetItemResponse getResponse = ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ); // Decrypt the record retrieved from DynamoDB final Map decrypted_record = - encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); // For demo, verify decryption - assert record.get(STRING_FIELD_NAME).s().equals(decrypted_record.get(STRING_FIELD_NAME).s()); - assert record.get(NUMBER_FIELD_NAME).n().equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); - assert record.get(BINARY_FIELD_NAME).b().equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(STRING_FIELD_NAME) + .s() + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record + .get(NUMBER_FIELD_NAME) + .n() + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record + .get(BINARY_FIELD_NAME) + .b() + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); } public static void main(final String[] args) throws GeneralSecurityException { if (args.length < 5) { throw new IllegalArgumentException( - "To run this example, include tableName, partitionKeyName, sortKeyName, " - + "partitionKeyValue, sortKeyValue as args"); + "To run this example, include tableName, partitionKeyName, sortKeyName, " + + "partitionKeyValue, sortKeyValue as args" + ); } final String tableName = args[0]; final String partitionKeyName = args[1]; @@ -134,7 +170,14 @@ public static void main(final String[] args) throws GeneralSecurityException { final String partitionKeyValue = args[3]; final String sortKeyValue = args[4]; try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { - encryptRecord(ddbClient, tableName, partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue); + encryptRecord( + ddbClient, + tableName, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); } } } diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java index f0afa62039..13204bccc1 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java @@ -20,50 +20,63 @@ /** Example showing use of AWS KMS CMP with record encryption functions directly. */ public class AwsKmsEncryptedItem { + private static final String STRING_FIELD_NAME = "example"; private static final String BINARY_FIELD_NAME = "and some binary"; private static final String NUMBER_FIELD_NAME = "some numbers"; private static final String IGNORED_FIELD_NAME = "leave me"; public static void encryptRecord( - final DynamoDbClient ddbClient, - final KmsClient kmsClient, - final String tableName, - final String cmkArn, - final String partitionKeyName, - final String sortKeyName, - final String partitionKeyValue, - final String sortKeyValue) - throws GeneralSecurityException { + final DynamoDbClient ddbClient, + final KmsClient kmsClient, + final String tableName, + final String cmkArn, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue + ) throws GeneralSecurityException { // Sample record to be encrypted final Map record = new HashMap<>(); - record.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); record.put( - BINARY_FIELD_NAME, - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); + BINARY_FIELD_NAME, + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); // Create DirectKmsMaterialsProvider - final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider(kmsClient, cmkArn); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider( + kmsClient, + cmkArn + ); // Create DynamoDBEncryptor final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); // Create EncryptionContext - final EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName(partitionKeyName) - .rangeKeyName(sortKeyName) - .build(); + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); // Configure EncryptionFlags for each attribute final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); - final EnumSet encryptAndSign = - EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); final Map> actions = new HashMap<>(); for (final String attributeName : record.keySet()) { @@ -82,42 +95,66 @@ public static void encryptRecord( // Encrypt the record final Map encrypted_record = - encryptor.encryptRecord(record, actions, encryptionContext); + encryptor.encryptRecord(record, actions, encryptionContext); // For demo, verify encryption assert encrypted_record.get(STRING_FIELD_NAME).b() != null; assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; - assert !record.get(BINARY_FIELD_NAME).b().equals(encrypted_record.get(BINARY_FIELD_NAME).b()); - assert record.get(IGNORED_FIELD_NAME).s().equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); + assert !record + .get(BINARY_FIELD_NAME) + .b() + .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(IGNORED_FIELD_NAME) + .s() + .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); // For demo, the encrypted item is put to DynamoDB. You skip this as needed for their use case. ddbClient.putItem( - PutItemRequest.builder().tableName(tableName).item(encrypted_record).build()); + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); // Get the item back from DynamoDB final Map keyToGet = new HashMap<>(); - keyToGet.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); - final GetItemResponse getResponse = - ddbClient.getItem( - GetItemRequest.builder().tableName(tableName).key(keyToGet).build()); + final GetItemResponse getResponse = ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ); // Decrypt the record retrieved from DynamoDB final Map decrypted_record = - encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); // For demo, verify decryption - assert record.get(STRING_FIELD_NAME).s().equals(decrypted_record.get(STRING_FIELD_NAME).s()); - assert record.get(NUMBER_FIELD_NAME).n().equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); - assert record.get(BINARY_FIELD_NAME).b().equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(STRING_FIELD_NAME) + .s() + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record + .get(NUMBER_FIELD_NAME) + .n() + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record + .get(BINARY_FIELD_NAME) + .b() + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); } public static void main(final String[] args) throws GeneralSecurityException { if (args.length < 6) { throw new IllegalArgumentException( - "To run this example, include tableName, cmkArn, partitionKeyName, sortKeyName, " - + "partitionKeyValue, sortKeyValue as args"); + "To run this example, include tableName, cmkArn, partitionKeyName, sortKeyName, " + + "partitionKeyValue, sortKeyValue as args" + ); } final String tableName = args[0]; final String cmkArn = args[1]; @@ -126,7 +163,16 @@ public static void main(final String[] args) throws GeneralSecurityException { final String partitionKeyValue = args[4]; final String sortKeyValue = args[5]; try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { - encryptRecord(ddbClient, KmsClient.create(), tableName, cmkArn, partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue); + encryptRecord( + ddbClient, + KmsClient.create(), + tableName, + cmkArn, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); } } } diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java index baa6cb076f..56c292b1a1 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java @@ -38,16 +38,23 @@ public static void main(String[] args) throws GeneralSecurityException { } public static void encryptRecord( - final DynamoDbClient ddbClient, - final String tableName, final String cmkArnEncrypt, final String cmkArnDecrypt) - throws GeneralSecurityException { - + final DynamoDbClient ddbClient, + final String tableName, + final String cmkArnEncrypt, + final String cmkArnDecrypt + ) throws GeneralSecurityException { // Extract regions from ARNs final String encryptRegion = cmkArnEncrypt.split(":")[3]; final String decryptRegion = cmkArnDecrypt.split(":")[3]; - final KmsClient kmsEncrypt = KmsClient.builder().region(Region.of(encryptRegion)).build(); - final KmsClient kmsDecrypt = KmsClient.builder().region(Region.of(decryptRegion)).build(); + final KmsClient kmsEncrypt = KmsClient + .builder() + .region(Region.of(encryptRegion)) + .build(); + final KmsClient kmsDecrypt = KmsClient + .builder() + .region(Region.of(decryptRegion)) + .build(); // Sample record to be encrypted final String partitionKeyName = "partition_key"; @@ -58,25 +65,33 @@ public static void encryptRecord( record.put("example", AttributeValue.builder().s("data").build()); record.put("some numbers", AttributeValue.builder().n("99").build()); record.put( - "and some binary", - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); + "and some binary", + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); record.put("leave me", AttributeValue.builder().s("alone").build()); // Set up encryptor with first region's KMS key final DirectKmsMaterialsProvider cmpEncrypt = - new DirectKmsMaterialsProvider(kmsEncrypt, cmkArnEncrypt); - final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmpEncrypt); - - final EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName(partitionKeyName) - .rangeKeyName(sortKeyName) - .build(); + new DirectKmsMaterialsProvider(kmsEncrypt, cmkArnEncrypt); + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance( + cmpEncrypt + ); + + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); - final EnumSet encryptAndSign = - EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); final Map> actions = new HashMap<>(); for (final String attributeName : record.keySet()) { switch (attributeName) { @@ -94,33 +109,53 @@ public static void encryptRecord( // Encrypt and save to table final Map encrypted_record = - encryptor.encryptRecord(record, actions, encryptionContext); + encryptor.encryptRecord(record, actions, encryptionContext); ddbClient.putItem( - PutItemRequest.builder().tableName(tableName).item(encrypted_record).build()); + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); // Set up decryptor using the replica key from second region final DirectKmsMaterialsProvider cmpDecrypt = - new DirectKmsMaterialsProvider(kmsDecrypt, cmkArnDecrypt); - final DynamoDBEncryptor decryptor = DynamoDBEncryptor.getInstance(cmpDecrypt); + new DirectKmsMaterialsProvider(kmsDecrypt, cmkArnDecrypt); + final DynamoDBEncryptor decryptor = DynamoDBEncryptor.getInstance( + cmpDecrypt + ); // Retrieve from same table final Map keyToGet = new HashMap<>(); - keyToGet.put(partitionKeyName, AttributeValue.builder().s("is this").build()); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s("is this").build() + ); keyToGet.put(sortKeyName, AttributeValue.builder().n("42").build()); - final Map encryptedItem = - ddbClient - .getItem(GetItemRequest.builder().tableName(tableName).key(keyToGet).build()) - .item(); + final Map encryptedItem = ddbClient + .getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ) + .item(); // Decrypt using replica key - demonstrates multi-region key capability final Map decrypted_record = - decryptor.decryptRecord(encryptedItem, actions, encryptionContext); + decryptor.decryptRecord(encryptedItem, actions, encryptionContext); // Verify decryption - assert record.get("example").s().equals(decrypted_record.get("example").s()); - assert record.get("some numbers").n().equals(decrypted_record.get("some numbers").n()); - assert record.get("and some binary").b().equals(decrypted_record.get("and some binary").b()); + assert record + .get("example") + .s() + .equals(decrypted_record.get("example").s()); + assert record + .get("some numbers") + .n() + .equals(decrypted_record.get("some numbers").n()); + assert record + .get("and some binary") + .b() + .equals(decrypted_record.get("and some binary").b()); ddbClient.close(); kmsEncrypt.close(); diff --git a/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java index 8d0be30007..f91ddf494e 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java @@ -25,59 +25,81 @@ * and the DirectKmsMaterialsProvider to encrypt your data. */ public class MostRecentEncryptedItem { + private static final String STRING_FIELD_NAME = "example"; private static final String BINARY_FIELD_NAME = "and some binary"; private static final String NUMBER_FIELD_NAME = "some numbers"; private static final String IGNORED_FIELD_NAME = "leave me"; public static void encryptRecord( - final DynamoDbClient ddbClient, - final KmsClient kmsClient, - final String tableName, - final String keyTableName, - final String cmkArn, - final String materialName, - final String partitionKeyName, - final String sortKeyName, - final String partitionKeyValue, - final String sortKeyValue) - throws GeneralSecurityException { + final DynamoDbClient ddbClient, + final KmsClient kmsClient, + final String tableName, + final String keyTableName, + final String cmkArn, + final String materialName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue + ) throws GeneralSecurityException { // Sample record to be encrypted final Map record = new HashMap<>(); - record.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); record.put( - BINARY_FIELD_NAME, - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); + BINARY_FIELD_NAME, + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); // Provider Configuration to protect the data keys - final DirectKmsMaterialsProvider kmsProv = new DirectKmsMaterialsProvider(kmsClient, cmkArn); - final DynamoDBEncryptor keyEncryptor = DynamoDBEncryptor.getInstance(kmsProv); - - final MetaStore metaStore = new MetaStore(ddbClient, keyTableName, keyEncryptor); + final DirectKmsMaterialsProvider kmsProv = new DirectKmsMaterialsProvider( + kmsClient, + cmkArn + ); + final DynamoDBEncryptor keyEncryptor = DynamoDBEncryptor.getInstance( + kmsProv + ); + + final MetaStore metaStore = new MetaStore( + ddbClient, + keyTableName, + keyEncryptor + ); // Provider configuration to protect the data - final CachingMostRecentProvider cmp = - new CachingMostRecentProvider(metaStore, materialName, 60_000); + final CachingMostRecentProvider cmp = new CachingMostRecentProvider( + metaStore, + materialName, + 60_000 + ); // Encryptor creation final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); // Information about the context of our data (normally just Table information) - final EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName(partitionKeyName) - .rangeKeyName(sortKeyName) - .build(); + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); // Describe what actions need to be taken for each attribute final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); - final EnumSet encryptAndSign = - EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); final Map> actions = new HashMap<>(); for (final String attributeName : record.keySet()) { @@ -96,40 +118,66 @@ public static void encryptRecord( // Encrypt the plaintext record directly final Map encrypted_record = - encryptor.encryptRecord(record, actions, encryptionContext); + encryptor.encryptRecord(record, actions, encryptionContext); // For demo, encrypted record fields change as expected assert encrypted_record.get(STRING_FIELD_NAME).b() != null; assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; - assert !record.get(BINARY_FIELD_NAME).b().equals(encrypted_record.get(BINARY_FIELD_NAME).b()); - assert record.get(IGNORED_FIELD_NAME).s().equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); + assert !record + .get(BINARY_FIELD_NAME) + .b() + .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(IGNORED_FIELD_NAME) + .s() + .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); // For demo, the encrypted item is put to DynamoDB. You can skip this as needed. - ddbClient.putItem(PutItemRequest.builder().tableName(tableName).item(encrypted_record).build()); + ddbClient.putItem( + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); // Get the item back from DynamoDB final Map keyToGet = new HashMap<>(); - keyToGet.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); - final GetItemResponse getResponse = - ddbClient.getItem(GetItemRequest.builder().tableName(tableName).key(keyToGet).build()); + final GetItemResponse getResponse = ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ); // Decryption is identical final Map decrypted_record = - encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); // For demo, the decrypted fields match the original fields before encryption - assert record.get(STRING_FIELD_NAME).s().equals(decrypted_record.get(STRING_FIELD_NAME).s()); - assert record.get(NUMBER_FIELD_NAME).n().equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); - assert record.get(BINARY_FIELD_NAME).b().equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(STRING_FIELD_NAME) + .s() + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record + .get(NUMBER_FIELD_NAME) + .n() + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record + .get(BINARY_FIELD_NAME) + .b() + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); } public static void main(final String[] args) throws GeneralSecurityException { if (args.length < 8) { throw new IllegalArgumentException( - "To run this example, include tableName, keyTableName, cmkArn, materialName, " - + "partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue as args"); + "To run this example, include tableName, keyTableName, cmkArn, materialName, " + + "partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue as args" + ); } final String tableName = args[0]; final String keyTableName = args[1]; @@ -141,16 +189,17 @@ public static void main(final String[] args) throws GeneralSecurityException { final String sortKeyValue = args[7]; try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { encryptRecord( - ddbClient, - KmsClient.create(), - tableName, - keyTableName, - cmkArn, - materialName, - partitionKeyName, - sortKeyName, - partitionKeyValue, - sortKeyValue); + ddbClient, + KmsClient.create(), + tableName, + keyTableName, + cmkArn, + materialName, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); } } } diff --git a/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java b/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java index 86b93a31a1..43fb0d6ab7 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java @@ -12,6 +12,9 @@ import software.amazon.awssdk.core.SdkBytes; 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.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; @@ -29,7 +32,18 @@ public class SymmetricEncryptedItem { private static final String IGNORED_FIELD_NAME = "leave me"; public static void main(String[] args) throws GeneralSecurityException { + if (args.length < 5) { + throw new IllegalArgumentException( + "To run this example, include tableName, partitionKeyName, sortKeyName, " + + "partitionKeyValue, sortKeyValue as args" + ); + } final String tableName = args[0]; + final String partitionKeyName = args[1]; + final String sortKeyName = args[2]; + final String partitionKeyValue = args[3]; + final String sortKeyValue = args[4]; + // Both AES and HMAC keys are just random bytes. // You should never use the same keys for encryption and signing/integrity. final SecureRandom secureRandom = new SecureRandom(); @@ -40,34 +54,56 @@ public static void main(String[] args) throws GeneralSecurityException { final SecretKey wrappingKey = new SecretKeySpec(rawAes, "AES"); final SecretKey signingKey = new SecretKeySpec(rawHmac, "HmacSHA256"); - final DynamoDbClient ddbClient = DynamoDbClient.create(); - encryptRecord(ddbClient, tableName, wrappingKey, signingKey); - ddbClient.close(); + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord( + ddbClient, + tableName, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue, + wrappingKey, + signingKey + ); + } } public static void encryptRecord( - DynamoDbClient ddbClient, String tableName, SecretKey wrappingKey, SecretKey signingKey) - throws GeneralSecurityException { + final DynamoDbClient ddbClient, + final String tableName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue, + final SecretKey wrappingKey, + final SecretKey signingKey + ) throws GeneralSecurityException { // Sample record to be encrypted - final String partitionKeyName = "partition_attribute"; - final String sortKeyName = "sort_attribute"; final Map record = new HashMap<>(); - record.put(partitionKeyName, AttributeValue.builder().s("is this").build()); - record.put(sortKeyName, AttributeValue.builder().n("55").build()); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); record.put( - BINARY_FIELD_NAME, - AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {0x00, 0x01, 0x02})).build()); - record.put( - IGNORED_FIELD_NAME, - AttributeValue.builder().s("alone").build()); // We want to ignore this attribute + BINARY_FIELD_NAME, + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); // We want to ignore this attribute // Set up our configuration and clients. All of this is thread-safe and can be reused across // calls. // Provider Configuration - final WrappedMaterialsProvider cmp = - new WrappedMaterialsProvider(wrappingKey, wrappingKey, signingKey); + final WrappedMaterialsProvider cmp = new WrappedMaterialsProvider( + wrappingKey, + wrappingKey, + signingKey + ); // While the wrappedMaterialsProvider is better as it uses a unique encryption key per record, // many existing systems use the SymmetricStaticProvider which always uses the same encryption // key. @@ -77,22 +113,24 @@ public static void encryptRecord( final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); // Information about the context of our data (normally just Table information) - final EncryptionContext encryptionContext = - EncryptionContext.builder() - .tableName(tableName) - .hashKeyName(partitionKeyName) - .rangeKeyName(sortKeyName) - .build(); + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); // Describe what actions need to be taken for each attribute final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); - final EnumSet encryptAndSign = - EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); final Map> actions = new HashMap<>(); for (final String attributeName : record.keySet()) { switch (attributeName) { - case partitionKeyName: // fall through - case sortKeyName: + case "partition_key": + case "sort_key": // Partition and sort keys must not be encrypted but should be signed actions.put(attributeName, signOnly); break; @@ -109,32 +147,57 @@ public static void encryptRecord( // Encrypt the plaintext record directly final Map encrypted_record = - encryptor.encryptRecord(record, actions, encryptionContext); + encryptor.encryptRecord(record, actions, encryptionContext); // Encrypted record fields change as expected - assert encrypted_record.get(STRING_FIELD_NAME).b() - != null; // the encrypted string is stored as bytes - assert encrypted_record.get(NUMBER_FIELD_NAME).b() - != null; // the encrypted number is stored as bytes + assert encrypted_record.get(STRING_FIELD_NAME).b() != null; // the encrypted string is stored as bytes + assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; // the encrypted number is stored as bytes assert !record - .get(BINARY_FIELD_NAME) - .b() - .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); // the encrypted bytes have updated + .get(BINARY_FIELD_NAME) + .b() + .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); // the encrypted bytes have updated assert record - .get(IGNORED_FIELD_NAME) - .s() - .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); // ignored field is left as is + .get(IGNORED_FIELD_NAME) + .s() + .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); // ignored field is left as is - // We could now put the encrypted item to DynamoDB just as we would any other item. - // We're skipping it to to keep the example simpler. + // For demo, the encrypted item is put to DynamoDB. You can skip this as needed for your use case. + ddbClient.putItem( + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); - // Decryption is identical. We'll pretend that we retrieved the record from DynamoDB. + // Get the item back from DynamoDB + final Map keyToGet = new HashMap<>(); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + + final GetItemResponse getResponse = ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ); + + // Decrypt the record retrieved from DynamoDB final Map decrypted_record = - encryptor.decryptRecord(encrypted_record, actions, encryptionContext); + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); // The decrypted fields match the original fields before encryption - assert record.get(STRING_FIELD_NAME).s().equals(decrypted_record.get(STRING_FIELD_NAME).s()); - assert record.get(NUMBER_FIELD_NAME).n().equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); - assert record.get(BINARY_FIELD_NAME).b().equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(STRING_FIELD_NAME) + .s() + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record + .get(NUMBER_FIELD_NAME) + .n() + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record + .get(BINARY_FIELD_NAME) + .b() + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); } } diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java index ce2c41c63c..1cf753d558 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java @@ -14,12 +14,13 @@ public void testAsymmetricEncryption() throws Exception { try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { AsymmetricEncryptedItem.encryptRecord( - ddbClient, - TestUtils.TEST_DDB_TABLE_NAME, - "partition_key", - "sort_key", - partitionKeyValue, - sortKeyValue); + ddbClient, + TestUtils.TEST_DDB_TABLE_NAME, + "partition_key", + "sort_key", + partitionKeyValue, + sortKeyValue + ); } } } diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java index f0c82f2e42..9bac668c6e 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java @@ -15,14 +15,15 @@ public void testAwsKmsEncryption() throws Exception { try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { AwsKmsEncryptedItem.encryptRecord( - ddbClient, - KmsClient.create(), - TestUtils.TEST_DDB_TABLE_NAME, - TestUtils.TEST_KMS_KEY_ID, - "partition_key", - "sort_key", - partitionKeyValue, - sortKeyValue); + ddbClient, + KmsClient.create(), + TestUtils.TEST_DDB_TABLE_NAME, + TestUtils.TEST_KMS_KEY_ID, + "partition_key", + "sort_key", + partitionKeyValue, + sortKeyValue + ); } } } diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java index aa3f17c07e..ac622e3651 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java @@ -9,9 +9,9 @@ public class AwsKmsMultiRegionKeyTest { // Multi-region KMS keys (MRKs) - same key replicated across regions private static final String MRK_US_EAST_1 = - "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; private static final String MRK_EU_WEST_1 = - "arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + "arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; @Test public void testMultiRegionEncryption() throws Exception { @@ -19,14 +19,23 @@ public void testMultiRegionEncryption() throws Exception { // This demonstrates MRK capability without needing a Global Table final String tableName = TestUtils.TEST_DDB_TABLE_NAME; try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { - AwsKmsMultiRegionKey.encryptRecord(ddbClient, tableName, MRK_US_EAST_1, MRK_EU_WEST_1); + AwsKmsMultiRegionKey.encryptRecord( + ddbClient, + tableName, + MRK_US_EAST_1, + MRK_EU_WEST_1 + ); } - } @AfterMethod public void cleanup() { TestUtils.cleanUpDDBItem( - TestUtils.TEST_DDB_TABLE_NAME, "partition_key", "sort_key", "is this", "42"); + TestUtils.TEST_DDB_TABLE_NAME, + "partition_key", + "sort_key", + "is this", + "42" + ); } } diff --git a/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java index e310bdeadb..b2cc7c0aeb 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java @@ -8,7 +8,8 @@ public class MostRecentEncryptedItemTest { - private static final String KEY_TABLE_NAME = "v2MostRecentKeyProviderPerfTestKeys"; + private static final String KEY_TABLE_NAME = + "v2MostRecentKeyProviderPerfTestKeys"; private static final String MATERIAL_NAME = "testMaterial"; @Test @@ -18,16 +19,17 @@ public void testMostRecentEncryption() throws Exception { try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { MostRecentEncryptedItem.encryptRecord( - ddbClient, - KmsClient.create(), - TestUtils.TEST_DDB_TABLE_NAME, - KEY_TABLE_NAME, - TestUtils.TEST_KMS_KEY_ID, - MATERIAL_NAME, - "partition_key", - "sort_key", - partitionKeyValue, - sortKeyValue); + ddbClient, + KmsClient.create(), + TestUtils.TEST_DDB_TABLE_NAME, + KEY_TABLE_NAME, + TestUtils.TEST_KMS_KEY_ID, + MATERIAL_NAME, + "partition_key", + "sort_key", + partitionKeyValue, + sortKeyValue + ); } } } diff --git a/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java index 56d630a76f..5040c9f610 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import java.security.SecureRandom; +import java.util.UUID; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.testng.annotations.Test; @@ -19,10 +20,20 @@ public void testSymmetricEncryption() throws Exception { secureRandom.nextBytes(rawHmac); final SecretKey wrappingKey = new SecretKeySpec(rawAes, "AES"); final SecretKey signingKey = new SecretKeySpec(rawHmac, "HmacSHA256"); + final String partitionKeyValue = "SymmetricExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { SymmetricEncryptedItem.encryptRecord( - ddbClient, TestUtils.TEST_DDB_TABLE_NAME, wrappingKey, signingKey); + ddbClient, + TestUtils.TEST_DDB_TABLE_NAME, + "partition_key", + "sort_key", + partitionKeyValue, + sortKeyValue, + wrappingKey, + signingKey + ); } } } diff --git a/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java b/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java index 44599bd400..1c6354f337 100644 --- a/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java +++ b/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java @@ -10,10 +10,11 @@ public class TestUtils { // These are public KMS Keys that MUST only be used for testing, and MUST NOT be used for any production data public static final String TEST_KMS_KEY_ID = - "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"; + "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"; + public static final String TEST_DDB_TABLE_NAME = + "DynamoDbEncryptionInterceptorTestTable"; /** * Deletes an item from a DynamoDB table. @@ -25,19 +26,29 @@ public class TestUtils { * @param sortKeyValue The value of the sort key (can be null if table doesn't have a sort key) */ public static void cleanUpDDBItem( - final String tableName, - final String partitionKeyName, - final String sortKeyName, - final String partitionKeyValue, - final String sortKeyValue) { + final String tableName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue + ) { final DynamoDbClient ddb = DynamoDbClient.builder().build(); final HashMap keyToDelete = new HashMap<>(); - keyToDelete.put(partitionKeyName, AttributeValue.builder().s(partitionKeyValue).build()); + keyToDelete.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); if (sortKeyValue != null) { - keyToDelete.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + keyToDelete.put( + sortKeyName, + AttributeValue.builder().n(sortKeyValue).build() + ); } - final DeleteItemRequest deleteRequest = - DeleteItemRequest.builder().tableName(tableName).key(keyToDelete).build(); + final DeleteItemRequest deleteRequest = DeleteItemRequest + .builder() + .tableName(tableName) + .key(keyToDelete) + .build(); ddb.deleteItem(deleteRequest); } } From 5ff66b08568a150dd71879a4456d287c69af3066 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 14:05:49 -0800 Subject: [PATCH 32/38] m --- .../src/main/java/AwsKmsMultiRegionKey.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java index 56c292b1a1..105b9ee31c 100644 --- a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java +++ b/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java @@ -27,6 +27,11 @@ */ public class AwsKmsMultiRegionKey { + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + public static void main(String[] args) throws GeneralSecurityException { final String tableName = args[0]; final String cmkArn1 = args[1]; @@ -62,16 +67,16 @@ public static void encryptRecord( final Map record = new HashMap<>(); record.put(partitionKeyName, AttributeValue.builder().s("is this").build()); record.put(sortKeyName, AttributeValue.builder().n("42").build()); - record.put("example", AttributeValue.builder().s("data").build()); - record.put("some numbers", AttributeValue.builder().n("99").build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); record.put( - "and some binary", + BINARY_FIELD_NAME, AttributeValue .builder() .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) .build() ); - record.put("leave me", AttributeValue.builder().s("alone").build()); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); // Set up encryptor with first region's KMS key final DirectKmsMaterialsProvider cmpEncrypt = @@ -99,7 +104,7 @@ public static void encryptRecord( case "sort_key": actions.put(attributeName, signOnly); break; - case "leave me": + case IGNORED_FIELD_NAME: break; default: actions.put(attributeName, encryptAndSign); @@ -145,17 +150,17 @@ public static void encryptRecord( // Verify decryption assert record - .get("example") + .get(STRING_FIELD_NAME) .s() - .equals(decrypted_record.get("example").s()); + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); assert record - .get("some numbers") + .get(NUMBER_FIELD_NAME) .n() - .equals(decrypted_record.get("some numbers").n()); + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); assert record - .get("and some binary") + .get(BINARY_FIELD_NAME) .b() - .equals(decrypted_record.get("and some binary").b()); + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); ddbClient.close(); kmsEncrypt.close(); From 9c7a6e37f0ebaafc0c1655de607519ad76ac1624 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 14:10:00 -0800 Subject: [PATCH 33/38] m --- Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/.gitignore | 0 Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/build.gradle.kts | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/gradlew | 0 .../src/main/java/AsymmetricEncryptedItem.java | 0 .../src/main/java/AwsKmsEncryptedItem.java | 0 .../src/main/java/AwsKmsMultiRegionKey.java | 0 .../src/main/java/MostRecentEncryptedItem.java | 0 .../src/main/java/SymmetricEncryptedItem.java | 0 .../src/test/java/AsymmetricEncryptedItemTest.java | 0 .../src/test/java/AwsKmsEncryptedItemTest.java | 0 .../src/test/java/AwsKmsMultiRegionKeyTest.java | 0 .../src/test/java/MostRecentEncryptedItemTest.java | 0 .../src/test/java/SymmetricEncryptedItemTest.java | 0 .../java/{DDBEC => DDBECwithSDKV2}/src/test/java/TestUtils.java | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/.gitignore (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/build.gradle.kts (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/gradle/wrapper/gradle-wrapper.properties (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/gradlew (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/main/java/AsymmetricEncryptedItem.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/main/java/AwsKmsEncryptedItem.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/main/java/AwsKmsMultiRegionKey.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/main/java/MostRecentEncryptedItem.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/main/java/SymmetricEncryptedItem.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/test/java/AsymmetricEncryptedItemTest.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/test/java/AwsKmsEncryptedItemTest.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/test/java/AwsKmsMultiRegionKeyTest.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/test/java/MostRecentEncryptedItemTest.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/test/java/SymmetricEncryptedItemTest.java (100%) rename Examples/runtimes/java/{DDBEC => DDBECwithSDKV2}/src/test/java/TestUtils.java (100%) diff --git a/Examples/runtimes/java/DDBEC/.gitignore b/Examples/runtimes/java/DDBECwithSDKV2/.gitignore similarity index 100% rename from Examples/runtimes/java/DDBEC/.gitignore rename to Examples/runtimes/java/DDBECwithSDKV2/.gitignore diff --git a/Examples/runtimes/java/DDBEC/build.gradle.kts b/Examples/runtimes/java/DDBECwithSDKV2/build.gradle.kts similarity index 100% rename from Examples/runtimes/java/DDBEC/build.gradle.kts rename to Examples/runtimes/java/DDBECwithSDKV2/build.gradle.kts diff --git a/Examples/runtimes/java/DDBEC/gradle/wrapper/gradle-wrapper.properties b/Examples/runtimes/java/DDBECwithSDKV2/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from Examples/runtimes/java/DDBEC/gradle/wrapper/gradle-wrapper.properties rename to Examples/runtimes/java/DDBECwithSDKV2/gradle/wrapper/gradle-wrapper.properties diff --git a/Examples/runtimes/java/DDBEC/gradlew b/Examples/runtimes/java/DDBECwithSDKV2/gradlew similarity index 100% rename from Examples/runtimes/java/DDBEC/gradlew rename to Examples/runtimes/java/DDBECwithSDKV2/gradlew diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AsymmetricEncryptedItem.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/main/java/AsymmetricEncryptedItem.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AsymmetricEncryptedItem.java diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsEncryptedItem.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/main/java/AwsKmsEncryptedItem.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsEncryptedItem.java diff --git a/Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsMultiRegionKey.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/main/java/AwsKmsMultiRegionKey.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsMultiRegionKey.java diff --git a/Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/MostRecentEncryptedItem.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/main/java/MostRecentEncryptedItem.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/main/java/MostRecentEncryptedItem.java diff --git a/Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/SymmetricEncryptedItem.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/main/java/SymmetricEncryptedItem.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/main/java/SymmetricEncryptedItem.java diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AsymmetricEncryptedItemTest.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/test/java/AsymmetricEncryptedItemTest.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AsymmetricEncryptedItemTest.java diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsEncryptedItemTest.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/test/java/AwsKmsEncryptedItemTest.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsEncryptedItemTest.java diff --git a/Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsMultiRegionKeyTest.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/test/java/AwsKmsMultiRegionKeyTest.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsMultiRegionKeyTest.java diff --git a/Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/MostRecentEncryptedItemTest.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/test/java/MostRecentEncryptedItemTest.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/test/java/MostRecentEncryptedItemTest.java diff --git a/Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/SymmetricEncryptedItemTest.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/test/java/SymmetricEncryptedItemTest.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/test/java/SymmetricEncryptedItemTest.java diff --git a/Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/TestUtils.java similarity index 100% rename from Examples/runtimes/java/DDBEC/src/test/java/TestUtils.java rename to Examples/runtimes/java/DDBECwithSDKV2/src/test/java/TestUtils.java From 396a7c1635fa13c46649eba2f1fc31fb0154f206 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 14:10:59 -0800 Subject: [PATCH 34/38] m --- .github/workflows/ci_examples_java.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci_examples_java.yml b/.github/workflows/ci_examples_java.yml index c5efc2f03c..97183a4274 100644 --- a/.github/workflows/ci_examples_java.yml +++ b/.github/workflows/ci_examples_java.yml @@ -26,6 +26,7 @@ on: jobs: testJava: strategy: + fail-fast: false matrix: java-version: [8, 11, 17, 19] os: [macos-14] @@ -106,3 +107,4 @@ jobs: # Run migration examples gradle -p runtimes/java/Migration/PlaintextToAWSDBE test gradle -p runtimes/java/Migration/DDBECToAWSDBE test + gradle -p runtimes/java/Migration/DDBEC test From d6b149b8fade17426ccd249ee165afe443265e9c Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Fri, 6 Feb 2026 14:26:31 -0800 Subject: [PATCH 35/38] m --- .github/workflows/ci_examples_java.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_examples_java.yml b/.github/workflows/ci_examples_java.yml index 97183a4274..3fcd6d5503 100644 --- a/.github/workflows/ci_examples_java.yml +++ b/.github/workflows/ci_examples_java.yml @@ -107,4 +107,4 @@ jobs: # Run migration examples gradle -p runtimes/java/Migration/PlaintextToAWSDBE test gradle -p runtimes/java/Migration/DDBECToAWSDBE test - gradle -p runtimes/java/Migration/DDBEC test + gradle -p runtimes/java/Migration/DDBECwithSDKV2 test From 070f2e48522c76283d87d7de0bd45922df29c0e0 Mon Sep 17 00:00:00 2001 From: Rishav karanjit Date: Sun, 8 Feb 2026 23:52:51 -0800 Subject: [PATCH 36/38] fix --- .github/workflows/ci_examples_java.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci_examples_java.yml b/.github/workflows/ci_examples_java.yml index 3fcd6d5503..65dd3d34e1 100644 --- a/.github/workflows/ci_examples_java.yml +++ b/.github/workflows/ci_examples_java.yml @@ -104,7 +104,7 @@ jobs: run: | # Run simple examples gradle -p runtimes/java/DynamoDbEncryption test + gradle -p runtimes/java/DDBECwithSDKV2 test # Run migration examples gradle -p runtimes/java/Migration/PlaintextToAWSDBE test gradle -p runtimes/java/Migration/DDBECToAWSDBE test - gradle -p runtimes/java/Migration/DDBECwithSDKV2 test From a5efa995cad363c8e4c35276cb517cb1425f56f5 Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Mon, 9 Feb 2026 10:47:01 -0800 Subject: [PATCH 37/38] formatting --- .../src/main/java/AwsKmsMultiRegionKey.java | 41 +++++++++++++++---- .../java/AsymmetricEncryptedItemTest.java | 24 ++++++++--- .../test/java/AwsKmsEncryptedItemTest.java | 24 ++++++++--- .../test/java/AwsKmsMultiRegionKeyTest.java | 19 ++++++--- .../java/MostRecentEncryptedItemTest.java | 23 ++++++++--- .../test/java/SymmetricEncryptedItemTest.java | 23 +++++++++-- 6 files changed, 122 insertions(+), 32 deletions(-) diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsMultiRegionKey.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsMultiRegionKey.java index 105b9ee31c..ced97398ff 100644 --- a/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsMultiRegionKey.java +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsMultiRegionKey.java @@ -33,12 +33,32 @@ public class AwsKmsMultiRegionKey { private static final String IGNORED_FIELD_NAME = "leave me"; public static void main(String[] args) throws GeneralSecurityException { + if (args.length < 5) { + throw new IllegalArgumentException( + "To run this example, include tableName, cmkArn1, cmkArn2, " + + "partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue as args" + ); + } + final String tableName = args[0]; final String cmkArn1 = args[1]; final String cmkArn2 = args[2]; + final String partitionKeyName = args[3]; + final String sortKeyName = args[4]; + final String partitionKeyValue = args[3]; + final String sortKeyValue = args[4]; try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { - encryptRecord(ddbClient, tableName, cmkArn1, cmkArn2); + encryptRecord( + ddbClient, + tableName, + cmkArn1, + cmkArn2, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); } } @@ -46,7 +66,11 @@ public static void encryptRecord( final DynamoDbClient ddbClient, final String tableName, final String cmkArnEncrypt, - final String cmkArnDecrypt + final String cmkArnDecrypt, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue ) throws GeneralSecurityException { // Extract regions from ARNs final String encryptRegion = cmkArnEncrypt.split(":")[3]; @@ -62,11 +86,12 @@ public static void encryptRecord( .build(); // Sample record to be encrypted - final String partitionKeyName = "partition_key"; - final String sortKeyName = "sort_key"; final Map record = new HashMap<>(); - record.put(partitionKeyName, AttributeValue.builder().s("is this").build()); - record.put(sortKeyName, AttributeValue.builder().n("42").build()); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); record.put( @@ -134,9 +159,9 @@ public static void encryptRecord( final Map keyToGet = new HashMap<>(); keyToGet.put( partitionKeyName, - AttributeValue.builder().s("is this").build() + AttributeValue.builder().s(partitionKeyValue).build() ); - keyToGet.put(sortKeyName, AttributeValue.builder().n("42").build()); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); final Map encryptedItem = ddbClient .getItem( diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AsymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AsymmetricEncryptedItemTest.java index 1cf753d558..46fb42d051 100644 --- a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AsymmetricEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AsymmetricEncryptedItemTest.java @@ -2,25 +2,39 @@ // SPDX-License-Identifier: Apache-2.0 import java.util.UUID; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; public class AsymmetricEncryptedItemTest { + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + final String partitionKeyValue = "AsymmetricExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + @Test public void testAsymmetricEncryption() throws Exception { - final String partitionKeyValue = "AsymmetricExample-" + UUID.randomUUID(); - final String sortKeyValue = "0"; - try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { AsymmetricEncryptedItem.encryptRecord( ddbClient, TestUtils.TEST_DDB_TABLE_NAME, - "partition_key", - "sort_key", + partitionKeyName, + sortKeyName, partitionKeyValue, sortKeyValue ); } } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + "0" + ); + } } diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsEncryptedItemTest.java index 9bac668c6e..3266f83f4a 100644 --- a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsEncryptedItemTest.java @@ -2,28 +2,42 @@ // SPDX-License-Identifier: Apache-2.0 import java.util.UUID; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.kms.KmsClient; public class AwsKmsEncryptedItemTest { + final String partitionKeyValue = "AwsKmsExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + @Test public void testAwsKmsEncryption() throws Exception { - final String partitionKeyValue = "AwsKmsExample-" + UUID.randomUUID(); - final String sortKeyValue = "0"; - try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { AwsKmsEncryptedItem.encryptRecord( ddbClient, KmsClient.create(), TestUtils.TEST_DDB_TABLE_NAME, TestUtils.TEST_KMS_KEY_ID, - "partition_key", - "sort_key", + partitionKeyName, + sortKeyName, partitionKeyValue, sortKeyValue ); } } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } } diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsMultiRegionKeyTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsMultiRegionKeyTest.java index ac622e3651..a78c55d9ee 100644 --- a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsMultiRegionKeyTest.java +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsMultiRegionKeyTest.java @@ -1,6 +1,7 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import java.util.UUID; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; @@ -12,6 +13,10 @@ public class AwsKmsMultiRegionKeyTest { "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; private static final String MRK_EU_WEST_1 = "arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + final String partitionKeyValue = "AwsKmsExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; @Test public void testMultiRegionEncryption() throws Exception { @@ -23,7 +28,11 @@ public void testMultiRegionEncryption() throws Exception { ddbClient, tableName, MRK_US_EAST_1, - MRK_EU_WEST_1 + MRK_EU_WEST_1, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue ); } } @@ -32,10 +41,10 @@ public void testMultiRegionEncryption() throws Exception { public void cleanup() { TestUtils.cleanUpDDBItem( TestUtils.TEST_DDB_TABLE_NAME, - "partition_key", - "sort_key", - "is this", - "42" + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue ); } } diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/MostRecentEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/MostRecentEncryptedItemTest.java index b2cc7c0aeb..bf08dbf55c 100644 --- a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/MostRecentEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/MostRecentEncryptedItemTest.java @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import java.util.UUID; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.kms.KmsClient; @@ -11,12 +12,13 @@ public class MostRecentEncryptedItemTest { private static final String KEY_TABLE_NAME = "v2MostRecentKeyProviderPerfTestKeys"; private static final String MATERIAL_NAME = "testMaterial"; + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + final String partitionKeyValue = "MostRecentExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; @Test public void testMostRecentEncryption() throws Exception { - final String partitionKeyValue = "MostRecentExample-" + UUID.randomUUID(); - final String sortKeyValue = "0"; - try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { MostRecentEncryptedItem.encryptRecord( ddbClient, @@ -25,11 +27,22 @@ public void testMostRecentEncryption() throws Exception { KEY_TABLE_NAME, TestUtils.TEST_KMS_KEY_ID, MATERIAL_NAME, - "partition_key", - "sort_key", + partitionKeyName, + sortKeyName, partitionKeyValue, sortKeyValue ); } } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } } diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/SymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/SymmetricEncryptedItemTest.java index 5040c9f610..0e4c5b621f 100644 --- a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/SymmetricEncryptedItemTest.java +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/SymmetricEncryptedItemTest.java @@ -5,11 +5,17 @@ import java.util.UUID; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; public class SymmetricEncryptedItemTest { + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + final String partitionKeyValue = "SymmetricExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + @Test public void testSymmetricEncryption() throws Exception { // Generate random keys @@ -20,15 +26,13 @@ public void testSymmetricEncryption() throws Exception { secureRandom.nextBytes(rawHmac); final SecretKey wrappingKey = new SecretKeySpec(rawAes, "AES"); final SecretKey signingKey = new SecretKeySpec(rawHmac, "HmacSHA256"); - final String partitionKeyValue = "SymmetricExample-" + UUID.randomUUID(); - final String sortKeyValue = "0"; try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { SymmetricEncryptedItem.encryptRecord( ddbClient, TestUtils.TEST_DDB_TABLE_NAME, - "partition_key", - "sort_key", + partitionKeyName, + sortKeyName, partitionKeyValue, sortKeyValue, wrappingKey, @@ -36,4 +40,15 @@ public void testSymmetricEncryption() throws Exception { ); } } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } } From b2d24144460a43ef2650cbee6cca770df7c9ea7b Mon Sep 17 00:00:00 2001 From: rishav-karanjit Date: Mon, 9 Feb 2026 13:48:39 -0800 Subject: [PATCH 38/38] 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<