();
- keySchema.add(new KeySchemaElement().withAttributeName(partitionName).withKeyType(KeyType.HASH));
- keySchema.add(new KeySchemaElement().withAttributeName(sortName).withKeyType(KeyType.RANGE));
-
- ddb.createTable(new CreateTableRequest().withTableName(tableName)
- .withAttributeDefinitions(attrDef)
- .withKeySchema(keySchema)
- .withProvisionedThroughput(new ProvisionedThroughput(100L, 100L)));
- }
+ private TestUtils() {
+ throw new UnsupportedOperationException(
+ "This class exists to hold static resources and cannot be instantiated.");
+ }
+
+ /**
+ * These special test keys have been configured to allow Encrypt, Decrypt, and GenerateDataKey
+ * operations from any AWS principal and should be used when adding new KMS tests.
+ *
+ * This should go without saying, but never use these keys for production purposes (as anyone
+ * in the world can decrypt data encrypted using them).
+ */
+ public static final String US_WEST_2_KEY_ID =
+ "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f";
+
+ public static final String US_WEST_2 = "us-west-2";
+
+ public static void createDDBTable(
+ AmazonDynamoDB ddb, String tableName, String partitionName, String sortName) {
+ ArrayList attrDef = new ArrayList();
+ attrDef.add(
+ new AttributeDefinition()
+ .withAttributeName(partitionName)
+ .withAttributeType(ScalarAttributeType.S));
+ attrDef.add(
+ new AttributeDefinition()
+ .withAttributeName(sortName)
+ .withAttributeType(ScalarAttributeType.N));
+
+ ArrayList keySchema = new ArrayList();
+ keySchema.add(
+ new KeySchemaElement().withAttributeName(partitionName).withKeyType(KeyType.HASH));
+ keySchema.add(new KeySchemaElement().withAttributeName(sortName).withKeyType(KeyType.RANGE));
+
+ ddb.createTable(
+ new CreateTableRequest()
+ .withTableName(tableName)
+ .withAttributeDefinitions(attrDef)
+ .withKeySchema(keySchema)
+ .withProvisionedThroughput(new ProvisionedThroughput(100L, 100L)));
+ }
}
diff --git a/pom.xml b/pom.xml
index 996908ac..cd60b981 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
4.0.0
software.amazon.cryptools
dynamodbencryptionclient-pom
- 1.15.1
+ 1.15.2
pom
aws-dynamodb-encryption-java :: POM
diff --git a/sdk1/pom.xml b/sdk1/pom.xml
index f6f6e949..6f489a82 100644
--- a/sdk1/pom.xml
+++ b/sdk1/pom.xml
@@ -6,7 +6,7 @@
4.0.0
com.amazonaws
aws-dynamodb-encryption-java
- 1.15.1
+ 1.15.2
jar
aws-dynamodb-encryption-java :: SDK1
AWS DynamoDB Encryption Client for AWS Java SDK v1
diff --git a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/AttributeEncryptor.java b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/AttributeEncryptor.java
index 146e77d2..b7f367db 100644
--- a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/AttributeEncryptor.java
+++ b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/AttributeEncryptor.java
@@ -14,14 +14,6 @@
*/
package com.amazonaws.services.dynamodbv2.datamodeling;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.SaveBehavior;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingsRegistry.Mapping;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingsRegistry.Mappings;
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DoNotEncrypt;
@@ -33,255 +25,256 @@
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.TableAadOverride;
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.EncryptionMaterialsProvider;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
- * Encrypts all non-key fields prior to storing them in DynamoDB.
- * This must be used with @{link SaveBehavior#PUT} or @{link SaveBehavior#CLOBBER}.
- *
- * @author Greg Rubin
+ * Encrypts all non-key fields prior to storing them in DynamoDB. This must be used with @{link
+ * SaveBehavior#PUT} or @{link SaveBehavior#CLOBBER}.
+ *
+ * @author Greg Rubin
*/
public class AttributeEncryptor implements AttributeTransformer {
- private static final Log LOG = LogFactory.getLog(AttributeEncryptor.class);
- private final DynamoDBEncryptor encryptor;
- private final Map, ModelClassMetadata> metadataCache = new ConcurrentHashMap<>();
-
- public AttributeEncryptor(final DynamoDBEncryptor encryptor) {
- this.encryptor = encryptor;
+ private static final Log LOG = LogFactory.getLog(AttributeEncryptor.class);
+ private final DynamoDBEncryptor encryptor;
+ private final Map, ModelClassMetadata> metadataCache = new ConcurrentHashMap<>();
+
+ public AttributeEncryptor(final DynamoDBEncryptor encryptor) {
+ this.encryptor = encryptor;
+ }
+
+ public AttributeEncryptor(final EncryptionMaterialsProvider encryptionMaterialsProvider) {
+ encryptor = DynamoDBEncryptor.getInstance(encryptionMaterialsProvider);
+ }
+
+ public DynamoDBEncryptor getEncryptor() {
+ return encryptor;
+ }
+
+ @Override
+ public Map transform(final Parameters> parameters) {
+ // one map of attributeFlags per model class
+ final ModelClassMetadata metadata = getModelClassMetadata(parameters);
+
+ final Map attributeValues = parameters.getAttributeValues();
+ // If this class is marked as "DoNotTouch" then we know our encryptor will not change it at all
+ // so we may as well fast-return and do nothing. This also avoids emitting errors when they
+ // would not apply.
+ if (metadata.doNotTouch) {
+ return attributeValues;
}
- public AttributeEncryptor(final EncryptionMaterialsProvider encryptionMaterialsProvider) {
- encryptor = DynamoDBEncryptor.getInstance(encryptionMaterialsProvider);
+ // When AttributeEncryptor is used without SaveBehavior.PUT or CLOBBER, it is trying to
+ // transform only a subset
+ // of the actual fields stored in DynamoDB. This means that the generated signature will not
+ // cover any
+ // unmodified fields. Thus, upon untransform, the signature verification will fail as it won't
+ // cover all
+ // expected fields.
+ if (parameters.isPartialUpdate()) {
+ throw new DynamoDBMappingException(
+ "Use of AttributeEncryptor without SaveBehavior.PUT or SaveBehavior.CLOBBER is an error "
+ + "and can result in data-corruption. This occured while trying to save "
+ + parameters.getModelClass());
}
- public DynamoDBEncryptor getEncryptor() {
- return encryptor;
+ try {
+ return encryptor.encryptRecord(
+ attributeValues, metadata.getEncryptionFlags(), paramsToContext(parameters));
+ } catch (Exception ex) {
+ throw new DynamoDBMappingException(ex);
}
+ }
- @Override
- public Map transform(final Parameters> parameters) {
- // one map of attributeFlags per model class
- final ModelClassMetadata metadata = getModelClassMetadata(parameters);
-
- final Map attributeValues = parameters.getAttributeValues();
- // If this class is marked as "DoNotTouch" then we know our encryptor will not change it at all
- // so we may as well fast-return and do nothing. This also avoids emitting errors when they would not apply.
- if (metadata.doNotTouch) {
- return attributeValues;
- }
-
- // When AttributeEncryptor is used without SaveBehavior.PUT or CLOBBER, it is trying to transform only a subset
- // of the actual fields stored in DynamoDB. This means that the generated signature will not cover any
- // unmodified fields. Thus, upon untransform, the signature verification will fail as it won't cover all
- // expected fields.
- if (parameters.isPartialUpdate()) {
- throw new DynamoDBMappingException(
- "Use of AttributeEncryptor without SaveBehavior.PUT or SaveBehavior.CLOBBER is an error " +
- "and can result in data-corruption. This occured while trying to save " +
- parameters.getModelClass());
- }
+ @Override
+ public Map untransform(final Parameters> parameters) {
+ final Map> attributeFlags = getEncryptionFlags(parameters);
- try {
- return encryptor.encryptRecord(
- attributeValues,
- metadata.getEncryptionFlags(),
- paramsToContext(parameters));
- } catch (Exception ex) {
- throw new DynamoDBMappingException(ex);
- }
+ try {
+ return encryptor.decryptRecord(
+ parameters.getAttributeValues(), attributeFlags, paramsToContext(parameters));
+ } catch (Exception ex) {
+ throw new DynamoDBMappingException(ex);
}
-
- @Override
- public Map untransform(final Parameters> parameters) {
- final Map> attributeFlags = getEncryptionFlags(parameters);
-
- try {
- return encryptor.decryptRecord(
- parameters.getAttributeValues(),
- attributeFlags,
- paramsToContext(parameters));
- } catch (Exception ex) {
- throw new DynamoDBMappingException(ex);
- }
+ }
+
+ /*
+ * For any attributes we see from DynamoDB that aren't modeled in the mapper class,
+ * we either ignore them (the default behavior), or include them for encryption/signing
+ * based on the presence of the @HandleUnknownAttributes annotation (unless the class
+ * has @DoNotTouch, then we don't include them).
+ */
+ private Map> getEncryptionFlags(final Parameters> parameters) {
+ final ModelClassMetadata metadata = getModelClassMetadata(parameters);
+
+ // If the class is annotated with @DoNotTouch, then none of the attributes are
+ // encrypted or signed, so we don't need to bother looking for unknown attributes.
+ if (metadata.getDoNotTouch()) {
+ return metadata.getEncryptionFlags();
}
- /*
- * For any attributes we see from DynamoDB that aren't modeled in the mapper class,
- * we either ignore them (the default behavior), or include them for encryption/signing
- * based on the presence of the @HandleUnknownAttributes annotation (unless the class
- * has @DoNotTouch, then we don't include them).
- */
- private Map> getEncryptionFlags(final Parameters> parameters) {
- final ModelClassMetadata metadata = getModelClassMetadata(parameters);
-
- // If the class is annotated with @DoNotTouch, then none of the attributes are
- // encrypted or signed, so we don't need to bother looking for unknown attributes.
- if (metadata.getDoNotTouch()) {
- return metadata.getEncryptionFlags();
- }
+ final Set unknownAttributeBehavior = metadata.getUnknownAttributeBehavior();
+ final Map> attributeFlags = new HashMap<>();
+ attributeFlags.putAll(metadata.getEncryptionFlags());
- final Set unknownAttributeBehavior = metadata.getUnknownAttributeBehavior();
- final Map> attributeFlags = new HashMap<>();
- attributeFlags.putAll(metadata.getEncryptionFlags());
-
- for (final String attributeName : parameters.getAttributeValues().keySet()) {
- if (!attributeFlags.containsKey(attributeName) &&
- !encryptor.getSignatureFieldName().equals(attributeName) &&
- !encryptor.getMaterialDescriptionFieldName().equals(attributeName)) {
+ for (final String attributeName : parameters.getAttributeValues().keySet()) {
+ if (!attributeFlags.containsKey(attributeName)
+ && !encryptor.getSignatureFieldName().equals(attributeName)
+ && !encryptor.getMaterialDescriptionFieldName().equals(attributeName)) {
- attributeFlags.put(attributeName, unknownAttributeBehavior);
- }
- }
-
- return attributeFlags;
+ attributeFlags.put(attributeName, unknownAttributeBehavior);
+ }
}
- private ModelClassMetadata getModelClassMetadata(Parameters parameters) {
- // Due to the lack of explicit synchronization, it is possible that
- // elements in the cache will be added multiple times. Since they will
- // all be identical, this is okay. Avoiding explicit synchronization
- // means that in the general (retrieval) case, should never block and
- // should be extremely fast.
- final Class clazz = parameters.getModelClass();
- ModelClassMetadata metadata = metadataCache.get(clazz);
-
- if (metadata == null) {
- Map> attributeFlags = new HashMap<>();
-
- final boolean handleUnknownAttributes = handleUnknownAttributes(clazz);
- final EnumSet unknownAttributeBehavior = EnumSet.noneOf(EncryptionFlags.class);
-
- if (shouldTouch(clazz)) {
- Mappings mappings = DynamoDBMappingsRegistry.instance().mappingsOf(clazz);
-
- for (Mapping mapping : mappings.getMappings()) {
- final EnumSet flags = EnumSet.noneOf(EncryptionFlags.class);
- StandardAnnotationMaps.FieldMap> fieldMap = StandardAnnotationMaps.of(mapping.getter(), null);
- if (shouldTouch(fieldMap)) {
- if (shouldEncryptAttribute(clazz, mapping, fieldMap)) {
- flags.add(EncryptionFlags.ENCRYPT);
- }
- flags.add(EncryptionFlags.SIGN);
- }
- attributeFlags.put(mapping.getAttributeName(), Collections.unmodifiableSet(flags));
- }
-
- if (handleUnknownAttributes) {
- unknownAttributeBehavior.add(EncryptionFlags.SIGN);
-
- if (shouldEncrypt(clazz)) {
- unknownAttributeBehavior.add(EncryptionFlags.ENCRYPT);
- }
- }
+ return attributeFlags;
+ }
+
+ private ModelClassMetadata getModelClassMetadata(Parameters parameters) {
+ // Due to the lack of explicit synchronization, it is possible that
+ // elements in the cache will be added multiple times. Since they will
+ // all be identical, this is okay. Avoiding explicit synchronization
+ // means that in the general (retrieval) case, should never block and
+ // should be extremely fast.
+ final Class clazz = parameters.getModelClass();
+ ModelClassMetadata metadata = metadataCache.get(clazz);
+
+ if (metadata == null) {
+ Map> attributeFlags = new HashMap<>();
+
+ final boolean handleUnknownAttributes = handleUnknownAttributes(clazz);
+ final EnumSet unknownAttributeBehavior =
+ EnumSet.noneOf(EncryptionFlags.class);
+
+ if (shouldTouch(clazz)) {
+ Mappings mappings = DynamoDBMappingsRegistry.instance().mappingsOf(clazz);
+
+ for (Mapping mapping : mappings.getMappings()) {
+ final EnumSet flags = EnumSet.noneOf(EncryptionFlags.class);
+ StandardAnnotationMaps.FieldMap> fieldMap =
+ StandardAnnotationMaps.of(mapping.getter(), null);
+ if (shouldTouch(fieldMap)) {
+ if (shouldEncryptAttribute(clazz, mapping, fieldMap)) {
+ flags.add(EncryptionFlags.ENCRYPT);
}
-
- metadata = new ModelClassMetadata(Collections.unmodifiableMap(attributeFlags), doNotTouch(clazz),
- Collections.unmodifiableSet(unknownAttributeBehavior));
- metadataCache.put(clazz, metadata);
+ flags.add(EncryptionFlags.SIGN);
+ }
+ attributeFlags.put(mapping.getAttributeName(), Collections.unmodifiableSet(flags));
}
- return metadata;
- }
-
- /**
- * @return True if {@link DoNotTouch} is not present on the class level. False otherwise
- */
- private boolean shouldTouch(Class> clazz) {
- return !doNotTouch(clazz);
- }
-
- /**
- * @return True if {@link DoNotTouch} is not present on the getter level. False otherwise.
- */
- private boolean shouldTouch(StandardAnnotationMaps.FieldMap> fieldMap) {
- return !doNotTouch(fieldMap);
- }
-
- /**
- * @return True if {@link DoNotTouch} IS present on the class level. False otherwise.
- */
- private boolean doNotTouch(Class> clazz) {
- return clazz.isAnnotationPresent(DoNotTouch.class);
- }
-
- /**
- * @return True if {@link DoNotTouch} IS present on the getter level. False otherwise.
- */
- private boolean doNotTouch(StandardAnnotationMaps.FieldMap> fieldMap) {
- return fieldMap.actualOf(DoNotTouch.class) != null;
- }
-
- /**
- * @return True if {@link DoNotEncrypt} is NOT present on the class level. False otherwise.
- */
- private boolean shouldEncrypt(Class> clazz) {
- return !doNotEncrypt(clazz);
- }
- /**
- * @return True if {@link DoNotEncrypt} IS present on the class level. False otherwise.
- */
- private boolean doNotEncrypt(Class> clazz) {
- return clazz.isAnnotationPresent(DoNotEncrypt.class);
- }
+ if (handleUnknownAttributes) {
+ unknownAttributeBehavior.add(EncryptionFlags.SIGN);
- /**
- * @return True if {@link DoNotEncrypt} IS present on the getter level. False otherwise.
- */
- private boolean doNotEncrypt(StandardAnnotationMaps.FieldMap> fieldMap) {
- return fieldMap.actualOf(DoNotEncrypt.class) != null;
+ if (shouldEncrypt(clazz)) {
+ unknownAttributeBehavior.add(EncryptionFlags.ENCRYPT);
+ }
+ }
+ }
+
+ metadata =
+ new ModelClassMetadata(
+ Collections.unmodifiableMap(attributeFlags),
+ doNotTouch(clazz),
+ Collections.unmodifiableSet(unknownAttributeBehavior));
+ metadataCache.put(clazz, metadata);
}
-
- /**
- * @return True if the attribute should be encrypted, false otherwise.
- */
- private boolean shouldEncryptAttribute(
- final Class> clazz,
- final Mapping mapping,
- final StandardAnnotationMaps.FieldMap> fieldMap) {
-
- return !(doNotEncrypt(clazz) || doNotEncrypt(fieldMap) || mapping.isPrimaryKey() || mapping.isVersion());
+ return metadata;
+ }
+
+ /** @return True if {@link DoNotTouch} is not present on the class level. False otherwise */
+ private boolean shouldTouch(Class> clazz) {
+ return !doNotTouch(clazz);
+ }
+
+ /** @return True if {@link DoNotTouch} is not present on the getter level. False otherwise. */
+ private boolean shouldTouch(StandardAnnotationMaps.FieldMap> fieldMap) {
+ return !doNotTouch(fieldMap);
+ }
+
+ /** @return True if {@link DoNotTouch} IS present on the class level. False otherwise. */
+ private boolean doNotTouch(Class> clazz) {
+ return clazz.isAnnotationPresent(DoNotTouch.class);
+ }
+
+ /** @return True if {@link DoNotTouch} IS present on the getter level. False otherwise. */
+ private boolean doNotTouch(StandardAnnotationMaps.FieldMap> fieldMap) {
+ return fieldMap.actualOf(DoNotTouch.class) != null;
+ }
+
+ /** @return True if {@link DoNotEncrypt} is NOT present on the class level. False otherwise. */
+ private boolean shouldEncrypt(Class> clazz) {
+ return !doNotEncrypt(clazz);
+ }
+
+ /** @return True if {@link DoNotEncrypt} IS present on the class level. False otherwise. */
+ private boolean doNotEncrypt(Class> clazz) {
+ return clazz.isAnnotationPresent(DoNotEncrypt.class);
+ }
+
+ /** @return True if {@link DoNotEncrypt} IS present on the getter level. False otherwise. */
+ private boolean doNotEncrypt(StandardAnnotationMaps.FieldMap> fieldMap) {
+ return fieldMap.actualOf(DoNotEncrypt.class) != null;
+ }
+
+ /** @return True if the attribute should be encrypted, false otherwise. */
+ private boolean shouldEncryptAttribute(
+ final Class> clazz,
+ final Mapping mapping,
+ final StandardAnnotationMaps.FieldMap> fieldMap) {
+
+ return !(doNotEncrypt(clazz)
+ || doNotEncrypt(fieldMap)
+ || mapping.isPrimaryKey()
+ || mapping.isVersion());
+ }
+
+ private static EncryptionContext paramsToContext(Parameters> params) {
+ final Class> clazz = params.getModelClass();
+ final TableAadOverride override = clazz.getAnnotation(TableAadOverride.class);
+ final String tableName = ((override == null) ? params.getTableName() : override.tableName());
+
+ return new EncryptionContext.Builder()
+ .withHashKeyName(params.getHashKeyName())
+ .withRangeKeyName(params.getRangeKeyName())
+ .withTableName(tableName)
+ .withModeledClass(params.getModelClass())
+ .withAttributeValues(params.getAttributeValues())
+ .build();
+ }
+
+ private boolean handleUnknownAttributes(Class> clazz) {
+ return clazz.getAnnotation(HandleUnknownAttributes.class) != null;
+ }
+
+ private static class ModelClassMetadata {
+ private final Map> encryptionFlags;
+ private final boolean doNotTouch;
+ private final Set unknownAttributeBehavior;
+
+ public ModelClassMetadata(
+ Map> encryptionFlags,
+ boolean doNotTouch,
+ Set unknownAttributeBehavior) {
+ this.encryptionFlags = encryptionFlags;
+ this.doNotTouch = doNotTouch;
+ this.unknownAttributeBehavior = unknownAttributeBehavior;
}
- private static EncryptionContext paramsToContext(Parameters> params) {
- final Class> clazz = params.getModelClass();
- final TableAadOverride override = clazz.getAnnotation(TableAadOverride.class);
- final String tableName = ((override == null) ? params.getTableName() : override.tableName());
-
- return new EncryptionContext.Builder()
- .withHashKeyName(params.getHashKeyName())
- .withRangeKeyName(params.getRangeKeyName())
- .withTableName(tableName)
- .withModeledClass(params.getModelClass())
- .withAttributeValues(params.getAttributeValues()).build();
+ public Map> getEncryptionFlags() {
+ return encryptionFlags;
}
- private boolean handleUnknownAttributes(Class> clazz) {
- return clazz.getAnnotation(HandleUnknownAttributes.class) != null;
+ public boolean getDoNotTouch() {
+ return doNotTouch;
}
- private static class ModelClassMetadata {
- private final Map> encryptionFlags;
- private final boolean doNotTouch;
- private final Set unknownAttributeBehavior;
-
- public ModelClassMetadata(Map> encryptionFlags,
- boolean doNotTouch, Set unknownAttributeBehavior) {
- this.encryptionFlags = encryptionFlags;
- this.doNotTouch = doNotTouch;
- this.unknownAttributeBehavior = unknownAttributeBehavior;
- }
-
- public Map> getEncryptionFlags() {
- return encryptionFlags;
- }
-
- public boolean getDoNotTouch() {
- return doNotTouch;
- }
-
- public Set getUnknownAttributeBehavior() {
- return unknownAttributeBehavior;
- }
+ public Set getUnknownAttributeBehavior() {
+ return unknownAttributeBehavior;
}
+ }
}
diff --git a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DelegatedKey.java b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DelegatedKey.java
index e42e6e97..5445030f 100644
--- a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DelegatedKey.java
+++ b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DelegatedKey.java
@@ -19,7 +19,6 @@
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
-
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
@@ -27,120 +26,104 @@
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
+ * 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.
- */
- public byte[] encrypt(byte[] plainText, byte[] additionalAssociatedData, String algorithm)
- throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
- NoSuchPaddingException;
+ /**
+ * 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.
+ */
+ public 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.
- */
- public byte[] decrypt(byte[] cipherText, byte[] additionalAssociatedData, String algorithm)
- throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
- NoSuchPaddingException, InvalidAlgorithmParameterException;
+ /**
+ * 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.
+ */
+ public 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.
- */
- public byte[] wrap(Key key, byte[] additionalAssociatedData, String algorithm) throws InvalidKeyException,
- NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException;
+ /**
+ * 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.
+ */
+ public 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.
- */
- public Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType,
- byte[] additionalAssociatedData, String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException,
- InvalidKeyException;
+ /**
+ * 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.
+ */
+ public 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
- */
- public byte[] sign(byte[] dataToSign, String algorithm) throws GeneralSecurityException;
+ /**
+ * Calculates and returns a signature for dataToSign
.
+ *
+ * @param dataToSign
+ * @param algorithm
+ * @return the signature
+ * @throws UnsupportedOperationException if signing is not supported
+ */
+ public 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
- */
- public boolean verify(byte[] dataToSign, byte[] signature, String algorithm);
+ /**
+ * 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
+ */
+ public boolean verify(byte[] dataToSign, byte[] signature, String algorithm);
}
diff --git a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DoNotEncrypt.java b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DoNotEncrypt.java
index 501bc642..1f8921cf 100644
--- a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DoNotEncrypt.java
+++ b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DoNotEncrypt.java
@@ -14,21 +14,18 @@
*/
package com.amazonaws.services.dynamodbv2.datamodeling.encryption;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDB;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDB;
-
/**
* Prevents the associated item (class or attribute) from being encrypted.
- *
- * @author Greg Rubin
+ *
+ * @author Greg Rubin
*/
@DynamoDB
@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
-public @interface DoNotEncrypt {
-
-}
+public @interface DoNotEncrypt {}
diff --git a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DoNotTouch.java b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DoNotTouch.java
index d2a817fd..4f87abb1 100644
--- a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DoNotTouch.java
+++ b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DoNotTouch.java
@@ -14,21 +14,18 @@
*/
package com.amazonaws.services.dynamodbv2.datamodeling.encryption;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDB;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDB;
-
/**
* Prevents the associated item from being encrypted or signed.
- *
- * @author Greg Rubin
+ *
+ * @author Greg Rubin
*/
@DynamoDB
@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
-public @interface DoNotTouch {
-
-}
+public @interface DoNotTouch {}
diff --git a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBEncryptor.java b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBEncryptor.java
index 7a70291c..d31260b2 100644
--- a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBEncryptor.java
+++ b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBEncryptor.java
@@ -14,6 +14,14 @@
*/
package com.amazonaws.services.dynamodbv2.datamodeling.encryption;
+import com.amazonaws.services.dynamodbv2.datamodeling.AttributeEncryptor;
+import com.amazonaws.services.dynamodbv2.datamodeling.encryption.materials.DecryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.encryption.materials.EncryptionMaterials;
+import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.EncryptionMaterialsProvider;
+import com.amazonaws.services.dynamodbv2.datamodeling.internal.AttributeValueMarshaller;
+import com.amazonaws.services.dynamodbv2.datamodeling.internal.ByteBufferInputStream;
+import com.amazonaws.services.dynamodbv2.datamodeling.internal.Utils;
+import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -33,577 +41,578 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
-
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
-import com.amazonaws.services.dynamodbv2.datamodeling.AttributeEncryptor;
-import com.amazonaws.services.dynamodbv2.datamodeling.encryption.materials.DecryptionMaterials;
-import com.amazonaws.services.dynamodbv2.datamodeling.encryption.materials.EncryptionMaterials;
-import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.EncryptionMaterialsProvider;
-import com.amazonaws.services.dynamodbv2.datamodeling.internal.AttributeValueMarshaller;
-import com.amazonaws.services.dynamodbv2.datamodeling.internal.ByteBufferInputStream;
-import com.amazonaws.services.dynamodbv2.datamodeling.internal.Utils;
-import com.amazonaws.services.dynamodbv2.model.AttributeValue;
-
/**
- * The low-level API used by {@link AttributeEncryptor} to perform crypto
- * operations on the record attributes.
- *
- * @author Greg Rubin
+ * The low-level API used by {@link AttributeEncryptor} to perform crypto operations on the record
+ * attributes.
+ *
+ * @author Greg Rubin
*/
public class DynamoDBEncryptor {
- private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA";
- private static final String DEFAULT_METADATA_FIELD = "*amzn-ddb-map-desc*";
- private static final String DEFAULT_SIGNATURE_FIELD = "*amzn-ddb-map-sig*";
- private static final String DEFAULT_DESCRIPTION_BASE = "amzn-ddb-map-"; // Same as the Mapper
- private static final Charset UTF8 = Charset.forName("UTF-8");
- private static final String SYMMETRIC_ENCRYPTION_MODE = "/CBC/PKCS5Padding";
- private static final ConcurrentHashMap BLOCK_SIZE_CACHE = new ConcurrentHashMap<>();
- private static final Function BLOCK_SIZE_CALCULATOR = (transformation) -> {
+ 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();
+ final Cipher c = Cipher.getInstance(transformation);
+ return c.getBlockSize();
} catch (final GeneralSecurityException ex) {
- throw new IllegalArgumentException("Algorithm does not exist", 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;
-
- public static final String DEFAULT_SIGNING_ALGORITHM_HEADER = DEFAULT_DESCRIPTION_BASE + "signingAlg";
- private Function encryptionContextOverrideOperator;
-
- protected DynamoDBEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) {
- this.encryptionMaterialsProvider = provider;
- this.descriptionBase = descriptionBase;
- symmetricEncryptionModeHeader = this.descriptionBase + "sym-mode";
- signingAlgorithmHeader = this.descriptionBase + "signingAlg";
- }
-
- public static DynamoDBEncryptor getInstance(EncryptionMaterialsProvider provider, String descriptionbase) {
- return new DynamoDBEncryptor(provider, descriptionbase);
- }
-
- public static DynamoDBEncryptor getInstance(EncryptionMaterialsProvider provider) {
- return getInstance(provider, DEFAULT_DESCRIPTION_BASE);
- }
-
- /**
- * Returns a decrypted version of the provided DynamoDb record. The signature is verified across
- * all provided fields. All fields (except those listed in doNotEncrypt
are
- * decrypted.
- *
- * @param itemAttributes
- * the DynamoDbRecord
- * @param context
- * additional information used to successfully select the encryption materials and
- * decrypt the data. This should include (at least) the tableName and the
- * materialDescription.
- * @param doNotDecrypt
- * those fields which should not be encrypted
- * @return a plaintext version of the DynamoDb record
- * @throws SignatureException
- * if the signature is invalid or cannot be verified
- * @throws GeneralSecurityException
- */
- public Map decryptAllFieldsExcept(Map itemAttributes,
- EncryptionContext context, String... doNotDecrypt) throws GeneralSecurityException {
- return decryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotDecrypt));
- }
-
- /**
- * @see #decryptAllFieldsExcept(Map, EncryptionContext, String...)
- */
- public Map decryptAllFieldsExcept(
- Map itemAttributes,
- EncryptionContext context, Collection doNotDecrypt)
- throws GeneralSecurityException {
- Map> attributeFlags = allDecryptionFlagsExcept(
- itemAttributes, doNotDecrypt);
- return decryptRecord(itemAttributes, attributeFlags, context);
+ };
+
+ 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;
+
+ public static final String DEFAULT_SIGNING_ALGORITHM_HEADER =
+ DEFAULT_DESCRIPTION_BASE + "signingAlg";
+ private Function encryptionContextOverrideOperator;
+
+ protected DynamoDBEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) {
+ this.encryptionMaterialsProvider = provider;
+ this.descriptionBase = descriptionBase;
+ symmetricEncryptionModeHeader = this.descriptionBase + "sym-mode";
+ signingAlgorithmHeader = this.descriptionBase + "signingAlg";
+ }
+
+ public static DynamoDBEncryptor getInstance(
+ EncryptionMaterialsProvider provider, String descriptionbase) {
+ return new DynamoDBEncryptor(provider, descriptionbase);
+ }
+
+ public static DynamoDBEncryptor getInstance(EncryptionMaterialsProvider provider) {
+ return getInstance(provider, DEFAULT_DESCRIPTION_BASE);
+ }
+
+ /**
+ * Returns a decrypted version of the provided DynamoDb record. The signature is verified across
+ * all provided fields. All fields (except those listed in doNotEncrypt
are
+ * decrypted.
+ *
+ * @param itemAttributes the DynamoDbRecord
+ * @param context additional information used to successfully select the encryption materials and
+ * decrypt the data. This should include (at least) the tableName and the materialDescription.
+ * @param doNotDecrypt those fields which should not be encrypted
+ * @return a plaintext version of the DynamoDb record
+ * @throws SignatureException if the signature is invalid or cannot be verified
+ * @throws GeneralSecurityException
+ */
+ public Map decryptAllFieldsExcept(
+ Map itemAttributes, EncryptionContext context, String... doNotDecrypt)
+ throws GeneralSecurityException {
+ return decryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotDecrypt));
+ }
+
+ /** @see #decryptAllFieldsExcept(Map, EncryptionContext, String...) */
+ public Map decryptAllFieldsExcept(
+ Map itemAttributes,
+ EncryptionContext context,
+ Collection doNotDecrypt)
+ throws GeneralSecurityException {
+ Map> attributeFlags =
+ allDecryptionFlagsExcept(itemAttributes, doNotDecrypt);
+ return decryptRecord(itemAttributes, attributeFlags, context);
+ }
+
+ /**
+ * Returns the decryption flags for all item attributes except for those explicitly specified to
+ * be excluded.
+ *
+ * @param doNotDecrypt fields to be excluded
+ */
+ public Map> allDecryptionFlagsExcept(
+ Map itemAttributes, String... doNotDecrypt) {
+ return allDecryptionFlagsExcept(itemAttributes, Arrays.asList(doNotDecrypt));
+ }
+
+ /**
+ * Returns the decryption flags for all item attributes except for those explicitly specified to
+ * be excluded.
+ *
+ * @param doNotDecrypt fields to be excluded
+ */
+ public Map> allDecryptionFlagsExcept(
+ Map itemAttributes, Collection doNotDecrypt) {
+ Map> attributeFlags = new HashMap>();
+
+ for (String fieldName : doNotDecrypt) {
+ attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN));
}
- /**
- * 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));
+ for (String fieldName : itemAttributes.keySet()) {
+ if (!attributeFlags.containsKey(fieldName)
+ && !fieldName.equals(getMaterialDescriptionFieldName())
+ && !fieldName.equals(getSignatureFieldName())) {
+ attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN));
+ }
}
-
- /**
- * Returns the decryption flags for all item attributes except for those
- * explicitly specified to be excluded.
- * @param doNotDecrypt fields to be excluded
- */
- public Map> allDecryptionFlagsExcept(
- Map itemAttributes,
- Collection doNotDecrypt) {
- Map> attributeFlags = new HashMap>();
-
- for (String fieldName : doNotDecrypt) {
- attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN));
- }
-
- for (String fieldName : itemAttributes.keySet()) {
- if (!attributeFlags.containsKey(fieldName) &&
- !fieldName.equals(getMaterialDescriptionFieldName()) &&
- !fieldName.equals(getSignatureFieldName())) {
- attributeFlags.put(fieldName,
- EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN));
- }
- }
- return attributeFlags;
+ return attributeFlags;
+ }
+
+ /**
+ * Returns an encrypted version of the provided DynamoDb record. All fields are signed. All fields
+ * (except those listed in doNotEncrypt
) are encrypted.
+ *
+ * @param itemAttributes a DynamoDb Record
+ * @param context additional information used to successfully select the encryption materials and
+ * encrypt the data. This should include (at least) the tableName.
+ * @param doNotEncrypt those fields which should not be encrypted
+ * @return a ciphertext version of the DynamoDb record
+ * @throws GeneralSecurityException
+ */
+ public Map encryptAllFieldsExcept(
+ Map itemAttributes, EncryptionContext context, String... doNotEncrypt)
+ throws GeneralSecurityException {
+
+ return encryptAllFieldsExcept(itemAttributes, context, Arrays.asList(doNotEncrypt));
+ }
+
+ public Map encryptAllFieldsExcept(
+ Map itemAttributes,
+ EncryptionContext context,
+ Collection doNotEncrypt)
+ throws GeneralSecurityException {
+ Map> attributeFlags =
+ allEncryptionFlagsExcept(itemAttributes, doNotEncrypt);
+ return encryptRecord(itemAttributes, attributeFlags, context);
+ }
+
+ /**
+ * Returns the encryption flags for all item attributes except for those explicitly specified to
+ * be excluded.
+ *
+ * @param doNotEncrypt fields to be excluded
+ */
+ public Map> allEncryptionFlagsExcept(
+ Map itemAttributes, String... doNotEncrypt) {
+ return allEncryptionFlagsExcept(itemAttributes, Arrays.asList(doNotEncrypt));
+ }
+
+ /**
+ * Returns the encryption flags for all item attributes except for those explicitly specified to
+ * be excluded.
+ *
+ * @param doNotEncrypt fields to be excluded
+ */
+ public Map> allEncryptionFlagsExcept(
+ Map itemAttributes, Collection doNotEncrypt) {
+ Map> attributeFlags = new HashMap>();
+ for (String fieldName : doNotEncrypt) {
+ attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN));
}
-
- /**
- * 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));
+
+ for (String fieldName : itemAttributes.keySet()) {
+ if (!attributeFlags.containsKey(fieldName)) {
+ attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN));
+ }
}
-
- public Map encryptAllFieldsExcept(
- Map itemAttributes,
- EncryptionContext context,
- Collection doNotEncrypt)
- throws GeneralSecurityException {
- Map> attributeFlags = allEncryptionFlagsExcept(
- itemAttributes, doNotEncrypt);
- return encryptRecord(itemAttributes, attributeFlags, context);
+ return attributeFlags;
+ }
+
+ public Map decryptRecord(
+ Map itemAttributes,
+ Map> attributeFlags,
+ EncryptionContext context)
+ throws GeneralSecurityException {
+ if (attributeFlags.isEmpty()) {
+ return itemAttributes;
}
+ // Copy to avoid changing anyone elses objects
+ itemAttributes = new HashMap(itemAttributes);
- /**
- * 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));
- }
+ Map materialDescription = Collections.emptyMap();
+ DecryptionMaterials materials;
+ SecretKey decryptionKey;
- /**
- * Returns the encryption flags for all item attributes except for those
- * explicitly specified to be excluded.
- * @param doNotEncrypt fields to be excluded
- */
- public Map> allEncryptionFlagsExcept(
- Map itemAttributes,
- Collection doNotEncrypt) {
- Map> attributeFlags =
- new HashMap>();
- for (String fieldName : doNotEncrypt) {
- attributeFlags.put(fieldName, EnumSet.of(EncryptionFlags.SIGN));
- }
+ DynamoDBSigner signer = DynamoDBSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng());
- for (String fieldName : itemAttributes.keySet()) {
- if (!attributeFlags.containsKey(fieldName)) {
- attributeFlags.put(fieldName,
- EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN));
- }
- }
- return attributeFlags;
+ if (itemAttributes.containsKey(materialDescriptionFieldName)) {
+ materialDescription = unmarshallDescription(itemAttributes.get(materialDescriptionFieldName));
}
-
- public Map decryptRecord(
- Map itemAttributes,
- Map> attributeFlags,
- EncryptionContext context) throws GeneralSecurityException {
- if (attributeFlags.isEmpty()) {
- 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)
+ // Copy the material description and attribute values into the context
+ context =
+ new EncryptionContext.Builder(context)
.withMaterialDescription(materialDescription)
.withAttributeValues(itemAttributes)
.build();
- Function encryptionContextOverrideOperator = getEncryptionContextOverrideOperator();
- if (encryptionContextOverrideOperator != null) {
- context = encryptionContextOverrideOperator.apply(context);
- }
-
- materials = encryptionMaterialsProvider.getDecryptionMaterials(context);
- decryptionKey = materials.getDecryptionKey();
- if (materialDescription.containsKey(signingAlgorithmHeader)) {
- String signingAlg = materialDescription.get(signingAlgorithmHeader);
- signer = DynamoDBSigner.getInstance(signingAlg, Utils.getRng());
- }
-
- ByteBuffer signature;
- if (!itemAttributes.containsKey(signatureFieldName) || itemAttributes.get(signatureFieldName).getB() == null) {
- signature = ByteBuffer.allocate(0);
- } else {
- signature = itemAttributes.get(signatureFieldName).getB().asReadOnlyBuffer();
- }
- itemAttributes.remove(signatureFieldName);
-
- String associatedData = "TABLE>" + context.getTableName() + " encryptionContextOverrideOperator =
+ getEncryptionContextOverrideOperator();
+ if (encryptionContextOverrideOperator != null) {
+ context = encryptionContextOverrideOperator.apply(context);
}
- /**
- * Returns the encrypted (and signed) record, which is a map of item
- * attributes. There is no side effect on the input parameters upon calling
- * this method.
- *
- * @param itemAttributes
- * the input record
- * @param attributeFlags
- * the corresponding encryption flags
- * @param context
- * encryption context
- * @return a new instance of item attributes encrypted as necessary
- * @throws GeneralSecurityException
- * if failed to encrypt the record
- */
- public Map encryptRecord(
- Map itemAttributes,
- Map> attributeFlags,
- EncryptionContext context) throws GeneralSecurityException {
- if (attributeFlags.isEmpty()) {
- return itemAttributes;
- }
- // Copy to avoid changing anyone elses objects
- itemAttributes = new HashMap(itemAttributes);
-
- // Copy the attribute values into the context
- context = new EncryptionContext.Builder(context)
- .withAttributeValues(itemAttributes)
- .build();
-
- Function encryptionContextOverrideOperator =
- getEncryptionContextOverrideOperator();
- if (encryptionContextOverrideOperator != null) {
- context = encryptionContextOverrideOperator.apply(context);
- }
-
- EncryptionMaterials materials = encryptionMaterialsProvider.getEncryptionMaterials(context);
- // We need to copy this because we modify it to record other encryption details
- Map materialDescription = new HashMap(
- materials.getMaterialDescription());
- SecretKey encryptionKey = materials.getEncryptionKey();
-
- 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().getB().asReadOnlyBuffer();
- 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));
- }
- }
+ itemAttributes.remove(signatureFieldName);
+
+ String associatedData = "TABLE>" + context.getTableName() + " encryptRecord(
+ Map itemAttributes,
+ Map> attributeFlags,
+ EncryptionContext context)
+ throws GeneralSecurityException {
+ if (attributeFlags.isEmpty()) {
+ return itemAttributes;
}
+ // Copy to avoid changing anyone elses objects
+ itemAttributes = new HashMap(itemAttributes);
- protected static int getBlockSize(final String encryptionMode) {
- return BLOCK_SIZE_CACHE.computeIfAbsent(encryptionMode, BLOCK_SIZE_CALCULATOR);
- }
+ // Copy the attribute values into the context
+ context = new EncryptionContext.Builder(context).withAttributeValues(itemAttributes).build();
- /**
- * 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(new AttributeValue().withB(cipherText));
- }
- }
- }
-
- /**
- * 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;
+ Function encryptionContextOverrideOperator =
+ getEncryptionContextOverrideOperator();
+ if (encryptionContextOverrideOperator != null) {
+ context = encryptionContextOverrideOperator.apply(context);
}
- /**
- * Set the name of the DynamoDB field used to store the signature.
- *
- * @param signatureFieldName
- */
- public void setSignatureFieldName(final String signatureFieldName) {
- this.signatureFieldName = signatureFieldName;
+ 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();
+
+ 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());
}
- /**
- * 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;
+ if (materials.getSigningKey() instanceof PrivateKey) {
+ materialDescription.put(signingAlgorithmHeader, signer.getSigningAlgorithm());
}
-
- /**
- * Set the name of the DynamoDB field used to store metadata used by the
- * DynamoDBEncryptedMapper
- *
- * @param materialDescriptionFieldName
- */
- public void setMaterialDescriptionFieldName(final String materialDescriptionFieldName) {
- this.materialDescriptionFieldName = materialDescriptionFieldName;
+ if (!materialDescription.isEmpty()) {
+ itemAttributes.put(materialDescriptionFieldName, marshallDescription(materialDescription));
}
-
- /**
- * 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)
- */
- protected 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();
- AttributeValue result = new AttributeValue();
- result.setB(ByteBuffer.wrap(bos.toByteArray()));
- return result;
- } catch (IOException ex) {
- // Due to the objects in use, an IOException is not possible.
- throw new RuntimeException("Unexpected exception", ex);
+
+ 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().getB().asReadOnlyBuffer();
+ 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));
+ }
}
-
- public String getSigningAlgorithmHeader() {
- return signingAlgorithmHeader;
+ }
+
+ protected 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;
}
- /**
- * @see #marshallDescription(Map)
- */
- protected static Map unmarshallDescription(AttributeValue attributeValue) {
- attributeValue.getB().mark();
- try (DataInputStream in = new DataInputStream(
- new ByteBufferInputStream(attributeValue.getB())) ) {
- 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);
- } finally {
- attributeValue.getB().reset();
+ 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(new AttributeValue().withB(cipherText));
+ }
}
-
- /**
- * @param encryptionContextOverrideOperator the nullable operator which will be used to override
- * the EncryptionContext.
- * @see com.amazonaws.services.dynamodbv2.datamodeling.encryption.utils.EncryptionContextOperators
- */
- public final void setEncryptionContextOverrideOperator(
- Function encryptionContextOverrideOperator) {
- this.encryptionContextOverrideOperator = encryptionContextOverrideOperator;
- }
-
- /**
- * @return the operator used to override the EncryptionContext
- * @see #setEncryptionContextOverrideOperator(Function)
- */
- public final Function getEncryptionContextOverrideOperator() {
- return encryptionContextOverrideOperator;
+ }
+
+ /**
+ * 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
+ */
+ public 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
+ */
+ public 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)
+ */
+ protected 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();
+ AttributeValue result = new AttributeValue();
+ result.setB(ByteBuffer.wrap(bos.toByteArray()));
+ return result;
+ } catch (IOException ex) {
+ // Due to the objects in use, an IOException is not possible.
+ throw new RuntimeException("Unexpected exception", ex);
}
-
- 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;
- }
+ }
+
+ public String getSigningAlgorithmHeader() {
+ return signingAlgorithmHeader;
+ }
+ /** @see #marshallDescription(Map) */
+ protected static Map unmarshallDescription(AttributeValue attributeValue) {
+ attributeValue.getB().mark();
+ try (DataInputStream in =
+ new DataInputStream(new ByteBufferInputStream(attributeValue.getB()))) {
+ 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);
}
-
- byte[] result = new byte[buffer.remaining()];
- buffer.get(result);
+ } 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);
+ } finally {
+ attributeValue.getB().reset();
+ }
+ }
+
+ /**
+ * @param encryptionContextOverrideOperator the nullable operator which will be used to override
+ * the EncryptionContext.
+ * @see com.amazonaws.services.dynamodbv2.datamodeling.encryption.utils.EncryptionContextOperators
+ */
+ public final void setEncryptionContextOverrideOperator(
+ Function encryptionContextOverrideOperator) {
+ this.encryptionContextOverrideOperator = encryptionContextOverrideOperator;
+ }
+
+ /**
+ * @return the operator used to override the EncryptionContext
+ * @see #setEncryptionContextOverrideOperator(Function)
+ */
+ public final Function
+ getEncryptionContextOverrideOperator() {
+ return encryptionContextOverrideOperator;
+ }
+
+ private static byte[] toByteArray(ByteBuffer buffer) {
+ buffer = buffer.duplicate();
+ // We can only return the array directly if:
+ // 1. The ByteBuffer exposes an array
+ // 2. The ByteBuffer starts at the beginning of the array
+ // 3. The ByteBuffer uses the entire array
+ if (buffer.hasArray() && buffer.arrayOffset() == 0) {
+ byte[] result = buffer.array();
+ if (buffer.remaining() == result.length) {
return result;
+ }
}
+
+ byte[] result = new byte[buffer.remaining()];
+ buffer.get(result);
+ return result;
+ }
}
diff --git a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBSigner.java b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBSigner.java
index cdded8fd..bf5c7394 100644
--- a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBSigner.java
+++ b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBSigner.java
@@ -14,6 +14,9 @@
*/
package com.amazonaws.services.dynamodbv2.datamodeling.encryption;
+import com.amazonaws.services.dynamodbv2.datamodeling.internal.AttributeValueMarshaller;
+import com.amazonaws.services.dynamodbv2.datamodeling.internal.Utils;
+import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -33,217 +36,221 @@
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 com.amazonaws.services.dynamodbv2.datamodeling.internal.AttributeValueMarshaller;
-import com.amazonaws.services.dynamodbv2.datamodeling.internal.Utils;
-import com.amazonaws.services.dynamodbv2.model.AttributeValue;
-
-/**
- * @author Greg Rubin
- */
+/** @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;
- }
+ private static final ConcurrentHashMap cache =
+ new ConcurrentHashMap();
- /**
- * @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");
- }
+ protected static final Charset UTF8 = Charset.forName("UTF-8");
+ private final SecureRandom rnd;
+ private final SecretKey hmacComparisonKey;
+ private final String signingAlgorithm;
- 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");
- }
+ /**
+ * @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;
+ }
- 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);
- }
+ /**
+ * @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");
+ }
- /**
- * 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");
- }
+ 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");
}
+ }
- byte[] calculateSignature(Map itemAttributes,
- Map> attributeFlags, byte[] associatedData,
- DelegatedKey key) throws GeneralSecurityException {
- byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData);
- return key.sign(stringToSign, key.getAlgorithm());
- }
+ 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();
- byte[] calculateSignature(Map itemAttributes,
- Map> attributeFlags, byte[] associatedData,
- SecretKey key) throws GeneralSecurityException {
- if (key instanceof DelegatedKey) {
- return calculateSignature(itemAttributes, attributeFlags, associatedData, (DelegatedKey)key);
+ 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();
}
- byte[] stringToSign = calculateStringToSign(itemAttributes, attributeFlags, associatedData);
- Mac hmac = Mac.getInstance(key.getAlgorithm());
- hmac.init(key);
- hmac.update(stringToSign);
- return hmac.doFinal();
+ }
+ return out.toByteArray();
+ } catch (IOException ex) {
+ // Due to the objects in use, an IOException is not possible.
+ throw new RuntimeException("Unexpected exception", ex);
}
+ }
- 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();
+ /** 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());
+ }
- String getSigningAlgorithm() {
- return signingAlgorithm;
+ byte[] calculateSignature(
+ Map itemAttributes,
+ Map