Skip to content

Commit ffe233b

Browse files
bhuvanguBhuvan Gupta
and
Bhuvan Gupta
authored
fix: Allow reading old plaintext records after adding new encrypted field to schema (#152)
* fix : NPE if reading record without a signature fields * Fix code formatting * Add integration test for missing signature fields in document. * Updated decrypt logic to be less nested. Improve unit tests * Add IT test for missing "MaterialDesc" fields. * Fix code format. * Remove unwanted if condition and add better unit tests. * Rename Test class to better represent the intent. * Fix Unit test * Remove `if` condition which throw a new error. * Rename IT class to be better * Rename unit test method to be better * Correct formatting Co-authored-by: Bhuvan Gupta <[email protected]>
1 parent 86d821e commit ffe233b

File tree

3 files changed

+152
-1
lines changed

3 files changed

+152
-1
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public Map<String, AttributeValue> decryptRecord(
234234
Map<String, Set<EncryptionFlags>> attributeFlags,
235235
EncryptionContext context)
236236
throws GeneralSecurityException {
237-
if (attributeFlags.isEmpty()) {
237+
if (!itemContainsFieldsToDecryptOrSign(itemAttributes.keySet(), attributeFlags)) {
238238
return itemAttributes;
239239
}
240240
// Copy to avoid changing anyone elses objects
@@ -291,6 +291,13 @@ public Map<String, AttributeValue> decryptRecord(
291291
return itemAttributes;
292292
}
293293

294+
private boolean itemContainsFieldsToDecryptOrSign(
295+
Set<String> attributeNamesToCheck, Map<String, Set<EncryptionFlags>> attributeFlags) {
296+
return attributeNamesToCheck.stream()
297+
.filter(attributeFlags::containsKey)
298+
.anyMatch(attributeName -> !attributeFlags.get(attributeName).isEmpty());
299+
}
300+
294301
/**
295302
* Returns the encrypted (and signed) record, which is a map of item attributes. There is no side
296303
* effect on the input parameters upon calling this method.

sdk1/src/test/java/com/amazonaws/services/dynamodbv2/datamodeling/encryption/DynamoDBEncryptorTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.amazonaws.services.dynamodbv2.datamodeling.encryption;
1616

1717
import static com.amazonaws.services.dynamodbv2.datamodeling.encryption.utils.EncryptionContextOperators.overrideEncryptionContextTableName;
18+
import static java.util.stream.Collectors.toMap;
1819
import static org.hamcrest.MatcherAssert.assertThat;
1920
import static org.hamcrest.Matchers.equalTo;
2021
import static org.hamcrest.Matchers.not;
@@ -23,6 +24,7 @@
2324
import static org.testng.AssertJUnit.assertNotNull;
2425
import static org.testng.AssertJUnit.assertNull;
2526
import static org.testng.AssertJUnit.assertTrue;
27+
import static org.testng.collections.Sets.newHashSet;
2628

2729
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.materials.DecryptionMaterials;
2830
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.materials.EncryptionMaterials;
@@ -54,6 +56,7 @@
5456
import org.bouncycastle.jce.ECNamedCurveTable;
5557
import org.bouncycastle.jce.provider.BouncyCastleProvider;
5658
import org.bouncycastle.jce.spec.ECParameterSpec;
59+
import org.mockito.internal.util.collections.Sets;
5760
import org.testng.Assert;
5861
import org.testng.annotations.BeforeClass;
5962
import org.testng.annotations.BeforeMethod;
@@ -498,6 +501,32 @@ public void toByteArray() throws ReflectiveOperationException {
498501
assertToByteArray("Direct", expected, buff);
499502
}
500503

504+
@Test
505+
public void testDecryptWithPlaintextItem() throws GeneralSecurityException {
506+
Map<String, Set<EncryptionFlags>> attributeWithEmptyEncryptionFlags =
507+
attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet()));
508+
509+
Map<String, AttributeValue> decryptedAttributes =
510+
encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context);
511+
assertThat(decryptedAttributes, AttrMatcher.match(attribs));
512+
}
513+
514+
/*
515+
Test decrypt with a map that contains a new key (not included in attribs) with an encryption flag set that contains ENCRYPT and SIGN.
516+
*/
517+
@Test
518+
public void testDecryptWithPlainTextItemAndAdditionNewAttributeHavingEncryptionFlag()
519+
throws GeneralSecurityException {
520+
Map<String, Set<EncryptionFlags>> attributeWithEmptyEncryptionFlags =
521+
attribs.keySet().stream().collect(toMap(k -> k, k -> newHashSet()));
522+
attributeWithEmptyEncryptionFlags.put(
523+
"newAttribute", Sets.newSet(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN));
524+
525+
Map<String, AttributeValue> decryptedAttributes =
526+
encryptor.decryptRecord(attribs, attributeWithEmptyEncryptionFlags, context);
527+
assertThat(decryptedAttributes, AttrMatcher.match(attribs));
528+
}
529+
501530
private void assertToByteArray(
502531
final String msg, final byte[] expected, final ByteBuffer testValue)
503532
throws ReflectiveOperationException {
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
5+
* in compliance with the License. A copy of the License is located at
6+
*
7+
* http://aws.amazon.com/apache2.0
8+
*
9+
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.amazonaws.services.dynamodbv2.mapper.integration;
14+
15+
import static org.testng.Assert.assertEquals;
16+
import static org.testng.Assert.assertNull;
17+
18+
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
19+
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
20+
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
21+
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
22+
import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DoNotTouch;
23+
import com.amazonaws.services.dynamodbv2.mapper.encryption.TestDynamoDBMapperFactory;
24+
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
25+
import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
26+
import java.util.HashMap;
27+
import java.util.Map;
28+
import org.testng.annotations.BeforeClass;
29+
import org.testng.annotations.Test;
30+
31+
public class PlaintextItemITCase extends DynamoDBMapperCryptoIntegrationTestBase {
32+
private static final String STRING_ATTRIBUTE = "stringAttribute";
33+
private static Map<String, AttributeValue> plaintextItem = new HashMap<>();
34+
// Test data
35+
static {
36+
plaintextItem.put(KEY_NAME, new AttributeValue().withS("" + startKey++));
37+
plaintextItem.put(STRING_ATTRIBUTE, new AttributeValue().withS("" + startKey++));
38+
}
39+
40+
@BeforeClass
41+
public static void setUp() throws Exception {
42+
DynamoDBMapperCryptoIntegrationTestBase.setUp();
43+
// Insert the data
44+
dynamo.putItem(new PutItemRequest(TABLE_NAME, plaintextItem));
45+
}
46+
47+
@Test
48+
public void testLoadWithPlaintextItem() {
49+
DynamoDBMapper util = TestDynamoDBMapperFactory.createDynamoDBMapper(dynamo);
50+
UntouchedTable load = util.load(UntouchedTable.class, plaintextItem.get(KEY_NAME).getS());
51+
52+
assertEquals(load.getKey(), plaintextItem.get(KEY_NAME).getS());
53+
assertEquals(load.getStringAttribute(), plaintextItem.get(STRING_ATTRIBUTE).getS());
54+
}
55+
56+
@Test
57+
public void testLoadWithPlaintextItemWithModelHavingNewEncryptedAttribute() {
58+
DynamoDBMapper util = TestDynamoDBMapperFactory.createDynamoDBMapper(dynamo);
59+
UntouchedWithNewEncryptedAttributeTable load =
60+
util.load(
61+
UntouchedWithNewEncryptedAttributeTable.class, plaintextItem.get(KEY_NAME).getS());
62+
63+
assertEquals(load.getKey(), plaintextItem.get(KEY_NAME).getS());
64+
assertEquals(load.getStringAttribute(), plaintextItem.get(STRING_ATTRIBUTE).getS());
65+
assertNull(load.getNewAttribute());
66+
}
67+
68+
@DynamoDBTable(tableName = "aws-java-sdk-util-crypto")
69+
public static class UntouchedTable {
70+
71+
private String key;
72+
73+
private String stringAttribute;
74+
75+
@DynamoDBHashKey
76+
@DoNotTouch
77+
public String getKey() {
78+
return key;
79+
}
80+
81+
public void setKey(String key) {
82+
this.key = key;
83+
}
84+
85+
@DynamoDBAttribute
86+
@DoNotTouch
87+
public String getStringAttribute() {
88+
return stringAttribute;
89+
}
90+
91+
public void setStringAttribute(String stringAttribute) {
92+
this.stringAttribute = stringAttribute;
93+
}
94+
95+
@Override
96+
public boolean equals(Object o) {
97+
if (this == o) return true;
98+
if (o == null || getClass() != o.getClass()) return false;
99+
UntouchedTable that = (UntouchedTable) o;
100+
return key.equals(that.key) && stringAttribute.equals(that.stringAttribute);
101+
}
102+
}
103+
104+
public static final class UntouchedWithNewEncryptedAttributeTable extends UntouchedTable {
105+
private String newAttribute;
106+
107+
public String getNewAttribute() {
108+
return newAttribute;
109+
}
110+
111+
public void setNewAttribute(String newAttribute) {
112+
this.newAttribute = newAttribute;
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)