> v1Actions) {
}
// Convert SDK V1 EncryptionContext to SDK V2
- private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext convertEncryptionContextV1ToV2(
+ private static com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext convertEncryptionContextV1ToV2(
final EncryptionContext v1Context
) {
- final software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.Builder builder =
- software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext
+ final com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext.Builder builder =
+ com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext
.builder()
.tableName(v1Context.getTableName())
.hashKeyName(v1Context.getHashKeyName())
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 41622bd9f4..a61ef6a11d 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,11 +1,11 @@
package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.DynamoDBEncryptor;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.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 {
diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/DelegatedKey.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/DelegatedKey.java
new file mode 100644
index 0000000000..6955c410a9
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/DelegatedKey.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/DynamoDBEncryptor.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/DynamoDBEncryptor.java
new file mode 100644
index 0000000000..1a560caafe
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/DynamoDBEncryptor.java
@@ -0,0 +1,775 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.exceptions.DynamoDbEncryptionException;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.DecryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.EncryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers.EncryptionMaterialsProvider;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.utils.EncryptionContextOperators;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.AttributeValueMarshaller;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.ByteBufferInputStream;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.Utils;
+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.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor;
+
+/**
+ * The low-level API for performing crypto operations on the record attributes.
+ *
+ * @author Greg Rubin
+ */
+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*";
+ 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<
+ EncryptionContext,
+ EncryptionContext
+ > 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<
+ String,
+ Set
+ >();
+
+ 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<
+ String,
+ Set
+ >();
+ 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<
+ EncryptionContext,
+ EncryptionContext
+ > 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<
+ EncryptionContext,
+ EncryptionContext
+ > 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
+ */
+ public 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
+ */
+ public 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<
+ EncryptionContext,
+ EncryptionContext
+ > encryptionContextOverrideOperator
+ ) {
+ this.encryptionContextOverrideOperator = encryptionContextOverrideOperator;
+ }
+
+ /**
+ * @return the operator used to override the EncryptionContext
+ * @see #setEncryptionContextOverrideOperator(Function)
+ */
+ private Function<
+ EncryptionContext,
+ EncryptionContext
+ > 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/DynamoDbSigner.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/DynamoDbSigner.java
new file mode 100644
index 0000000000..d4ae37c717
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/DynamoDbSigner.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.AttributeValueMarshaller;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.Utils;
+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.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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/EncryptionContext.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/EncryptionContext.java
new file mode 100644
index 0000000000..a850465623
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/EncryptionContext.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.DecryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.EncryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers.EncryptionMaterialsProvider;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/EncryptionFlags.java
similarity index 83%
rename from DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java
rename to DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/EncryptionFlags.java
index 47329f7128..8611598a79 100644
--- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionFlags.java
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/EncryptionFlags.java
@@ -12,12 +12,12 @@
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption;
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption;
/**
- * @author Greg Rubin
+ * @author Greg Rubin
*/
public enum EncryptionFlags {
- ENCRYPT,
- SIGN
+ 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/exceptions/DynamoDbEncryptionException.java
similarity index 51%
rename from DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java
rename to DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/exceptions/DynamoDbEncryptionException.java
index f245d66e31..20678afdfa 100644
--- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/exceptions/DynamoDbEncryptionException.java
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/exceptions/DynamoDbEncryptionException.java
@@ -12,36 +12,37 @@
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions;
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.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);
- }
+ private static final long serialVersionUID = -7565904179772520868L;
- /**
- * Standard constructor
- * @param message exception message
- */
- public DynamoDbEncryptionException(String message) {
- super(message);
- }
+ /**
+ * Standard constructor
+ * @param cause exception cause
+ */
+ public DynamoDbEncryptionException(Throwable cause) {
+ super(cause);
+ }
- /**
- * Standard constructor
- * @param message exception message
- * @param cause exception cause
- */
- public DynamoDbEncryptionException(String message, Throwable cause) {
- super(message, 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/AbstractRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/AbstractRawMaterials.java
new file mode 100644
index 0000000000..ee7cd0ae8c
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/AbstractRawMaterials.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/AsymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/AsymmetricRawMaterials.java
new file mode 100644
index 0000000000..0b5c350649
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/AsymmetricRawMaterials.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/CryptographicMaterials.java
similarity index 80%
rename from DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java
rename to DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/CryptographicMaterials.java
index 033d331f5b..5307e39db7 100644
--- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/CryptographicMaterials.java
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/CryptographicMaterials.java
@@ -12,13 +12,13 @@
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials;
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials;
import java.util.Map;
/**
- * @author Greg Rubin
+ * @author Greg Rubin
*/
public interface CryptographicMaterials {
- Map getMaterialDescription();
+ 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/DecryptionMaterials.java
similarity index 80%
rename from DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java
rename to DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/DecryptionMaterials.java
index 00f8548bc7..ad35a778b9 100644
--- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/DecryptionMaterials.java
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/DecryptionMaterials.java
@@ -12,16 +12,15 @@
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials;
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials;
import java.security.Key;
-
import javax.crypto.SecretKey;
/**
- * @author Greg Rubin
+ * @author Greg Rubin
*/
public interface DecryptionMaterials extends CryptographicMaterials {
- SecretKey getDecryptionKey();
- Key getVerificationKey();
+ 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/EncryptionMaterials.java
similarity index 81%
rename from DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java
rename to DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/EncryptionMaterials.java
index ecef9e9fc8..5acb5eed6f 100644
--- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/materials/EncryptionMaterials.java
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/EncryptionMaterials.java
@@ -12,16 +12,15 @@
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials;
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials;
import java.security.Key;
-
import javax.crypto.SecretKey;
/**
- * @author Greg Rubin
+ * @author Greg Rubin
*/
public interface EncryptionMaterials extends CryptographicMaterials {
- SecretKey getEncryptionKey();
- Key getSigningKey();
+ SecretKey getEncryptionKey();
+ Key getSigningKey();
}
diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/SymmetricRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/SymmetricRawMaterials.java
new file mode 100644
index 0000000000..c0fa8e9dce
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/SymmetricRawMaterials.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/WrappedRawMaterials.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/WrappedRawMaterials.java
new file mode 100644
index 0000000000..de42899ad4
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/materials/WrappedRawMaterials.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.DelegatedKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.Base64;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.Utils;
+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;
+
+/**
+ * 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/AsymmetricStaticProvider.java
similarity index 51%
rename from DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java
rename to DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/AsymmetricStaticProvider.java
index b49e2b9a20..e6588f793d 100644
--- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/AsymmetricStaticProvider.java
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/AsymmetricStaticProvider.java
@@ -12,35 +12,53 @@
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-package software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers;
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.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
+ *
+ * @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);
- }
+ 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/CachingMostRecentProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/CachingMostRecentProvider.java
new file mode 100644
index 0000000000..ffd311bdda
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/CachingMostRecentProvider.java
@@ -0,0 +1,199 @@
+// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.DecryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.EncryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers.store.ProviderStore;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.TTLCache;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.TTLCache.EntryLoader;
+import io.netty.util.internal.ObjectUtil;
+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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/DirectKmsMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/DirectKmsMaterialsProvider.java
new file mode 100644
index 0000000000..979ab9a736
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/DirectKmsMaterialsProvider.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers;
+
+import static com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.WrappedRawMaterials.CONTENT_KEY_ALGORITHM;
+import static com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.WrappedRawMaterials.ENVELOPE_KEY;
+import static com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.WrappedRawMaterials.KEY_WRAPPING_ALGORITHM;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.exceptions.DynamoDbEncryptionException;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.DecryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.EncryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.SymmetricRawMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.WrappedRawMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.Base64;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.Hkdf;
+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.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
+ );
+
+ final String envelopeKey = materialDescription.get(ENVELOPE_KEY);
+
+ // DDBEC with SDK v1 does not do this check and returns NPE
+ // DDBEC with SDK v2 does not return NPE but return DynamoDbEncryptionException
+ if (envelopeKey == null) {
+ throw new DynamoDbEncryptionException(
+ "Missing " +
+ ENVELOPE_KEY +
+ " in material description. " +
+ "This item may have been encrypted with a different encryption format."
+ );
+ }
+
+ ec.put("*" + CONTENT_KEY_ALGORITHM + "*", providedEncAlg);
+ ec.put("*" + SIGNING_KEY_ALGORITHM + "*", providedSigAlg);
+
+ populateKmsEcFromEc(context, ec);
+
+ DecryptRequest.Builder request = DecryptRequest.builder();
+ request.ciphertextBlob(SdkBytes.fromByteArray(Base64.decode(envelopeKey)));
+ 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/EncryptionMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/EncryptionMaterialsProvider.java
new file mode 100644
index 0000000000..576eb444cc
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/EncryptionMaterialsProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.DecryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/KeyStoreMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/KeyStoreMaterialsProvider.java
new file mode 100644
index 0000000000..396ef965e8
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/KeyStoreMaterialsProvider.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.exceptions.DynamoDbEncryptionException;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.AsymmetricRawMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.DecryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.EncryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.SymmetricRawMaterials;
+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;
+
+/**
+ * @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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/SymmetricStaticProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/SymmetricStaticProvider.java
new file mode 100644
index 0000000000..ccdd530817
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/SymmetricStaticProvider.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.CryptographicMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.DecryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.EncryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.SymmetricRawMaterials;
+import java.security.KeyPair;
+import java.util.Collections;
+import java.util.Map;
+import javax.crypto.SecretKey;
+
+/**
+ * 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/WrappedMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/WrappedMaterialsProvider.java
new file mode 100644
index 0000000000..5e5a10cfa2
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/WrappedMaterialsProvider.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2014-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.exceptions.DynamoDbEncryptionException;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.CryptographicMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.DecryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.EncryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.materials.WrappedRawMaterials;
+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;
+
+/**
+ * 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/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/store/MetaStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/store/MetaStore.java
new file mode 100644
index 0000000000..5fe8d3e3b2
--- /dev/null
+++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/com/amazonaws/services/dynamodbv2/datamodeling/sdkv2/encryption/providers/store/MetaStore.java
@@ -0,0 +1,589 @@
+/*
+ * 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 com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers.store;
+
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.DynamoDBEncryptor;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.EncryptionContext;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers.EncryptionMaterialsProvider;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.encryption.providers.WrappedMaterialsProvider;
+import com.amazonaws.services.dynamodbv2.datamodeling.sdkv2.internal.Utils;
+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;
+
+/**
+ * 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