diff --git a/.github/workflows/ci_examples_java.yml b/.github/workflows/ci_examples_java.yml index c5efc2f03c..7da10401cd 100644 --- a/.github/workflows/ci_examples_java.yml +++ b/.github/workflows/ci_examples_java.yml @@ -106,3 +106,4 @@ jobs: # Run migration examples gradle -p runtimes/java/Migration/PlaintextToAWSDBE test gradle -p runtimes/java/Migration/DDBECToAWSDBE test + gradle -p runtimes/java/Migration/DDBECv2ToAWSDBE 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/DirectKmsMaterialsProvider.java b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java index 425a4119f2..83501d2ee4 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java +++ b/DynamoDbEncryption/runtimes/java/src/main/sdkv2/software/amazon/cryptools/dynamodbencryptionclientsdk2/encryption/providers/DirectKmsMaterialsProvider.java @@ -109,13 +109,24 @@ public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) { final String providedEncAlg = materialDescription.get(CONTENT_KEY_ALGORITHM); final String providedSigAlg = materialDescription.get(SIGNING_KEY_ALGORITHM); + final String envelopeKey = materialDescription.get(ENVELOPE_KEY); + + // DDBEC with SDK v1 does not do this check and returns NPE + // DDBEC with SDK v2 does not return NPE but return DynamoDbEncryptionException + if (envelopeKey == null) { + throw new DynamoDbEncryptionException( + "Missing " + ENVELOPE_KEY + " in material description. " + + "This item may have been encrypted with a different encryption format." + ); + } + ec.put("*" + CONTENT_KEY_ALGORITHM + "*", providedEncAlg); ec.put("*" + SIGNING_KEY_ALGORITHM + "*", providedSigAlg); populateKmsEcFromEc(context, ec); DecryptRequest.Builder request = DecryptRequest.builder(); - request.ciphertextBlob(SdkBytes.fromByteArray(Base64.decode(materialDescription.get(ENVELOPE_KEY)))); + request.ciphertextBlob(SdkBytes.fromByteArray(Base64.decode(envelopeKey))); request.encryptionContext(ec); final DecryptResponse decryptResponse = decrypt(request.build(), context); validateEncryptionKeyId(decryptResponse.keyId(), context); 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/Migration/DDBECToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java b/Examples/runtimes/java/Migration/DDBECToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java index c2df453287..ec19b43ad4 100644 --- a/Examples/runtimes/java/Migration/DDBECToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java +++ b/Examples/runtimes/java/Migration/DDBECToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java @@ -63,7 +63,7 @@ public static void MigrationStep3( final List allowedUnsignedAttributes = Arrays.asList("attribute3"); - // 3. Create the DynamoDb Encryption Interceptor with the above configuration. + // 2. Create the DynamoDb Encryption Interceptor with the above configuration. // Do not configure any legacy behavior. final Map tableConfigs = new HashMap<>(); @@ -85,7 +85,7 @@ public static void MigrationStep3( .build() ); - // 4. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + // 3. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above final DynamoDbClient ddb = DynamoDbClient .builder() .overrideConfiguration( @@ -96,7 +96,7 @@ public static void MigrationStep3( ) .build(); - // 5. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // 4. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, // and create a Table with your modelled class final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient .builder() @@ -107,7 +107,7 @@ public static void MigrationStep3( schemaOnEncrypt ); - // 6. Put an item into your table using the DynamoDb Enhanced Client. + // 5. Put an item into your table using the DynamoDb Enhanced Client. // This item will be encrypted in the latest format, using the // configuration from your modelled class to decide // which attribute to encrypt and/or sign. @@ -120,7 +120,7 @@ public static void MigrationStep3( table.putItem(item); - // 7. Get an item back from the table using the DynamoDb Enhanced Client. + // 6. Get an item back from the table using the DynamoDb Enhanced Client. // If this is an item written in the old format (e.g. any item written // during Step 0 or 1), then we fail to return the item. // If this is an item written in the new format (e.g. any item written diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitattributes b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitattributes new file mode 100644 index 0000000000..097f9f98d9 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitignore b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitignore new file mode 100644 index 0000000000..1b6985c009 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/.gitignore @@ -0,0 +1,5 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/build.gradle.kts b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/build.gradle.kts new file mode 100644 index 0000000000..d548fe6f58 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/build.gradle.kts @@ -0,0 +1,121 @@ +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 = "AWSDatabaseEncryptionSDKMigrationExamples" + +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") + + // https://mvnrepository.com/artifact/org.testng/testng + 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/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.jar b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..943f0cbfa7 Binary files /dev/null and b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.properties b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..f398c33c4b --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/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/Migration/DDBECv2ToAWSDBE/gradlew b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew new file mode 100755 index 0000000000..65dcd68d65 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/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/Migration/DDBECv2ToAWSDBE/gradlew.bat b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew.bat new file mode 100644 index 0000000000..93e3f59f13 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/settings.gradle.kts b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/settings.gradle.kts new file mode 100644 index 0000000000..f4b7d8d7d9 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/settings.gradle.kts @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/7.6/userguide/multi_project_builds.html + */ + +rootProject.name = "AWSDatabaseEncryptionSDKMigrationExamples" diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java new file mode 100644 index 0000000000..9b34dfe042 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep1.java @@ -0,0 +1,216 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.CreateDynamoDbEncryptionInterceptorInput; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedClientEncryption; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedTableEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyOverride; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyPolicy; +import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; + +/* + Migration Step 1: This is an example demonstrating how to start using the + AWS Database Encryption SDK with a pre-existing table used with DynamoDB Encryption Client (SDK V2). + In this example, you configure a DynamoDb Encryption Interceptor to do the following: + - Read items encrypted in the old format + - Continue to encrypt items in the old format on write + - Read items encrypted in the new format + While this step configures your client to be ready to start reading items encrypted, + we do not yet expect to be reading any items in the new format. + Before you move on to step 2, ensure that these changes have successfully been deployed + to all of your readers. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) + */ +public class MigrationExampleStep1 { + + public static void MigrationStep1( + final String kmsKeyId, + final String ddbTableName, + final String partitionKeyValue, + final int sortReadValue + ) { + // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + // For this migration, we will continue to encrypt and decrypt data using the kmsKeyId + // used in Step 0 by configuring an AWS KMS Keyring with that same kmsKeyId. + // We will use the `CreateMrkMultiKeyring` method to create this keyring, + // as it will correctly handle both single region and Multi-Region KMS Keys. + // Note that we are now using the AWS SDK for Java v2 KMS client. + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMrkMultiKeyringInput keyringInput = + CreateAwsKmsMrkMultiKeyringInput.builder().generator(kmsKeyId).build(); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring( + keyringInput + ); + + // 2. Create a Table Schema over your annotated class. + // See SimpleClass.java in this directory for the update to use the new DynamoDb Enhanced Client annotations. + // All primary key attributes will be signed but not encrypted (SIGN_ONLY) + // and by default all non-primary key attributes will be encrypted and signed (ENCRYPT_AND_SIGN). + // If you want a particular non-primary key attribute to be signed but not encrypted, + // use the `DynamoDbEncryptionSignOnly` annotation. + // If you want a particular attribute to be neither signed nor encrypted (DO_NOTHING), + // use the `DynamoDbEncryptionDoNothing` annotation. + final TableSchema schemaOnEncrypt = TableSchema.fromBean( + SimpleClass.class + ); + + // 3. Configure which attributes we expect to be excluded in the signature + // when reading items. This value represents all unsigned attributes + // across our entire dataset. If you ever want to add new unsigned attributes + // in the future, you must make an update to this field to all your readers + // before deploying any change to start writing that new data. It is not safe + // to remove attributes from this field. + final List allowedUnsignedAttributes = Arrays.asList("attribute3"); + + // 4. Configure the attributeActionsOnEncrypt that are configured on our modelled class in Step 0 + // in an explicit map. + final Map legacyActions = new HashMap<>(); + legacyActions.put("partition_key", CryptoAction.SIGN_ONLY); + legacyActions.put("sort_key", CryptoAction.SIGN_ONLY); + legacyActions.put("attribute1", CryptoAction.ENCRYPT_AND_SIGN); + legacyActions.put("attribute2", CryptoAction.SIGN_ONLY); + legacyActions.put("attribute3", CryptoAction.DO_NOTHING); + + // 5. Configure the same DynamoDBEncryptor that we did in Step 0. + final KmsClient kmsClient = KmsClient.create(); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider( + kmsClient, + kmsKeyId + ); + final DynamoDBEncryptor oldEncryptor = DynamoDBEncryptor.getInstance(cmp); + + // 6. Configure our legacy behavior, inputting the DynamoDBEncryptor and attribute actions + // created above. For Legacy Policy, use `FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT`. + // With this policy, you will continue to read and write items using the old format, + // but will be able to start reading new items in the new format as soon as they appear. + final LegacyOverride legacyOverride = LegacyOverride + .builder() + .encryptor(oldEncryptor) + .policy(LegacyPolicy.FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT) + .attributeActionsOnEncrypt(legacyActions) + .build(); + + // 7. Create the DynamoDb Encryption Interceptor with the above configuration. + final Map tableConfigs = + new HashMap<>(); + tableConfigs.put( + ddbTableName, + DynamoDbEnhancedTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .keyring(kmsKeyring) + .allowedUnsignedAttributes(allowedUnsignedAttributes) + .schemaOnEncrypt(schemaOnEncrypt) + .legacyOverride(legacyOverride) + .build() + ); + final DynamoDbEncryptionInterceptor interceptor = + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ); + + // 8. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(interceptor) + .build() + ) + .build(); + + // 9. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // and create a Table with your modelled class + final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient + .builder() + .dynamoDbClient(ddb) + .build(); + final DynamoDbTable table = enhancedClient.table( + ddbTableName, + schemaOnEncrypt + ); + + // 10. Put an item into your table using the DynamoDb Enhanced Client. + // This item will be encrypted in the legacy format, using the + // old DynamoDBEncryptor and the legacy attribute actions. + // In this step, we do not expect items to be encrypted any differently + // from before. + final SimpleClass item = new SimpleClass(); + item.setPartitionKey(partitionKeyValue); + item.setSortKey(1); + item.setAttribute1("encrypt and sign me!"); + item.setAttribute2("sign me!"); + item.setAttribute3("ignore me!"); + + table.putItem(item); + + // 11. Get an item back from the table using the DynamoDb Enhanced Client. + // If this is an item written in the old format (e.g. any item written + // during Step 0 or 1), then we will attempt to decrypt the item + // using the legacy behavior. + // If this is an item written in the new format (e.g. any item written + // during Step 2 or after), then we will attempt to decrypt the item using + // the non-legacy behavior. + final Key key = Key + .builder() + .partitionValue(partitionKeyValue) + .sortValue(sortReadValue) + .build(); + + final SimpleClass decryptedItem = + table.getItem((GetItemEnhancedRequest.Builder requestBuilder) -> + requestBuilder.key(key) + ); + + // Demonstrate we get the expected item back + assert decryptedItem.getPartitionKey().equals(partitionKeyValue); + assert decryptedItem.getAttribute1().equals("encrypt and sign me!"); + assert decryptedItem.getAttribute2().equals("sign me!"); + assert decryptedItem.getAttribute3().equals("ignore me!"); + } + + public static void main(final String[] args) { + if (args.length < 4) { + throw new IllegalArgumentException( + "To run this example, include the kmsKeyId, ddbTableName, and sortReadValue as args." + ); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + final String partitionKeyValue = args[2]; + // You can manipulate this value to demonstrate reading records written in other steps + final int sortReadValue = Integer.parseInt(args[3]); + MigrationStep1(kmsKeyId, ddbTableName, partitionKeyValue, sortReadValue); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java new file mode 100644 index 0000000000..2f50a57be1 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep2.java @@ -0,0 +1,190 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.CreateDynamoDbEncryptionInterceptorInput; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedClientEncryption; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedTableEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyOverride; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyPolicy; +import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.DynamoDBEncryptor; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.providers.DirectKmsMaterialsProvider; + +/* + Migration Step 2: This is an example demonstrating how to update your configuration + to start writing items using the latest encryption format, but still continue + to read any items written using the old encryption format. + + Once you deploy this change to your system, you will have a dataset + containing items in both the old and new format. + Because the changes in Step 1 have been deployed to all our readers, + we can be sure that our entire system is ready to read this new data. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) + */ +public class MigrationExampleStep2 { + + public static void MigrationStep2( + final String kmsKeyId, + final String ddbTableName, + final String partitionKeyValue, + final int sortReadValue + ) { + // 1. Continue to configure your Keyring, Table Schema, legacy attribute actions, + // and allowedUnsignedAttributes, and old DynamoDBEncryptor as you did in Step 1. + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMrkMultiKeyringInput keyringInput = + CreateAwsKmsMrkMultiKeyringInput.builder().generator(kmsKeyId).build(); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring( + keyringInput + ); + + final TableSchema schemaOnEncrypt = TableSchema.fromBean( + SimpleClass.class + ); + + final List allowedUnsignedAttributes = Arrays.asList("attribute3"); + + final Map legacyActions = new HashMap<>(); + legacyActions.put("partition_key", CryptoAction.SIGN_ONLY); + legacyActions.put("sort_key", CryptoAction.SIGN_ONLY); + legacyActions.put("attribute1", CryptoAction.ENCRYPT_AND_SIGN); + legacyActions.put("attribute2", CryptoAction.SIGN_ONLY); + legacyActions.put("attribute3", CryptoAction.DO_NOTHING); + + // Configure the DynamoDBEncryptor using DDBEC SDK V2. + final KmsClient kmsClient = KmsClient.create(); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider( + kmsClient, + kmsKeyId + ); + final DynamoDBEncryptor oldEncryptor = DynamoDBEncryptor.getInstance(cmp); + + // 2. When configuring our legacy behavior, use `FORBID_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT`. + // With this policy, you will continue to read items in both formats, + // but will only write new items using the new format. + final LegacyOverride legacyOverride = LegacyOverride + .builder() + .encryptor(oldEncryptor) + .policy(LegacyPolicy.FORBID_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT) + .attributeActionsOnEncrypt(legacyActions) + .build(); + + // 3. Create the DynamoDb Encryption Interceptor with the above configuration. + final Map tableConfigs = + new HashMap<>(); + tableConfigs.put( + ddbTableName, + DynamoDbEnhancedTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .keyring(kmsKeyring) + .allowedUnsignedAttributes(allowedUnsignedAttributes) + .schemaOnEncrypt(schemaOnEncrypt) + .legacyOverride(legacyOverride) + .build() + ); + final DynamoDbEncryptionInterceptor interceptor = + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ); + + // 4. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(interceptor) + .build() + ) + .build(); + + // 5. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // and create a Table with your modelled class + final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient + .builder() + .dynamoDbClient(ddb) + .build(); + final DynamoDbTable table = enhancedClient.table( + ddbTableName, + schemaOnEncrypt + ); + + // 6. Put an item into your table using the DynamoDb Enhanced Client. + // This item will be encrypted in the latest format, using the + // configuration from your modelled class to decide + // which attribute to encrypt and/or sign. + final SimpleClass item = new SimpleClass(); + item.setPartitionKey(partitionKeyValue); + item.setSortKey(2); + item.setAttribute1("encrypt and sign me!"); + item.setAttribute2("sign me!"); + item.setAttribute3("ignore me!"); + + table.putItem(item); + + // 7. Get an item back from the table using the DynamoDb Enhanced Client. + // If this is an item written in the old format (e.g. any item written + // during Step 0 or 1), then we will attempt to decrypt the item + // using the legacy behavior. + // If this is an item written in the new format (e.g. any item written + // during Step 2 or after), then we will attempt to decrypt the item using + // the non-legacy behavior. + final Key key = Key + .builder() + .partitionValue(partitionKeyValue) + .sortValue(sortReadValue) + .build(); + + final SimpleClass decryptedItem = + table.getItem((GetItemEnhancedRequest.Builder requestBuilder) -> + requestBuilder.key(key) + ); + + // Demonstrate we get the expected item back + assert decryptedItem.getPartitionKey().equals(partitionKeyValue); + assert decryptedItem.getAttribute1().equals("encrypt and sign me!"); + } + + public static void main(final String[] args) { + if (args.length < 3) { + throw new IllegalArgumentException( + "To run this example, include the kmsKeyId, ddbTableName, and sortReadValue as args." + ); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + final String partitionKeyValue = args[2]; + // You can manipulate this value to demonstrate reading records written in other steps + final int sortReadValue = Integer.parseInt(args[3]); + MigrationStep2(kmsKeyId, ddbTableName, partitionKeyValue, sortReadValue); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java new file mode 100644 index 0000000000..3aa1f799c9 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/MigrationExampleStep3.java @@ -0,0 +1,159 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.CreateDynamoDbEncryptionInterceptorInput; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedClientEncryption; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedTableEncryptionConfig; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; + +/* + Migration Step 3: This is an example demonstrating how to update your configuration + to stop accepting reading items encrypted using the old format. + In order to proceed with this step, you will need to re-encrypt all + old items in your table. + + Once you complete Step 3, you can be sure that all items being read by your system + ensure the security properties configured for the new format. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) + */ +public class MigrationExampleStep3 { + + public static void MigrationStep3( + final String kmsKeyId, + final String ddbTableName, + final String partitionKeyValue, + final int sortReadValue + ) { + // 1. Continue to configure your Keyring, Table Schema, + // and allowedUnsignedAttributes as you did in Step 1. + // However, now you can remove the configuration for the old DynamoDBEncryptor + // and the legacy attribute actions. + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMrkMultiKeyringInput keyringInput = + CreateAwsKmsMrkMultiKeyringInput.builder().generator(kmsKeyId).build(); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring( + keyringInput + ); + + final TableSchema schemaOnEncrypt = TableSchema.fromBean( + SimpleClass.class + ); + + final List allowedUnsignedAttributes = Arrays.asList("attribute3"); + + // 2. Create the DynamoDb Encryption Interceptor with the above configuration. + // Do not configure any legacy behavior. + final Map tableConfigs = + new HashMap<>(); + tableConfigs.put( + ddbTableName, + DynamoDbEnhancedTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .keyring(kmsKeyring) + .allowedUnsignedAttributes(allowedUnsignedAttributes) + .schemaOnEncrypt(schemaOnEncrypt) + .build() + ); + final DynamoDbEncryptionInterceptor interceptor = + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ); + + // 3. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(interceptor) + .build() + ) + .build(); + + // 4. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // and create a Table with your modelled class + final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient + .builder() + .dynamoDbClient(ddb) + .build(); + final DynamoDbTable table = enhancedClient.table( + ddbTableName, + schemaOnEncrypt + ); + + // 5. Put an item into your table using the DynamoDb Enhanced Client. + // This item will be encrypted in the latest format, using the + // configuration from your modelled class to decide + // which attribute to encrypt and/or sign. + final SimpleClass item = new SimpleClass(); + item.setPartitionKey(partitionKeyValue); + item.setSortKey(3); + item.setAttribute1("encrypt and sign me!"); + item.setAttribute2("sign me!"); + item.setAttribute3("ignore me!"); + + table.putItem(item); + + // 6. Get an item back from the table using the DynamoDb Enhanced Client. + // If this is an item written in the old format (e.g. any item written + // during Step 0 or 1), then we fail to return the item. + // If this is an item written in the new format (e.g. any item written + // during Step 2 or after), then we will attempt to decrypt the item using + // the non-legacy behavior. + final Key key = Key + .builder() + .partitionValue(partitionKeyValue) + .sortValue(sortReadValue) + .build(); + + final SimpleClass decryptedItem = + table.getItem((GetItemEnhancedRequest.Builder requestBuilder) -> + requestBuilder.key(key) + ); + + // Demonstrate we get the expected item back + assert decryptedItem.getPartitionKey().equals(partitionKeyValue); + assert decryptedItem.getAttribute1().equals("encrypt and sign me!"); + } + + public static void main(final String[] args) { + if (args.length < 4) { + throw new IllegalArgumentException( + "To run this example, include the kmsKeyId, ddbTableName, partitionKeyValue and sortReadValue as args." + ); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + final String partitionKeyValue = args[2]; + // You can manipulate this value to demonstrate reading records written in other steps + final int sortReadValue = Integer.parseInt(args[3]); + MigrationStep3(kmsKeyId, ddbTableName, partitionKeyValue, sortReadValue); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/SimpleClass.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/SimpleClass.java new file mode 100644 index 0000000000..c03d2506a3 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/awsdbe/SimpleClass.java @@ -0,0 +1,70 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; + +/** + * This class is used by the Migration Examples with DynamoDB Enhanced Client. + */ +@DynamoDbBean +public class SimpleClass { + + private String partitionKey; + private int sortKey; + private String attribute1; + private String attribute2; + private String attribute3; + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + @DynamoDbAttribute(value = "attribute1") + public String getAttribute1() { + return this.attribute1; + } + + public void setAttribute1(String attribute1) { + this.attribute1 = attribute1; + } + + @DynamoDbEncryptionSignOnly + @DynamoDbAttribute(value = "attribute2") + public String getAttribute2() { + return this.attribute2; + } + + public void setAttribute2(String attribute2) { + this.attribute2 = attribute2; + } + + @DynamoDbEncryptionDoNothing + @DynamoDbAttribute(value = "attribute3") + public String getAttribute3() { + return this.attribute3; + } + + public void setAttribute3(String attribute3) { + this.attribute3 = attribute3; + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java new file mode 100644 index 0000000000..fd0ad3f32a --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/main/java/software/amazon/cryptography/examples/migration/ddbec/MigrationExampleStep0.java @@ -0,0 +1,157 @@ +package software.amazon.cryptography.examples.migration.ddbec; + +import java.security.GeneralSecurityException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +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; + +/* + Migration Step 0: This is an example demonstrating use with the DynamoDb Encryption Client + with AWS SDK for Java V2, and is the starting state for our migration to the + AWS Database Encryption SDK for DynamoDb. + + In this example we configure a DynamoDBEncryptor to encrypt and decrypt items. + The encryption and decryption of data is configured to use a KMS Key as the root of trust. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (N) +*/ +public class MigrationExampleStep0 { + + public static void MigrationStep0( + final String kmsKeyId, + final String ddbTableName, + final String partitionKeyValue, + final int sortReadValue + ) throws GeneralSecurityException { + // 1. Create the DirectKmsMaterialsProvider that protects your data keys. + // This uses the AWS SDK for Java V2 KmsClient. + final KmsClient kmsClient = KmsClient.create(); + final DirectKmsMaterialsProvider cmp = new DirectKmsMaterialsProvider( + kmsClient, + kmsKeyId + ); + + // 2. Create the DynamoDBEncryptor using the Material Provider created above + final DynamoDBEncryptor encryptor = DynamoDBEncryptor.getInstance(cmp); + + // 3. Configure attribute flags for encryption. + // For each attribute, specify whether to ENCRYPT and/or SIGN. + final Map> attributeFlags = new HashMap<>(); + // Keys are signed but never encrypted + attributeFlags.put("partition_key", EnumSet.of(EncryptionFlags.SIGN)); + attributeFlags.put("sort_key", EnumSet.of(EncryptionFlags.SIGN)); + // attribute1 - encrypt and sign + attributeFlags.put( + "attribute1", + EnumSet.of(EncryptionFlags.ENCRYPT, EncryptionFlags.SIGN) + ); + // attribute2 - sign only + attributeFlags.put("attribute2", EnumSet.of(EncryptionFlags.SIGN)); + // attribute3 - no flags (do nothing) + + // 4. Create a DynamoDB client + final DynamoDbClient ddbClient = DynamoDbClient.create(); + + // 5. Create the item to encrypt + final Map item = new HashMap<>(); + item.put( + "partition_key", + AttributeValue.builder().s(partitionKeyValue).build() + ); + item.put("sort_key", AttributeValue.builder().n("0").build()); + item.put( + "attribute1", + AttributeValue.builder().s("encrypt and sign me!").build() + ); + item.put("attribute2", AttributeValue.builder().s("sign me!").build()); + item.put("attribute3", AttributeValue.builder().s("ignore me!").build()); + + // 6. Create the EncryptionContext for this operation + final EncryptionContext encryptionContext = EncryptionContext + .builder() + .tableName(ddbTableName) + .hashKeyName("partition_key") + .rangeKeyName("sort_key") + .build(); + + // 7. Encrypt the item using encryptRecord + final Map encryptedItem = encryptor.encryptRecord( + item, + attributeFlags, + encryptionContext + ); + + // Verify attribute1 is encrypted + assert encryptedItem.get("attribute1").b() != null; + // Verify attribute2 and attribute3 are not encrypted (sign-only and do-nothing) + assert encryptedItem.get("attribute2").s().equals("sign me!"); + assert encryptedItem.get("attribute3").s().equals("ignore me!"); + + // 8. Put the encrypted item into DynamoDB + ddbClient.putItem( + PutItemRequest + .builder() + .tableName(ddbTableName) + .item(encryptedItem) + .build() + ); + + // 9. Get the encrypted item back from DynamoDB + final Map key = new HashMap<>(); + key.put( + "partition_key", + AttributeValue.builder().s(partitionKeyValue).build() + ); + key.put( + "sort_key", + AttributeValue.builder().n(String.valueOf(sortReadValue)).build() + ); + + final GetItemResponse response = ddbClient.getItem( + GetItemRequest.builder().tableName(ddbTableName).key(key).build() + ); + + // 10. Decrypt the item using decryptRecord + final Map decryptedItem = encryptor.decryptRecord( + response.item(), + attributeFlags, + encryptionContext + ); + + // Demonstrate we get the expected item back + assert decryptedItem.get("partition_key").s().equals(partitionKeyValue); + assert decryptedItem.get("attribute1").s().equals("encrypt and sign me!"); + assert decryptedItem.get("attribute2").s().equals("sign me!"); + assert decryptedItem.get("attribute3").s().equals("ignore me!"); + } + + public static void main(final String[] args) throws GeneralSecurityException { + if (args.length < 4) { + throw new IllegalArgumentException( + "To run this example, include the kmsKeyId, ddbTableName, partitionKeyValue and sortReadValue as args." + ); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + final String partitionKeyValue = args[2]; + // You can manipulate this value to demonstrate reading records written in other steps + final int sortReadValue = Integer.parseInt(args[3]); + MigrationStep0(kmsKeyId, ddbTableName, partitionKeyValue, sortReadValue); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java new file mode 100644 index 0000000000..3acb5f6aff --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep1.java @@ -0,0 +1,67 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.security.GeneralSecurityException; +import java.util.UUID; + +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.migration.ddbec.MigrationExampleStep0; + +public class TestMigrationExampleStep1 { + + @Test + public void TestMigrationStep1() throws GeneralSecurityException { + final String partitionKeyValue = "TestMigrationStep1-DDBEC-with-sdk-v2" + UUID.randomUUID(); + // Successfully executes step 1 + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 1 + ); + + // Given: Step 0 has succeeded + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 0 + ); + // When: Execute Step 1 with sortReadValue=0, Then: Success (i.e. can read values in old format) + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 0 + ); + + // Given: Step 2 has succeeded + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 2 + ); + // When: Execute Step 1 with sortReadValue=2, Then: Success (i.e. can read values in new format) + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 2 + ); + + // Given: Step 3 has succeeded + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 3 + ); + // When: Execute Step 1 with sortReadValue=3, Then: Success (i.e. can read values in new format) + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 3 + ); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java new file mode 100644 index 0000000000..4c819c4c7a --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep2.java @@ -0,0 +1,67 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import java.security.GeneralSecurityException; +import java.util.UUID; + +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.migration.ddbec.MigrationExampleStep0; + +public class TestMigrationExampleStep2 { + + @Test + public void TestMigrationStep2() throws GeneralSecurityException { + final String partitionKeyValue = "TestMigrationStep2-DDBEC-with-sdk-v2" + UUID.randomUUID(); + // Successfully executes step 2 + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 2 + ); + + // Given: Step 0 has succeeded + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 0 + ); + // When: Execute Step 2 with sortReadValue=0, Then: Success (i.e. can read values in old format) + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 0 + ); + + // Given: Step 1 has succeeded + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 1 + ); + // When: Execute Step 2 with sortReadValue=1, Then: Success (i.e. can read values in old format) + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 1 + ); + + // Given: Step 3 has succeeded + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 3 + ); + // When: Execute Step 2 with sortReadValue=3, Then: Success (i.e. can read values in new format) + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 3 + ); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java new file mode 100644 index 0000000000..97e675cc74 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestMigrationExampleStep3.java @@ -0,0 +1,80 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +import static org.testng.Assert.assertThrows; + +import java.security.GeneralSecurityException; +import java.util.UUID; + +import org.testng.annotations.Test; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.cryptography.examples.migration.ddbec.MigrationExampleStep0; + +public class TestMigrationExampleStep3 { + + @Test + public void TestMigrationStep3() throws GeneralSecurityException { + final String partitionKeyValue = "TestMigrationStep3-DDBEC-with-sdk-v2" + UUID.randomUUID(); + // Successfully executes Step 3 + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 3 + ); + + // Given: Step 0 has succeeded + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 0 + ); + // When: Execute Step 3 with sortReadValue=0, Then: throws SdkClientException (i.e. cannot read values in old format) + assertThrows( + SdkClientException.class, + () -> { + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 0 + ); + } + ); + + // Given: Step 1 has succeeded + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 1 + ); + // When: Execute Step 3 with sortReadValue=1, Then: throws SdkClientException (i.e. cannot read values in old format) + assertThrows( + SdkClientException.class, + () -> { + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 1 + ); + } + ); + + // Given: Step 2 has succeeded + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 2 + ); + // When: Execute Step 3 with sortReadValue=2, Then: Success (i.e. can read values in new format) + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 2 + ); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestUtils.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestUtils.java new file mode 100644 index 0000000000..53c987bfc0 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/awsdbe/TestUtils.java @@ -0,0 +1,12 @@ +package software.amazon.cryptography.examples.migration.awsdbe; + +public class TestUtils { + + // This is a public KMS Key that MUST only be used for testing, and MUST NOT be used for any production data + public static 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"; +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java new file mode 100644 index 0000000000..0a6ff84215 --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestMigrationExampleStep0.java @@ -0,0 +1,82 @@ +package software.amazon.cryptography.examples.migration.ddbec; + +import static org.testng.Assert.assertThrows; + +import java.security.GeneralSecurityException; +import java.util.UUID; + +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.migration.awsdbe.MigrationExampleStep1; +import software.amazon.cryptography.examples.migration.awsdbe.MigrationExampleStep2; +import software.amazon.cryptography.examples.migration.awsdbe.MigrationExampleStep3; +import software.amazon.cryptools.dynamodbencryptionclientsdk2.encryption.exceptions.DynamoDbEncryptionException; + +public class TestMigrationExampleStep0 { + + @Test + public void TestMigrationStep0() throws GeneralSecurityException { + final String partitionKeyValue = "TestMigrationStep0-DDBEC-with-sdk-v2" + UUID.randomUUID(); + // Successfully executes Step 0 + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 0 + ); + + // Given: Step 1 has succeeded + MigrationExampleStep1.MigrationStep1( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 1 + ); + // When: Execute Step 0 with sortReadValue=1, Then: Success (i.e. can read values in old format) + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 1 + ); + + // Given: Step 2 has succeeded + MigrationExampleStep2.MigrationStep2( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 2 + ); + // When: Execute Step 0 with sortReadValue=2, Then: throws exception (i.e. cannot read values in new format) + assertThrows( + DynamoDbEncryptionException.class, + () -> { + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 2 + ); + } + ); + + // Given: Step 3 has succeeded + MigrationExampleStep3.MigrationStep3( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 3 + ); + // When: Execute Step 0 with sortReadValue=3, Then: throws exception (i.e. cannot read values in new format) + assertThrows( + DynamoDbEncryptionException.class, + () -> { + MigrationExampleStep0.MigrationStep0( + TestUtils.TEST_KMS_KEY_ID, + TestUtils.TEST_DDB_TABLE_NAME, + partitionKeyValue, + 3 + ); + } + ); + } +} diff --git a/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestUtils.java b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestUtils.java new file mode 100644 index 0000000000..76d9f5c88d --- /dev/null +++ b/Examples/runtimes/java/Migration/DDBECv2ToAWSDBE/src/test/java/software/amazon/cryptography/examples/migration/ddbec/TestUtils.java @@ -0,0 +1,12 @@ +package software.amazon.cryptography.examples.migration.ddbec; + +public class TestUtils { + + // This is a public KMS Key that MUST only be used for testing, and MUST NOT be used for any production data + public static 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"; +}