From 555ed324e1fde72f235426c4b4523e465a2074a7 Mon Sep 17 00:00:00 2001 From: Valerie Lambert Date: Wed, 3 Feb 2021 17:14:06 -0800 Subject: [PATCH] fix!: Remove the MostRecentProvider. BREAKING CHANGE: Removes the MostRecentProvider, which is replaced by the CachingMostRecentProvider. --- CHANGELOG.md | 4 + README.md | 2 +- examples/pom.xml | 6 +- pom.xml | 2 +- sdk1/pom.xml | 2 +- .../providers/MostRecentProvider.java | 213 ------- .../providers/MostRecentProviderTests.java | 544 ------------------ 7 files changed, 10 insertions(+), 763 deletions(-) delete mode 100644 sdk1/src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/MostRecentProvider.java delete mode 100644 sdk1/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/MostRecentProviderTests.java 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(); - } - } - } - } - ); - } -}