Skip to content

Commit 1ad4783

Browse files
Improve synchronization logic
1 parent f87cf5a commit 1ad4783

File tree

13 files changed

+118
-124
lines changed

13 files changed

+118
-124
lines changed

src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBEncryptor.java

+7-14
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.nio.charset.Charset;
2424
import java.security.GeneralSecurityException;
2525
import java.security.PrivateKey;
26-
import java.security.SecureRandom;
2726
import java.security.SignatureException;
2827
import java.util.Arrays;
2928
import java.util.Collection;
@@ -43,6 +42,7 @@
4342
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.EncryptionMaterialsProvider;
4443
import com.amazonaws.services.dynamodbv2.datamodeling.internal.AttributeValueMarshaller;
4544
import com.amazonaws.services.dynamodbv2.datamodeling.internal.ByteBufferInputStream;
45+
import com.amazonaws.services.dynamodbv2.datamodeling.internal.Utils;
4646
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
4747

4848
/**
@@ -52,7 +52,6 @@
5252
* @author Greg Rubin
5353
*/
5454
public class DynamoDBEncryptor {
55-
private static final SecureRandom rnd = new SecureRandom();
5655
private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA";
5756
private static final String DEFAULT_METADATA_FIELD = "*amzn-ddb-map-desc*";
5857
private static final String DEFAULT_SIGNATURE_FIELD = "*amzn-ddb-map-sig*";
@@ -233,7 +232,7 @@ public Map<String, AttributeValue> decryptRecord(
233232
DecryptionMaterials materials;
234233
SecretKey decryptionKey;
235234

236-
DynamoDBSigner signer = DynamoDBSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, rnd);
235+
DynamoDBSigner signer = DynamoDBSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng());
237236

238237
if (itemAttributes.containsKey(materialDescriptionFieldName)) {
239238
materialDescription = unmarshallDescription(itemAttributes.get(materialDescriptionFieldName));
@@ -248,7 +247,7 @@ public Map<String, AttributeValue> decryptRecord(
248247
decryptionKey = materials.getDecryptionKey();
249248
if (materialDescription.containsKey(signingAlgorithmHeader)) {
250249
String signingAlg = materialDescription.get(signingAlgorithmHeader);
251-
signer = DynamoDBSigner.getInstance(signingAlg, rnd);
250+
signer = DynamoDBSigner.getInstance(signingAlg, Utils.getRng());
252251
}
253252

254253
ByteBuffer signature;
@@ -309,7 +308,7 @@ public Map<String, AttributeValue> encryptRecord(
309308
// The description must be stored after encryption because its data
310309
// is necessary for proper decryption.
311310
final String signingAlgo = materialDescription.get(signingAlgorithmHeader);
312-
DynamoDBSigner signer = DynamoDBSigner.getInstance(signingAlgo == null ? DEFAULT_SIGNATURE_ALGORITHM : signingAlgo, rnd);
311+
DynamoDBSigner signer = DynamoDBSigner.getInstance(signingAlgo == null ? DEFAULT_SIGNATURE_ALGORITHM : signingAlgo, Utils.getRng());
313312

314313
if (materials.getSigningKey() instanceof PrivateKey ) {
315314
materialDescription.put(signingAlgorithmHeader, signer.getSigningAlgorithm());
@@ -356,7 +355,7 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
356355
}
357356
byte[] iv = new byte[ivSize];
358357
cipherText.get(iv);
359-
cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv), rnd);
358+
cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng());
360359
plainText = ByteBuffer.allocate(
361360
cipher.getOutputSize(cipherText.remaining()));
362361
cipher.doFinal(cipherText, plainText);
@@ -406,8 +405,8 @@ private void actualEncryption(Map<String, AttributeValue> itemAttributes,
406405
}
407406
// Encryption format: <iv><ciphertext>
408407
// Note a unique iv is generated per attribute
409-
byte[] iv = getRandom(ivSize);
410-
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new IvParameterSpec(iv), rnd);
408+
byte[] iv = Utils.getRandom(ivSize);
409+
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng());
411410
cipherText = ByteBuffer.allocate(ivSize + cipher.getOutputSize(plainText.remaining()));
412411
cipherText.put(iv);
413412
cipher.doFinal(plainText, cipherText);
@@ -535,12 +534,6 @@ protected static Map<String, String> unmarshallDescription(AttributeValue attrib
535534
attributeValue.getB().reset();
536535
}
537536
}
538-
539-
private static byte[] getRandom(int bytes) {
540-
byte[] result = new byte[bytes];
541-
rnd.nextBytes(result);
542-
return result;
543-
}
544537

545538
private static byte[] toByteArray(ByteBuffer buffer) {
546539
if (buffer.hasArray()) {

src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBSigner.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import javax.crypto.spec.SecretKeySpec;
4040

4141
import com.amazonaws.services.dynamodbv2.datamodeling.internal.AttributeValueMarshaller;
42+
import com.amazonaws.services.dynamodbv2.datamodeling.internal.Utils;
4243
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
4344

4445
/**
@@ -77,7 +78,7 @@ static DynamoDBSigner getInstance(String signingAlgorithm, SecureRandom rnd) {
7778
*/
7879
private DynamoDBSigner(String signingAlgorithm, SecureRandom rnd) {
7980
if (rnd == null) {
80-
rnd = new SecureRandom();
81+
rnd = Utils.getRng();
8182
}
8283
this.rnd = rnd;
8384
this.signingAlgorithm = signingAlgorithm;

src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/materials/WrappedRawMaterials.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.security.Key;
2020
import java.security.KeyPair;
2121
import java.security.NoSuchAlgorithmException;
22-
import java.security.SecureRandom;
2322
import java.util.Collections;
2423
import java.util.Map;
2524

@@ -30,6 +29,7 @@
3029
import javax.crypto.SecretKey;
3130

3231
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DelegatedKey;
32+
import com.amazonaws.services.dynamodbv2.datamodeling.internal.Utils;
3333
import com.amazonaws.util.Base64;
3434

3535
/**
@@ -63,7 +63,6 @@ public class WrappedRawMaterials extends AbstractRawMaterials {
6363
*/
6464
public static final String ENVELOPE_KEY = "amzn-ddb-env-key";
6565
private static final String DEFAULT_ALGORITHM = "AES/256";
66-
private static final SecureRandom rand = new SecureRandom();
6766

6867
protected final Key wrappingKey;
6968
protected final Key unwrappingKey;
@@ -151,7 +150,7 @@ public byte[] wrapKey(SecretKey key, String wrappingAlg) throws NoSuchAlgorithmE
151150
return ((DelegatedKey)wrappingKey).wrap(key, null, wrappingAlg);
152151
} else {
153152
Cipher cipher = Cipher.getInstance(wrappingAlg);
154-
cipher.init(Cipher.WRAP_MODE, wrappingKey, rand);
153+
cipher.init(Cipher.WRAP_MODE, wrappingKey, Utils.getRng());
155154
byte[] encryptedKey = cipher.wrap(key);
156155
return encryptedKey;
157156
}
@@ -164,7 +163,7 @@ protected SecretKey unwrapKey(Map<String, String> description, byte[] encryptedK
164163
description.get(CONTENT_KEY_ALGORITHM), Cipher.SECRET_KEY, null, wrappingAlgorithm);
165164
} else {
166165
Cipher cipher = Cipher.getInstance(wrappingAlgorithm);
167-
cipher.init(Cipher.UNWRAP_MODE, unwrappingKey, rand);
166+
cipher.init(Cipher.UNWRAP_MODE, unwrappingKey, Utils.getRng());
168167
return (SecretKey) cipher.unwrap(encryptedKey,
169168
description.get(CONTENT_KEY_ALGORITHM), Cipher.SECRET_KEY);
170169
}
@@ -183,9 +182,9 @@ protected SecretKey generateContentKey(final String algorithm) throws NoSuchAlgo
183182
}
184183

185184
if (keyLen > 0) {
186-
kg.init(keyLen, rand);
185+
kg.init(keyLen, Utils.getRng());
187186
} else {
188-
kg.init(rand);
187+
kg.init(Utils.getRng());
189188
}
190189
return kg.generateKey();
191190
}

src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/MostRecentProvider.java

+62-36
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
*/
1313
package com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers;
1414

15-
import com.amazonaws.services.dynamodbv2.datamodeling.internal.LRUCache;
15+
import java.util.concurrent.atomic.AtomicReference;
16+
import java.util.concurrent.locks.ReentrantLock;
17+
1618
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext;
1719
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.materials.DecryptionMaterials;
1820
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.materials.EncryptionMaterials;
1921
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.store.ProviderStore;
22+
import com.amazonaws.services.dynamodbv2.datamodeling.internal.LRUCache;
2023

2124
/**
2225
* This meta-Provider encrypts data with the most recent version of keying materials from a
@@ -25,14 +28,14 @@
2528
* is not currently configurable.
2629
*/
2730
public class MostRecentProvider implements EncryptionMaterialsProvider {
28-
private final Object lock;
31+
private static final long MILLI_TO_NANO = 1000000L;
32+
private static final long TTL_GRACE_IN_NANO = 500 * MILLI_TO_NANO;
33+
private final ReentrantLock lock = new ReentrantLock(true);
2934
private final ProviderStore keystore;
3035
private final String materialName;
31-
private final long ttlInMillis;
36+
private final long ttlInNanos;
3237
private final LRUCache<EncryptionMaterialsProvider> cache;
33-
private EncryptionMaterialsProvider currentProvider;
34-
private long currentVersion;
35-
private long lastUpdated;
38+
private final AtomicReference<State> state = new AtomicReference<>(new State());
3639

3740
/**
3841
* Creates a new {@link MostRecentProvider}.
@@ -43,30 +46,45 @@ public class MostRecentProvider implements EncryptionMaterialsProvider {
4346
public MostRecentProvider(final ProviderStore keystore, final String materialName, final long ttlInMillis) {
4447
this.keystore = checkNotNull(keystore, "keystore must not be null");
4548
this.materialName = checkNotNull(materialName, "materialName must not be null");
46-
this.ttlInMillis = ttlInMillis;
49+
this.ttlInNanos = ttlInMillis * MILLI_TO_NANO;
4750
this.cache = new LRUCache<EncryptionMaterialsProvider>(1000);
48-
this.lock = new Object();
49-
currentProvider = null;
50-
currentVersion = -1;
51-
lastUpdated = 0;
5251
}
5352

5453
@Override
5554
public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) {
56-
synchronized (lock) {
57-
if ((System.currentTimeMillis() - lastUpdated) > ttlInMillis) {
58-
long newVersion = keystore.getMaxVersion(materialName);
59-
if (newVersion < 0) {
60-
currentVersion = 0;
61-
currentProvider = keystore.getOrCreate(materialName, currentVersion);
62-
} else if (newVersion != currentVersion) {
63-
currentVersion = newVersion;
64-
currentProvider = keystore.getProvider(materialName, currentVersion);
65-
cache.add(Long.toString(currentVersion), currentProvider);
66-
}
67-
lastUpdated = System.currentTimeMillis();
55+
State s = state.get();
56+
if (System.nanoTime() - s.lastUpdated <= ttlInNanos) {
57+
return s.provider.getEncryptionMaterials(context);
58+
}
59+
if (s.provider == null || System.nanoTime() - s.lastUpdated > ttlInNanos + TTL_GRACE_IN_NANO) {
60+
// Either we don't have a provider at all, or we're more than 500 milliseconds past
61+
// our update time. Either way, grab the lock and force an update.
62+
lock.lock();
63+
} else if (!lock.tryLock()) {
64+
// If we can't get the lock immediately, just use the current provider
65+
return s.provider.getEncryptionMaterials(context);
66+
}
67+
68+
try {
69+
final long newVersion = keystore.getMaxVersion(materialName);
70+
final long currentVersion;
71+
final EncryptionMaterialsProvider currentProvider;
72+
if (newVersion < 0) {
73+
currentVersion = 0;
74+
currentProvider = keystore.getOrCreate(materialName, currentVersion);
75+
s = new State(currentProvider, currentVersion);
76+
state.set(s);
77+
} else if (newVersion != s.currentVersion) {
78+
currentVersion = newVersion;
79+
currentProvider = keystore.getProvider(materialName, currentVersion);
80+
cache.add(Long.toString(currentVersion), currentProvider);
81+
s = new State(currentProvider, currentVersion);
82+
state.set(s);
6883
}
69-
return currentProvider.getEncryptionMaterials(context);
84+
85+
return s.provider.getEncryptionMaterials(context);
86+
} finally {
87+
lock.unlock();
7088
}
7189
}
7290

@@ -86,11 +104,7 @@ public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) {
86104
*/
87105
@Override
88106
public void refresh() {
89-
synchronized (lock) {
90-
lastUpdated = 0;
91-
currentVersion = -1;
92-
currentProvider = null;
93-
}
107+
state.set(new State());
94108
cache.clear();
95109
}
96110

@@ -99,27 +113,23 @@ public String getMaterialName() {
99113
}
100114

101115
public long getTtlInMills() {
102-
return ttlInMillis;
116+
return ttlInNanos / MILLI_TO_NANO;
103117
}
104118

105119
/**
106120
* The current version of the materials being used for encryption. Returns -1 if we do not
107121
* currently have a current version.
108122
*/
109123
public long getCurrentVersion() {
110-
synchronized (lock) {
111-
return currentVersion;
112-
}
124+
return state.get().currentVersion;
113125
}
114126

115127
/**
116128
* The last time the current version was updated. Returns 0 if we do not currently have a
117129
* current version.
118130
*/
119131
public long getLastUpdated() {
120-
synchronized (lock) {
121-
return lastUpdated;
122-
}
132+
return state.get().lastUpdated / MILLI_TO_NANO;
123133
}
124134

125135
private static <V> V checkNotNull(final V ref, final String errMsg) {
@@ -129,4 +139,20 @@ private static <V> V checkNotNull(final V ref, final String errMsg) {
129139
return ref;
130140
}
131141
}
142+
143+
private static class State {
144+
public final EncryptionMaterialsProvider provider;
145+
public final long currentVersion;
146+
public final long lastUpdated;
147+
148+
public State() {
149+
this(null, -1);
150+
}
151+
152+
public State(EncryptionMaterialsProvider provider, long currentVersion) {
153+
this.provider = provider;
154+
this.currentVersion = currentVersion;
155+
this.lastUpdated = currentVersion == -1 ? 0 : System.nanoTime();
156+
}
157+
}
132158
}

src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/store/MetaStore.java

+3-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import java.nio.ByteBuffer;
1616
import java.security.GeneralSecurityException;
17-
import java.security.SecureRandom;
1817
import java.util.Arrays;
1918
import java.util.Collections;
2019
import java.util.HashMap;
@@ -32,6 +31,7 @@
3231
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext;
3332
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.EncryptionMaterialsProvider;
3433
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.providers.WrappedMaterialsProvider;
34+
import com.amazonaws.services.dynamodbv2.datamodeling.internal.Utils;
3535
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
3636
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
3737
import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
@@ -70,7 +70,6 @@ public class MetaStore extends ProviderStore {
7070
private static final String DEFAULT_HASH_KEY = "N";
7171
private static final String DEFAULT_RANGE_KEY = "V";
7272

73-
private final SecureRandom rng = new SecureRandom();
7473
private final Map<String, ExpectedAttributeValue> doesNotExist;
7574
private final String tableName;
7675
private final AmazonDynamoDB ddb;
@@ -106,12 +105,8 @@ public EncryptionMaterialsProvider getProvider(final String materialName, final
106105

107106
@Override
108107
public EncryptionMaterialsProvider getOrCreate(final String materialName, final long nextId) {
109-
final byte[] rawEncKey = new byte[32];
110-
rng.nextBytes(rawEncKey);
111-
final byte[] rawIntKey = new byte[32];
112-
rng.nextBytes(rawIntKey);
113-
final SecretKeySpec encryptionKey = new SecretKeySpec(rawEncKey, DEFAULT_ENCRYPTION);
114-
final SecretKeySpec integrityKey = new SecretKeySpec(rawIntKey, DEFAULT_INTEGRITY);
108+
final SecretKeySpec encryptionKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_ENCRYPTION);
109+
final SecretKeySpec integrityKey = new SecretKeySpec(Utils.getRandom(32), DEFAULT_INTEGRITY);
115110
final Map<String, AttributeValue> ciphertext = conditionalPut(encryptKeys(materialName,
116111
nextId, encryptionKey, integrityKey));
117112
return decryptProvider(ciphertext);

0 commit comments

Comments
 (0)