Skip to content

Commit 5045e9f

Browse files
Fix to number of Bytes in DirectKmsMaterialProvider. Support multiple materials in MostRecentProvider.
1 parent 4ae1b4a commit 5045e9f

File tree

8 files changed

+605
-94
lines changed

8 files changed

+605
-94
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,13 @@ public Map<String, AttributeValue> encryptRecord(
308308
// The description must be stored after encryption because its data
309309
// is necessary for proper decryption.
310310
final String signingAlgo = materialDescription.get(signingAlgorithmHeader);
311-
DynamoDBSigner signer = DynamoDBSigner.getInstance(signingAlgo == null ? DEFAULT_SIGNATURE_ALGORITHM : signingAlgo, Utils.getRng());
312-
311+
DynamoDBSigner signer;
312+
if (signingAlgo != null) {
313+
signer = DynamoDBSigner.getInstance(signingAlgo, Utils.getRng());
314+
} else {
315+
signer = DynamoDBSigner.getInstance(DEFAULT_SIGNATURE_ALGORITHM, Utils.getRng());
316+
}
317+
313318
if (materials.getSigningKey() instanceof PrivateKey ) {
314319
materialDescription.put(signingAlgorithmHeader, signer.getSigningAlgorithm());
315320
}

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

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.amazonaws.services.kms.model.GenerateDataKeyRequest;
4343
import com.amazonaws.services.kms.model.GenerateDataKeyResult;
4444
import com.amazonaws.util.Base64;
45+
import com.amazonaws.util.StringUtils;
4546
import com.amazonaws.util.VersionInfoUtils;
4647

4748
/**
@@ -126,6 +127,7 @@ public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) {
126127
request.setCiphertextBlob(ByteBuffer.wrap(Base64.decode(materialDescription.get(ENVELOPE_KEY))));
127128
request.setEncryptionContext(ec);
128129
final DecryptResult decryptResult = kms.decrypt(request);
130+
validateEncryptionKeyId(decryptResult.getKeyId(), context);
129131

130132
final Hkdf kdf;
131133
try {
@@ -153,9 +155,16 @@ public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) {
153155
ec.put("*" + SIGNING_KEY_ALGORITHM + "*", sigKeyDesc);
154156
populateKmsEcFromEc(context, ec);
155157

158+
final String keyId = selectEncryptionKeyId(context);
159+
if (StringUtils.isNullOrEmpty(keyId)) {
160+
throw new DynamoDBMappingException("Encryption key id is empty.");
161+
}
162+
156163
final GenerateDataKeyRequest req = appendUserAgent(new GenerateDataKeyRequest());
157-
req.setKeyId(encryptionKeyId);
158-
req.setNumberOfBytes(256);
164+
req.setKeyId(keyId);
165+
// NumberOfBytes parameter is used because we're not using this key as an AES-256 key,
166+
// we're using it as an HKDF-SHA256 key.
167+
req.setNumberOfBytes(256 / 8);
159168
req.setEncryptionContext(ec);
160169

161170
final GenerateDataKeyResult dataKeyResult = kms.generateDataKey(req);
@@ -182,6 +191,36 @@ public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) {
182191
return new SymmetricRawMaterials(encryptionKey, signatureKey, materialDescription);
183192
}
184193

194+
/**
195+
* Get encryption key id.
196+
* @return encryption key id.
197+
*/
198+
protected String getEncryptionKeyId() {
199+
return this.encryptionKeyId;
200+
}
201+
202+
/**
203+
* Select encryption key id to be used to generate data key. The default implementation of this method returns
204+
* {@link DirectKmsMaterialProvider#encryptionKeyId}.
205+
* @param context encryption context.
206+
* @return the encryptionKeyId.
207+
* @throws DynamoDBMappingException when we fails to select a valid encryption key id.
208+
*/
209+
protected String selectEncryptionKeyId(EncryptionContext context) throws DynamoDBMappingException {
210+
return getEncryptionKeyId();
211+
}
212+
213+
/**
214+
* Validate the encryption key id. The default implementation of this method does nothing.
215+
* @param encryptionKeyId encryption key id from {@link DecryptResult}.
216+
* @param context encryption context.
217+
* @throws DynamoDBMappingException when encryptionKeyId is invalid.
218+
*/
219+
protected void validateEncryptionKeyId(String encryptionKeyId, EncryptionContext context)
220+
throws DynamoDBMappingException {
221+
// No action taken.
222+
}
223+
185224
/**
186225
* Extracts relevant information from {@code context} and uses it to populate fields in
187226
* {@code kmsEc}. Currently, these fields are:
@@ -234,7 +273,7 @@ private static byte[] toArray(final ByteBuffer buff) {
234273
return result;
235274
}
236275

237-
private final <X extends AmazonWebServiceRequest> X appendUserAgent(final X request) {
276+
private static <X extends AmazonWebServiceRequest> X appendUserAgent(final X request) {
238277
request.getRequestClientOptions().appendUserAgent(USER_AGENT);
239278
return request;
240279
}

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

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
* Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
55
* in compliance with the License. A copy of the License is located at
@@ -30,12 +30,11 @@
3030
public class MostRecentProvider implements EncryptionMaterialsProvider {
3131
private static final long MILLI_TO_NANO = 1000000L;
3232
private static final long TTL_GRACE_IN_NANO = 500 * MILLI_TO_NANO;
33-
private final ReentrantLock lock = new ReentrantLock(true);
3433
private final ProviderStore keystore;
35-
private final String materialName;
34+
protected final String defaultMaterialName;
3635
private final long ttlInNanos;
3736
private final LRUCache<EncryptionMaterialsProvider> cache;
38-
private final AtomicReference<State> state = new AtomicReference<>(new State());
37+
private final LRUCache<LockedState> currentVersions;
3938

4039
/**
4140
* Creates a new {@link MostRecentProvider}.
@@ -45,22 +44,26 @@ public class MostRecentProvider implements EncryptionMaterialsProvider {
4544
*/
4645
public MostRecentProvider(final ProviderStore keystore, final String materialName, final long ttlInMillis) {
4746
this.keystore = checkNotNull(keystore, "keystore must not be null");
48-
this.materialName = checkNotNull(materialName, "materialName must not be null");
47+
this.defaultMaterialName = materialName;
4948
this.ttlInNanos = ttlInMillis * MILLI_TO_NANO;
5049
this.cache = new LRUCache<EncryptionMaterialsProvider>(1000);
50+
this.currentVersions = new LRUCache<>(1000);
5151
}
5252

5353
@Override
5454
public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) {
55-
State s = state.get();
55+
final String materialName = getMaterialName(context);
56+
final LockedState ls = getCurrentVersion(materialName);
57+
58+
final State s = ls.getState();
5659
if (s.provider != null && System.nanoTime() - s.lastUpdated <= ttlInNanos) {
5760
return s.provider.getEncryptionMaterials(context);
5861
}
5962
if (s.provider == null || System.nanoTime() - s.lastUpdated > ttlInNanos + TTL_GRACE_IN_NANO) {
6063
// Either we don't have a provider at all, or we're more than 500 milliseconds past
6164
// our update time. Either way, grab the lock and force an update.
62-
lock.lock();
63-
} else if (!lock.tryLock()) {
65+
ls.lock();
66+
} else if (!ls.tryLock()) {
6467
// If we can't get the lock immediately, just use the current provider
6568
return s.provider.getEncryptionMaterials(context);
6669
}
@@ -73,36 +76,37 @@ public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) {
7376
// First version of the material, so we want to allow creation
7477
currentVersion = 0;
7578
currentProvider = keystore.getOrCreate(materialName, currentVersion);
76-
cache.add(Long.toString(currentVersion), currentProvider);
79+
cache.add(buildCacheKey(materialName, currentVersion), currentProvider);
7780
} else if (newVersion != s.currentVersion) {
7881
// We're retrieving an existing version, so we avoid the creation
7982
// flow as it is slower
8083
currentVersion = newVersion;
8184
currentProvider = keystore.getProvider(materialName, currentVersion);
82-
cache.add(Long.toString(currentVersion), currentProvider);
85+
cache.add(buildCacheKey(materialName, currentVersion), currentProvider);
8386
} else {
8487
// Our version hasn't changed, so we'll just re-use the existing
8588
// provider to avoid the overhead of retrieving and building a new one
8689
currentVersion = newVersion;
8790
currentProvider = s.provider;
8891
// There is no need to add this to the cache as it's already there
8992
}
90-
s = new State(currentProvider, currentVersion);
91-
state.set(s);
9293

93-
return s.provider.getEncryptionMaterials(context);
94+
ls.update(currentProvider, currentVersion);
95+
96+
return ls.getState().provider.getEncryptionMaterials(context);
9497
} finally {
95-
lock.unlock();
98+
ls.unlock();
9699
}
97100
}
98101

99102
public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) {
103+
final String materialName = getMaterialName(context);
100104
final long version = keystore.getVersionFromMaterialDescription(
101105
context.getMaterialDescription());
102-
EncryptionMaterialsProvider provider = cache.get(Long.toString(version));
106+
EncryptionMaterialsProvider provider = cache.get(buildCacheKey(materialName, version));
103107
if (provider == null) {
104108
provider = keystore.getProvider(materialName, version);
105-
cache.add(Long.toString(version), provider);
109+
cache.add(buildCacheKey(materialName, version), provider);
106110
}
107111
return provider.getDecryptionMaterials(context);
108112
}
@@ -112,12 +116,12 @@ public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) {
112116
*/
113117
@Override
114118
public void refresh() {
115-
state.set(new State());
119+
currentVersions.clear();
116120
cache.clear();
117121
}
118122

119123
public String getMaterialName() {
120-
return materialName;
124+
return defaultMaterialName;
121125
}
122126

123127
public long getTtlInMills() {
@@ -129,15 +133,36 @@ public long getTtlInMills() {
129133
* currently have a current version.
130134
*/
131135
public long getCurrentVersion() {
132-
return state.get().currentVersion;
136+
return getCurrentVersion(getMaterialName()).getState().currentVersion;
133137
}
134138

135139
/**
136140
* The last time the current version was updated. Returns 0 if we do not currently have a
137141
* current version.
138142
*/
139143
public long getLastUpdated() {
140-
return state.get().lastUpdated / MILLI_TO_NANO;
144+
return getCurrentVersion(getMaterialName()).getState().lastUpdated / MILLI_TO_NANO;
145+
}
146+
147+
protected String getMaterialName(final EncryptionContext context) {
148+
return defaultMaterialName;
149+
}
150+
151+
private LockedState getCurrentVersion(final String materialName) {
152+
final LockedState result = currentVersions.get(materialName);
153+
if (result == null) {
154+
currentVersions.add(materialName, new LockedState());
155+
return currentVersions.get(materialName);
156+
} else {
157+
return result;
158+
}
159+
}
160+
161+
private static String buildCacheKey(final String materialName, final long version) {
162+
StringBuilder result = new StringBuilder(materialName);
163+
result.append('#');
164+
result.append(version);
165+
return result.toString();
141166
}
142167

143168
private static <V> V checkNotNull(final V ref, final String errMsg) {
@@ -148,6 +173,34 @@ private static <V> V checkNotNull(final V ref, final String errMsg) {
148173
}
149174
}
150175

176+
private static class LockedState {
177+
private final ReentrantLock lock = new ReentrantLock(true);
178+
private volatile AtomicReference<State> state = new AtomicReference<>(new State());
179+
180+
public State getState() {
181+
return state.get();
182+
}
183+
184+
public void unlock() {
185+
lock.unlock();
186+
}
187+
188+
public boolean tryLock() {
189+
return lock.tryLock();
190+
}
191+
192+
public void lock() {
193+
lock.lock();
194+
}
195+
196+
public void update(EncryptionMaterialsProvider provider, long currentVersion) {
197+
if (!lock.isHeldByCurrentThread()) {
198+
throw new IllegalStateException("Lock not held by current thread");
199+
}
200+
state.set(new State(provider, currentVersion));
201+
}
202+
}
203+
151204
private static class State {
152205
public final EncryptionMaterialsProvider provider;
153206
public final long currentVersion;

src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/internal/AttributeValueMarshaller.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public static ByteBuffer marshall(final AttributeValue attributeValue) {
8080

8181
private static void marshall(final AttributeValue attributeValue, final DataOutputStream out)
8282
throws IOException {
83+
8384
if (attributeValue.getB() != null) {
8485
out.writeChar('b');
8586
writeBytes(attributeValue.getB(), out);
@@ -92,8 +93,8 @@ private static void marshall(final AttributeValue attributeValue, final DataOutp
9293
} else if (attributeValue.getNS() != null) {
9394
out.writeChar('N');
9495

95-
List<String> ns = new ArrayList<String>(attributeValue.getNS().size());
96-
for (String n : attributeValue.getNS()) {
96+
final List<String> ns = new ArrayList<String>(attributeValue.getNS().size());
97+
for (final String n : attributeValue.getNS()) {
9798
ns.add(trimZeros(n));
9899
}
99100
writeStringList(ns, out);
@@ -113,6 +114,11 @@ private static void marshall(final AttributeValue attributeValue, final DataOutp
113114
out.writeChar('L');
114115
out.writeInt(l.size());
115116
for (final AttributeValue attr : l) {
117+
if (attr == null) {
118+
throw new NullPointerException(
119+
"Encountered null list entry value while marshalling attribute value "
120+
+ attributeValue);
121+
}
116122
marshall(attr, out);
117123
}
118124
} else if (attributeValue.getM() != null) {
@@ -123,7 +129,17 @@ private static void marshall(final AttributeValue attributeValue, final DataOutp
123129
out.writeInt(m.size());
124130
for (final String mKey : mKeys) {
125131
marshall(new AttributeValue().withS(mKey), out);
126-
marshall(m.get(mKey), out);
132+
133+
final AttributeValue mValue = m.get(mKey);
134+
135+
if (mValue == null) {
136+
throw new NullPointerException(
137+
"Encountered null map value for key "
138+
+ mKey
139+
+ " while marshalling attribute value "
140+
+ attributeValue);
141+
}
142+
marshall(mValue, out);
127143
}
128144
} else {
129145
throw new IllegalArgumentException("A seemingly empty AttributeValue is indicative of invalid input or potential errors");

0 commit comments

Comments
 (0)