diff --git a/CHANGELOG.md b/CHANGELOG.md
index dac50f7e..5102db35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,8 @@
# Changelog
+## 2.0.0 -- 2021-02-04
+Removes MostRecentProvider.
+MostRecentProvider is replaced by CachingMostRecentProvider as of 1.15.0.
+
## 1.15.0 -- 2021-02-04
Adds the CachingMostRecentProvider and deprecates MostRecentProvider.
diff --git a/README.md b/README.md
index f7630405..e7816de1 100644
--- a/README.md
+++ b/README.md
@@ -119,7 +119,7 @@ You can download the [latest snapshot release][download] or pick it up from Mave
com.amazonaws
aws-dynamodb-encryption-java
- 1.15.0
+ 2.0.0
```
diff --git a/examples/pom.xml b/examples/pom.xml
index 695f9534..b4c4a6be 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -8,12 +8,12 @@
software.amazon.cryptools
dynamodbencryptionclient-pom
- 1.15.0
+ 2.0.0
dynamodbencryptionclient-sdk1examples
jar
- 1.15.0
+ 2.0.0
aws-dynamodb-encryption-java :: SDK1 Examples
Examples for AWS DynamoDB Encryption Client for AWS Java SDK v1
@@ -27,7 +27,7 @@
com.amazonaws
aws-dynamodb-encryption-java
- 1.15.0
+ 2.0.0
diff --git a/pom.xml b/pom.xml
index 79ce1b75..059ed20f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
4.0.0
software.amazon.cryptools
dynamodbencryptionclient-pom
- 1.15.0
+ 2.0.0
pom
aws-dynamodb-encryption-java :: POM
diff --git a/sdk1/pom.xml b/sdk1/pom.xml
index 36d9325e..b902d530 100644
--- a/sdk1/pom.xml
+++ b/sdk1/pom.xml
@@ -6,7 +6,7 @@
4.0.0
com.amazonaws
aws-dynamodb-encryption-java
- 1.15.0
+ 2.0.0
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/encryption/providers/MostRecentProvider.java b/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/MostRecentProvider.java
deleted file mode 100644
index ce926f16..00000000
--- a/sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/MostRecentProvider.java
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-package com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers;
-
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.ReentrantLock;
-
-import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext;
-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.store.ProviderStore;
-import com.amazonaws.services.dynamodbv2.datamodeling.internal.LRUCache;
-
-import static com.amazonaws.services.dynamodbv2.datamodeling.internal.Utils.checkNotNull;
-
-/**
- * This meta-Provider encrypts data with the most recent version of keying materials from a
- * {@link ProviderStore} and decrypts using whichever version is appropriate. It also caches the
- * results from the {@link ProviderStore} to avoid excessive load on the backing systems. The cache
- * is not currently configurable.
- *
- * @deprecated This provider uses a TTL value to determine when to ping the keystore
- * to get the current materials version, instead of using the TTL value to determine
- * when to expire cached materials. This is unintuitive behavior for users of this provider
- * who may wish to use a TTL to force the keystore to re-obtain materials.
- *
- * Use the CachingMostRecentProvider, which uses a user defined TTL value to
- * also expire the cached materials themselves, forcing
- * the keystore to regularly re-obtain materials.
- */
-@Deprecated
-public class MostRecentProvider implements EncryptionMaterialsProvider {
- private static final long MILLI_TO_NANO = 1000000L;
- private static final long TTL_GRACE_IN_NANO = 500 * MILLI_TO_NANO;
- private final ProviderStore keystore;
- protected final String defaultMaterialName;
- private final long ttlInNanos;
- private final LRUCache cache;
- private final LRUCache currentVersions;
-
- /**
- * Creates a new {@link MostRecentProvider}.
- *
- * @param ttlInMillis
- * The length of time in milliseconds to cache the most recent provider
- */
- public MostRecentProvider(final ProviderStore keystore, final String materialName, final long ttlInMillis) {
- this.keystore = checkNotNull(keystore, "keystore must not be null");
- this.defaultMaterialName = materialName;
- this.ttlInNanos = ttlInMillis * MILLI_TO_NANO;
- this.cache = new LRUCache(1000);
- this.currentVersions = new LRUCache<>(1000);
- }
-
- @Override
- public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) {
- final String materialName = getMaterialName(context);
- final LockedState ls = getCurrentVersion(materialName);
-
- final State s = ls.getState();
- if (s.provider != null && System.nanoTime() - s.lastUpdated <= ttlInNanos) {
- return s.provider.getEncryptionMaterials(context);
- }
- if (s.provider == null || System.nanoTime() - s.lastUpdated > ttlInNanos + TTL_GRACE_IN_NANO) {
- // Either we don't have a provider at all, or we're more than 500 milliseconds past
- // our update time. Either way, grab the lock and force an update.
- ls.lock();
- } else if (!ls.tryLock()) {
- // If we can't get the lock immediately, just use the current provider
- return s.provider.getEncryptionMaterials(context);
- }
-
- try {
- final long newVersion = keystore.getMaxVersion(materialName);
- final long currentVersion;
- final EncryptionMaterialsProvider currentProvider;
- if (newVersion < 0) {
- // First version of the material, so we want to allow creation
- currentVersion = 0;
- currentProvider = keystore.getOrCreate(materialName, currentVersion);
- cache.add(buildCacheKey(materialName, currentVersion), currentProvider);
- } else if (newVersion != s.currentVersion) {
- // We're retrieving an existing version, so we avoid the creation
- // flow as it is slower
- currentVersion = newVersion;
- currentProvider = keystore.getProvider(materialName, currentVersion);
- cache.add(buildCacheKey(materialName, currentVersion), currentProvider);
- } else {
- // Our version hasn't changed, so we'll just re-use the existing
- // provider to avoid the overhead of retrieving and building a new one
- currentVersion = newVersion;
- currentProvider = s.provider;
- // There is no need to add this to the cache as it's already there
- }
-
- ls.update(currentProvider, currentVersion);
-
- return ls.getState().provider.getEncryptionMaterials(context);
- } finally {
- ls.unlock();
- }
- }
-
- public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) {
- final String materialName = getMaterialName(context);
- final long version = keystore.getVersionFromMaterialDescription(
- context.getMaterialDescription());
- EncryptionMaterialsProvider provider = cache.get(buildCacheKey(materialName, version));
- if (provider == null) {
- provider = keystore.getProvider(materialName, version);
- cache.add(buildCacheKey(materialName, version), provider);
- }
- return provider.getDecryptionMaterials(context);
- }
-
- /**
- * Completely empties the cache of both the current and old versions.
- */
- @Override
- public void refresh() {
- currentVersions.clear();
- cache.clear();
- }
-
- public String getMaterialName() {
- return defaultMaterialName;
- }
-
- public long getTtlInMills() {
- return ttlInNanos / MILLI_TO_NANO;
- }
-
- /**
- * The current version of the materials being used for encryption. Returns -1 if we do not
- * currently have a current version.
- */
- public long getCurrentVersion() {
- return getCurrentVersion(getMaterialName()).getState().currentVersion;
- }
-
- /**
- * The last time the current version was updated. Returns 0 if we do not currently have a
- * current version.
- */
- public long getLastUpdated() {
- return getCurrentVersion(getMaterialName()).getState().lastUpdated / MILLI_TO_NANO;
- }
-
- protected String getMaterialName(final EncryptionContext context) {
- return defaultMaterialName;
- }
-
- private LockedState getCurrentVersion(final String materialName) {
- final LockedState result = currentVersions.get(materialName);
- if (result == null) {
- currentVersions.add(materialName, new LockedState());
- return currentVersions.get(materialName);
- } else {
- return result;
- }
- }
-
- private static String buildCacheKey(final String materialName, final long version) {
- StringBuilder result = new StringBuilder(materialName);
- result.append('#');
- result.append(version);
- return result.toString();
- }
-
- private static class LockedState {
- private final ReentrantLock lock = new ReentrantLock(true);
- private volatile AtomicReference state = new AtomicReference<>(new State());
-
- public State getState() {
- return state.get();
- }
-
- public void unlock() {
- lock.unlock();
- }
-
- public boolean tryLock() {
- return lock.tryLock();
- }
-
- public void lock() {
- lock.lock();
- }
-
- public void update(EncryptionMaterialsProvider provider, long currentVersion) {
- if (!lock.isHeldByCurrentThread()) {
- throw new IllegalStateException("Lock not held by current thread");
- }
- state.set(new State(provider, currentVersion));
- }
- }
-
- private static class State {
- public final EncryptionMaterialsProvider provider;
- public final long currentVersion;
- public final long lastUpdated;
-
- public State() {
- this(null, -1);
- }
-
- public State(EncryptionMaterialsProvider provider, long currentVersion) {
- this.provider = provider;
- this.currentVersion = currentVersion;
- this.lastUpdated = currentVersion == -1 ? 0 : System.nanoTime();
- }
- }
-}
diff --git a/sdk1/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/MostRecentProviderTests.java b/sdk1/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/MostRecentProviderTests.java
deleted file mode 100644
index ac5dda84..00000000
--- a/sdk1/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/MostRecentProviderTests.java
+++ /dev/null
@@ -1,544 +0,0 @@
-// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
-// SPDX-License-Identifier: Apache-2.0
-package com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers;
-
-import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
-import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor;
-import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext;
-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.store.MetaStore;
-import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.store.ProviderStore;
-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;
-
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertNull;
-import static org.testng.AssertJUnit.assertTrue;
-
-@SuppressWarnings("deprecation")
-public class MostRecentProviderTests {
- private static final String TABLE_NAME = "keystoreTable";
- private static final String MATERIAL_NAME = "material";
- private static final String MATERIAL_PARAM = "materialName";
- private static final SecretKey AES_KEY = new SecretKeySpec(new byte[]{0,
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, "AES");
- private static final SecretKey HMAC_KEY = new SecretKeySpec(new byte[]{0,
- 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256");
- private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY);
- private static final DynamoDBEncryptor ENCRYPTOR = DynamoDBEncryptor.getInstance(BASE_PROVIDER);
-
- private AmazonDynamoDB client;
- private Map methodCalls;
- private ProviderStore store;
- private EncryptionContext ctx;
-
- @BeforeMethod
- public void setup() {
- methodCalls = new HashMap();
- client = instrument(DynamoDBEmbedded.create(), AmazonDynamoDB.class, methodCalls);
- MetaStore.createTable(client, TABLE_NAME, new ProvisionedThroughput(1L, 1L));
- store = new MetaStore(client, TABLE_NAME, ENCRYPTOR);
- ctx = new EncryptionContext.Builder().build();
- methodCalls.clear();
- }
-
- @Test
- public void constructor() {
- final MostRecentProvider prov = new MostRecentProvider(store, MATERIAL_NAME, 100);
- assertEquals(MATERIAL_NAME, prov.getMaterialName());
- assertEquals(100, prov.getTtlInMills());
- assertEquals(-1, prov.getCurrentVersion());
- assertEquals(0, prov.getLastUpdated());
- }
-
- @Test
- public void singleVersion() throws InterruptedException {
- final MostRecentProvider prov = new MostRecentProvider(store, MATERIAL_NAME, 500);
- assertNull(methodCalls.get("putItem"));
- final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- // Ensure the cache is working
- final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx);
- assertTrue(methodCalls.isEmpty());
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription()));
- // Let the TTL be exceeded
- Thread.sleep(500);
- final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx);
- assertEquals(1, methodCalls.size());
- assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version
- assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription()));
-
- assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey());
- assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey());
- // Check algorithms. Right now we only support AES and HmacSHA256
- assertEquals("AES", eMat1.getEncryptionKey().getAlgorithm());
- assertEquals("HmacSHA256", eMat1.getSigningKey().getAlgorithm());
-
- // Ensure we can decrypt all of them without hitting ddb more than the minimum
- final MostRecentProvider prov2 = new MostRecentProvider(store, MATERIAL_NAME, 500);
- final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1));
- methodCalls.clear();
- assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey());
- assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey());
- final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2));
- assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey());
- assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey());
- final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3));
- assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey());
- assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey());
- assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty());
- }
-
- @Test
- public void singleVersionWithRefresh() throws InterruptedException {
- final MostRecentProvider prov = new MostRecentProvider(store, MATERIAL_NAME, 500);
- assertNull(methodCalls.get("putItem"));
- final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- // Ensure the cache is working
- final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx);
- assertTrue(methodCalls.isEmpty());
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription()));
- prov.refresh();
- final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx);
- assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version
- assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription()));
- prov.refresh();
-
- assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey());
- assertEquals(eMat1.getSigningKey(), eMat3.getSigningKey());
-
- // Ensure that after cache refresh we only get one more hit as opposed to multiple
- prov.getEncryptionMaterials(ctx);
- Thread.sleep(700);
- // Force refresh
- prov.getEncryptionMaterials(ctx);
- methodCalls.clear();
- // Check to ensure no more hits
- assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey());
- assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey());
- assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey());
- assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey());
- assertEquals(eMat1.getSigningKey(), prov.getEncryptionMaterials(ctx).getSigningKey());
- assertTrue(methodCalls.isEmpty());
-
- // Ensure we can decrypt all of them without hitting ddb more than the minimum
- final MostRecentProvider prov2 = new MostRecentProvider(store, MATERIAL_NAME, 500);
- final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1));
- methodCalls.clear();
- assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey());
- assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey());
- final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2));
- assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey());
- assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey());
- final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3));
- assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey());
- assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey());
- assertTrue(methodCalls.isEmpty());
- }
-
- @Test
- public void twoVersions() throws InterruptedException {
- final MostRecentProvider prov = new MostRecentProvider(store, MATERIAL_NAME, 500);
- assertNull(methodCalls.get("putItem"));
- final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- // Create the new material
- store.newProvider(MATERIAL_NAME);
- methodCalls.clear();
-
- // Ensure the cache is working
- final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx);
- assertTrue(methodCalls.isEmpty());
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription()));
- assertTrue(methodCalls.isEmpty());
- // Let the TTL be exceeded
- Thread.sleep(500);
- final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx);
-
- assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version
- assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0)); // To retrieve current version
- assertNull(methodCalls.get("putItem")); // No attempt to create a new item
- assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription()));
-
- assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey());
- assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey()));
-
- // Ensure we can decrypt all of them without hitting ddb more than the minimum
- final MostRecentProvider prov2 = new MostRecentProvider(store, MATERIAL_NAME, 500);
- final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1));
- methodCalls.clear();
- assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey());
- assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey());
- final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2));
- assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey());
- assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey());
- final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3));
- assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey());
- assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey());
- // Get item will be hit once for the one old key
- assertEquals(1, methodCalls.size());
- assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0));
- }
-
- @Test
- public void twoVersionsWithRefresh() throws InterruptedException {
- final MostRecentProvider prov = new MostRecentProvider(store, MATERIAL_NAME, 100);
- assertNull(methodCalls.get("putItem"));
- final EncryptionMaterials eMat1 = prov.getEncryptionMaterials(ctx);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- // Create the new material
- store.newProvider(MATERIAL_NAME);
- methodCalls.clear();
- // Ensure the cache is working
- final EncryptionMaterials eMat2 = prov.getEncryptionMaterials(ctx);
- assertTrue(methodCalls.isEmpty());
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2.getMaterialDescription()));
- prov.refresh();
- final EncryptionMaterials eMat3 = prov.getEncryptionMaterials(ctx);
- assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version
- assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0));
- assertEquals(1, store.getVersionFromMaterialDescription(eMat3.getMaterialDescription()));
-
- assertEquals(eMat1.getSigningKey(), eMat2.getSigningKey());
- assertFalse(eMat1.getSigningKey().equals(eMat3.getSigningKey()));
-
- // Ensure we can decrypt all of them without hitting ddb more than the minimum
- final MostRecentProvider prov2 = new MostRecentProvider(store, MATERIAL_NAME, 500);
- final DecryptionMaterials dMat1 = prov2.getDecryptionMaterials(ctx(eMat1));
- methodCalls.clear();
- assertEquals(eMat1.getEncryptionKey(), dMat1.getDecryptionKey());
- assertEquals(eMat1.getSigningKey(), dMat1.getVerificationKey());
- final DecryptionMaterials dMat2 = prov2.getDecryptionMaterials(ctx(eMat2));
- assertEquals(eMat2.getEncryptionKey(), dMat2.getDecryptionKey());
- assertEquals(eMat2.getSigningKey(), dMat2.getVerificationKey());
- final DecryptionMaterials dMat3 = prov2.getDecryptionMaterials(ctx(eMat3));
- assertEquals(eMat3.getEncryptionKey(), dMat3.getDecryptionKey());
- assertEquals(eMat3.getSigningKey(), dMat3.getVerificationKey());
- // Get item will be hit once for the one old key
- assertEquals(1, methodCalls.size());
- assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0));
- }
-
- @Test
- public void singleVersionTwoMaterials() throws InterruptedException {
- final Map attr1 = Collections.singletonMap(MATERIAL_PARAM, new AttributeValue("material1"));
- final EncryptionContext ctx1 = ctx(attr1);
- final Map attr2 = Collections.singletonMap(MATERIAL_PARAM, new AttributeValue("material2"));
- final EncryptionContext ctx2 = ctx(attr2);
-
- final MostRecentProvider prov = new ExtendedProvider(store, 500);
- assertNull(methodCalls.get("putItem"));
- final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- // Ensure the two materials are, in fact, different
- assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey()));
-
- // Ensure the cache is working
- final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1);
- assertTrue(methodCalls.isEmpty());
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription()));
- final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2);
- assertTrue(methodCalls.isEmpty());
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription()));
-
- // Let the TTL be exceeded
- Thread.sleep(500);
- final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1);
- assertEquals(1, methodCalls.size());
- assertEquals(1, (int) methodCalls.get("query")); // To find current version
- assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription()));
- methodCalls.clear();
- final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2);
- assertEquals(1, methodCalls.size());
- assertEquals(1, (int) methodCalls.get("query")); // To find current version
- assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription()));
-
- assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey());
- assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey());
- assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey());
- assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey());
- // Check algorithms. Right now we only support AES and HmacSHA256
- assertEquals("AES", eMat1_1.getEncryptionKey().getAlgorithm());
- assertEquals("AES", eMat1_2.getEncryptionKey().getAlgorithm());
- assertEquals("HmacSHA256", eMat1_1.getSigningKey().getAlgorithm());
- assertEquals("HmacSHA256", eMat1_2.getSigningKey().getAlgorithm());
-
- // Ensure we can decrypt all of them without hitting ddb more than the minimum
- final MostRecentProvider prov2 = new ExtendedProvider(store, 500);
- final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1));
- final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2));
- methodCalls.clear();
- assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey());
- assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey());
- assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey());
- assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey());
- final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1));
- final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2));
- assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey());
- assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey());
- assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey());
- assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey());
- final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1));
- final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2));
- assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey());
- assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey());
- assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey());
- assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey());
- assertTrue("Expected no calls but was " + methodCalls.toString(), methodCalls.isEmpty());
- }
-
- @Test
- public void singleVersionWithTwoMaterialsWithRefresh() throws InterruptedException {
- final Map attr1 = Collections.singletonMap(MATERIAL_PARAM, new AttributeValue("material1"));
- final EncryptionContext ctx1 = ctx(attr1);
- final Map attr2 = Collections.singletonMap(MATERIAL_PARAM, new AttributeValue("material2"));
- final EncryptionContext ctx2 = ctx(attr2);
-
- final MostRecentProvider prov = new ExtendedProvider(store, 500);
- assertNull(methodCalls.get("putItem"));
- final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- // Ensure the two materials are, in fact, different
- assertFalse(eMat1_1.getSigningKey().equals(eMat1_2.getSigningKey()));
-
- // Ensure the cache is working
- final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1);
- assertTrue(methodCalls.isEmpty());
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription()));
- final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2);
- assertTrue(methodCalls.isEmpty());
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription()));
-
- prov.refresh();
- final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1);
- assertEquals(1, (int) methodCalls.getOrDefault("query", 0)); // To find current version
- assertEquals(1, (int) methodCalls.getOrDefault("getItem", 0));
- final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2);
- assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version
- assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription()));
- prov.refresh();
-
- assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey());
- assertEquals(eMat1_1.getSigningKey(), eMat3_1.getSigningKey());
- assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey());
- assertEquals(eMat1_2.getSigningKey(), eMat3_2.getSigningKey());
-
- // Ensure that after cache refresh we only get one more hit as opposed to multiple
- prov.getEncryptionMaterials(ctx1);
- prov.getEncryptionMaterials(ctx2);
- Thread.sleep(700);
- // Force refresh
- prov.getEncryptionMaterials(ctx1);
- prov.getEncryptionMaterials(ctx2);
- methodCalls.clear();
- // Check to ensure no more hits
- assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey());
- assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey());
- assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey());
- assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey());
- assertEquals(eMat1_1.getSigningKey(), prov.getEncryptionMaterials(ctx1).getSigningKey());
-
- assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey());
- assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey());
- assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey());
- assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey());
- assertEquals(eMat1_2.getSigningKey(), prov.getEncryptionMaterials(ctx2).getSigningKey());
- assertTrue(methodCalls.isEmpty());
-
- // Ensure we can decrypt all of them without hitting ddb more than the minimum
- final MostRecentProvider prov2 = new ExtendedProvider(store, 500);
- final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1));
- final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2));
- methodCalls.clear();
- assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey());
- assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey());
- assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey());
- assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey());
- final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1));
- final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2));
- assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey());
- assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey());
- assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey());
- assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey());
- final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1));
- final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2));
- assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey());
- assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey());
- assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey());
- assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey());
- assertTrue(methodCalls.isEmpty());
- }
-
- @Test
- public void twoVersionsWithTwoMaterialsWithRefresh() throws InterruptedException {
- final Map attr1 = Collections.singletonMap(MATERIAL_PARAM, new AttributeValue("material1"));
- final EncryptionContext ctx1 = ctx(attr1);
- final Map attr2 = Collections.singletonMap(MATERIAL_PARAM, new AttributeValue("material2"));
- final EncryptionContext ctx2 = ctx(attr2);
-
- final MostRecentProvider prov = new ExtendedProvider(store, 500);
- assertNull(methodCalls.get("putItem"));
- final EncryptionMaterials eMat1_1 = prov.getEncryptionMaterials(ctx1);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- final EncryptionMaterials eMat1_2 = prov.getEncryptionMaterials(ctx2);
- // It's a new provider, so we see a single putItem
- assertEquals(1, (int) methodCalls.getOrDefault("putItem", 0));
- methodCalls.clear();
- // Create the new material
- store.newProvider("material1");
- store.newProvider("material2");
- methodCalls.clear();
- // Ensure the cache is working
- final EncryptionMaterials eMat2_1 = prov.getEncryptionMaterials(ctx1);
- final EncryptionMaterials eMat2_2 = prov.getEncryptionMaterials(ctx2);
- assertTrue(methodCalls.isEmpty());
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1_1.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2_1.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat1_2.getMaterialDescription()));
- assertEquals(0, store.getVersionFromMaterialDescription(eMat2_2.getMaterialDescription()));
- prov.refresh();
- final EncryptionMaterials eMat3_1 = prov.getEncryptionMaterials(ctx1);
- final EncryptionMaterials eMat3_2 = prov.getEncryptionMaterials(ctx2);
- assertEquals(2, (int) methodCalls.getOrDefault("query", 0)); // To find current version
- assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0));
- assertEquals(1, store.getVersionFromMaterialDescription(eMat3_1.getMaterialDescription()));
- assertEquals(1, store.getVersionFromMaterialDescription(eMat3_2.getMaterialDescription()));
-
- assertEquals(eMat1_1.getSigningKey(), eMat2_1.getSigningKey());
- assertFalse(eMat1_1.getSigningKey().equals(eMat3_1.getSigningKey()));
- assertEquals(eMat1_2.getSigningKey(), eMat2_2.getSigningKey());
- assertFalse(eMat1_2.getSigningKey().equals(eMat3_2.getSigningKey()));
-
- // Ensure we can decrypt all of them without hitting ddb more than the minimum
- final MostRecentProvider prov2 = new ExtendedProvider(store, 500);
- final DecryptionMaterials dMat1_1 = prov2.getDecryptionMaterials(ctx(eMat1_1, attr1));
- final DecryptionMaterials dMat1_2 = prov2.getDecryptionMaterials(ctx(eMat1_2, attr2));
- methodCalls.clear();
- assertEquals(eMat1_1.getEncryptionKey(), dMat1_1.getDecryptionKey());
- assertEquals(eMat1_2.getEncryptionKey(), dMat1_2.getDecryptionKey());
- assertEquals(eMat1_1.getSigningKey(), dMat1_1.getVerificationKey());
- assertEquals(eMat1_2.getSigningKey(), dMat1_2.getVerificationKey());
- final DecryptionMaterials dMat2_1 = prov2.getDecryptionMaterials(ctx(eMat2_1, attr1));
- final DecryptionMaterials dMat2_2 = prov2.getDecryptionMaterials(ctx(eMat2_2, attr2));
- assertEquals(eMat2_1.getEncryptionKey(), dMat2_1.getDecryptionKey());
- assertEquals(eMat2_2.getEncryptionKey(), dMat2_2.getDecryptionKey());
- assertEquals(eMat2_1.getSigningKey(), dMat2_1.getVerificationKey());
- assertEquals(eMat2_2.getSigningKey(), dMat2_2.getVerificationKey());
- final DecryptionMaterials dMat3_1 = prov2.getDecryptionMaterials(ctx(eMat3_1, attr1));
- final DecryptionMaterials dMat3_2 = prov2.getDecryptionMaterials(ctx(eMat3_2, attr2));
- assertEquals(eMat3_1.getEncryptionKey(), dMat3_1.getDecryptionKey());
- assertEquals(eMat3_2.getEncryptionKey(), dMat3_2.getDecryptionKey());
- assertEquals(eMat3_1.getSigningKey(), dMat3_1.getVerificationKey());
- assertEquals(eMat3_2.getSigningKey(), dMat3_2.getVerificationKey());
- // Get item will be hit twice, once for each old key
- assertEquals(1, methodCalls.size());
- assertEquals(2, (int) methodCalls.getOrDefault("getItem", 0));
- }
-
- private static EncryptionContext ctx(final Map attr) {
- return new EncryptionContext.Builder()
- .withAttributeValues(attr).build();
- }
-
- private static EncryptionContext ctx(final EncryptionMaterials mat, Map attr) {
- return new EncryptionContext.Builder()
- .withAttributeValues(attr)
- .withMaterialDescription(mat.getMaterialDescription()).build();
- }
-
- private static EncryptionContext ctx(final EncryptionMaterials mat) {
- return new EncryptionContext.Builder()
- .withMaterialDescription(mat.getMaterialDescription()).build();
- }
-
- private static class ExtendedProvider extends MostRecentProvider {
- public ExtendedProvider(ProviderStore keystore, long ttlInMillis) {
- super(keystore, null, ttlInMillis);
- }
-
- @Override
- public long getCurrentVersion() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- protected String getMaterialName(final EncryptionContext context) {
- return context.getAttributeValues().get(MATERIAL_PARAM).getS();
- }
- }
-
- @SuppressWarnings("unchecked")
- private static T instrument(final T obj, final Class clazz, final Map map) {
- return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
- new InvocationHandler() {
- private final Object lock = new Object();
-
- @Override
- public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
- synchronized (lock) {
- try {
- final Integer oldCount = map.get(method.getName());
- if (oldCount != null) {
- map.put(method.getName(), oldCount + 1);
- } else {
- map.put(method.getName(), 1);
- }
- return method.invoke(obj, args);
- } catch (final InvocationTargetException ex) {
- throw ex.getCause();
- }
- }
- }
- }
- );
- }
-}