diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java index 1fcc8007d..3739da4f7 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.ArrayList; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -40,6 +41,7 @@ public static DynamoDbEncryptionInterceptor CreateDynamoDbEncryptionInterceptor( .build(); } + // return all attribute names that are keys in any index private static Set attributeNamesUsedInIndices( final TableMetadata tableMetadata ) { @@ -59,6 +61,7 @@ private static Set attributeNamesUsedInIndices( return allIndexAttributes; } + // return attributes used in the primary table index private static Set attributeNamesUsedInPrimaryKey( final TableMetadata tableMetadata ) { @@ -69,62 +72,85 @@ private static Set attributeNamesUsedInPrimaryKey( return keyAttributes; } - private static DynamoDbTableEncryptionConfig getTableConfig( - final DynamoDbEnhancedTableEncryptionConfig configWithSchema, - final String tableName - ) { - Map actions = new HashMap<>(); + private static void throwUsageError(String tableName, String attributeName, String usage, String usage2) + { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Attribute %s of table %s is used as both %s and %s.", + attributeName, tableName, usage, usage2)) + .build(); + + } + + // Any given attribute MUST have one and only one Cryptographic Action. + // i.e: It can't be both SignOnly and DoNothing. + // validateAttributeUsage throws an error if + // the given attribute is marked with `usage` and another Cryptographic Action. + // For example, for a SignOnly, signOnly will be empty, and an error must be reported + // if the attribute exists in any of the other sets. + private static void validateAttributeUsage( + String tableName, + String attributeName, + String usage, + Optional> signOnly, + Optional> signAndInclude, + Optional> doNothing + ) + { + if (signOnly.isPresent()) { + if (signOnly.get().contains(attributeName)) { + throwUsageError(tableName, attributeName, usage, "@DynamoDbEncryptionSignOnly"); + } + } + if (signAndInclude.isPresent()) { + if (signAndInclude.get().contains(attributeName)) { + throwUsageError(tableName, attributeName, usage, "@DynamoDbEncryptionSignAndIncludeInEncryptionContext"); + } + } + if (doNothing.isPresent()) { + if (doNothing.get().contains(attributeName)) { + throwUsageError(tableName, attributeName, usage, "@DynamoDbEncryptionDoNothing"); + } + } + } - TableSchema topTableSchema = configWithSchema.schemaOnEncrypt(); + // return a map containing all top level attributes in the schema + // If an attribute is used in an index, it is SignOnly + // Else if an attribute is tagged with a single action, it gets that action + // Else if an attribute is tagged with a multiple actions, an error is thrown + // Else if an attribute is not tagged, it is to be encrypted + private static Map getActionsFromSchema(String tableName, TableSchema topTableSchema) + { Set signOnlyAttributes = getSignOnlyAttributes(topTableSchema); Set signAndIncludeAttributes = getSignAndIncludeInEncryptionContextAttributes(topTableSchema); Set doNothingAttributes = getDoNothingAttributes(topTableSchema); Set keyAttributes = attributeNamesUsedInIndices(topTableSchema.tableMetadata()); Set tableKeys = attributeNamesUsedInPrimaryKey(topTableSchema.tableMetadata()); - - if (!Collections.disjoint(keyAttributes, doNothingAttributes)) { - throw DynamoDbEncryptionException.builder() - .message(String.format( - "Cannot use @DynamoDbEncryptionDoNothing on primary key attributes. Found on Table Name: %s", - tableName)) - .build(); - } else if (!Collections.disjoint(signOnlyAttributes, doNothingAttributes)) { - throw DynamoDbEncryptionException.builder() - .message(String.format( - "Cannot use @DynamoDbEncryptionDoNothing and @DynamoDbEncryptionSignOnly on same attribute. Found on Table Name: %s", - tableName)) - .build(); - } else if (!Collections.disjoint(signOnlyAttributes, signAndIncludeAttributes)) { - throw DynamoDbEncryptionException.builder() - .message(String.format( - "Cannot use @DynamoDbEncryptionSignAndIncludeInEncryptionContext and @DynamoDbEncryptionSignOnly on same attribute. Found on Table Name: %s", - tableName)) - .build(); - } else if (!Collections.disjoint(doNothingAttributes, signAndIncludeAttributes)) { - throw DynamoDbEncryptionException.builder() - .message(String.format( - "Cannot use @DynamoDbEncryptionSignAndIncludeInEncryptionContext and @DynamoDbEncryptionDoNothing on same attribute. Found on Table Name: %s", - tableName)) - .build(); - } - List attributeNames = topTableSchema.attributeNames(); + + Map actions = new HashMap<>(); StringBuilder path = new StringBuilder(); path.append(tableName).append("."); for (String attributeName : attributeNames) { if (tableKeys.contains(attributeName)) { - if (signAndIncludeAttributes.isEmpty()) { + if (signAndIncludeAttributes.isEmpty()) { + validateAttributeUsage(tableName, attributeName, "a primary key", Optional.empty(), Optional.of(signAndIncludeAttributes), Optional.of(doNothingAttributes)); actions.put(attributeName, CryptoAction.SIGN_ONLY); - } else { + } else { + validateAttributeUsage(tableName, attributeName, "a primary key", Optional.of(signOnlyAttributes), Optional.empty(), Optional.of(doNothingAttributes)); actions.put(attributeName, CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT); - } + } } else if (keyAttributes.contains(attributeName)) { + validateAttributeUsage(tableName, attributeName, "an index key", Optional.empty(), Optional.of(signAndIncludeAttributes), Optional.of(doNothingAttributes)); actions.put(attributeName, CryptoAction.SIGN_ONLY); } else if (signOnlyAttributes.contains(attributeName)) { + validateAttributeUsage(tableName, attributeName, "@DynamoDbEncryptionSignOnly", Optional.empty(), Optional.of(signAndIncludeAttributes), Optional.of(doNothingAttributes)); actions.put(attributeName, CryptoAction.SIGN_ONLY); } else if (signAndIncludeAttributes.contains(attributeName)) { + validateAttributeUsage(tableName, attributeName, "@DynamoDbEncryptionSignAndIncludeInEncryptionContext", Optional.of(signOnlyAttributes), Optional.empty(), Optional.of(doNothingAttributes)); actions.put(attributeName, CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT); } else if (doNothingAttributes.contains(attributeName)) { + validateAttributeUsage(tableName, attributeName, "@DynamoDbEncryptionDoNothing", Optional.of(signOnlyAttributes), Optional.of(signAndIncludeAttributes), Optional.empty()); actions.put(attributeName, CryptoAction.DO_NOTHING); } else { // non-key attributes are ENCRYPT_AND_SIGN unless otherwise annotated @@ -134,12 +160,101 @@ private static DynamoDbTableEncryptionConfig getTableConfig( // Detect Encryption Flags that are Ignored b/c they are in a Nested Class scanForIgnoredEncryptionTags(topTableSchema, attributeName, path); } + return actions; + } + + // given action maps from multiple tables, merge them into one + // we throw an error if the one attribute is given two different actions + private static Map mergeActions(List> actionList) + { + // most common case + if (actionList.size() == 1) { + return actionList.get(0); + } + + // Gather set of all attributes + HashSet attributes = new HashSet<>(); + for (Map config : actionList) { + attributes.addAll(config.keySet()); + } + + // for each attribute, ensure that everyone agrees on its action + Map actions = new HashMap<>(); + for (String attr : attributes) { + Optional action = Optional.empty(); + for (Map config : actionList) { + CryptoAction act = config.get(attr); + if (act != null) { + if (action.isPresent()) { + if (!action.get().equals(act)) { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Attribute %s set to %s in one table and %s in another.", + attr, action.get(), act)) + .build(); + } + } else { + action = Optional.of(act); + } + } + } + actions.put(attr, action.get()); + } + return actions; + } + + // return the partition key name + // throw an error if two schemas disagree + private static String getPartitionKeyName(List> schemas) + { + String partitionName = schemas.get(0).tableMetadata().primaryPartitionKey(); + for (TableSchema schema : schemas) { + String part = schema.tableMetadata().primaryPartitionKey(); + if (!partitionName.equals(part)) { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Primary Key set to %s in one table and %s in another.", + partitionName, part)) + .build(); + } + } + return partitionName; + } + + // return the sort key name + // throw an error if two schemas disagree + private static Optional getSortKeyName(List> schemas) + { + Optional sortName = schemas.get(0).tableMetadata().primarySortKey(); + for (TableSchema schema : schemas) { + Optional sort = schema.tableMetadata().primarySortKey(); + if (!sortName.equals(sort)) { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Primary Key set to %s in one table and %s in another.", + sortName, sort)) + .build(); + } + } + return sortName; + } + + // Convert enhanced client config to regular config + private static DynamoDbTableEncryptionConfig getTableConfig( + final DynamoDbEnhancedTableEncryptionConfig configWithSchema, + final String tableName + ) { + List> actionList = new ArrayList<>(); + for (TableSchema schema : configWithSchema.schemaOnEncrypt()) { + actionList.add(getActionsFromSchema(tableName, schema)); + } + Map actions = mergeActions(actionList); DynamoDbTableEncryptionConfig.Builder builder = DynamoDbTableEncryptionConfig.builder(); - String partitionName = topTableSchema.tableMetadata().primaryPartitionKey(); + String partitionName = getPartitionKeyName(configWithSchema.schemaOnEncrypt()); builder = builder.partitionKeyName(partitionName); - Optional sortName = topTableSchema.tableMetadata().primarySortKey(); + Optional sortName = getSortKeyName(configWithSchema.schemaOnEncrypt()); if (sortName.isPresent()) { builder = builder.sortKeyName(sortName.get()); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedTableEncryptionConfig.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedTableEncryptionConfig.java index 1b468a9d6..a1b3eeb85 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedTableEncryptionConfig.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedTableEncryptionConfig.java @@ -1,6 +1,7 @@ package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient; import java.util.List; +import java.util.ArrayList; import java.util.Objects; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyOverride; @@ -14,7 +15,7 @@ public class DynamoDbEnhancedTableEncryptionConfig { private final String logicalTableName; - private final TableSchema schemaOnEncrypt; + private final List> schemaOnEncrypt; private final List allowedUnsignedAttributes; private final String allowedUnsignedAttributePrefix; private final Keyring keyring; @@ -39,7 +40,7 @@ protected DynamoDbEnhancedTableEncryptionConfig(BuilderImpl builder) { public String logicalTableName() { return this.logicalTableName; } - public TableSchema schemaOnEncrypt() { + public List> schemaOnEncrypt() { return this.schemaOnEncrypt; } @@ -83,7 +84,7 @@ public interface Builder { String logicalTableName(); Builder logicalTableName(String logicalTableName); Builder schemaOnEncrypt(TableSchema schemaOnEncrypt); - TableSchema schemaOnEncrypt(); + List> schemaOnEncrypt(); Builder allowedUnsignedAttributes(List allowedUnsignedAttributes); List allowedUnsignedAttributes(); Builder allowedUnsignedAttributePrefix(String allowedUnsignedAttributePrefix); @@ -101,7 +102,7 @@ public interface Builder { protected static class BuilderImpl implements Builder { protected String logicalTableName; - protected TableSchema schemaOnEncrypt; + protected List> schemaOnEncrypt; protected List allowedUnsignedAttributes; protected String allowedUnsignedAttributePrefix; protected Keyring keyring; @@ -132,11 +133,14 @@ public Builder logicalTableName(String logicalTableName) { public String logicalTableName() { return this.logicalTableName; } public Builder schemaOnEncrypt(TableSchema schemaOnEncrypt) { - this.schemaOnEncrypt = schemaOnEncrypt; + if (Objects.isNull(this.schemaOnEncrypt())) { + this.schemaOnEncrypt = new ArrayList(); + } + this.schemaOnEncrypt.add(schemaOnEncrypt); return this; } - public TableSchema schemaOnEncrypt() { + public List> schemaOnEncrypt() { return this.schemaOnEncrypt; } diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java index 3630ade13..90a01fa8f 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java @@ -33,6 +33,9 @@ public void TestMultipleTables() { TableSchema simpleSchema = TableSchema.fromBean(SimpleClass.class); TableSchema signOnlySchema = TableSchema.fromBean(SignOnlyClass.class); TableSchema signAndIncludeSchema = TableSchema.fromBean(SignAndIncludeInEncryptionContextClass.class); + TableSchema singleTable1Schema = TableSchema.fromBean(SingleTable1.class); + TableSchema singleTable2Schema = TableSchema.fromBean(SingleTable2.class); + TableSchema singleTable3Schema = TableSchema.fromBean(SingleTable3.class); Map tableConfigs = new HashMap<>(); tableConfigs.put("SimpleClassTestTable", DynamoDbEnhancedTableEncryptionConfig.builder() @@ -53,13 +56,23 @@ public void TestMultipleTables() { .keyring(createKmsKeyring()) .schemaOnEncrypt(signAndIncludeSchema) .build()); + tableConfigs.put("SingleTableTestTable", + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName("SingleTableTestTable") + .keyring(createKmsKeyring()) + .allowedUnsignedAttributes(Arrays.asList("doNothing")) + .allowedUnsignedAttributePrefix("extraDoNothing") + .schemaOnEncrypt(singleTable1Schema) + .schemaOnEncrypt(singleTable2Schema) + .schemaOnEncrypt(singleTable3Schema) + .build()); DynamoDbEncryptionInterceptor interceptor = DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( CreateDynamoDbEncryptionInterceptorInput.builder() .tableEncryptionConfigs(tableConfigs) .build() ); - assertEquals(3, interceptor.config().tableEncryptionConfigs().size()); + assertEquals(4, interceptor.config().tableEncryptionConfigs().size()); DynamoDbTableEncryptionConfig simpleConfig = interceptor.config().tableEncryptionConfigs().get("SimpleClassTestTable"); assertEquals(CryptoAction.DO_NOTHING, simpleConfig.attributeActionsOnEncrypt().get("doNothing")); @@ -79,6 +92,41 @@ public void TestMultipleTables() { assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, signAndIncludeConfig.attributeActionsOnEncrypt().get("sort_key")); assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, signAndIncludeConfig.attributeActionsOnEncrypt().get("attr1")); assertEquals(CryptoAction.SIGN_ONLY, signAndIncludeConfig.attributeActionsOnEncrypt().get("attr2")); + + DynamoDbTableEncryptionConfig singleTableConfig = interceptor.config().tableEncryptionConfigs().get("SingleTableTestTable"); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("partition_key")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("sort_key")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("doNothing")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("signOnly")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("encryptAndSign")); + + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing1")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing2")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing3")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing12")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing13")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing23")); + + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly1")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly2")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly3")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly12")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly13")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly23")); + + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign1")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign2")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign3")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign12")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign13")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign23")); + + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude1")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude2")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude3")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude12")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude13")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude23")); } @Test @@ -140,7 +188,7 @@ public void TestEnhancedCreateWithAlgorithmSuite() { @Test( expectedExceptions = DynamoDbEncryptionException.class, - expectedExceptionsMessageRegExp = "Cannot use @DynamoDbEncryptionDoNothing on primary key attributes. Found on Table Name: DynamoDbEncryptionInterceptorTestTable" + expectedExceptionsMessageRegExp = "Attribute id of table DynamoDbEncryptionInterceptorTestTable is used as both a primary key and @DynamoDbEncryptionDoNothing." ) public void TestDoNothingOnPartitionAttribute() { TableSchema schemaOnEncrypt = TableSchema.fromBean(InvalidAnnotatedPartitionClass.class); @@ -202,7 +250,7 @@ public void TestInconsistentSignatureScopeIncorrect() { @Test( expectedExceptions = DynamoDbEncryptionException.class, - expectedExceptionsMessageRegExp = "Cannot use @DynamoDbEncryptionDoNothing on primary key attributes. Found on Table Name: DynamoDbEncryptionInterceptorTestTable" + expectedExceptionsMessageRegExp = "Attribute sortKey of table DynamoDbEncryptionInterceptorTestTable is used as both a primary key and @DynamoDbEncryptionDoNothing." ) public void TestDoNothingOnSortAttribute() { TableSchema schemaOnEncrypt = TableSchema.fromBean(InvalidAnnotatedSortClass.class); @@ -221,7 +269,7 @@ public void TestDoNothingOnSortAttribute() { @Test( expectedExceptions = DynamoDbEncryptionException.class, - expectedExceptionsMessageRegExp = "Cannot use @DynamoDbEncryptionDoNothing and @DynamoDbEncryptionSignOnly on same attribute. Found on Table Name: DynamoDbEncryptionInterceptorTestTable" + expectedExceptionsMessageRegExp = "Attribute invalid of table DynamoDbEncryptionInterceptorTestTable is used as both @DynamoDbEncryptionSignOnly and @DynamoDbEncryptionDoNothing." ) public void TestDoubleAnnotationOnAttribute() { TableSchema schemaOnEncrypt = TableSchema.fromBean(InvalidDoubleAnnotationClass.class); @@ -258,6 +306,29 @@ public void TestFlattenedNestedBeanAnnotationMissingUnauthenticatedAttributes() .build()); } + @Test( + expectedExceptions = DynamoDbEncryptionException.class, + expectedExceptionsMessageRegExp = "Attribute partition_key set to SIGN_ONLY in one table and SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT in another." + ) + public void TestIncompatibleClasses() { + TableSchema schemaOnEncrypt1 = + TableSchema.fromBean(SignOnlyClass.class); + TableSchema schemaOnEncrypt2 = + TableSchema.fromBean(SignAndIncludeInEncryptionContextClass.class); + Map tableConfigs = new HashMap<>(); + tableConfigs.put(TEST_TABLE_NAME, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(TEST_TABLE_NAME) + .keyring(createKmsKeyring()) + .schemaOnEncrypt(schemaOnEncrypt1) + .schemaOnEncrypt(schemaOnEncrypt2) + .build()); + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build()); + } + @Test public void TestFlattenedNestedBeanAnnotation() { TableSchema schemaOnEncrypt = diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable1.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable1.java new file mode 100644 index 000000000..b0e9794f1 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable1.java @@ -0,0 +1,123 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.validdatamodels; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignAndIncludeInEncryptionContext; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SingleTable1 { + + private String partitionKey; + private int sortKey; + private String encryptAndSign; + private String doNothing; + private String signOnly; + + private String extraSignOnly1; + private String extraSignOnly12; + private String extraSignOnly13; + private String extraSignAndInclude1; + private String extraSignAndInclude12; + private String extraSignAndInclude13; + private String extraDoNothing1; + private String extraDoNothing12; + private String extraDoNothing13; + private String extraEncryptAndSign1; + private String extraEncryptAndSign12; + private String extraEncryptAndSign13; + + public String getExtraEncryptAndSign1() {return this.extraEncryptAndSign1;} + public String getExtraEncryptAndSign12() {return this.extraEncryptAndSign12;} + public String getExtraEncryptAndSign13() {return this.extraEncryptAndSign13;} + + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing1() {return this.extraDoNothing1;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing12() {return this.extraDoNothing12;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing13() {return this.extraDoNothing13;} + + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly1() {return this.extraSignOnly1;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly12() {return this.extraSignOnly12;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly13() {return this.extraSignOnly13;} + + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude1() {return this.extraSignAndInclude1;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude12() {return this.extraSignAndInclude12;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude13() {return this.extraSignAndInclude13;} + + public void setExtraDoNothing1(String text) {this.extraDoNothing1 = text;} + public void setExtraDoNothing12(String text) {this.extraDoNothing12 = text;} + public void setExtraDoNothing13(String text) {this.extraDoNothing13 = text;} + + public void setExtraSignOnly1(String text) {this.extraSignOnly1 = text;} + public void setExtraSignOnly12(String text) {this.extraSignOnly12 = text;} + public void setExtraSignOnly13(String text) {this.extraSignOnly13 = text;} + + public void setExtraSignAndInclude1(String text) {this.extraSignAndInclude1 = text;} + public void setExtraSignAndInclude12(String text) {this.extraSignAndInclude12 = text;} + public void setExtraSignAndInclude13(String text) {this.extraSignAndInclude13 = text;} + + public void setExtraEncryptAndSign1(String text) {this.extraEncryptAndSign1 = text;} + public void setExtraEncryptAndSign12(String text) {this.extraEncryptAndSign12 = text;} + public void setExtraEncryptAndSign13(String text) {this.extraEncryptAndSign13 = text;} + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getEncryptAndSign() { + return this.encryptAndSign; + } + + public void setEncryptAndSign(String encryptAndSign) { + this.encryptAndSign = encryptAndSign; + } + + @DynamoDbEncryptionSignOnly + public String getSignOnly() { + return this.signOnly; + } + + public void setSignOnly(String signOnly) { + this.signOnly = signOnly; + } + + @DynamoDbEncryptionDoNothing + public String getDoNothing() { + return this.doNothing; + } + + public void setDoNothing(String doNothing) { + this.doNothing = doNothing; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable2.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable2.java new file mode 100644 index 000000000..2031f5c31 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable2.java @@ -0,0 +1,124 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.validdatamodels; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignAndIncludeInEncryptionContext; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SingleTable2 { + + private String partitionKey; + private int sortKey; + private String encryptAndSign; + private String doNothing; + private String signOnly; + + private String extraSignOnly2; + private String extraSignOnly12; + private String extraSignOnly23; + private String extraSignAndInclude2; + private String extraSignAndInclude12; + private String extraSignAndInclude23; + private String extraDoNothing2; + private String extraDoNothing12; + private String extraDoNothing23; + private String extraEncryptAndSign2; + private String extraEncryptAndSign12; + private String extraEncryptAndSign23; + + public String getExtraEncryptAndSign2() {return this.extraEncryptAndSign2;} + public String getExtraEncryptAndSign12() {return this.extraEncryptAndSign12;} + public String getExtraEncryptAndSign23() {return this.extraEncryptAndSign23;} + + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing2() {return this.extraDoNothing2;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing12() {return this.extraDoNothing12;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing23() {return this.extraDoNothing23;} + + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly2() {return this.extraSignOnly2;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly12() {return this.extraSignOnly12;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly23() {return this.extraSignOnly23;} + + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude2() {return this.extraSignAndInclude2;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude12() {return this.extraSignAndInclude12;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude23() {return this.extraSignAndInclude23;} + + public void setExtraDoNothing2(String text) {this.extraDoNothing2 = text;} + public void setExtraDoNothing12(String text) {this.extraDoNothing12 = text;} + public void setExtraDoNothing23(String text) {this.extraDoNothing23 = text;} + + public void setExtraSignOnly2(String text) {this.extraSignOnly2 = text;} + public void setExtraSignOnly12(String text) {this.extraSignOnly12 = text;} + public void setExtraSignOnly23(String text) {this.extraSignOnly23 = text;} + + public void setExtraSignAndInclude2(String text) {this.extraSignAndInclude2 = text;} + public void setExtraSignAndInclude12(String text) {this.extraSignAndInclude12 = text;} + public void setExtraSignAndInclude23(String text) {this.extraSignAndInclude23 = text;} + + public void setExtraEncryptAndSign2(String text) {this.extraEncryptAndSign2 = text;} + public void setExtraEncryptAndSign12(String text) {this.extraEncryptAndSign12 = text;} + public void setExtraEncryptAndSign23(String text) {this.extraEncryptAndSign23 = text;} + + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getEncryptAndSign() { + return this.encryptAndSign; + } + + public void setEncryptAndSign(String encryptAndSign) { + this.encryptAndSign = encryptAndSign; + } + + @DynamoDbEncryptionSignOnly + public String getSignOnly() { + return this.signOnly; + } + + public void setSignOnly(String signOnly) { + this.signOnly = signOnly; + } + + @DynamoDbEncryptionDoNothing + public String getDoNothing() { + return this.doNothing; + } + + public void setDoNothing(String doNothing) { + this.doNothing = doNothing; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable3.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable3.java new file mode 100644 index 000000000..faad4846c --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable3.java @@ -0,0 +1,123 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.validdatamodels; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignAndIncludeInEncryptionContext; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SingleTable3 { + + private String partitionKey; + private int sortKey; + private String encryptAndSign; + private String doNothing; + private String signOnly; + + private String extraSignOnly3; + private String extraSignOnly23; + private String extraSignOnly13; + private String extraSignAndInclude3; + private String extraSignAndInclude23; + private String extraSignAndInclude13; + private String extraDoNothing3; + private String extraDoNothing23; + private String extraDoNothing13; + private String extraEncryptAndSign3; + private String extraEncryptAndSign23; + private String extraEncryptAndSign13; + + public String getExtraEncryptAndSign3() {return this.extraEncryptAndSign3;} + public String getExtraEncryptAndSign23() {return this.extraEncryptAndSign23;} + public String getExtraEncryptAndSign13() {return this.extraEncryptAndSign13;} + + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing3() {return this.extraDoNothing3;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing23() {return this.extraDoNothing23;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing13() {return this.extraDoNothing13;} + + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly3() {return this.extraSignOnly3;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly23() {return this.extraSignOnly23;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly13() {return this.extraSignOnly13;} + + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude3() {return this.extraSignAndInclude3;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude23() {return this.extraSignAndInclude23;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude13() {return this.extraSignAndInclude13;} + + public void setExtraDoNothing3(String text) {this.extraDoNothing3 = text;} + public void setExtraDoNothing23(String text) {this.extraDoNothing23 = text;} + public void setExtraDoNothing13(String text) {this.extraDoNothing13 = text;} + + public void setExtraSignOnly3(String text) {this.extraSignOnly3 = text;} + public void setExtraSignOnly23(String text) {this.extraSignOnly23 = text;} + public void setExtraSignOnly13(String text) {this.extraSignOnly13 = text;} + + public void setExtraSignAndInclude3(String text) {this.extraSignAndInclude3 = text;} + public void setExtraSignAndInclude23(String text) {this.extraSignAndInclude23 = text;} + public void setExtraSignAndInclude13(String text) {this.extraSignAndInclude13 = text;} + + public void setExtraEncryptAndSign3(String text) {this.extraEncryptAndSign3 = text;} + public void setExtraEncryptAndSign23(String text) {this.extraEncryptAndSign23 = text;} + public void setExtraEncryptAndSign13(String text) {this.extraEncryptAndSign13 = text;} + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getEncryptAndSign() { + return this.encryptAndSign; + } + + public void setEncryptAndSign(String encryptAndSign) { + this.encryptAndSign = encryptAndSign; + } + + @DynamoDbEncryptionSignOnly + public String getSignOnly() { + return this.signOnly; + } + + public void setSignOnly(String signOnly) { + this.signOnly = signOnly; + } + + @DynamoDbEncryptionDoNothing + public String getDoNothing() { + return this.doNothing; + } + + public void setDoNothing(String doNothing) { + this.doNothing = doNothing; + } +} diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass2.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass2.java new file mode 100644 index 000000000..2c8246b49 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass2.java @@ -0,0 +1,70 @@ +package software.amazon.cryptography.examples.enhanced; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SimpleClass2 { + + private String partitionKey; + private int sortKey; + private String attribute4; + private String attribute5; + private String attribute3; + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getAttribute4() { + return this.attribute4; + } + + public void setAttribute4(String attribute4) { + this.attribute4 = attribute4; + } + + @DynamoDbEncryptionSignOnly + public String getAttribute5() { + return this.attribute5; + } + + public void setAttribute5(String attribute5) { + this.attribute5 = attribute5; + } + + @DynamoDbEncryptionDoNothing + public String getAttribute3() { + return this.attribute3; + } + + @DynamoDbAttribute(value = ":attribute3") + public void setAttribute3(String attribute3) { + this.attribute3 = attribute3; + } +} + diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass3.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass3.java new file mode 100644 index 000000000..f98305e0f --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass3.java @@ -0,0 +1,70 @@ +package software.amazon.cryptography.examples.enhanced; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SimpleClass3 { + + private String partitionKey; + private int sortKey; + private String attribute6; + private String attribute2; + private String attribute7; + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getAttribute6() { + return this.attribute6; + } + + public void setAttribute6(String attribute6) { + this.attribute6 = attribute6; + } + + @DynamoDbEncryptionSignOnly + public String getAttribute2() { + return this.attribute2; + } + + public void setAttribute2(String attribute2) { + this.attribute2 = attribute2; + } + + @DynamoDbEncryptionDoNothing + public String getAttribute7() { + return this.attribute7; + } + + @DynamoDbAttribute(value = ":attribute7") + public void setAttribute7(String attribute7) { + this.attribute7 = attribute7; + } +} + diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SingleTableExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SingleTableExample.java new file mode 100644 index 000000000..5cc7c4d6a --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SingleTableExample.java @@ -0,0 +1,213 @@ +package software.amazon.cryptography.examples.enhanced; + +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.enhanced.dynamodb.Document; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.DBEAlgorithmSuiteId; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.CreateDynamoDbEncryptionInterceptorInput; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedClientEncryption; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedTableEncryptionConfig; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.model.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.List; + +/* + This example sets up DynamoDb Encryption for the DynamoDb Enhanced Client + and uses the high level putItem() and getItem() APIs to demonstrate + putting a client-side encrypted item into DynamoDb + and then retrieving and decrypting that item from DynamoDb. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + */ +public class SingleTableExample { + + public static void TransactWriteItems(String kmsKeyId, String ddbTableName) { + // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + // For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + // We will use the `CreateMrkMultiKeyring` method to create this keyring, + // as it will correctly handle both single region and Multi-Region KMS Keys. + final MaterialProviders matProv = MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMrkMultiKeyringInput keyringInput = CreateAwsKmsMrkMultiKeyringInput.builder() + .generator(kmsKeyId) + .build(); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput); + + // 2. Create a Table Schema over your annotated class (See SimpleClass.java in this directory). + // By default, all primary key attributes will be signed but not encrypted (SIGN_ONLY) + // and all non-primary key attributes will be encrypted and signed (ENCRYPT_AND_SIGN). + // If you want a particular non-primary key attribute to be signed but not encrypted, + // use the `DynamoDbEncryptionSignOnly` annotation. + // If you want a particular attribute to be neither signed nor encrypted (DO_NOTHING), + // use the `DynamoDbEncryptionDoNothing` annotation. + final TableSchema tableSchema1 = TableSchema.fromBean(SimpleClass.class); + final TableSchema tableSchema2 = TableSchema.fromBean(SimpleClass2.class); + final TableSchema tableSchema3 = TableSchema.fromBean(SimpleClass3.class); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can immediately start writing to a new `DynamoDbEncryptionDoNothing` attribute + // as long as it's name uses this prefix, without any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we have designed our DynamoDb table such that any attribute name with + // the ":" prefix should be considered unauthenticated. + final String unsignAttrPrefix = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to, + final Map tableConfigs = new HashMap<>(); + tableConfigs.put(ddbTableName, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(ddbTableName) + .keyring(kmsKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + .schemaOnEncrypt(tableSchema1) + .schemaOnEncrypt(tableSchema2) + .schemaOnEncrypt(tableSchema3) + // Specifying an algorithm suite is not required, + // but is done here to demonstrate how to do so. + // We suggest using the + // `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + // which includes AES-GCM with key derivation, signing, and key commitment. + // This is also the default algorithm suite if one is not specified in this config. + // For more information on supported algorithm suites, see: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + .algorithmSuiteId( + DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384) + .build()); + + // 5. Create the DynamoDb Encryption Interceptor, using the DynamoDbEnhancedClientEncryption helper + final DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ); + + // 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient.builder() + .overrideConfiguration( + ClientOverrideConfiguration.builder() + .addExecutionInterceptor(encryptionInterceptor) + .build()) + .build(); + + // 7. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // and create a Table with your modelled class + final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() + .dynamoDbClient(ddb) + .build(); + final DynamoDbTable table1 = enhancedClient.table(ddbTableName, tableSchema1); + final DynamoDbTable table2 = enhancedClient.table(ddbTableName, tableSchema2); + final DynamoDbTable table3 = enhancedClient.table(ddbTableName, tableSchema3); + + // 8. Put an item into your table using the DynamoDb Enhanced Client. + // The item will be encrypted client-side according to your + // configuration above before it is sent to DynamoDb. + final SimpleClass item1 = new SimpleClass(); + item1.setPartitionKey("EnhancedPutGetExample1"); + item1.setSortKey(0); + item1.setAttribute1("item1 encrypt and sign me!"); + item1.setAttribute2("item1 sign me!"); + item1.setAttribute3("item1 ignore me!"); + + final SimpleClass2 item2 = new SimpleClass2(); + item2.setPartitionKey("EnhancedPutGetExample2"); + item2.setSortKey(0); + item2.setAttribute4("item2 encrypt and sign me!"); + item2.setAttribute5("item2 sign me!"); + item2.setAttribute3("item2 ignore me!"); + + final SimpleClass3 item3 = new SimpleClass3(); + item3.setPartitionKey("EnhancedPutGetExample3"); + item3.setSortKey(0); + item3.setAttribute6("item3 encrypt and sign me!"); + item3.setAttribute2("item3 sign me!"); + item3.setAttribute7("item3 ignore me!"); + + // Create enhanced request providing the table schema and the item types we want to write + final TransactWriteItemsEnhancedRequest request = TransactWriteItemsEnhancedRequest.builder() + .addPutItem(table1, item1) + .addPutItem(table2, item2) + .addPutItem(table3, item3) + .build(); + enhancedClient.transactWriteItems(request); + + // 9. Get the item back from the table using the DynamoDb Enhanced Client. + // The item will be decrypted client-side, and you will get back the + // original item. + final SimpleClass key1 = new SimpleClass(); + key1.setPartitionKey("EnhancedPutGetExample1"); + key1.setSortKey(0); + + final SimpleClass2 key2 = new SimpleClass2(); + key2.setPartitionKey("EnhancedPutGetExample2"); + key2.setSortKey(0); + + final SimpleClass3 key3 = new SimpleClass3(); + key3.setPartitionKey("EnhancedPutGetExample3"); + key3.setSortKey(0); + + final TransactGetItemsEnhancedRequest getRequest = TransactGetItemsEnhancedRequest.builder() + .addGetItem(table1, key1) + .addGetItem(table2, key2) + .addGetItem(table3, key3) + .build(); + List getResult = enhancedClient.transactGetItems(getRequest); + + final SimpleClass result1 = getResult.get(0).getItem(table1); + final SimpleClass2 result2 = getResult.get(1).getItem(table2); + final SimpleClass3 result3 = getResult.get(2).getItem(table3); + + // Demonstrate we get the original item back + assert result1.getAttribute1().equals("item1 encrypt and sign me!"); + assert result2.getAttribute4().equals("item2 encrypt and sign me!"); + assert result3.getAttribute6().equals("item3 encrypt and sign me!"); + } + + public static void main(final String[] args) { + if (args.length < 2) { + throw new IllegalArgumentException("To run this example, include the kmsKeyId as args[0] and ddbTableName as args[1]"); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + TransactWriteItems(kmsKeyId, ddbTableName); + } +} diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/enhanced/TestSingleTableExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/enhanced/TestSingleTableExample.java new file mode 100644 index 000000000..7a3dff2a1 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/enhanced/TestSingleTableExample.java @@ -0,0 +1,11 @@ +package software.amazon.cryptography.examples.enhanced; + +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.TestUtils; + +public class TestSingleTableExample { + @Test + public void TestEnhancedSingleTable() { + SingleTableExample.TransactWriteItems(TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME); + } +}