diff --git a/.github/workflows/ci_examples_java.yml b/.github/workflows/ci_examples_java.yml index c5efc2f03c..65dd3d34e1 100644 --- a/.github/workflows/ci_examples_java.yml +++ b/.github/workflows/ci_examples_java.yml @@ -26,6 +26,7 @@ on: jobs: testJava: strategy: + fail-fast: false matrix: java-version: [8, 11, 17, 19] os: [macos-14] @@ -103,6 +104,7 @@ jobs: run: | # Run simple examples gradle -p runtimes/java/DynamoDbEncryption test + gradle -p runtimes/java/DDBECwithSDKV2 test # Run migration examples gradle -p runtimes/java/Migration/PlaintextToAWSDBE test gradle -p runtimes/java/Migration/DDBECToAWSDBE test diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml index 3f237ee0d0..7038076c45 100644 --- a/.github/workflows/pull.yml +++ b/.github/workflows/pull.yml @@ -1,6 +1,10 @@ # This workflow runs for every pull request name: PR CI +permissions: + contents: read + id-token: write + on: pull_request: diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java index b7ee420dcc..a0c3eb05e9 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/InternalLegacyOverride.java @@ -16,7 +16,6 @@ import StandardLibraryInternal.InternalResult; import Wrappers_Compile.Option; import Wrappers_Compile.Result; -import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor; import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext; import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionFlags; import dafny.DafnyMap; @@ -28,38 +27,31 @@ import software.amazon.awssdk.core.SdkBytes; import software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor; import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.LegacyPolicy; -import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.ToNative; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction; public class InternalLegacyOverride extends _ExternBase_InternalLegacyOverride { - private DynamoDBEncryptor encryptor; - private Map> actions; - private EncryptionContext encryptionContext; - private LegacyPolicy _policy; - private DafnySequence materialDescriptionFieldName; - private DafnySequence signatureFieldName; + private final LegacyEncryptorAdapter _encryptorAdapter; + private final LegacyPolicy _policy; + private final DafnySequence materialDescriptionFieldNameDafny; + private final DafnySequence signatureFieldNameDafny; private InternalLegacyOverride( - DynamoDBEncryptor encryptor, - Map> actions, - EncryptionContext encryptionContext, + LegacyEncryptorAdapter encryptorAdapter, LegacyPolicy policy ) { - this.encryptor = encryptor; - this.actions = actions; - this.encryptionContext = encryptionContext; + this._encryptorAdapter = encryptorAdapter; this._policy = policy; // It is possible that these values // have been customized by the customer. - this.materialDescriptionFieldName = + this.materialDescriptionFieldNameDafny = software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - encryptor.getMaterialDescriptionFieldName() + encryptorAdapter.getMaterialDescriptionFieldName() ); - this.signatureFieldName = + this.signatureFieldNameDafny = software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence( - encryptor.getSignatureFieldName() + encryptorAdapter.getSignatureFieldName() ); } @@ -78,8 +70,8 @@ public boolean IsLegacyInput( //# attributes for the material description and the signature. return ( input.is_DecryptItemInput() && - input._encryptedItem.contains(materialDescriptionFieldName) && - input._encryptedItem.contains(signatureFieldName) + input._encryptedItem.contains(materialDescriptionFieldNameDafny) && + input._encryptedItem.contains(signatureFieldNameDafny) ); } @@ -111,17 +103,13 @@ > EncryptItem( final Map< String, - com.amazonaws.services.dynamodbv2.model.AttributeValue - > encryptedItem = encryptor.encryptRecord( - V2MapToV1Map(plaintextItem), - actions, - encryptionContext - ); + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > encryptedItem = _encryptorAdapter.encryptRecord(plaintextItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.EncryptItemOutput .builder() - .encryptedItem(V1MapToV2Map(encryptedItem)) + .encryptedItem(encryptedItem) .build(); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.EncryptItemOutput dafnyOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.ToDafny.EncryptItemOutput( @@ -162,19 +150,15 @@ > DecryptItem( .DecryptItemInput(input) .encryptedItem(); - final Map< + Map< String, - com.amazonaws.services.dynamodbv2.model.AttributeValue - > plaintextItem = encryptor.decryptRecord( - V2MapToV1Map(encryptedItem), - actions, - encryptionContext - ); + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > plaintextItem = _encryptorAdapter.decryptRecord(encryptedItem); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput nativeOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model.DecryptItemOutput .builder() - .plaintextItem(V1MapToV2Map(plaintextItem)) + .plaintextItem(plaintextItem) .build(); final software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.DecryptItemOutput dafnyOutput = software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.ToDafny.DecryptItemOutput( @@ -224,11 +208,35 @@ public static Result, Error> Build( return CreateBuildFailure(maybeEncryptionContext.error()); } + final LegacyEncryptorAdapter encryptorAdapter; + if (maybeEncryptor instanceof com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor) { + encryptorAdapter = + new V1EncryptorAdapter( + (com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor) maybeEncryptor, + maybeActions.value(), + maybeEncryptionContext.value() + ); + } else if ( + maybeEncryptor instanceof + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor + ) { + encryptorAdapter = + new V2EncryptorAdapter( + (software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor) maybeEncryptor, + convertActionsV1ToV2(maybeActions.value()), + convertEncryptionContextV1ToV2(maybeEncryptionContext.value()) + ); + } else { + return CreateBuildFailure( + createError( + "Unsupported encryptor type: " + maybeEncryptor.getClass().getName() + ) + ); + } + final InternalLegacyOverride internalLegacyOverride = new InternalLegacyOverride( - (DynamoDBEncryptor) maybeEncryptor, - maybeActions.value(), - maybeEncryptionContext.value(), + encryptorAdapter, legacyOverride.dtor_policy() ); @@ -250,7 +258,61 @@ public static Error createError(String message) { public static boolean isDynamoDBEncryptor( software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor maybe ) { - return maybe instanceof DynamoDBEncryptor; + return ( + maybe instanceof com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor || + maybe instanceof + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor + ); + } + + // Convert SDK V1 EncryptionFlags to SDK V2 + private static Map< + String, + Set< + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags + > + > convertActionsV1ToV2(Map> v1Actions) { + Map< + String, + Set< + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags + > + > v2Actions = new HashMap<>(); + for (Map.Entry> entry : v1Actions.entrySet()) { + Set< + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags + > v2Flags = new HashSet<>(); + for (EncryptionFlags v1Flag : entry.getValue()) { + v2Flags.add( + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags.valueOf( + v1Flag.name() + ) + ); + } + v2Actions.put(entry.getKey(), v2Flags); + } + return v2Actions; + } + + // Convert SDK V1 EncryptionContext to SDK V2 + private static software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext convertEncryptionContextV1ToV2( + final EncryptionContext v1Context + ) { + final software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext.Builder builder = + software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext + .builder() + .tableName(v1Context.getTableName()) + .hashKeyName(v1Context.getHashKeyName()) + .rangeKeyName(v1Context.getRangeKeyName()) + .developerContext(v1Context.getDeveloperContext()); + + if (v1Context.getMaterialDescription() != null) { + builder.materialDescription(v1Context.getMaterialDescription()); + } + if (v1Context.getAttributeValues() != null) { + builder.attributeValues(V1MapToV2Map(v1Context.getAttributeValues())); + } + return builder.build(); } public static String ToNativeString(DafnySequence s) { @@ -377,10 +439,16 @@ public static com.amazonaws.services.dynamodbv2.model.AttributeValue V2Attribute case SS: return attribute.withSS(value.ss()); case UNKNOWN_TO_SDK_VERSION: - throw new IllegalArgumentException("omfg"); + throw new IllegalArgumentException( + "Unsupported AttributeValue type: UNKNOWN_TO_SDK_VERSION. This may indicate a newer DynamoDB attribute type that is not supported by this SDK version." + ); } - throw new IllegalArgumentException("omfg"); + throw new IllegalArgumentException( + "Unexpected AttributeValue type: " + + value.type() + + ". Unable to convert from SDK v2 to SDK v1 format." + ); } public static Map< @@ -392,6 +460,9 @@ > V2MapToV1Map( software.amazon.awssdk.services.dynamodb.model.AttributeValue > input ) { + if (input == null) { + return null; + } return input .entrySet() .stream() @@ -459,6 +530,9 @@ public static software.amazon.awssdk.services.dynamodb.model.AttributeValue V1At > V1MapToV2Map( Map input ) { + if (input == null) { + return null; + } return input .entrySet() .stream() diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java new file mode 100644 index 0000000000..284f8d675f --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/LegacyEncryptorAdapter.java @@ -0,0 +1,27 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; + +import java.security.GeneralSecurityException; +import java.util.Map; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +public interface LegacyEncryptorAdapter { + Map encryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException; + + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > decryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException; + + String getMaterialDescriptionFieldName(); + String getSignatureFieldName(); +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java new file mode 100644 index 0000000000..f563281495 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V1EncryptorAdapter.java @@ -0,0 +1,68 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; + +import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V1MapToV2Map; +import static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy.InternalLegacyOverride.V2MapToV1Map; + +import com.amazonaws.services.dynamodbv2.datamodeling.encryption.DynamoDBEncryptor; +import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionContext; +import com.amazonaws.services.dynamodbv2.datamodeling.encryption.EncryptionFlags; +import java.security.GeneralSecurityException; +import java.util.Map; +import java.util.Set; + +public class V1EncryptorAdapter implements LegacyEncryptorAdapter { + + private final DynamoDBEncryptor encryptor; + private final Map> actions; + private final EncryptionContext encryptionContext; + + V1EncryptorAdapter( + DynamoDBEncryptor encryptor, + Map> actions, + EncryptionContext encryptionContext + ) { + this.encryptor = encryptor; + this.actions = actions; + this.encryptionContext = encryptionContext; + } + + @Override + public Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > encryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException { + return V1MapToV2Map( + encryptor.encryptRecord(V2MapToV1Map(item), actions, encryptionContext) + ); + } + + @Override + public Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > decryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException { + return V1MapToV2Map( + encryptor.decryptRecord(V2MapToV1Map(item), actions, encryptionContext) + ); + } + + @Override + public String getMaterialDescriptionFieldName() { + return encryptor.getMaterialDescriptionFieldName(); + } + + @Override + public String getSignatureFieldName() { + return encryptor.getSignatureFieldName(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java new file mode 100644 index 0000000000..41622bd9f4 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/internaldafny/legacy/V2EncryptorAdapter.java @@ -0,0 +1,61 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.legacy; + +import java.security.GeneralSecurityException; +import java.util.Map; +import java.util.Set; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; + +public class V2EncryptorAdapter implements LegacyEncryptorAdapter { + + private final DynamoDBEncryptor encryptor; + private final Map> actions; + private final EncryptionContext encryptionContext; + + V2EncryptorAdapter( + DynamoDBEncryptor encryptor, + Map> actions, + EncryptionContext encryptionContext + ) { + this.encryptor = encryptor; + this.actions = actions; + this.encryptionContext = encryptionContext; + } + + @Override + public Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > encryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException { + return encryptor.encryptRecord(item, actions, encryptionContext); + } + + @Override + public Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > decryptRecord( + Map< + String, + software.amazon.awssdk.services.dynamodb.model.AttributeValue + > item + ) throws GeneralSecurityException { + return encryptor.decryptRecord(item, actions, encryptionContext); + } + + @Override + public String getMaterialDescriptionFieldName() { + return encryptor.getMaterialDescriptionFieldName(); + } + + @Override + public String getSignatureFieldName() { + return encryptor.getSignatureFieldName(); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDBEncryptor.java similarity index 98% rename from DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java rename to DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDBEncryptor.java index 95e6ec73c7..b52494e76e 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptor.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDBEncryptor.java @@ -34,6 +34,7 @@ import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.ILegacyDynamoDbEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; @@ -48,7 +49,7 @@ * * @author Greg Rubin */ -public class DynamoDbEncryptor { +public class DynamoDBEncryptor implements ILegacyDynamoDbEncryptor { private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256withRSA"; private static final String DEFAULT_METADATA_FIELD = "*amzn-ddb-map-desc*"; private static final String DEFAULT_SIGNATURE_FIELD = "*amzn-ddb-map-sig*"; @@ -79,19 +80,19 @@ public class DynamoDbEncryptor { private Function encryptionContextOverrideOperator; - protected DynamoDbEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { + protected DynamoDBEncryptor(EncryptionMaterialsProvider provider, String descriptionBase) { this.encryptionMaterialsProvider = provider; this.descriptionBase = descriptionBase; symmetricEncryptionModeHeader = this.descriptionBase + "sym-mode"; signingAlgorithmHeader = this.descriptionBase + "signingAlg"; } - public static DynamoDbEncryptor getInstance( + public static DynamoDBEncryptor getInstance( EncryptionMaterialsProvider provider, String descriptionbase) { - return new DynamoDbEncryptor(provider, descriptionbase); + return new DynamoDBEncryptor(provider, descriptionbase); } - public static DynamoDbEncryptor getInstance(EncryptionMaterialsProvider provider) { + public static DynamoDBEncryptor getInstance(EncryptionMaterialsProvider provider) { return getInstance(provider, DEFAULT_DESCRIPTION_BASE); } @@ -454,7 +455,7 @@ private void actualEncryption(Map itemAttributes, * * @return the name of the DynamoDB field used to store the signature */ - String getSignatureFieldName() { + public String getSignatureFieldName() { return signatureFieldName; } @@ -474,7 +475,7 @@ void setSignatureFieldName(final String signatureFieldName) { * @return the name of the DynamoDB field used to store metadata used by the * DynamoDBEncryptedMapper */ - String getMaterialDescriptionFieldName() { + public String getMaterialDescriptionFieldName() { return materialDescriptionFieldName; } diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java index 9a78ad9b04..f0ae27e26c 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/EncryptionContext.java @@ -83,7 +83,7 @@ public Map getAttributeValues() { /** * This object has no meaning (and will not be set or examined) by any core libraries. * It exists to allow custom object mappers and data access layers to pass - * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDbEncryptor}. + * data to {@link EncryptionMaterialsProvider}s through the {@link DynamoDBEncryptor}. */ public Object getDeveloperContext() { return developerContext; diff --git a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java index c0fbe5e06f..9744061094 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStore.java @@ -44,7 +44,7 @@ import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; import software.amazon.awssdk.services.dynamodb.model.QueryRequest; import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.EncryptionMaterialsProvider; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; @@ -95,7 +95,7 @@ public class MetaStore extends ProviderStore { // private final DynamoDbEncryptionConfiguration encryptionConfiguration; private final String tableName; private final DynamoDbClient ddb; - private final DynamoDbEncryptor encryptor; + private final DynamoDBEncryptor encryptor; private final EncryptionContext ddbCtx; private final ExtraDataSupplier extraDataSupplier; @@ -129,7 +129,7 @@ public interface ExtraDataSupplier { * @param encryptor used to perform crypto operations on the record attributes. */ public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor) { + final DynamoDBEncryptor encryptor) { this(ddb, tableName, encryptor, EMPTY_EXTRA_DATA_SUPPLIER); } @@ -142,7 +142,7 @@ public MetaStore(final DynamoDbClient ddb, final String tableName, * @param extraDataSupplier provides extra data that should be stored along with the material. */ public MetaStore(final DynamoDbClient ddb, final String tableName, - final DynamoDbEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { + final DynamoDBEncryptor encryptor, final ExtraDataSupplier extraDataSupplier) { this.ddb = checkNotNull(ddb, "ddb must not be null"); this.tableName = checkNotNull(tableName, "tableName must not be null"); this.encryptor = checkNotNull(encryptor, "encryptor must not be null"); @@ -389,7 +389,7 @@ private EncryptionMaterialsProvider decryptProvider(final Map getPlainText(final Map encryptedRecord; Map> actions; EncryptionContext encryptionContext = @@ -690,7 +690,7 @@ private Map getItems(Map map, St private void assertVersionCompatibility(EncryptionMaterialsProvider provider, String tableName) throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(provider); Map response; Map decryptedRecord; EncryptionContext encryptionContext = @@ -805,7 +805,7 @@ private void assertVersionCompatibility(EncryptionMaterialsProvider provider, St private void assertVersionCompatibility_2(EncryptionMaterialsProvider provider, String tableName) throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(provider); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(provider); Map response; Map decryptedRecord; EncryptionContext encryptionContext = diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java index fd3bf37ace..b2f76bd374 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEncryptionTest.java @@ -54,7 +54,7 @@ public class DelegatedEncryptionTest { private static DelegatedKey macKey; private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; + private DynamoDBEncryptor encryptor; private Map attribs; private EncryptionContext context; @@ -71,7 +71,7 @@ public static void setupClass() { public void setUp() { prov = new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); attribs = new HashMap<>(); attribs.put("intValue", AttributeValue.builder().n("123").build()); @@ -189,7 +189,7 @@ public void signedOnly() throws GeneralSecurityException { @Test public void signedOnlyNullCryptoKey() throws GeneralSecurityException { prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); @@ -234,7 +234,7 @@ public void RsaSignedOnly() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-" @@ -262,7 +262,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-" diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java index ce22c396fa..c052c6d1a8 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DelegatedEnvelopeEncryptionTest.java @@ -55,7 +55,7 @@ public class DelegatedEnvelopeEncryptionTest { private static DelegatedKey macKey; private EncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; + private DynamoDBEncryptor encryptor; private Map attribs; private EncryptionContext context; @@ -73,7 +73,7 @@ public void setUp() throws Exception { prov = new WrappedMaterialsProvider( encryptionKey, encryptionKey, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); attribs = new HashMap(); attribs.put("intValue", AttributeValue.builder().n("123").build()); @@ -174,7 +174,7 @@ public void badVersionNumber() throws GeneralSecurityException { @Test public void signedOnlyNullCryptoKey() throws GeneralSecurityException { prov = new SymmetricStaticProvider(null, macKey, Collections.emptyMap()); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); @@ -218,7 +218,7 @@ public void RsaSignedOnly() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); @@ -246,7 +246,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java index 87fb8353bb..b332e9c2d6 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/DynamoDbEncryptorTest.java @@ -64,7 +64,7 @@ public class DynamoDbEncryptorTest { private static SecretKey macKey; private InstrumentedEncryptionMaterialsProvider prov; - private DynamoDbEncryptor encryptor; + private DynamoDBEncryptor encryptor; private Map attribs; private EncryptionContext context; private static final String OVERRIDDEN_TABLE_NAME = "TheBestTableName"; @@ -85,7 +85,7 @@ public void setUp() { prov = new InstrumentedEncryptionMaterialsProvider( new SymmetricStaticProvider(encryptionKey, macKey, Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "enryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "enryptor-"); attribs = new HashMap<>(); attribs.put("intValue", AttributeValue.builder().n("123").build()); @@ -255,7 +255,7 @@ public void signedOnlyNullCryptoKey() throws GeneralSecurityException { prov = new InstrumentedEncryptionMaterialsProvider( new SymmetricStaticProvider(null, macKey, Collections.emptyMap())); - encryptor = DynamoDbEncryptor.getInstance(prov, "encryptor-"); + encryptor = DynamoDBEncryptor.getInstance(prov, "encryptor-"); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); assertThat(encryptedAttributes, AttrMatcher.invert(attribs)); @@ -299,7 +299,7 @@ public void RsaSignedOnly() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); @@ -327,7 +327,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { rsaGen.initialize(2048, Utils.getRng()); KeyPair sigPair = rsaGen.generateKeyPair(); encryptor = - DynamoDbEncryptor.getInstance( + DynamoDBEncryptor.getInstance( new SymmetricStaticProvider( encryptionKey, sigPair, Collections.emptyMap()), "encryptor-"); @@ -347,7 +347,7 @@ public void RsaSignedOnlyBadSignature() throws GeneralSecurityException { */ @Test public void testNullEncryptionContextOperator() throws GeneralSecurityException { - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator(null); encryptor.encryptAllFieldsExcept(attribs, context, Collections.emptyList()); } @@ -359,7 +359,7 @@ public void testNullEncryptionContextOperator() throws GeneralSecurityException public void testTableNameOverriddenEncryptionContextOperator() throws GeneralSecurityException { // Ensure that the table name is different from what we override the table to. assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator( overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); Map encryptedItems = @@ -378,8 +378,8 @@ public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor( throws GeneralSecurityException { // Ensure that the table name is different from what we override the table to. assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); + DynamoDBEncryptor encryptorWithoutOverride = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator( overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); Map encryptedItems = @@ -402,8 +402,8 @@ public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor( throws GeneralSecurityException { // Ensure that the table name is different from what we override the table to. assertThat(context.getTableName(), not(equalTo(OVERRIDDEN_TABLE_NAME))); - DynamoDbEncryptor encryptor = DynamoDbEncryptor.getInstance(prov); - DynamoDbEncryptor encryptorWithoutOverride = DynamoDbEncryptor.getInstance(prov); + DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(prov); + DynamoDBEncryptor encryptorWithoutOverride = DynamoDBEncryptor.getInstance(prov); encryptor.setEncryptionContextOverrideOperator( overrideEncryptionContextTableName(context.getTableName(), OVERRIDDEN_TABLE_NAME)); Map encryptedItems = @@ -418,7 +418,7 @@ public void testTableNameOverriddenEncryptionContextOperatorWithSecondEncryptor( @Test public void EcdsaSignedOnly() throws GeneralSecurityException { - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + encryptor = DynamoDBEncryptor.getInstance(getMaterialProviderwithECDSA()); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); @@ -440,7 +440,7 @@ public void EcdsaSignedOnly() throws GeneralSecurityException { @Test(expectedExceptions = SignatureException.class) public void EcdsaSignedOnlyBadSignature() throws GeneralSecurityException { - encryptor = DynamoDbEncryptor.getInstance(getMaterialProviderwithECDSA()); + encryptor = DynamoDBEncryptor.getInstance(getMaterialProviderwithECDSA()); Map encryptedAttributes = encryptor.encryptAllFieldsExcept(attribs, context, attribs.keySet().toArray(new String[0])); @@ -500,7 +500,7 @@ public void testDecryptWithPlainTextItemAndAdditionNewAttributeHavingEncryptionF private void assertToByteArray( final String msg, final byte[] expected, final ByteBuffer testValue) throws ReflectiveOperationException { - Method m = DynamoDbEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); + Method m = DynamoDBEncryptor.class.getDeclaredMethod("toByteArray", ByteBuffer.class); m.setAccessible(true); int oldPosition = testValue.position(); @@ -537,7 +537,7 @@ private EncryptionMaterialsProvider getMaterialProviderwithECDSA() g.initialize(ecSpec, Utils.getRng()); KeyPair keypair = g.generateKeyPair(); Map description = new HashMap<>(); - description.put(DynamoDbEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); + description.put(DynamoDBEncryptor.DEFAULT_SIGNING_ALGORITHM_HEADER, "SHA384withECDSA"); return new SymmetricStaticProvider(null, keypair, description); } diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java index f286648332..e3b4078bc8 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/CachingMostRecentProviderTests.java @@ -8,7 +8,7 @@ import static org.testng.AssertJUnit.assertTrue; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.EncryptionMaterials; @@ -40,7 +40,7 @@ public class CachingMostRecentProviderTests { new SecretKeySpec(new byte[] {0, 1, 2, 3, 4, 5, 6, 7}, "HmacSHA256"); private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); + private static final DynamoDBEncryptor ENCRYPTOR = DynamoDBEncryptor.getInstance(BASE_PROVIDER); private DynamoDbClient client; private Map methodCalls; diff --git a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java index 3449908a6d..f98f807fda 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java +++ b/DynamoDbEncryption/runtimes/java/src/test/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/store/MetaStoreTests.java @@ -28,7 +28,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDbEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.materials.DecryptionMaterials; @@ -56,8 +56,8 @@ public class MetaStoreTests { 2, 4, 6, 8, 10, 12, 14 }, "HmacSHA256"); private static final EncryptionMaterialsProvider BASE_PROVIDER = new SymmetricStaticProvider(AES_KEY, HMAC_KEY); private static final EncryptionMaterialsProvider TARGET_BASE_PROVIDER = new SymmetricStaticProvider(TARGET_AES_KEY, TARGET_HMAC_KEY); - private static final DynamoDbEncryptor ENCRYPTOR = DynamoDbEncryptor.getInstance(BASE_PROVIDER); - private static final DynamoDbEncryptor TARGET_ENCRYPTOR = DynamoDbEncryptor.getInstance(TARGET_BASE_PROVIDER); + private static final DynamoDBEncryptor ENCRYPTOR = DynamoDBEncryptor.getInstance(BASE_PROVIDER); + private static final DynamoDBEncryptor TARGET_ENCRYPTOR = DynamoDBEncryptor.getInstance(TARGET_BASE_PROVIDER); private final LocalDynamoDb localDynamoDb = new LocalDynamoDb(); private final LocalDynamoDb targetLocalDynamoDb = new LocalDynamoDb(); diff --git a/Examples/runtimes/java/DDBECwithSDKV2/.gitignore b/Examples/runtimes/java/DDBECwithSDKV2/.gitignore new file mode 100644 index 0000000000..221fe55b59 --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/.gitignore @@ -0,0 +1,30 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* diff --git a/Examples/runtimes/java/DDBECwithSDKV2/build.gradle.kts b/Examples/runtimes/java/DDBECwithSDKV2/build.gradle.kts new file mode 100644 index 0000000000..b3e20a4f25 --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/build.gradle.kts @@ -0,0 +1,120 @@ +import java.io.File +import java.io.FileInputStream +import java.util.Properties +import java.net.URI +import javax.annotation.Nullable +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent + +plugins { + `java` + `java-library` +} + +var props = Properties().apply { + load(FileInputStream(File(rootProject.rootDir, "../../../../project.properties"))) +} + +group = "software.amazon.cryptography" +version = "1.0-SNAPSHOT" +description = "DDBECExamples" + +var mplVersion = props.getProperty("mplDependencyJavaVersion") +var ddbecVersion = props.getProperty("projectJavaVersion") + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(8)) + sourceSets["main"].java { + srcDir("src/main/java") + } + sourceSets["test"].java { + srcDir("src/test/java") + } +} + +var caUrl: URI? = null +@Nullable +val caUrlStr: String? = System.getenv("CODEARTIFACT_REPO_URL") +if (!caUrlStr.isNullOrBlank()) { + caUrl = URI.create(caUrlStr) +} + +var caPassword: String? = null +@Nullable +val caPasswordString: String? = System.getenv("CODEARTIFACT_TOKEN") +if (!caPasswordString.isNullOrBlank()) { + caPassword = caPasswordString +} + +repositories { + mavenLocal() + maven { + name = "DynamoDB Local Release Repository - US West (Oregon) Region" + url = URI.create("https://s3-us-west-2.amazonaws.com/dynamodb-local/release") + } + mavenCentral() + if (caUrl != null && caPassword != null) { + maven { + name = "CodeArtifact" + url = caUrl!! + credentials { + username = "aws" + password = caPassword!! + } + } + } +} + +dependencies { + implementation("software.amazon.cryptography:aws-database-encryption-sdk-dynamodb:${ddbecVersion}") + implementation("software.amazon.cryptography:aws-cryptographic-material-providers:${mplVersion}") + + implementation(platform("software.amazon.awssdk:bom:2.19.1")) + implementation("software.amazon.awssdk:dynamodb") + implementation("software.amazon.awssdk:dynamodb-enhanced") + implementation("software.amazon.awssdk:kms") + + testImplementation("org.testng:testng:7.5") +} + +tasks.withType() { + options.encoding = "UTF-8" +} + +tasks.test { + useTestNG() + + // This will show System.out.println statements + testLogging.showStandardStreams = true + + testLogging { + lifecycle { + events = mutableSetOf(TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED) + exceptionFormat = TestExceptionFormat.FULL + showExceptions = true + showCauses = true + showStackTraces = true + showStandardStreams = true + } + info.events = lifecycle.events + info.exceptionFormat = lifecycle.exceptionFormat + } + + // See https://github.com/gradle/kotlin-dsl/issues/836 + addTestListener(object : TestListener { + override fun beforeSuite(suite: TestDescriptor) {} + override fun beforeTest(testDescriptor: TestDescriptor) {} + override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) {} + + override fun afterSuite(suite: TestDescriptor, result: TestResult) { + if (suite.parent == null) { // root suite + logger.lifecycle("----") + logger.lifecycle("Test result: ${result.resultType}") + logger.lifecycle("Test summary: ${result.testCount} tests, " + + "${result.successfulTestCount} succeeded, " + + "${result.failedTestCount} failed, " + + "${result.skippedTestCount} skipped") + } + } + }) +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/gradle/wrapper/gradle-wrapper.properties b/Examples/runtimes/java/DDBECwithSDKV2/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..f398c33c4b --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/Examples/runtimes/java/DDBECwithSDKV2/gradlew b/Examples/runtimes/java/DDBECwithSDKV2/gradlew new file mode 100755 index 0000000000..65dcd68d65 --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AsymmetricEncryptedItem.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AsymmetricEncryptedItem.java new file mode 100644 index 0000000000..b7385c7d2b --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AsymmetricEncryptedItem.java @@ -0,0 +1,183 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.AsymmetricStaticProvider; + +/** + * Example showing use of RSA keys for encryption and signing. For ease of the example, we create + * new random ones every time. + */ +public class AsymmetricEncryptedItem { + + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + + public static void encryptRecord( + final DynamoDbClient ddbClient, + final String tableName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue + ) throws GeneralSecurityException { + final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + // You should never use the same key for encryption and signing + final KeyPair wrappingKeys = keyGen.generateKeyPair(); + final KeyPair signingKeys = keyGen.generateKeyPair(); + + // Sample record to be encrypted + final Map record = new HashMap<>(); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); + record.put( + BINARY_FIELD_NAME, + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); + + // Create AsymmetricStaticProvider with wrapping and signing keys + final AsymmetricStaticProvider cmp = new AsymmetricStaticProvider( + wrappingKeys, + signingKeys + ); + + // Create DynamoDBEncryptor + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // Create EncryptionContext + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + // Configure EncryptionFlags for each attribute + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); + final Map> actions = new HashMap<>(); + + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case "partition_key": + case "sort_key": + actions.put(attributeName, signOnly); + break; + default: + if (!attributeName.equals(IGNORED_FIELD_NAME)) { + actions.put(attributeName, encryptAndSign); + } + break; + } + } + + // Encrypt the record + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + + // For demo, verify encryption + assert encrypted_record.get(STRING_FIELD_NAME).b() != null; + assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; + assert !record + .get(BINARY_FIELD_NAME) + .b() + .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(IGNORED_FIELD_NAME) + .s() + .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); + + // For demo, the encrypted item is put to DynamoDB. You skip this as needed for their use case. + ddbClient.putItem( + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); + + // Get the item back from DynamoDB + final Map keyToGet = new HashMap<>(); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + + final GetItemResponse getResponse = ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ); + + // Decrypt the record retrieved from DynamoDB + final Map decrypted_record = + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + + // For demo, verify decryption + assert record + .get(STRING_FIELD_NAME) + .s() + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record + .get(NUMBER_FIELD_NAME) + .n() + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record + .get(BINARY_FIELD_NAME) + .b() + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + } + + public static void main(final String[] args) throws GeneralSecurityException { + if (args.length < 5) { + throw new IllegalArgumentException( + "To run this example, include tableName, partitionKeyName, sortKeyName, " + + "partitionKeyValue, sortKeyValue as args" + ); + } + final String tableName = args[0]; + final String partitionKeyName = args[1]; + final String sortKeyName = args[2]; + final String partitionKeyValue = args[3]; + final String sortKeyValue = args[4]; + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord( + ddbClient, + tableName, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsEncryptedItem.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsEncryptedItem.java new file mode 100644 index 0000000000..13204bccc1 --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsEncryptedItem.java @@ -0,0 +1,178 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.security.GeneralSecurityException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; + +/** Example showing use of AWS KMS CMP with record encryption functions directly. */ +public class AwsKmsEncryptedItem { + + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + + public static void encryptRecord( + final DynamoDbClient ddbClient, + final KmsClient kmsClient, + final String tableName, + final String cmkArn, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue + ) throws GeneralSecurityException { + // Sample record to be encrypted + final Map record = new HashMap<>(); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); + record.put( + BINARY_FIELD_NAME, + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); + + // Create DirectKmsMaterialsProvider + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider( + kmsClient, + cmkArn + ); + + // Create DynamoDBEncryptor + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // Create EncryptionContext + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + // Configure EncryptionFlags for each attribute + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); + final Map> actions = new HashMap<>(); + + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case "partition_key": + case "sort_key": + actions.put(attributeName, signOnly); + break; + default: + if (!attributeName.equals(IGNORED_FIELD_NAME)) { + actions.put(attributeName, encryptAndSign); + } + break; + } + } + + // Encrypt the record + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + + // For demo, verify encryption + assert encrypted_record.get(STRING_FIELD_NAME).b() != null; + assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; + assert !record + .get(BINARY_FIELD_NAME) + .b() + .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(IGNORED_FIELD_NAME) + .s() + .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); + + // For demo, the encrypted item is put to DynamoDB. You skip this as needed for their use case. + ddbClient.putItem( + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); + + // Get the item back from DynamoDB + final Map keyToGet = new HashMap<>(); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + + final GetItemResponse getResponse = ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ); + + // Decrypt the record retrieved from DynamoDB + final Map decrypted_record = + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + + // For demo, verify decryption + assert record + .get(STRING_FIELD_NAME) + .s() + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record + .get(NUMBER_FIELD_NAME) + .n() + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record + .get(BINARY_FIELD_NAME) + .b() + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + } + + public static void main(final String[] args) throws GeneralSecurityException { + if (args.length < 6) { + throw new IllegalArgumentException( + "To run this example, include tableName, cmkArn, partitionKeyName, sortKeyName, " + + "partitionKeyValue, sortKeyValue as args" + ); + } + final String tableName = args[0]; + final String cmkArn = args[1]; + final String partitionKeyName = args[2]; + final String sortKeyName = args[3]; + final String partitionKeyValue = args[4]; + final String sortKeyValue = args[5]; + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord( + ddbClient, + KmsClient.create(), + tableName, + cmkArn, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsMultiRegionKey.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsMultiRegionKey.java new file mode 100644 index 0000000000..ced97398ff --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/AwsKmsMultiRegionKey.java @@ -0,0 +1,194 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.security.GeneralSecurityException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; + +/** + * Example showing use of AWS KMS CMP with an AWS KMS Multi-Region Key. We encrypt a record with a + * key in one region, then decrypt the ciphertext with the same key replicated to another region. + * + *

This example demonstrates that data encrypted with one MRK can be decrypted with its replica + * key from a different region, without requiring a Global Table. + */ +public class AwsKmsMultiRegionKey { + + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + + public static void main(String[] args) throws GeneralSecurityException { + if (args.length < 5) { + throw new IllegalArgumentException( + "To run this example, include tableName, cmkArn1, cmkArn2, " + + "partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue as args" + ); + } + + final String tableName = args[0]; + final String cmkArn1 = args[1]; + final String cmkArn2 = args[2]; + final String partitionKeyName = args[3]; + final String sortKeyName = args[4]; + final String partitionKeyValue = args[3]; + final String sortKeyValue = args[4]; + + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord( + ddbClient, + tableName, + cmkArn1, + cmkArn2, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } + } + + public static void encryptRecord( + final DynamoDbClient ddbClient, + final String tableName, + final String cmkArnEncrypt, + final String cmkArnDecrypt, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue + ) throws GeneralSecurityException { + // Extract regions from ARNs + final String encryptRegion = cmkArnEncrypt.split(":")[3]; + final String decryptRegion = cmkArnDecrypt.split(":")[3]; + + final KmsClient kmsEncrypt = KmsClient + .builder() + .region(Region.of(encryptRegion)) + .build(); + final KmsClient kmsDecrypt = KmsClient + .builder() + .region(Region.of(decryptRegion)) + .build(); + + // Sample record to be encrypted + final Map record = new HashMap<>(); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); + record.put( + BINARY_FIELD_NAME, + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); + + // Set up encryptor with first region's KMS key + final DirectKmsMaterialsProvider cmpEncrypt = + new DirectKmsMaterialsProvider(kmsEncrypt, cmkArnEncrypt); + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance( + cmpEncrypt + ); + + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); + final Map> actions = new HashMap<>(); + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case "partition_key": + case "sort_key": + actions.put(attributeName, signOnly); + break; + case IGNORED_FIELD_NAME: + break; + default: + actions.put(attributeName, encryptAndSign); + break; + } + } + + // Encrypt and save to table + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + ddbClient.putItem( + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); + + // Set up decryptor using the replica key from second region + final DirectKmsMaterialsProvider cmpDecrypt = + new DirectKmsMaterialsProvider(kmsDecrypt, cmkArnDecrypt); + final DynamoDBEncryptor decryptor = DynamoDBEncryptor.getInstance( + cmpDecrypt + ); + + // Retrieve from same table + final Map keyToGet = new HashMap<>(); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + + final Map encryptedItem = ddbClient + .getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ) + .item(); + + // Decrypt using replica key - demonstrates multi-region key capability + final Map decrypted_record = + decryptor.decryptRecord(encryptedItem, actions, encryptionContext); + + // Verify decryption + assert record + .get(STRING_FIELD_NAME) + .s() + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record + .get(NUMBER_FIELD_NAME) + .n() + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record + .get(BINARY_FIELD_NAME) + .b() + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + + ddbClient.close(); + kmsEncrypt.close(); + kmsDecrypt.close(); + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/MostRecentEncryptedItem.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/MostRecentEncryptedItem.java new file mode 100644 index 0000000000..f91ddf494e --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/MostRecentEncryptedItem.java @@ -0,0 +1,205 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.security.GeneralSecurityException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.CachingMostRecentProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.store.MetaStore; + +/** + * This demonstrates how to use the CachingMostRecentProvider backed by a MetaStore + * and the DirectKmsMaterialsProvider to encrypt your data. + */ +public class MostRecentEncryptedItem { + + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + + public static void encryptRecord( + final DynamoDbClient ddbClient, + final KmsClient kmsClient, + final String tableName, + final String keyTableName, + final String cmkArn, + final String materialName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue + ) throws GeneralSecurityException { + // Sample record to be encrypted + final Map record = new HashMap<>(); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); + record.put( + BINARY_FIELD_NAME, + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); + + // Provider Configuration to protect the data keys + final DirectKmsMaterialsProvider kmsProv = new DirectKmsMaterialsProvider( + kmsClient, + cmkArn + ); + final DynamoDBEncryptor keyEncryptor = DynamoDBEncryptor.getInstance( + kmsProv + ); + + final MetaStore metaStore = new MetaStore( + ddbClient, + keyTableName, + keyEncryptor + ); + + // Provider configuration to protect the data + final CachingMostRecentProvider cmp = new CachingMostRecentProvider( + metaStore, + materialName, + 60_000 + ); + + // Encryptor creation + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // Information about the context of our data (normally just Table information) + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + // Describe what actions need to be taken for each attribute + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); + final Map> actions = new HashMap<>(); + + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case "partition_key": + case "sort_key": + actions.put(attributeName, signOnly); + break; + default: + if (!attributeName.equals(IGNORED_FIELD_NAME)) { + actions.put(attributeName, encryptAndSign); + } + break; + } + } + + // Encrypt the plaintext record directly + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + + // For demo, encrypted record fields change as expected + assert encrypted_record.get(STRING_FIELD_NAME).b() != null; + assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; + assert !record + .get(BINARY_FIELD_NAME) + .b() + .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); + assert record + .get(IGNORED_FIELD_NAME) + .s() + .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); + + // For demo, the encrypted item is put to DynamoDB. You can skip this as needed. + ddbClient.putItem( + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); + + // Get the item back from DynamoDB + final Map keyToGet = new HashMap<>(); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + + final GetItemResponse getResponse = ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ); + + // Decryption is identical + final Map decrypted_record = + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + + // For demo, the decrypted fields match the original fields before encryption + assert record + .get(STRING_FIELD_NAME) + .s() + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record + .get(NUMBER_FIELD_NAME) + .n() + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record + .get(BINARY_FIELD_NAME) + .b() + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + } + + public static void main(final String[] args) throws GeneralSecurityException { + if (args.length < 8) { + throw new IllegalArgumentException( + "To run this example, include tableName, keyTableName, cmkArn, materialName, " + + "partitionKeyName, sortKeyName, partitionKeyValue, sortKeyValue as args" + ); + } + final String tableName = args[0]; + final String keyTableName = args[1]; + final String cmkArn = args[2]; + final String materialName = args[3]; + final String partitionKeyName = args[4]; + final String sortKeyName = args[5]; + final String partitionKeyValue = args[6]; + final String sortKeyValue = args[7]; + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord( + ddbClient, + KmsClient.create(), + tableName, + keyTableName, + cmkArn, + materialName, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/SymmetricEncryptedItem.java b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/SymmetricEncryptedItem.java new file mode 100644 index 0000000000..43fb0d6ab7 --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/main/java/SymmetricEncryptedItem.java @@ -0,0 +1,203 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; +import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionContext; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.EncryptionFlags; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.WrappedMaterialsProvider; + +/** + * Example showing use of an AES key for encryption and an HmacSHA256 key for signing. For ease of + * the example, we create new random ones every time. + */ +public class SymmetricEncryptedItem { + + private static final String STRING_FIELD_NAME = "example"; + private static final String BINARY_FIELD_NAME = "and some binary"; + private static final String NUMBER_FIELD_NAME = "some numbers"; + private static final String IGNORED_FIELD_NAME = "leave me"; + + public static void main(String[] args) throws GeneralSecurityException { + if (args.length < 5) { + throw new IllegalArgumentException( + "To run this example, include tableName, partitionKeyName, sortKeyName, " + + "partitionKeyValue, sortKeyValue as args" + ); + } + final String tableName = args[0]; + final String partitionKeyName = args[1]; + final String sortKeyName = args[2]; + final String partitionKeyValue = args[3]; + final String sortKeyValue = args[4]; + + // Both AES and HMAC keys are just random bytes. + // You should never use the same keys for encryption and signing/integrity. + final SecureRandom secureRandom = new SecureRandom(); + byte[] rawAes = new byte[32]; + byte[] rawHmac = new byte[32]; + secureRandom.nextBytes(rawAes); + secureRandom.nextBytes(rawHmac); + final SecretKey wrappingKey = new SecretKeySpec(rawAes, "AES"); + final SecretKey signingKey = new SecretKeySpec(rawHmac, "HmacSHA256"); + + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + encryptRecord( + ddbClient, + tableName, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue, + wrappingKey, + signingKey + ); + } + } + + public static void encryptRecord( + final DynamoDbClient ddbClient, + final String tableName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue, + final SecretKey wrappingKey, + final SecretKey signingKey + ) throws GeneralSecurityException { + // Sample record to be encrypted + final Map record = new HashMap<>(); + record.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + record.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + record.put(STRING_FIELD_NAME, AttributeValue.builder().s("data").build()); + record.put(NUMBER_FIELD_NAME, AttributeValue.builder().n("99").build()); + record.put( + BINARY_FIELD_NAME, + AttributeValue + .builder() + .b(SdkBytes.fromByteArray(new byte[] { 0x00, 0x01, 0x02 })) + .build() + ); + record.put(IGNORED_FIELD_NAME, AttributeValue.builder().s("alone").build()); // We want to ignore this attribute + + // Set up our configuration and clients. All of this is thread-safe and can be reused across + // calls. + // Provider Configuration + final WrappedMaterialsProvider cmp = new WrappedMaterialsProvider( + wrappingKey, + wrappingKey, + signingKey + ); + // While the wrappedMaterialsProvider is better as it uses a unique encryption key per record, + // many existing systems use the SymmetricStaticProvider which always uses the same encryption + // key. + // final SymmetricStaticProvider cmp = new SymmetricStaticProvider(encryptionKey, + // signingKey); + // Encryptor creation + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // Information about the context of our data (normally just Table information) + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(tableName) + .hashKeyName(partitionKeyName) + .rangeKeyName(sortKeyName) + .build(); + + // Describe what actions need to be taken for each attribute + final EnumSet signOnly = EnumSet.of(EncryptionFlags.SIGN); + final EnumSet encryptAndSign = EnumSet.of( + EncryptionFlags.ENCRYPT, + EncryptionFlags.SIGN + ); + final Map> actions = new HashMap<>(); + for (final String attributeName : record.keySet()) { + switch (attributeName) { + case "partition_key": + case "sort_key": + // Partition and sort keys must not be encrypted but should be signed + actions.put(attributeName, signOnly); + break; + case IGNORED_FIELD_NAME: + // For this example, we are neither signing nor encrypting this field + break; + default: + // We want to encrypt and sign everything else + actions.put(attributeName, encryptAndSign); + break; + } + } + // End set-up + + // Encrypt the plaintext record directly + final Map encrypted_record = + encryptor.encryptRecord(record, actions, encryptionContext); + + // Encrypted record fields change as expected + assert encrypted_record.get(STRING_FIELD_NAME).b() != null; // the encrypted string is stored as bytes + assert encrypted_record.get(NUMBER_FIELD_NAME).b() != null; // the encrypted number is stored as bytes + assert !record + .get(BINARY_FIELD_NAME) + .b() + .equals(encrypted_record.get(BINARY_FIELD_NAME).b()); // the encrypted bytes have updated + assert record + .get(IGNORED_FIELD_NAME) + .s() + .equals(encrypted_record.get(IGNORED_FIELD_NAME).s()); // ignored field is left as is + + // For demo, the encrypted item is put to DynamoDB. You can skip this as needed for your use case. + ddbClient.putItem( + PutItemRequest + .builder() + .tableName(tableName) + .item(encrypted_record) + .build() + ); + + // Get the item back from DynamoDB + final Map keyToGet = new HashMap<>(); + keyToGet.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + keyToGet.put(sortKeyName, AttributeValue.builder().n(sortKeyValue).build()); + + final GetItemResponse getResponse = ddbClient.getItem( + GetItemRequest.builder().tableName(tableName).key(keyToGet).build() + ); + + // Decrypt the record retrieved from DynamoDB + final Map decrypted_record = + encryptor.decryptRecord(getResponse.item(), actions, encryptionContext); + + // The decrypted fields match the original fields before encryption + assert record + .get(STRING_FIELD_NAME) + .s() + .equals(decrypted_record.get(STRING_FIELD_NAME).s()); + assert record + .get(NUMBER_FIELD_NAME) + .n() + .equals(decrypted_record.get(NUMBER_FIELD_NAME).n()); + assert record + .get(BINARY_FIELD_NAME) + .b() + .equals(decrypted_record.get(BINARY_FIELD_NAME).b()); + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AsymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AsymmetricEncryptedItemTest.java new file mode 100644 index 0000000000..46fb42d051 --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AsymmetricEncryptedItemTest.java @@ -0,0 +1,40 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.util.UUID; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +public class AsymmetricEncryptedItemTest { + + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + final String partitionKeyValue = "AsymmetricExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + + @Test + public void testAsymmetricEncryption() throws Exception { + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + AsymmetricEncryptedItem.encryptRecord( + ddbClient, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } + } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + "0" + ); + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsEncryptedItemTest.java new file mode 100644 index 0000000000..3266f83f4a --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsEncryptedItemTest.java @@ -0,0 +1,43 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.util.UUID; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; + +public class AwsKmsEncryptedItemTest { + + final String partitionKeyValue = "AwsKmsExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + + @Test + public void testAwsKmsEncryption() throws Exception { + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + AwsKmsEncryptedItem.encryptRecord( + ddbClient, + KmsClient.create(), + TestUtils.TEST_DDB_TABLE_NAME, + TestUtils.TEST_KMS_KEY_ID, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } + } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsMultiRegionKeyTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsMultiRegionKeyTest.java new file mode 100644 index 0000000000..a78c55d9ee --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/AwsKmsMultiRegionKeyTest.java @@ -0,0 +1,50 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.util.UUID; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +public class AwsKmsMultiRegionKeyTest { + + // Multi-region KMS keys (MRKs) - same key replicated across regions + private static final String MRK_US_EAST_1 = + "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + private static final String MRK_EU_WEST_1 = + "arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + final String partitionKeyValue = "AwsKmsExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + + @Test + public void testMultiRegionEncryption() throws Exception { + // Encrypt with us-east-1 MRK, decrypt with eu-west-1 replica MRK + // This demonstrates MRK capability without needing a Global Table + final String tableName = TestUtils.TEST_DDB_TABLE_NAME; + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + AwsKmsMultiRegionKey.encryptRecord( + ddbClient, + tableName, + MRK_US_EAST_1, + MRK_EU_WEST_1, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } + } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/MostRecentEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/MostRecentEncryptedItemTest.java new file mode 100644 index 0000000000..bf08dbf55c --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/MostRecentEncryptedItemTest.java @@ -0,0 +1,48 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.util.UUID; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; + +public class MostRecentEncryptedItemTest { + + private static final String KEY_TABLE_NAME = + "v2MostRecentKeyProviderPerfTestKeys"; + private static final String MATERIAL_NAME = "testMaterial"; + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + final String partitionKeyValue = "MostRecentExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + + @Test + public void testMostRecentEncryption() throws Exception { + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + MostRecentEncryptedItem.encryptRecord( + ddbClient, + KmsClient.create(), + TestUtils.TEST_DDB_TABLE_NAME, + KEY_TABLE_NAME, + TestUtils.TEST_KMS_KEY_ID, + MATERIAL_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } + } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/SymmetricEncryptedItemTest.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/SymmetricEncryptedItemTest.java new file mode 100644 index 0000000000..0e4c5b621f --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/SymmetricEncryptedItemTest.java @@ -0,0 +1,54 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.security.SecureRandom; +import java.util.UUID; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +public class SymmetricEncryptedItemTest { + + final String partitionKeyName = "partition_key"; + final String sortKeyName = "sort_key"; + final String partitionKeyValue = "SymmetricExample-" + UUID.randomUUID(); + final String sortKeyValue = "0"; + + @Test + public void testSymmetricEncryption() throws Exception { + // Generate random keys + final SecureRandom secureRandom = new SecureRandom(); + byte[] rawAes = new byte[32]; + byte[] rawHmac = new byte[32]; + secureRandom.nextBytes(rawAes); + secureRandom.nextBytes(rawHmac); + final SecretKey wrappingKey = new SecretKeySpec(rawAes, "AES"); + final SecretKey signingKey = new SecretKeySpec(rawHmac, "HmacSHA256"); + + try (final DynamoDbClient ddbClient = DynamoDbClient.create()) { + SymmetricEncryptedItem.encryptRecord( + ddbClient, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue, + wrappingKey, + signingKey + ); + } + } + + @AfterMethod + public void cleanup() { + TestUtils.cleanUpDDBItem( + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyName, + sortKeyName, + partitionKeyValue, + sortKeyValue + ); + } +} diff --git a/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/TestUtils.java b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/TestUtils.java new file mode 100644 index 0000000000..1c6354f337 --- /dev/null +++ b/Examples/runtimes/java/DDBECwithSDKV2/src/test/java/TestUtils.java @@ -0,0 +1,54 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import java.util.HashMap; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; +import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest; + +public class TestUtils { + + // These are public KMS Keys that MUST only be used for testing, and MUST NOT be used for any production data + public static final String TEST_KMS_KEY_ID = + "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"; + + // Our tests require access to DDB Table with this name + public static final String TEST_DDB_TABLE_NAME = + "DynamoDbEncryptionInterceptorTestTable"; + + /** + * Deletes an item from a DynamoDB table. + * + * @param tableName The name of the DynamoDB table + * @param partitionKeyName The name of partition key + * @param sortKeyName The name of sort key + * @param partitionKeyValue The value of the partition key + * @param sortKeyValue The value of the sort key (can be null if table doesn't have a sort key) + */ + public static void cleanUpDDBItem( + final String tableName, + final String partitionKeyName, + final String sortKeyName, + final String partitionKeyValue, + final String sortKeyValue + ) { + final DynamoDbClient ddb = DynamoDbClient.builder().build(); + final HashMap keyToDelete = new HashMap<>(); + keyToDelete.put( + partitionKeyName, + AttributeValue.builder().s(partitionKeyValue).build() + ); + if (sortKeyValue != null) { + keyToDelete.put( + sortKeyName, + AttributeValue.builder().n(sortKeyValue).build() + ); + } + final DeleteItemRequest deleteRequest = DeleteItemRequest + .builder() + .tableName(tableName) + .key(keyToDelete) + .build(); + ddb.deleteItem(deleteRequest); + } +}