Skip to content

Commit

Permalink
Merge pull request #47 from SalusaSecondus/FeatureMerge
Browse files Browse the repository at this point in the history
Feature merge
  • Loading branch information
SalusaSecondus authored Aug 29, 2018
2 parents 6371f96 + 692a3c1 commit c4fc260
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 74 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ You can download the [latest snapshot release][download] or pick it up from Mave
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-dynamodb-encryption-java</artifactId>
<version>1.11.2</version>
<version>1.12.0</version>
</dependency>
```

Expand Down Expand Up @@ -164,4 +164,4 @@ For signing, the user specified signing key can be either symmetric or asymmetri
[materialprovider]: src/main/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/providers/EncryptionMaterialsProvider.java
[privatekey]: http://docs.oracle.com/javase/7/docs/api/java/security/PrivateKey.html
[secretkey]: http://docs.oracle.com/javase/7/docs/api/javax/crypto/SecretKey.html
[download]: https://github.com/aws/aws-dynamodb-encryption-java/releases/tag/1.11.2
[download]: https://github.com/aws/aws-dynamodb-encryption-java/releases/tag/1.12.0
12 changes: 6 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<groupId>com.amazonaws</groupId>
<artifactId>aws-dynamodb-encryption-java</artifactId>
<version>1.11.2</version>
<version>1.12.0</version>
<packaging>jar</packaging>

<name>aws-dynamodb-encryption-java</name>
Expand Down Expand Up @@ -43,7 +43,7 @@
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bom</artifactId>
<version>1.11.315</version>
<version>1.11.380</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand All @@ -64,14 +64,14 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-ext-jdk15on</artifactId>
<version>1.50</version>
<version>1.60</version>
<scope>test</scope>
</dependency>

Expand Down Expand Up @@ -114,8 +114,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
Expand Down Expand Up @@ -58,7 +60,16 @@ public class DynamoDBEncryptor {
private static final String DEFAULT_DESCRIPTION_BASE = "amzn-ddb-map-"; // Same as the Mapper
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final String SYMMETRIC_ENCRYPTION_MODE = "/CBC/PKCS5Padding";

private static final ConcurrentHashMap<String, Integer> BLOCK_SIZE_CACHE = new ConcurrentHashMap<>();
private static final Function<String, Integer> BLOCK_SIZE_CALCULATOR = (transformation) -> {
try {
final Cipher c = Cipher.getInstance(transformation);
return c.getBlockSize();
} catch (final GeneralSecurityException ex) {
throw new IllegalArgumentException("Algorithm does not exist", ex);
}
};

private static final int CURRENT_VERSION = 0;

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

for (Map.Entry<String, AttributeValue> entry: itemAttributes.entrySet()) {
Set<EncryptionFlags> flags = attributeFlags.get(entry.getKey());
Expand All @@ -354,15 +365,13 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
plainText = ByteBuffer.wrap(((DelegatedKey)encryptionKey).decrypt(toByteArray(cipherText), null, encryptionMode));
} else {
if (cipher == null) {
cipher = Cipher.getInstance(
encryptionMode);
ivSize = cipher.getBlockSize();
blockSize = getBlockSize(encryptionMode);
cipher = Cipher.getInstance(encryptionMode);
}
byte[] iv = new byte[ivSize];
byte[] iv = new byte[blockSize];
cipherText.get(iv);
cipher.init(Cipher.DECRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng());
plainText = ByteBuffer.allocate(
cipher.getOutputSize(cipherText.remaining()));
plainText = ByteBuffer.allocate(cipher.getOutputSize(cipherText.remaining()));
cipher.doFinal(cipherText, plainText);
plainText.rewind();
}
Expand All @@ -371,6 +380,10 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
}
}

protected static int getBlockSize(final String encryptionMode) {
return BLOCK_SIZE_CACHE.computeIfAbsent(encryptionMode, BLOCK_SIZE_CALCULATOR);
}

/**
* This method has the side effect of replacing the plaintext
* attribute-values of "itemAttributes" with ciphertext attribute-values
Expand All @@ -388,7 +401,7 @@ private void actualEncryption(Map<String, AttributeValue> itemAttributes,
encryptionMode = encryptionKey.getAlgorithm() + SYMMETRIC_ENCRYPTION_MODE;
}
Cipher cipher = null;
int ivSize = -1;
int blockSize = -1;

for (Map.Entry<String, AttributeValue> entry: itemAttributes.entrySet()) {
Set<EncryptionFlags> flags = attributeFlags.get(entry.getKey());
Expand All @@ -405,16 +418,22 @@ private void actualEncryption(Map<String, AttributeValue> itemAttributes,
dk.encrypt(toByteArray(plainText), null, encryptionMode));
} else {
if (cipher == null) {
blockSize = getBlockSize(encryptionMode);
cipher = Cipher.getInstance(encryptionMode);
ivSize = cipher.getBlockSize();
}
// Encryption format: <iv><ciphertext>
// Note a unique iv is generated per attribute
byte[] iv = Utils.getRandom(ivSize);
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new IvParameterSpec(iv), Utils.getRng());
cipherText = ByteBuffer.allocate(ivSize + cipher.getOutputSize(plainText.remaining()));
cipherText.put(iv);
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, Utils.getRng());
cipherText = ByteBuffer.allocate(blockSize + cipher.getOutputSize(plainText.remaining()));
cipherText.position(blockSize);
cipher.doFinal(plainText, cipherText);
cipherText.flip();
final byte[] iv = cipher.getIV();
if (iv.length != blockSize) {
throw new IllegalStateException(String.format("Generated IV length (%d) not equal to block size (%d)",
iv.length, blockSize));
}
cipherText.put(iv);
cipherText.rewind();
}
// Replace the plaintext attribute value with the encrypted content
Expand Down Expand Up @@ -539,17 +558,22 @@ protected static Map<String, String> unmarshallDescription(AttributeValue attrib
attributeValue.getB().reset();
}
}

private static byte[] toByteArray(ByteBuffer buffer) {
if (buffer.hasArray()) {
buffer = buffer.duplicate();
// We can only return the array directly if:
// 1. The ByteBuffer exposes an array
// 2. The ByteBuffer starts at the beginning of the array
// 3. The ByteBuffer uses the entire array
if (buffer.hasArray() && buffer.arrayOffset() == 0) {
byte[] result = buffer.array();
buffer.rewind();
return result;
} else {
byte[] result = new byte[buffer.remaining()];
buffer.get(result);
buffer.rewind();
return result;
if (buffer.remaining() == result.length) {
return result;
}
}

byte[] result = new byte[buffer.remaining()];
buffer.get(result);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,32 @@ public long getVersionFromMaterialDescription(final Map<String, String> descript
throw new IllegalArgumentException("No meta id found");
}
}

/**
* This API retrieves the intermediate keys from the source region and replicates it in the target region.
* @param materialName
* @param version
* @param targetMetaStore
*/
public void replicate(final String materialName, final long version, final MetaStore targetMetaStore) {
try {
final Map<String, AttributeValue> ddbKey = new HashMap<String, AttributeValue>();
ddbKey.put(DEFAULT_HASH_KEY, new AttributeValue().withS(materialName));
ddbKey.put(DEFAULT_RANGE_KEY, new AttributeValue().withN(Long.toString(version)));
final Map<String, AttributeValue> item = ddbGet(ddbKey);
if (item == null || item.isEmpty()) {
throw new IndexOutOfBoundsException("No material found: " + materialName + "#" + version);
}

final Map<String, AttributeValue> plainText = getPlainText(item);
final Map<String, AttributeValue> encryptedText = targetMetaStore.getEncryptedText(plainText);
final PutItemRequest put = new PutItemRequest().withTableName(targetMetaStore.tableName).withItem(encryptedText)
.withExpected(doesNotExist);
targetMetaStore.ddb.putItem(put);
} catch (ConditionalCheckFailedException e) {
//Item already present.
}
}
/**
* Creates a DynamoDB Table with the correct properties to be used with a ProviderStore.
*/
Expand Down Expand Up @@ -187,36 +213,43 @@ private Map<String, AttributeValue> encryptKeys(final String name, final long ve
plaintext
.put(INTEGRITY_KEY_FIELD, new AttributeValue().withB(ByteBuffer.wrap(integrityKey.getEncoded())));
plaintext.put(INTEGRITY_ALGORITHM_FIELD, new AttributeValue().withS(integrityKey.getAlgorithm()));
return getEncryptedText(plaintext);
}

private EncryptionMaterialsProvider decryptProvider(final Map<String, AttributeValue> item) {
final Map<String, AttributeValue> plaintext = getPlainText(item);

final String type = plaintext.get(MATERIAL_TYPE_VERSION).getS();
final SecretKey encryptionKey;
final SecretKey integrityKey;
// This switch statement is to make future extensibility easier and more obvious
switch (type) {
case "0": // Only currently supported type
encryptionKey = new SecretKeySpec(plaintext.get(ENCRYPTION_KEY_FIELD).getB().array(),
plaintext.get(ENCRYPTION_ALGORITHM_FIELD).getS());
integrityKey = new SecretKeySpec(plaintext.get(INTEGRITY_KEY_FIELD).getB().array(), plaintext
.get(INTEGRITY_ALGORITHM_FIELD).getS());
break;
default:
throw new IllegalStateException("Unsupported material type: " + type);
}
return new WrappedMaterialsProvider(encryptionKey, encryptionKey, integrityKey,
buildDescription(plaintext));
}

private Map<String, AttributeValue> getPlainText(Map<String, AttributeValue> item) {
try {
return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, DEFAULT_HASH_KEY,
DEFAULT_RANGE_KEY);
return encryptor.decryptAllFieldsExcept(item,
ddbCtx, DEFAULT_HASH_KEY, DEFAULT_RANGE_KEY);
} catch (final GeneralSecurityException e) {
throw new AmazonClientException(e);
}
}

private EncryptionMaterialsProvider decryptProvider(final Map<String, AttributeValue> item) {
private Map<String, AttributeValue> getEncryptedText(Map<String, AttributeValue> plaintext) {
try {
final Map<String, AttributeValue> plaintext = encryptor.decryptAllFieldsExcept(item,
ddbCtx, DEFAULT_HASH_KEY, DEFAULT_RANGE_KEY);

final String type = plaintext.get(MATERIAL_TYPE_VERSION).getS();
final SecretKey encryptionKey;
final SecretKey integrityKey;
// This switch statement is to make future extensibility easier and more obvious
switch (type) {
case "0": // Only currently supported type
encryptionKey = new SecretKeySpec(plaintext.get(ENCRYPTION_KEY_FIELD).getB().array(),
plaintext.get(ENCRYPTION_ALGORITHM_FIELD).getS());
integrityKey = new SecretKeySpec(plaintext.get(INTEGRITY_KEY_FIELD).getB().array(), plaintext
.get(INTEGRITY_ALGORITHM_FIELD).getS());
break;
default:
throw new IllegalStateException("Unsupported material type: " + type);
}
return new WrappedMaterialsProvider(encryptionKey, encryptionKey, integrityKey,
buildDescription(plaintext));
return encryptor.encryptAllFieldsExcept(plaintext, ddbCtx, DEFAULT_HASH_KEY,
DEFAULT_RANGE_KEY);
} catch (final GeneralSecurityException e) {
throw new AmazonClientException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
*/
package com.amazonaws.services.dynamodbv2.datamodeling.internal;

import java.util.AbstractMap.SimpleImmutableEntry;
import com.amazonaws.annotation.ThreadSafe;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import com.amazonaws.annotation.ThreadSafe;
import java.util.Map.Entry;

/**
* A bounded cache that has a LRU eviction policy when the cache is full.
Expand Down Expand Up @@ -98,11 +99,16 @@ public void clear() {
// The more complicated logic is to ensure that the listener is
// actually called for all entries.
if (listener != null) {
Set<String> keys = new TreeSet<String>(map.keySet());
for (String key : keys) {
T val = map.get(key);
listener.onRemoval(new SimpleImmutableEntry<String, T>(key, val));
map.remove(key);
List<Entry<String, T>> removedEntries = new ArrayList<Entry<String, T>>();
synchronized (map) {
Iterator<Entry<String, T>> it = map.entrySet().iterator();
while(it.hasNext()) {
removedEntries.add(it.next());
it.remove();
}
}
for (Entry<String, T> entry : removedEntries) {
listener.onRemoval(entry);
}
} else {
map.clear();
Expand All @@ -126,7 +132,7 @@ private LRUHashMap(final int maxSize, final RemovalListener<T> listener) {
}

@Override
protected boolean removeEldestEntry(final Map.Entry<String, T> eldest) {
protected boolean removeEldestEntry(final Entry<String, T> eldest) {
if (size() > maxSize) {
if (listener != null) {
listener.onRemoval(eldest);
Expand All @@ -138,6 +144,6 @@ protected boolean removeEldestEntry(final Map.Entry<String, T> eldest) {
}

public static interface RemovalListener<T> {
public void onRemoval(Map.Entry<String, T> entry);
public void onRemoval(Entry<String, T> entry);
}
}
Loading

0 comments on commit c4fc260

Please sign in to comment.