From ea43801be1725e213dc2e7ee9c3499e7ecefc2cc Mon Sep 17 00:00:00 2001 From: John Walker Date: Fri, 14 Dec 2018 11:22:29 -0800 Subject: [PATCH 1/5] Direct copies of DirectKMS and MetaStore --- .../providers/DirectKmsMaterialProvider.java | 40 +++- .../encryption/providers/store/MetaStore.java | 172 +++++++++++++++--- 2 files changed, 187 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/DirectKmsMaterialProvider.java b/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/DirectKmsMaterialProvider.java index 14d6cd36..3f918b36 100644 --- a/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/DirectKmsMaterialProvider.java +++ b/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/DirectKmsMaterialProvider.java @@ -126,7 +126,7 @@ public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { DecryptRequest request = appendUserAgent(new DecryptRequest()); request.setCiphertextBlob(ByteBuffer.wrap(Base64.decode(materialDescription.get(ENVELOPE_KEY)))); request.setEncryptionContext(ec); - final DecryptResult decryptResult = kms.decrypt(request); + final DecryptResult decryptResult = decrypt(request, context); validateEncryptionKeyId(decryptResult.getKeyId(), context); final Hkdf kdf; @@ -167,7 +167,7 @@ public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { req.setNumberOfBytes(256 / 8); req.setEncryptionContext(ec); - final GenerateDataKeyResult dataKeyResult = kms.generateDataKey(req); + final GenerateDataKeyResult dataKeyResult = generateDataKey(req, context); final Map materialDescription = new HashMap<>(); materialDescription.putAll(description); @@ -192,7 +192,8 @@ public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) { } /** - * Get encryption key id. + * Get encryption key id that is used to create the {@link EncryptionMaterials}. + * * @return encryption key id. */ protected String getEncryptionKeyId() { @@ -202,6 +203,7 @@ protected String getEncryptionKeyId() { /** * Select encryption key id to be used to generate data key. The default implementation of this method returns * {@link DirectKmsMaterialProvider#encryptionKeyId}. + * * @param context encryption context. * @return the encryptionKeyId. * @throws DynamoDBMappingException when we fails to select a valid encryption key id. @@ -211,7 +213,9 @@ protected String selectEncryptionKeyId(EncryptionContext context) throws DynamoD } /** - * Validate the encryption key id. The default implementation of this method does nothing. + * Validate the encryption key id. The default implementation of this method does not validate + * encryption key id. + * * @param encryptionKeyId encryption key id from {@link DecryptResult}. * @param context encryption context. * @throws DynamoDBMappingException when encryptionKeyId is invalid. @@ -221,6 +225,34 @@ protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext // 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 DecryptResult 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 GenerateDataKeyResult 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: diff --git a/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java b/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java index 98506ffd..3ebc6fa5 100644 --- a/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java +++ b/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java @@ -17,8 +17,10 @@ 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; @@ -70,25 +72,88 @@ public class MetaStore extends ProviderStore { 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 String tableName; private final AmazonDynamoDB 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 AmazonDynamoDB 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 AmazonDynamoDB 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"); - ddbCtx = new EncryptionContext.Builder().withTableName(this.tableName) + this.ddbCtx = new EncryptionContext.Builder().withTableName(this.tableName) .withHashKeyName(DEFAULT_HASH_KEY).withRangeKeyName(DEFAULT_RANGE_KEY).build(); final Map tmpExpected = new HashMap(); tmpExpected.put(DEFAULT_HASH_KEY, new ExpectedAttributeValue().withExists(false)); tmpExpected.put(DEFAULT_RANGE_KEY, new ExpectedAttributeValue().withExists(false)); doesNotExist = Collections.unmodifiableMap(tmpExpected); + + this.doNotEncrypt = getSignedOnlyFields(extraDataSupplier); } @Override @@ -105,10 +170,8 @@ public EncryptionMaterialsProvider getProvider(final String materialName, final @Override public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) { - final SecretKeySpec encryptionKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_ENCRYPTION); - final SecretKeySpec integrityKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_INTEGRITY); - final Map ciphertext = conditionalPut(encryptKeys(materialName, - nextId, encryptionKey, integrityKey)); + final Map plaintext = createMaterialItem(materialName, nextId); + final Map ciphertext = conditionalPut(getEncryptedText(plaintext)); return decryptProvider(ciphertext); } @@ -145,9 +208,10 @@ public long getVersionFromMaterialDescription(final Map descript /** * This API retrieves the intermediate keys from the source region and replicates it in the target region. - * @param materialName - * @param version - * @param targetMetaStore + * + * @param materialName material name of the encryption material. + * @param version version of the encryption material. + * @param targetMetaStore target MetaStore where the encryption material to be stored. */ public void replicate(final String materialName, final long version, final MetaStore targetMetaStore) { try { @@ -168,8 +232,14 @@ public void replicate(final String materialName, final long version, final MetaS //Item already present. } } + /** * Creates a DynamoDB Table with the correct properties to be used with a ProviderStore. + * + * @param ddb interface for accessing DynamoDB + * @param tableName name of table that stores the meta data of the material. + * @param provisionedThroughput required provisioned throughput of the this table. + * @return result of create table request. */ public static CreateTableResult createTable(final AmazonDynamoDB ddb, final String tableName, final ProvisionedThroughput provisionedThroughput) { @@ -181,6 +251,44 @@ public static CreateTableResult createTable(final AmazonDynamoDB ddb, final Stri } + /** + * Empty extra data supplier. This default class is intended to simplify the default + * implementation of {@link MetaStore}. + */ + private static class EmptyExtraDataSupplier implements ExtraDataSupplier { + @Override + public Map getAttributes(String materialName, long version) { + return Collections.emptyMap(); + } + + @Override + public Set getSignedOnlyFieldNames() { + return Collections.emptySet(); + } + } + + /** + * Get a set of fields that must be signed but not encrypted. + * + * @param extraDataSupplier extra data supplier that is used to return sign only field names. + * @return fields that must be signed. + */ + private static Set getSignedOnlyFields(final ExtraDataSupplier extraDataSupplier) { + final Set signedOnlyFields = extraDataSupplier.getSignedOnlyFieldNames(); + for (final String signedOnlyField : signedOnlyFields) { + if (ENCRYPTED_FIELDS.contains(signedOnlyField)) { + throw new IllegalArgumentException(signedOnlyField + " must be encrypted"); + } + } + + // fields that should not be encrypted + final Set doNotEncryptFields = new HashSet<>(); + doNotEncryptFields.add(DEFAULT_HASH_KEY); + doNotEncryptFields.add(DEFAULT_RANGE_KEY); + doNotEncryptFields.addAll(signedOnlyFields); + return Collections.unmodifiableSet(doNotEncryptFields); + } + private Map conditionalPut(final Map item) { try { final PutItemRequest put = new PutItemRequest().withTableName(tableName).withItem(item) @@ -201,19 +309,29 @@ private Map ddbGet(final Map ddb .withKey(ddbKey)).getItem(); } - private Map encryptKeys(final String name, final long version, - final SecretKeySpec encryptionKey, final SecretKeySpec integrityKey) { + /** + * Build an material item for a given material name and version with newly generated + * encryption and integrity keys. + * + * @param materialName material name. + * @param version version of the material. + * @return newly generated plaintext material item. + */ + private Map createMaterialItem(final String materialName, final long version) { + final SecretKeySpec encryptionKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_ENCRYPTION); + final SecretKeySpec integrityKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_INTEGRITY); + final Map plaintext = new HashMap(); - plaintext.put(DEFAULT_HASH_KEY, new AttributeValue().withS(name)); + plaintext.put(DEFAULT_HASH_KEY, new AttributeValue().withS(materialName)); plaintext.put(DEFAULT_RANGE_KEY, new AttributeValue().withN(Long.toString(version))); plaintext.put(MATERIAL_TYPE_VERSION, new AttributeValue().withS("0")); - plaintext.put(ENCRYPTION_KEY_FIELD, - new AttributeValue().withB(ByteBuffer.wrap(encryptionKey.getEncoded()))); + plaintext.put(ENCRYPTION_KEY_FIELD, new AttributeValue().withB(ByteBuffer.wrap(encryptionKey.getEncoded()))); plaintext.put(ENCRYPTION_ALGORITHM_FIELD, new AttributeValue().withS(encryptionKey.getAlgorithm())); - plaintext - .put(INTEGRITY_KEY_FIELD, new AttributeValue().withB(ByteBuffer.wrap(integrityKey.getEncoded()))); + plaintext.put(INTEGRITY_KEY_FIELD, new AttributeValue().withB(ByteBuffer.wrap(integrityKey.getEncoded()))); plaintext.put(INTEGRITY_ALGORITHM_FIELD, new AttributeValue().withS(integrityKey.getAlgorithm())); - return getEncryptedText(plaintext); + plaintext.putAll(extraDataSupplier.getAttributes(materialName, version)); + + return plaintext; } private EncryptionMaterialsProvider decryptProvider(final Map item) { @@ -237,19 +355,31 @@ private EncryptionMaterialsProvider decryptProvider(final Map getPlainText(Map item) { + /** + * Decrypts attributes in the ciphertext item using {@link DynamoDBEncryptor}. + * except the attribute names specified in doNotEncrypt. + * @param ciphertext the ciphertext to be decrypted. + * @throws AmazonClientException when failed to decrypt material item. + * @return decrypted item. + */ + private Map getPlainText(final Map ciphertext) { try { - return encryptor.decryptAllFieldsExcept(item, - ddbCtx, DEFAULT_HASH_KEY, DEFAULT_RANGE_KEY); + return encryptor.decryptAllFieldsExcept(ciphertext, ddbCtx, doNotEncrypt); } catch (final GeneralSecurityException e) { throw new AmazonClientException(e); } } + /** + * Encrypts attributes in the plaintext item using {@link DynamoDBEncryptor}. + * except the attribute names specified in doNotEncrypt. + * + * @throws AmazonClientException when failed to encrypt material item. + * @param plaintext plaintext to be encrypted. + */ private Map getEncryptedText(Map plaintext) { try { - return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, DEFAULT_HASH_KEY, - DEFAULT_RANGE_KEY); + return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, doNotEncrypt); } catch (final GeneralSecurityException e) { throw new AmazonClientException(e); } From 56228750c3aee1ce22bd609bb00b4947d1f2356a Mon Sep 17 00:00:00 2001 From: John Walker Date: Fri, 14 Dec 2018 11:23:25 -0800 Subject: [PATCH 2/5] Add private getMaterialItem method in MetaStore Moves duplicated code into a common method in the MetaStore. --- .../encryption/providers/store/MetaStore.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java b/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java index 3ebc6fa5..711d2bc3 100644 --- a/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java +++ b/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java @@ -158,13 +158,7 @@ public MetaStore(final AmazonDynamoDB ddb, final String tableName, @Override public EncryptionMaterialsProvider getProvider(final String materialName, final long version) { - final Map ddbKey = new HashMap(); - ddbKey.put(DEFAULT_HASH_KEY, new AttributeValue().withS(materialName)); - ddbKey.put(DEFAULT_RANGE_KEY, new AttributeValue().withN(Long.toString(version))); - final Map item = ddbGet(ddbKey); - if (item == null || item.isEmpty()) { - throw new IndexOutOfBoundsException("No material found: " + materialName + "#" + version); - } + Map item = getMaterialItem(materialName, version); return decryptProvider(item); } @@ -215,14 +209,7 @@ public long getVersionFromMaterialDescription(final Map descript */ public void replicate(final String materialName, final long version, final MetaStore targetMetaStore) { try { - final Map ddbKey = new HashMap(); - ddbKey.put(DEFAULT_HASH_KEY, new AttributeValue().withS(materialName)); - ddbKey.put(DEFAULT_RANGE_KEY, new AttributeValue().withN(Long.toString(version))); - final Map item = ddbGet(ddbKey); - if (item == null || item.isEmpty()) { - throw new IndexOutOfBoundsException("No material found: " + materialName + "#" + version); - } - + Map item = getMaterialItem(materialName, version); final Map plainText = getPlainText(item); final Map encryptedText = targetMetaStore.getEncryptedText(plainText); final PutItemRequest put = new PutItemRequest().withTableName(targetMetaStore.tableName).withItem(encryptedText) @@ -233,6 +220,17 @@ public void replicate(final String materialName, final long version, final MetaS } } + private Map getMaterialItem(final String materialName, final long version) { + final Map ddbKey = new HashMap(); + ddbKey.put(DEFAULT_HASH_KEY, new AttributeValue().withS(materialName)); + ddbKey.put(DEFAULT_RANGE_KEY, new AttributeValue().withN(Long.toString(version))); + final Map item = ddbGet(ddbKey); + if (item == null || item.isEmpty()) { + throw new IndexOutOfBoundsException("No material found: " + materialName + "#" + version); + } + return item; + } + /** * Creates a DynamoDB Table with the correct properties to be used with a ProviderStore. * From 3c319a6ad9e1cfbdf0ff6ead505543fc481686cc Mon Sep 17 00:00:00 2001 From: John Walker Date: Mon, 7 Jan 2019 17:51:59 -0800 Subject: [PATCH 3/5] Use diamond operator for HashMaps in MetaStore There isn't any change in behavior here. Just style fixes. --- .../datamodeling/encryption/providers/store/MetaStore.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java b/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java index 711d2bc3..9406c801 100644 --- a/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java +++ b/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java @@ -148,7 +148,7 @@ public MetaStore(final AmazonDynamoDB ddb, final String tableName, this.ddbCtx = new EncryptionContext.Builder().withTableName(this.tableName) .withHashKeyName(DEFAULT_HASH_KEY).withRangeKeyName(DEFAULT_RANGE_KEY).build(); - final Map tmpExpected = new HashMap(); + final Map tmpExpected = new HashMap<>(); tmpExpected.put(DEFAULT_HASH_KEY, new ExpectedAttributeValue().withExists(false)); tmpExpected.put(DEFAULT_RANGE_KEY, new ExpectedAttributeValue().withExists(false)); doesNotExist = Collections.unmodifiableMap(tmpExpected); @@ -221,7 +221,7 @@ public void replicate(final String materialName, final long version, final MetaS } private Map getMaterialItem(final String materialName, final long version) { - final Map ddbKey = new HashMap(); + final Map ddbKey = new HashMap<>(); ddbKey.put(DEFAULT_HASH_KEY, new AttributeValue().withS(materialName)); ddbKey.put(DEFAULT_RANGE_KEY, new AttributeValue().withN(Long.toString(version))); final Map item = ddbGet(ddbKey); @@ -294,7 +294,7 @@ private Map conditionalPut(final Map ddbKey = new HashMap(); + final Map ddbKey = new HashMap<>(); ddbKey.put(DEFAULT_HASH_KEY, item.get(DEFAULT_HASH_KEY)); ddbKey.put(DEFAULT_RANGE_KEY, item.get(DEFAULT_RANGE_KEY)); return ddbGet(ddbKey); From 04be22bd07f529cfdea24928a79b5a1973d2c827 Mon Sep 17 00:00:00 2001 From: John Walker Date: Fri, 18 Jan 2019 17:54:56 -0800 Subject: [PATCH 4/5] Add DirectKmsMaterialProviderTest and MetaStoreTests Add the internal tests for ExtraDataSupplier in MetaStore --- .../DirectKmsMaterialProviderTest.java | 28 +++++--- .../providers/store/MetaStoreTests.java | 69 +++++++++++++++++++ 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/DirectKmsMaterialProviderTest.java b/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/DirectKmsMaterialProviderTest.java index 388ee435..8eb71f73 100644 --- a/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/DirectKmsMaterialProviderTest.java +++ b/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/DirectKmsMaterialProviderTest.java @@ -20,6 +20,8 @@ import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.testing.FakeKMS; import com.amazonaws.services.kms.AWSKMS; +import com.amazonaws.services.kms.model.DecryptRequest; +import com.amazonaws.services.kms.model.DecryptResult; import com.amazonaws.services.kms.model.GenerateDataKeyRequest; import com.amazonaws.services.kms.model.GenerateDataKeyResult; import com.amazonaws.util.Base64; @@ -50,7 +52,7 @@ public class DirectKmsMaterialProviderTest { @BeforeMethod public void setUp() { - description = new HashMap(); + description = new HashMap<>(); description.put("TestKey", "test value"); description = Collections.unmodifiableMap(description); ctx = new EncryptionContext.Builder().build(); @@ -87,7 +89,7 @@ public void simple() throws GeneralSecurityException { public void simpleWithKmsEc() throws GeneralSecurityException { DirectKmsMaterialProvider prov = new DirectKmsMaterialProvider(kms, keyId); - Map attrVals = new HashMap(); + Map attrVals = new HashMap<>(); attrVals.put("hk", new AttributeValue("HashKeyValue")); attrVals.put("rk", new AttributeValue("RangeKeyValue")); @@ -116,7 +118,7 @@ public void simpleWithKmsEc() throws GeneralSecurityException { public void simpleWithKmsEc2() throws GeneralSecurityException { DirectKmsMaterialProvider prov = new DirectKmsMaterialProvider(kms, keyId); - Map attrVals = new HashMap(); + Map attrVals = new HashMap<>(); attrVals.put("hk", new AttributeValue().withN("10")); attrVals.put("rk", new AttributeValue().withN("20")); @@ -145,7 +147,7 @@ public void simpleWithKmsEc2() throws GeneralSecurityException { public void simpleWithKmsEc3() throws GeneralSecurityException { DirectKmsMaterialProvider prov = new DirectKmsMaterialProvider(kms, keyId); - Map attrVals = new HashMap(); + Map attrVals = new HashMap<>(); attrVals.put("hk", new AttributeValue().withB(ByteBuffer.wrap("Foo".getBytes(StandardCharsets.UTF_8)))); attrVals.put("rk", @@ -198,7 +200,7 @@ public void testRefresh() { @Test public void explicitContentKeyAlgorithm() throws GeneralSecurityException { - Map desc = new HashMap(); + Map desc = new HashMap<>(); desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES"); DirectKmsMaterialProvider prov = new DirectKmsMaterialProvider(kms, keyId, desc); @@ -215,7 +217,7 @@ public void explicitContentKeyAlgorithm() throws GeneralSecurityException { @Test public void explicitContentKeyLength128() throws GeneralSecurityException { - Map desc = new HashMap(); + Map desc = new HashMap<>(); desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/128"); DirectKmsMaterialProvider prov = new DirectKmsMaterialProvider(kms, keyId, desc); @@ -234,7 +236,7 @@ public void explicitContentKeyLength128() throws GeneralSecurityException { @Test public void explicitContentKeyLength256() throws GeneralSecurityException { - Map desc = new HashMap(); + Map desc = new HashMap<>(); desc.put(WrappedRawMaterials.CONTENT_KEY_ALGORITHM, "AES/256"); DirectKmsMaterialProvider prov = new DirectKmsMaterialProvider(kms, keyId, desc); @@ -336,7 +338,7 @@ public GenerateDataKeyResult generateDataKey(GenerateDataKeyRequest r) { } private static class ExtendedKmsMaterialProvider extends DirectKmsMaterialProvider { - protected final String encryptionKeyIdAttributeName; + private final String encryptionKeyIdAttributeName; public ExtendedKmsMaterialProvider(AWSKMS kms, String encryptionKeyId, String encryptionKeyIdAttributeName) { super(kms, encryptionKeyId); @@ -365,6 +367,16 @@ protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext throw new DynamoDBMappingException("encryption key ids do not match."); } } + + @Override + protected DecryptResult decrypt(DecryptRequest request, EncryptionContext context) { + return super.decrypt(request, context); + } + + @Override + protected GenerateDataKeyResult generateDataKey(GenerateDataKeyRequest request, EncryptionContext context) { + return super.generateDataKey(request, context); + } } private static EncryptionContext ctx(EncryptionMaterials mat) { diff --git a/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStoreTests.java b/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStoreTests.java index 7cbc3390..47a084dd 100644 --- a/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStoreTests.java +++ b/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStoreTests.java @@ -21,6 +21,7 @@ import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.EncryptionMaterialsProvider; import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.SymmetricStaticProvider; import com.amazonaws.services.dynamodbv2.local.embedded.DynamoDBEmbedded; +import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -31,6 +32,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNotNull; @@ -59,6 +64,28 @@ public class MetaStoreTests { private MetaStore targetStore; private EncryptionContext ctx; + private static class TestExtraDataSupplier implements MetaStore.ExtraDataSupplier { + + private final Map attributeValueMap; + private final Set signedOnlyFieldNames; + + public TestExtraDataSupplier(final Map attributeValueMap, + final Set signedOnlyFieldNames) { + this.attributeValueMap = attributeValueMap; + this.signedOnlyFieldNames = signedOnlyFieldNames; + } + + @Override + public Map getAttributes(String materialName, long version) { + return this.attributeValueMap; + } + + @Override + public Set getSignedOnlyFieldNames() { + return this.signedOnlyFieldNames; + } + } + @BeforeMethod public void setup() { client = synchronize(DynamoDBEmbedded.create(), AmazonDynamoDB.class); @@ -181,6 +208,34 @@ public void getOrCreateCollision() { assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); } + @Test + public void getOrCreateWithContextSupplier() { + final Map attributeValueMap = new HashMap<>(); + attributeValueMap.put("CustomKeyId", new AttributeValue().withS("testCustomKeyId")); + attributeValueMap.put("KeyToken", new AttributeValue().withS("testKeyToken")); + + final Set signedOnlyAttributes = new HashSet<>(); + signedOnlyAttributes.add("CustomKeyId"); + + final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( + attributeValueMap, signedOnlyAttributes); + + final MetaStore metaStore = new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); + + assertEquals(-1, metaStore.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov1 = metaStore.getOrCreate(MATERIAL_NAME, 0); + assertEquals(0, metaStore.getMaxVersion(MATERIAL_NAME)); + final EncryptionMaterialsProvider prov2 = metaStore.getOrCreate(MATERIAL_NAME, 0); + + final EncryptionMaterials eMat = prov1.getEncryptionMaterials(ctx); + final SecretKey encryptionKey = eMat.getEncryptionKey(); + assertNotNull(encryptionKey); + + final DecryptionMaterials dMat = prov2.getDecryptionMaterials(ctx(eMat)); + assertEquals(encryptionKey, dMat.getDecryptionKey()); + assertEquals(eMat.getSigningKey(), dMat.getVerificationKey()); + } + @Test public void replicateIntermediateKeysTest() { assertEquals(-1, store.getMaxVersion(MATERIAL_NAME)); @@ -231,6 +286,20 @@ public void invalidVersion() { store.getProvider(MATERIAL_NAME, 1000); } + @Test(expected = IllegalArgumentException.class) + public void invalidSignedOnlyField() { + final Map attributeValueMap = new HashMap<>(); + attributeValueMap.put("enc", new AttributeValue().withS("testEncryptionKey")); + + final Set signedOnlyAttributes = new HashSet<>(); + signedOnlyAttributes.add("enc"); + + final TestExtraDataSupplier extraDataSupplier = new TestExtraDataSupplier( + attributeValueMap, signedOnlyAttributes); + + new MetaStore(client, SOURCE_TABLE_NAME, ENCRYPTOR, extraDataSupplier); + } + private static EncryptionContext ctx(final EncryptionMaterials mat) { return new EncryptionContext.Builder() .withMaterialDescription(mat.getMaterialDescription()).build(); From 8d840810d1ea516cc9150cb8825b0a79568f03cb Mon Sep 17 00:00:00 2001 From: John Walker Date: Fri, 18 Jan 2019 19:43:02 -0800 Subject: [PATCH 5/5] Fix expected exception on @Test Fixes an expected exception test for testng. --- .../datamodeling/encryption/providers/store/MetaStoreTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStoreTests.java b/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStoreTests.java index 47a084dd..ed6e5fde 100644 --- a/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStoreTests.java +++ b/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStoreTests.java @@ -286,7 +286,7 @@ public void invalidVersion() { store.getProvider(MATERIAL_NAME, 1000); } - @Test(expected = IllegalArgumentException.class) + @Test(expectedExceptions = IllegalArgumentException.class) public void invalidSignedOnlyField() { final Map attributeValueMap = new HashMap<>(); attributeValueMap.put("enc", new AttributeValue().withS("testEncryptionKey"));