Skip to content

Commit c4fc260

Browse files
Merge pull request #47 from SalusaSecondus/FeatureMerge
Feature merge
2 parents 6371f96 + 692a3c1 commit c4fc260

File tree

7 files changed

+205
-74
lines changed

7 files changed

+205
-74
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ You can download the [latest snapshot release][download] or pick it up from Mave
106106
<dependency>
107107
<groupId>com.amazonaws</groupId>
108108
<artifactId>aws-dynamodb-encryption-java</artifactId>
109-
<version>1.11.2</version>
109+
<version>1.12.0</version>
110110
</dependency>
111111
```
112112

@@ -164,4 +164,4 @@ For signing, the user specified signing key can be either symmetric or asymmetri
164164
[materialprovider]: src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/EncryptionMaterialsProvider.java
165165
[privatekey]: http://docs.oracle.com/javase/7/docs/api/java/security/PrivateKey.html
166166
[secretkey]: http://docs.oracle.com/javase/7/docs/api/javax/crypto/SecretKey.html
167-
[download]: https://github.com/aws/aws-dynamodb-encryption-java/releases/tag/1.11.2
167+
[download]: https://github.com/aws/aws-dynamodb-encryption-java/releases/tag/1.12.0

pom.xml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<groupId>com.amazonaws</groupId>
55
<artifactId>aws-dynamodb-encryption-java</artifactId>
6-
<version>1.11.2</version>
6+
<version>1.12.0</version>
77
<packaging>jar</packaging>
88

99
<name>aws-dynamodb-encryption-java</name>
@@ -43,7 +43,7 @@
4343
<dependency>
4444
<groupId>com.amazonaws</groupId>
4545
<artifactId>aws-java-sdk-bom</artifactId>
46-
<version>1.11.315</version>
46+
<version>1.11.380</version>
4747
<type>pom</type>
4848
<scope>import</scope>
4949
</dependency>
@@ -64,14 +64,14 @@
6464
<dependency>
6565
<groupId>junit</groupId>
6666
<artifactId>junit</artifactId>
67-
<version>4.8.1</version>
67+
<version>4.12</version>
6868
<scope>test</scope>
6969
</dependency>
7070

7171
<dependency>
7272
<groupId>org.bouncycastle</groupId>
7373
<artifactId>bcprov-ext-jdk15on</artifactId>
74-
<version>1.50</version>
74+
<version>1.60</version>
7575
<scope>test</scope>
7676
</dependency>
7777

@@ -114,8 +114,8 @@
114114
<artifactId>maven-compiler-plugin</artifactId>
115115
<version>3.1</version>
116116
<configuration>
117-
<source>1.7</source>
118-
<target>1.7</target>
117+
<source>1.8</source>
118+
<target>1.8</target>
119119
</configuration>
120120
</plugin>
121121

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

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import java.util.HashMap;
3232
import java.util.Map;
3333
import java.util.Set;
34+
import java.util.concurrent.ConcurrentHashMap;
35+
import java.util.function.Function;
3436

3537
import javax.crypto.Cipher;
3638
import javax.crypto.SecretKey;
@@ -58,7 +60,16 @@ public class DynamoDBEncryptor {
5860
private static final String DEFAULT_DESCRIPTION_BASE = "amzn-ddb-map-"; // Same as the Mapper
5961
private static final Charset UTF8 = Charset.forName("UTF-8");
6062
private static final String SYMMETRIC_ENCRYPTION_MODE = "/CBC/PKCS5Padding";
61-
63+
private static final ConcurrentHashMap<String, Integer> BLOCK_SIZE_CACHE = new ConcurrentHashMap<>();
64+
private static final Function<String, Integer> BLOCK_SIZE_CALCULATOR = (transformation) -> {
65+
try {
66+
final Cipher c = Cipher.getInstance(transformation);
67+
return c.getBlockSize();
68+
} catch (final GeneralSecurityException ex) {
69+
throw new IllegalArgumentException("Algorithm does not exist", ex);
70+
}
71+
};
72+
6273
private static final int CURRENT_VERSION = 0;
6374

6475
private String signatureFieldName = DEFAULT_SIGNATURE_FIELD;
@@ -339,7 +350,7 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
339350
final String encryptionMode = encryptionKey != null ? encryptionKey.getAlgorithm() +
340351
materialDescription.get(symmetricEncryptionModeHeader) : null;
341352
Cipher cipher = null;
342-
int ivSize = -1;
353+
int blockSize = -1;
343354

344355
for (Map.Entry<String, AttributeValue> entry: itemAttributes.entrySet()) {
345356
Set<EncryptionFlags> flags = attributeFlags.get(entry.getKey());
@@ -354,15 +365,13 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
354365
plainText = ByteBuffer.wrap(((DelegatedKey)encryptionKey).decrypt(toByteArray(cipherText), null, encryptionMode));
355366
} else {
356367
if (cipher == null) {
357-
cipher = Cipher.getInstance(
358-
encryptionMode);
359-
ivSize = cipher.getBlockSize();
368+
blockSize = getBlockSize(encryptionMode);
369+
cipher = Cipher.getInstance(encryptionMode);
360370
}
361-
byte[] iv = new byte[ivSize];
371+
byte[] iv = new byte[blockSize];
362372
cipherText.get(iv);
363373
cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng());
364-
plainText = ByteBuffer.allocate(
365-
cipher.getOutputSize(cipherText.remaining()));
374+
plainText = ByteBuffer.allocate(cipher.getOutputSize(cipherText.remaining()));
366375
cipher.doFinal(cipherText, plainText);
367376
plainText.rewind();
368377
}
@@ -371,6 +380,10 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
371380
}
372381
}
373382

383+
protected static int getBlockSize(final String encryptionMode) {
384+
return BLOCK_SIZE_CACHE.computeIfAbsent(encryptionMode, BLOCK_SIZE_CALCULATOR);
385+
}
386+
374387
/**
375388
* This method has the side effect of replacing the plaintext
376389
* attribute-values of "itemAttributes" with ciphertext attribute-values
@@ -388,7 +401,7 @@ private void actualEncryption(Map<String, AttributeValue> itemAttributes,
388401
encryptionMode = encryptionKey.getAlgorithm() + SYMMETRIC_ENCRYPTION_MODE;
389402
}
390403
Cipher cipher = null;
391-
int ivSize = -1;
404+
int blockSize = -1;
392405

393406
for (Map.Entry<String, AttributeValue> entry: itemAttributes.entrySet()) {
394407
Set<EncryptionFlags> flags = attributeFlags.get(entry.getKey());
@@ -405,16 +418,22 @@ private void actualEncryption(Map<String, AttributeValue> itemAttributes,
405418
dk.encrypt(toByteArray(plainText), null, encryptionMode));
406419
} else {
407420
if (cipher == null) {
421+
blockSize = getBlockSize(encryptionMode);
408422
cipher = Cipher.getInstance(encryptionMode);
409-
ivSize = cipher.getBlockSize();
410423
}
411424
// Encryption format: <iv><ciphertext>
412425
// Note a unique iv is generated per attribute
413-
byte[] iv = Utils.getRandom(ivSize);
414-
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng());
415-
cipherText = ByteBuffer.allocate(ivSize + cipher.getOutputSize(plainText.remaining()));
416-
cipherText.put(iv);
426+
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, Utils.getRng());
427+
cipherText = ByteBuffer.allocate(blockSize + cipher.getOutputSize(plainText.remaining()));
428+
cipherText.position(blockSize);
417429
cipher.doFinal(plainText, cipherText);
430+
cipherText.flip();
431+
final byte[] iv = cipher.getIV();
432+
if (iv.length != blockSize) {
433+
throw new IllegalStateException(String.format("Generated IV length (%d) not equal to block size (%d)",
434+
iv.length, blockSize));
435+
}
436+
cipherText.put(iv);
418437
cipherText.rewind();
419438
}
420439
// Replace the plaintext attribute value with the encrypted content
@@ -539,17 +558,22 @@ protected static Map<String, String> unmarshallDescription(AttributeValue attrib
539558
attributeValue.getB().reset();
540559
}
541560
}
542-
561+
543562
private static byte[] toByteArray(ByteBuffer buffer) {
544-
if (buffer.hasArray()) {
563+
buffer = buffer.duplicate();
564+
// We can only return the array directly if:
565+
// 1. The ByteBuffer exposes an array
566+
// 2. The ByteBuffer starts at the beginning of the array
567+
// 3. The ByteBuffer uses the entire array
568+
if (buffer.hasArray() && buffer.arrayOffset() == 0) {
545569
byte[] result = buffer.array();
546-
buffer.rewind();
547-
return result;
548-
} else {
549-
byte[] result = new byte[buffer.remaining()];
550-
buffer.get(result);
551-
buffer.rewind();
552-
return result;
570+
if (buffer.remaining() == result.length) {
571+
return result;
572+
}
553573
}
574+
575+
byte[] result = new byte[buffer.remaining()];
576+
buffer.get(result);
577+
return result;
554578
}
555579
}

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

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,32 @@ public long getVersionFromMaterialDescription(final Map<String, String> descript
142142
throw new IllegalArgumentException("No meta id found");
143143
}
144144
}
145+
146+
/**
147+
* This API retrieves the intermediate keys from the source region and replicates it in the target region.
148+
* @param materialName
149+
* @param version
150+
* @param targetMetaStore
151+
*/
152+
public void replicate(final String materialName, final long version, final MetaStore targetMetaStore) {
153+
try {
154+
final Map<String, AttributeValue> ddbKey = new HashMap<String, AttributeValue>();
155+
ddbKey.put(DEFAULT_HASH_KEY, new AttributeValue().withS(materialName));
156+
ddbKey.put(DEFAULT_RANGE_KEY, new AttributeValue().withN(Long.toString(version)));
157+
final Map<String, AttributeValue> item = ddbGet(ddbKey);
158+
if (item == null || item.isEmpty()) {
159+
throw new IndexOutOfBoundsException("No material found: " + materialName + "#" + version);
160+
}
161+
162+
final Map<String, AttributeValue> plainText = getPlainText(item);
163+
final Map<String, AttributeValue> encryptedText = targetMetaStore.getEncryptedText(plainText);
164+
final PutItemRequest put = new PutItemRequest().withTableName(targetMetaStore.tableName).withItem(encryptedText)
165+
.withExpected(doesNotExist);
166+
targetMetaStore.ddb.putItem(put);
167+
} catch (ConditionalCheckFailedException e) {
168+
//Item already present.
169+
}
170+
}
145171
/**
146172
* Creates a DynamoDB Table with the correct properties to be used with a ProviderStore.
147173
*/
@@ -187,36 +213,43 @@ private Map<String, AttributeValue> encryptKeys(final String name, final long ve
187213
plaintext
188214
.put(INTEGRITY_KEY_FIELD, new AttributeValue().withB(ByteBuffer.wrap(integrityKey.getEncoded())));
189215
plaintext.put(INTEGRITY_ALGORITHM_FIELD, new AttributeValue().withS(integrityKey.getAlgorithm()));
216+
return getEncryptedText(plaintext);
217+
}
218+
219+
private EncryptionMaterialsProvider decryptProvider(final Map<String, AttributeValue> item) {
220+
final Map<String, AttributeValue> plaintext = getPlainText(item);
190221

222+
final String type = plaintext.get(MATERIAL_TYPE_VERSION).getS();
223+
final SecretKey encryptionKey;
224+
final SecretKey integrityKey;
225+
// This switch statement is to make future extensibility easier and more obvious
226+
switch (type) {
227+
case "0": // Only currently supported type
228+
encryptionKey = new SecretKeySpec(plaintext.get(ENCRYPTION_KEY_FIELD).getB().array(),
229+
plaintext.get(ENCRYPTION_ALGORITHM_FIELD).getS());
230+
integrityKey = new SecretKeySpec(plaintext.get(INTEGRITY_KEY_FIELD).getB().array(), plaintext
231+
.get(INTEGRITY_ALGORITHM_FIELD).getS());
232+
break;
233+
default:
234+
throw new IllegalStateException("Unsupported material type: " + type);
235+
}
236+
return new WrappedMaterialsProvider(encryptionKey, encryptionKey, integrityKey,
237+
buildDescription(plaintext));
238+
}
239+
240+
private Map<String, AttributeValue> getPlainText(Map<String, AttributeValue> item) {
191241
try {
192-
return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, DEFAULT_HASH_KEY,
193-
DEFAULT_RANGE_KEY);
242+
return encryptor.decryptAllFieldsExcept(item,
243+
ddbCtx, DEFAULT_HASH_KEY, DEFAULT_RANGE_KEY);
194244
} catch (final GeneralSecurityException e) {
195245
throw new AmazonClientException(e);
196246
}
197247
}
198248

199-
private EncryptionMaterialsProvider decryptProvider(final Map<String, AttributeValue> item) {
249+
private Map<String, AttributeValue> getEncryptedText(Map<String, AttributeValue> plaintext) {
200250
try {
201-
final Map<String, AttributeValue> plaintext = encryptor.decryptAllFieldsExcept(item,
202-
ddbCtx, DEFAULT_HASH_KEY, DEFAULT_RANGE_KEY);
203-
204-
final String type = plaintext.get(MATERIAL_TYPE_VERSION).getS();
205-
final SecretKey encryptionKey;
206-
final SecretKey integrityKey;
207-
// This switch statement is to make future extensibility easier and more obvious
208-
switch (type) {
209-
case "0": // Only currently supported type
210-
encryptionKey = new SecretKeySpec(plaintext.get(ENCRYPTION_KEY_FIELD).getB().array(),
211-
plaintext.get(ENCRYPTION_ALGORITHM_FIELD).getS());
212-
integrityKey = new SecretKeySpec(plaintext.get(INTEGRITY_KEY_FIELD).getB().array(), plaintext
213-
.get(INTEGRITY_ALGORITHM_FIELD).getS());
214-
break;
215-
default:
216-
throw new IllegalStateException("Unsupported material type: " + type);
217-
}
218-
return new WrappedMaterialsProvider(encryptionKey, encryptionKey, integrityKey,
219-
buildDescription(plaintext));
251+
return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, DEFAULT_HASH_KEY,
252+
DEFAULT_RANGE_KEY);
220253
} catch (final GeneralSecurityException e) {
221254
throw new AmazonClientException(e);
222255
}

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
*/
1515
package com.amazonaws.services.dynamodbv2.datamodeling.internal;
1616

17-
import java.util.AbstractMap.SimpleImmutableEntry;
17+
import com.amazonaws.annotation.ThreadSafe;
18+
19+
import java.util.ArrayList;
1820
import java.util.Collections;
21+
import java.util.Iterator;
1922
import java.util.LinkedHashMap;
23+
import java.util.List;
2024
import java.util.Map;
21-
import java.util.Set;
22-
import java.util.TreeSet;
23-
24-
import com.amazonaws.annotation.ThreadSafe;
25+
import java.util.Map.Entry;
2526

2627
/**
2728
* A bounded cache that has a LRU eviction policy when the cache is full.
@@ -98,11 +99,16 @@ public void clear() {
9899
// The more complicated logic is to ensure that the listener is
99100
// actually called for all entries.
100101
if (listener != null) {
101-
Set<String> keys = new TreeSet<String>(map.keySet());
102-
for (String key : keys) {
103-
T val = map.get(key);
104-
listener.onRemoval(new SimpleImmutableEntry<String, T>(key, val));
105-
map.remove(key);
102+
List<Entry<String, T>> removedEntries = new ArrayList<Entry<String, T>>();
103+
synchronized (map) {
104+
Iterator<Entry<String, T>> it = map.entrySet().iterator();
105+
while(it.hasNext()) {
106+
removedEntries.add(it.next());
107+
it.remove();
108+
}
109+
}
110+
for (Entry<String, T> entry : removedEntries) {
111+
listener.onRemoval(entry);
106112
}
107113
} else {
108114
map.clear();
@@ -126,7 +132,7 @@ private LRUHashMap(final int maxSize, final RemovalListener<T> listener) {
126132
}
127133

128134
@Override
129-
protected boolean removeEldestEntry(final Map.Entry<String, T> eldest) {
135+
protected boolean removeEldestEntry(final Entry<String, T> eldest) {
130136
if (size() > maxSize) {
131137
if (listener != null) {
132138
listener.onRemoval(eldest);
@@ -138,6 +144,6 @@ protected boolean removeEldestEntry(final Map.Entry<String, T> eldest) {
138144
}
139145

140146
public static interface RemovalListener<T> {
141-
public void onRemoval(Map.Entry<String, T> entry);
147+
public void onRemoval(Entry<String, T> entry);
142148
}
143149
}

0 commit comments

Comments
 (0)