From 2b9b458c0eb0092503792124b3ac241f5760819e Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Wed, 21 Aug 2024 21:42:43 -0700 Subject: [PATCH 01/12] chore: Add ECDH examples --- .../keyring/KmsEcdhKeyringExample.java | 591 ++++++++++++ .../keyring/RawEcdhKeyringExample.java | 877 ++++++++++++++++++ .../cryptography/examples/TestUtils.java | 4 + .../keyring/TestKmsEcdhKeyringExample.java | 47 + .../keyring/TestRawEcdhKeyringExample.java | 80 ++ 5 files changed, 1599 insertions(+) create mode 100644 Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java create mode 100644 Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java create mode 100644 Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestKmsEcdhKeyringExample.java create mode 100644 Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestRawEcdhKeyringExample.java diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java new file mode 100644 index 000000000..4bc016197 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java @@ -0,0 +1,591 @@ +package software.amazon.cryptography.examples.keyring; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Security; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.util.io.pem.PemWriter; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +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.dynamodb.model.PutItemResponse; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.GetPublicKeyRequest; +import software.amazon.awssdk.services.kms.model.GetPublicKeyResponse; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTableEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTablesEncryptionConfig; +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.CreateAwsKmsEcdhKeyringInput; +import software.amazon.cryptography.materialproviders.model.DBEAlgorithmSuiteId; +import software.amazon.cryptography.materialproviders.model.KmsEcdhStaticConfigurations; +import software.amazon.cryptography.materialproviders.model.KmsPrivateKeyToStaticPublicKeyInput; +import software.amazon.cryptography.materialproviders.model.KmsPublicKeyDiscoveryInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.primitives.model.ECDHCurveSpec; + +public class KmsEcdhKeyringExample { + + public static String EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME = + "KmsEccKeyringKeyringExamplePublicKeySender.pem"; + public static String EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME = + "KmsEccKeyringKeyringExamplePublicKeyRecipient.pem"; + + public static void KmsEcdhKeyringGetItemPutItem( + String ddbTableName, + String eccKeyArn, + String eccPublicKeySenderFileName, + String eccPublicKeyRecipientFileName + ) { + // 1. Load UTF-8 encoded public key PEM files as DER encoded bytes. + // You may have an ECC public key file already defined. + // If not, the main method in this class will call + // the KMS ECC key, retrieve its public key, and store it + // in a PEM file for example use. + ByteBuffer publicKeyRecipientByteBuffer = loadPublicKeyBytes( + eccPublicKeyRecipientFileName + ); + ByteBuffer publicKeySenderByteBuffer = loadPublicKeyBytes( + eccPublicKeySenderFileName + ); + + // 2. Create a KMS ECDH keyring. + // This keyring takes in: + // - kmsClient + // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + // - senderPublicKey: A ByteBuffer of a UTF-8 encoded public + // key for the key passed into kmsKeyId in DER format + // - recipientPublicKey: A ByteBuffer of a UTF-8 encoded public key + // for the recipient public key. + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsEcdhKeyringInput createAwsKmsEcdhKeyringInput = + CreateAwsKmsEcdhKeyringInput + .builder() + .kmsClient(KmsClient.create()) + .curveSpec(ECDHCurveSpec.ECC_NIST_P256) + .KeyAgreementScheme( + KmsEcdhStaticConfigurations + .builder() + .KmsPrivateKeyToStaticPublicKey( + KmsPrivateKeyToStaticPublicKeyInput + .builder() + .senderKmsIdentifier(eccKeyArn) + .senderPublicKey(publicKeySenderByteBuffer) + .recipientPublicKey(publicKeyRecipientByteBuffer) + .build() + ) + .build() + ) + .build(); + IKeyring kmsEcdhKeyring = matProv.CreateAwsKmsEcdhKeyring( + createAwsKmsEcdhKeyringInput + ); + + PutGetItemWithKeyring(kmsEcdhKeyring, ddbTableName); + } + + public static void KmsEcdhDiscoveryGetItem( + String ddbTableName, + String eccRecipientKeyArn + ) { + // 1. Create a KMS ECDH keyring. + // This keyring takes in: + // - kmsClient + // - recipientKmsIdentifier: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsEcdhKeyringInput createAwsKmsEcdhKeyringInput = + CreateAwsKmsEcdhKeyringInput + .builder() + .kmsClient(KmsClient.create()) + .curveSpec(ECDHCurveSpec.ECC_NIST_P256) + .KeyAgreementScheme( + KmsEcdhStaticConfigurations + .builder() + .KmsPublicKeyDiscovery( + KmsPublicKeyDiscoveryInput + .builder() + .recipientKmsIdentifier(eccRecipientKeyArn) + .build() + ) + .build() + ) + .build(); + IKeyring kmsEcdhKeyring = matProv.CreateAwsKmsEcdhKeyring( + createAwsKmsEcdhKeyringInput + ); + + GetItemWithKeyring(kmsEcdhKeyring, ddbTableName); + } + + private static void GetItemWithKeyring( + IKeyring kmsEcdhKeyring, + String ddbTableName + ) { + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + final Map attributeActions = new HashMap<>(); + attributeActions.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY + attributeActions.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY + attributeActions.put("sensitive_data", CryptoAction.ENCRYPT_AND_SIGN); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActions` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + final String unsignAttrPrefix = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite + // that does not use asymmetric signing. + final Map tableConfigs = + new HashMap<>(); + final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .partitionKeyName("partition_key") + .sortKeyName("sort_key") + .attributeActionsOnEncrypt(attributeActions) + .keyring(kmsEcdhKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + // Specify algorithmSuite without asymmetric signing here + // As of v3.0.0, the only supported algorithmSuite without asymmetric signing is + // ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384. + .algorithmSuiteId( + DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384 + ) + .build(); + tableConfigs.put(ddbTableName, config); + + // 5. Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddbClient = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(encryptionInterceptor) + .build() + ) + .build(); + + // 7. Get the item back from our table using the client. + // The client will decrypt the item client-side using the ECDH keyring + // and return the original item. + final HashMap keyToGet = new HashMap<>(); + keyToGet.put( + "partition_key", + AttributeValue.builder().s("awsKmsEcdhKeyringItem").build() + ); + keyToGet.put("sort_key", AttributeValue.builder().n("0").build()); + + final GetItemRequest getRequest = GetItemRequest + .builder() + .key(keyToGet) + .tableName(ddbTableName) + .build(); + + final GetItemResponse getResponse = ddbClient.getItem(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + assert 200 == getResponse.sdkHttpResponse().statusCode(); + final Map returnedItem = getResponse.item(); + assert returnedItem + .get("sensitive_data") + .s() + .equals("encrypt and sign me!"); + } + + private static void PutGetItemWithKeyring( + IKeyring awsKmsEcdhKeyring, + String ddbTableName + ) { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + final Map attributeActions = new HashMap<>(); + attributeActions.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY + attributeActions.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY + attributeActions.put("sensitive_data", CryptoAction.ENCRYPT_AND_SIGN); + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActions` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + final String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite + // that does not use asymmetric signing. + final Map tableConfigs = + new HashMap<>(); + final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .partitionKeyName("partition_key") + .sortKeyName("sort_key") + .attributeActionsOnEncrypt(attributeActions) + .keyring(awsKmsEcdhKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + // Specify algorithmSuite without asymmetric signing here + // As of v3.0.0, the only supported algorithmSuite without asymmetric signing is + // ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384. + .algorithmSuiteId( + DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384 + ) + .build(); + tableConfigs.put(ddbTableName, config); + + // 6. Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddbClient = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(encryptionInterceptor) + .build() + ) + .build(); + + // 8. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + final HashMap item = new HashMap<>(); + item.put( + "partition_key", + AttributeValue.builder().s("awsKmsEcdhKeyringItem").build() + ); + item.put("sort_key", AttributeValue.builder().n("0").build()); + item.put( + "sensitive_data", + AttributeValue.builder().s("encrypt and sign me!").build() + ); + + final PutItemRequest putRequest = PutItemRequest + .builder() + .tableName(ddbTableName) + .item(item) + .build(); + + final PutItemResponse putResponse = ddbClient.putItem(putRequest); + + // Demonstrate that PutItem succeeded + assert 200 == putResponse.sdkHttpResponse().statusCode(); + + // 9. Get the item back from our table using the client. + // The client will decrypt the item client-side using the RSA keyring + // and return the original item. + final HashMap keyToGet = new HashMap<>(); + keyToGet.put( + "partition_key", + AttributeValue.builder().s("awsKmsEcdhKeyringItem").build() + ); + keyToGet.put("sort_key", AttributeValue.builder().n("0").build()); + + final GetItemRequest getRequest = GetItemRequest + .builder() + .key(keyToGet) + .tableName(ddbTableName) + .build(); + + final GetItemResponse getResponse = ddbClient.getItem(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + assert 200 == getResponse.sdkHttpResponse().statusCode(); + final Map returnedItem = getResponse.item(); + assert returnedItem + .get("sensitive_data") + .s() + .equals("encrypt and sign me!"); + } + + private static ByteBuffer loadPublicKeyBytes(String eccPublicKeyFileName) { + try { + ByteBuffer publicKeyFileBytes = ByteBuffer.wrap( + Files.readAllBytes(Paths.get(eccPublicKeyFileName)) + ); + + PemReader pemReader = new PemReader( + new InputStreamReader( + new ByteArrayInputStream(publicKeyFileBytes.array()) + ) + ); + PemObject pemObject = pemReader.readPemObject(); + byte[] content = pemObject.getContent(); + SubjectPublicKeyInfo subjectPublicKeyInfo = + SubjectPublicKeyInfo.getInstance(content); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec( + subjectPublicKeyInfo.getEncoded() + ); + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC"); + PublicKey publicKey = keyFactory.generatePublic(x509KeySpec); + return ByteBuffer.wrap(publicKey.getEncoded()); + } catch (IOException e) { + throw new RuntimeException( + "IOException while reading public key from file", + e + ); + } catch ( + NoSuchProviderException + | NoSuchAlgorithmException + | InvalidKeySpecException e + ) { + throw new RuntimeException(e); + } + } + + public static void KmsEcdhKeyringGetItemPutItem( + String ddbTableName, + String eccKeyArn + ) { + KmsEcdhKeyringGetItemPutItem( + ddbTableName, + eccKeyArn, + EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME, + EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME + ); + } + + public static void main(final String[] args) { + if (args.length <= 1) { + throw new IllegalArgumentException( + "To run this example, include the ddbTable and eccKeyArn in args; optionally include eccPublicKeySenderFilename, eccPublicKeyRecipientFilename, and eccRecipientKeyArn arguments" + ); + } + final String ddbTableName = args[0]; + final String eccKeyArn = args[1]; + String eccPublicKeySenderFilename; + String eccPublicKeyRecipientFilename; + String eccRecipientKeyArn; + if (args.length == 4) { + eccPublicKeySenderFilename = args[2]; + eccPublicKeyRecipientFilename = args[3]; + } else { + eccPublicKeySenderFilename = EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME; + eccPublicKeyRecipientFilename = EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME; + } + if (args.length == 5) { + eccRecipientKeyArn = args[4]; + } else { + eccRecipientKeyArn = null; + } + + // You may provide your own ECC public keys at EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME and + // EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME. + // The EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME must be the public key for the ECC key represented at eccKeyArn. + // + // If these files are not present, this will write a UTF-8 encoded PEM file for you. + // In this example we use an ECC KMS Key as the recipient key. However; this is only for + // example purposes. You can use an ECC Key that is not stored in KMS as the recipient public key. + if (shouldGetNewPublicKeys()) { + writePublicKeyPemForEccKey( + eccKeyArn, + EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME + ); + writePublicKeyPemForEccKey( + eccRecipientKeyArn, + EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME + ); + } + + KmsEcdhKeyringGetItemPutItem( + ddbTableName, + eccKeyArn, + eccPublicKeySenderFilename, + eccPublicKeyRecipientFilename + ); + } + + static void writePublicKeyPemForEccKey( + String eccKeyArn, + String eccPublicKeyFilename + ) { + // Safety check: Validate file is not present + File publicKeyFile = new File(eccPublicKeyFilename); + if (publicKeyFile.exists()) { + throw new IllegalStateException( + "writePublicKeyPemForEccKey will not overwrite existing PEM files" + ); + } + // This code will call KMS to get the public key for the KMS ECC key. + // You must have kms:GetPublicKey permissions on the key for this to succeed. + // The public key will be written to the file EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME + // or EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME. + KmsClient getterForPublicKey = KmsClient.create(); + GetPublicKeyResponse response = getterForPublicKey.getPublicKey( + GetPublicKeyRequest.builder().keyId(eccKeyArn).build() + ); + byte[] publicKeyByteArray = response.publicKey().asByteArray(); + + StringWriter publicKeyStringWriter = new StringWriter(); + PemWriter publicKeyPemWriter = new PemWriter(publicKeyStringWriter); + try { + publicKeyPemWriter.writeObject( + new PemObject("PUBLIC KEY", publicKeyByteArray) + ); + publicKeyPemWriter.close(); + } catch (IOException e) { + throw new RuntimeException("IOException while writing public key PEM", e); + } + ByteBuffer publicKeyUtf8EncodedByteBufferToWrite = + StandardCharsets.UTF_8.encode(publicKeyStringWriter.toString()); + + try { + FileChannel fc = new FileOutputStream(eccPublicKeyFilename).getChannel(); + fc.write(publicKeyUtf8EncodedByteBufferToWrite); + fc.close(); + } catch (FileNotFoundException e) { + throw new RuntimeException( + "FileNotFoundException while opening public key FileChannel", + e + ); + } catch (IOException e) { + throw new RuntimeException( + "IOException while writing public key or closing FileChannel", + e + ); + } + } + + static boolean shouldGetNewPublicKeys() { + // Check if public keys already exist + File senderPublicKeyFile = new File(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME); + File recipientPublicKeyFile = new File( + EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME + ); + + if (senderPublicKeyFile.exists() || recipientPublicKeyFile.exists()) { + return false; + } + + if (!senderPublicKeyFile.exists() && recipientPublicKeyFile.exists()) { + throw new IllegalStateException( + "Missing public key sender file at " + + EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME + ); + } + + if (!recipientPublicKeyFile.exists() && senderPublicKeyFile.exists()) { + throw new IllegalStateException( + "Missing public key recipient file at " + + EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME + ); + } + + return true; + } +} diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java new file mode 100644 index 000000000..f22c8d805 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java @@ -0,0 +1,877 @@ +package software.amazon.cryptography.examples.keyring; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.jce.interfaces.ECPublicKey; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.bouncycastle.util.io.pem.PemWriter; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +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.dynamodb.model.PutItemResponse; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTableEncryptionConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTablesEncryptionConfig; +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.CreateRawEcdhKeyringInput; +import software.amazon.cryptography.materialproviders.model.EphemeralPrivateKeyToStaticPublicKeyInput; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.materialproviders.model.PublicKeyDiscoveryInput; +import software.amazon.cryptography.materialproviders.model.RawEcdhStaticConfigurations; +import software.amazon.cryptography.materialproviders.model.RawPrivateKeyToStaticPublicKeyInput; +import software.amazon.cryptography.primitives.model.ECDHCurveSpec; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + using the raw ECDH Keyring. This keyring takes in the sender's ECC + private key and the recipient's ECC Public Key to derive a shared secret. + The keyring uses the shared secret to derive a data key to protect the + data keys that encrypt and decrypt DynamoDb table items. + + This example takes in the sender's private key, the recipient's + public key, and the algorithm definition where the ECC keys lie. + This parameter takes in the sender's private key as a + UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures), the recipient's + DER-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + and the Curve Specification where the keys lie. If this example + + This example encrypts a test item using the provided ECC keys and puts the + encrypted item to the provided DynamoDb table. Then, it gets the + item from the table and decrypts it. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + */ +public class RawEcdhKeyringExample { + + public static String EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER = + "RawEcdhKeyringExamplePrivateKeySender.pem"; + public static String EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT = + "RawEcdhKeyringExamplePrivateKeyRecipient.pem"; + public static String EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT = + "RawEcdhKeyringExamplePublicKeyRecipient.pem"; + + public static void RawEcdhKeyringGetItemPutItem( + String ddbTableName, + ECDHCurveSpec curveSpec + ) { + // 1. Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + ByteBuffer privateKeyUtf8EncodedByteBuffer; + try { + privateKeyUtf8EncodedByteBuffer = + ByteBuffer.wrap( + Files.readAllBytes(Paths.get(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER)) + ); + } catch (IOException e) { + throw new RuntimeException( + "IOException while reading the private key from file", + e + ); + } + + ByteBuffer publicKeyByteBuffer; + try { + ByteBuffer publicKeyUtf8EncodedByteBuffer = ByteBuffer.wrap( + Files.readAllBytes(Paths.get(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)) + ); + PemReader pemReader = new PemReader( + new InputStreamReader( + new ByteArrayInputStream(publicKeyUtf8EncodedByteBuffer.array()) + ) + ); + PemObject pemObject = pemReader.readPemObject(); + byte[] content = pemObject.getContent(); + SubjectPublicKeyInfo subjectPublicKeyInfo = + SubjectPublicKeyInfo.getInstance(content); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec( + subjectPublicKeyInfo.getEncoded() + ); + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC"); + PublicKey publicKey = keyFactory.generatePublic(x509KeySpec); + publicKeyByteBuffer = ByteBuffer.wrap(publicKey.getEncoded()); + } catch (IOException e) { + throw new RuntimeException( + "IOException while reading the public key from file", + e + ); + } catch ( + NoSuchAlgorithmException + | NoSuchProviderException + | InvalidKeySpecException e + ) { + throw new RuntimeException(e); + } + + // 2. Create the keyring. + // This keyring uses static sender and recipient keys. This configuration calls for the keys + // to be + // The DynamoDb encryption client uses this to encrypt and decrypt items. + final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput + .builder() + .curveSpec(curveSpec) + .KeyAgreementScheme( + RawEcdhStaticConfigurations + .builder() + .RawPrivateKeyToStaticPublicKey( + RawPrivateKeyToStaticPublicKeyInput + .builder() + // Must be a UTF8 PEM-encoded private key + .senderStaticPrivateKey(privateKeyUtf8EncodedByteBuffer) + // Must be a DER-encoded X.509 public key + .recipientPublicKey(publicKeyByteBuffer) + .build() + ) + .build() + ) + .build(); + + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + IKeyring rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); + + PutGetExampleWithKeyring(rawEcdhKeyring, ddbTableName); + } + + public static void EphemeralRawEcdhKeyringPutItem( + String ddbTableName, + ECDHCurveSpec ecdhCurveSpec + ) { + // 1. Load public key from UTF-8 encoded PEM files into a DER encoded public key. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + ByteBuffer publicKeyByteBuffer; + try { + ByteBuffer publicKeyUtf8EncodedByteBuffer = ByteBuffer.wrap( + Files.readAllBytes(Paths.get(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT)) + ); + PemReader pemReader = new PemReader( + new InputStreamReader( + new ByteArrayInputStream(publicKeyUtf8EncodedByteBuffer.array()) + ) + ); + PemObject pemObject = pemReader.readPemObject(); + byte[] content = pemObject.getContent(); + SubjectPublicKeyInfo subjectPublicKeyInfo = + SubjectPublicKeyInfo.getInstance(content); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec( + subjectPublicKeyInfo.getEncoded() + ); + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC"); + PublicKey publicKey = keyFactory.generatePublic(x509KeySpec); + publicKeyByteBuffer = ByteBuffer.wrap(publicKey.getEncoded()); + } catch (IOException e) { + throw new RuntimeException( + "IOException while reading the public key from file", + e + ); + } catch ( + NoSuchAlgorithmException + | NoSuchProviderException + | InvalidKeySpecException e + ) { + throw new RuntimeException(e); + } + + // 2. Create the keyring. + // This keyring uses an ephemeral configuration. This configuration will always create a new + // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only + // encrypt data and CANNOT decrypt messages. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput + .builder() + .curveSpec(ecdhCurveSpec) + .KeyAgreementScheme( + RawEcdhStaticConfigurations + .builder() + .EphemeralPrivateKeyToStaticPublicKey( + EphemeralPrivateKeyToStaticPublicKeyInput + .builder() + .recipientPublicKey(publicKeyByteBuffer) + .build() + ) + .build() + ) + .build(); + + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + IKeyring rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); + + // A raw ecdh keyring with Ephemeral configuration cannot decrypt data since the key pair + // used as the sender is ephemeral. This means that at decrypt time it does not have + // the private key that corresponds to the public key that is stored on the message. + PutExampleWithKeyring(rawEcdhKeyring, ddbTableName); + } + + public static void DiscoveryRawEcdhKeyringGetItem( + String ddbTableName, + ECDHCurveSpec ecdhCurveSpec + ) { + // 1. Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + ByteBuffer privateKeyUtf8EncodedByteBuffer; + try { + privateKeyUtf8EncodedByteBuffer = + ByteBuffer.wrap( + Files.readAllBytes( + Paths.get(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT) + ) + ); + } catch (IOException e) { + throw new RuntimeException( + "IOException while reading the private key from file", + e + ); + } + + // 2. Create the keyring. + // This keyring uses an ephemeral configuration. This configuration will always create a new + // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only + // encrypt data and CANNOT decrypt messages. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput + .builder() + .curveSpec(ecdhCurveSpec) + .KeyAgreementScheme( + RawEcdhStaticConfigurations + .builder() + .PublicKeyDiscovery( + PublicKeyDiscoveryInput + .builder() + .recipientStaticPrivateKey(privateKeyUtf8EncodedByteBuffer) + .build() + ) + .build() + ) + .build(); + + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + IKeyring rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); + + // A raw ecdh keyring with discovery configuration cannot encrypt data since the keyring + // looks for its configured public key on the message. + GetExampleWithKeyring(rawEcdhKeyring, ddbTableName); + } + + public static void PutGetExampleWithKeyring( + IKeyring rawEcdhKeyring, + String ddbTableName + ) { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + final Map attributeActionsOnEncrypt = new HashMap<>(); + attributeActionsOnEncrypt.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY + attributeActionsOnEncrypt.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY + attributeActionsOnEncrypt.put( + "sensitive_data", + CryptoAction.ENCRYPT_AND_SIGN + ); + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + final String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + final Map tableConfigs = + new HashMap<>(); + final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .partitionKeyName("partition_key") + .sortKeyName("sort_key") + .attributeActionsOnEncrypt(attributeActionsOnEncrypt) + .keyring(rawEcdhKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + .build(); + tableConfigs.put(ddbTableName, config); + + // 6. Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(encryptionInterceptor) + .build() + ) + .build(); + + // 8. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + final HashMap item = new HashMap<>(); + item.put( + "partition_key", + AttributeValue.builder().s("rawEcdhKeyringItem").build() + ); + item.put("sort_key", AttributeValue.builder().n("0").build()); + item.put( + "sensitive_data", + AttributeValue.builder().s("encrypt and sign me!").build() + ); + + final PutItemRequest putRequest = PutItemRequest + .builder() + .tableName(ddbTableName) + .item(item) + .build(); + + final PutItemResponse putResponse = ddb.putItem(putRequest); + + // Demonstrate that PutItem succeeded + assert 200 == putResponse.sdkHttpResponse().statusCode(); + + // 9. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + final HashMap keyToGet = new HashMap<>(); + keyToGet.put( + "partition_key", + AttributeValue.builder().s("rawEcdhKeyringItem").build() + ); + keyToGet.put("sort_key", AttributeValue.builder().n("0").build()); + + final GetItemRequest getRequest = GetItemRequest + .builder() + .key(keyToGet) + .tableName(ddbTableName) + .build(); + + final GetItemResponse getResponse = ddb.getItem(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + assert 200 == getResponse.sdkHttpResponse().statusCode(); + final Map returnedItem = getResponse.item(); + assert returnedItem + .get("sensitive_data") + .s() + .equals("encrypt and sign me!"); + } + + public static void PutExampleWithKeyring( + IKeyring rawEcdhKeyring, + String ddbTableName + ) { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + final Map attributeActionsOnEncrypt = new HashMap<>(); + attributeActionsOnEncrypt.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY + attributeActionsOnEncrypt.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY + attributeActionsOnEncrypt.put( + "sensitive_data", + CryptoAction.ENCRYPT_AND_SIGN + ); + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + final String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + final Map tableConfigs = + new HashMap<>(); + final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .partitionKeyName("partition_key") + .sortKeyName("sort_key") + .attributeActionsOnEncrypt(attributeActionsOnEncrypt) + .keyring(rawEcdhKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + .build(); + tableConfigs.put(ddbTableName, config); + + // 6. Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(encryptionInterceptor) + .build() + ) + .build(); + + // 8. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + final HashMap item = new HashMap<>(); + item.put( + "partition_key", + AttributeValue.builder().s("rawEcdhKeyringItem").build() + ); + item.put("sort_key", AttributeValue.builder().n("0").build()); + item.put( + "sensitive_data", + AttributeValue.builder().s("encrypt and sign me!").build() + ); + + final PutItemRequest putRequest = PutItemRequest + .builder() + .tableName(ddbTableName) + .item(item) + .build(); + + final PutItemResponse putResponse = ddb.putItem(putRequest); + + // Demonstrate that PutItem succeeded + assert 200 == putResponse.sdkHttpResponse().statusCode(); + } + + public static void GetExampleWithKeyring( + IKeyring rawEcdhKeyring, + String ddbTableName + ) { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + final Map attributeActionsOnEncrypt = new HashMap<>(); + attributeActionsOnEncrypt.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY + attributeActionsOnEncrypt.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY + attributeActionsOnEncrypt.put( + "sensitive_data", + CryptoAction.ENCRYPT_AND_SIGN + ); + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + final String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + final Map tableConfigs = + new HashMap<>(); + final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig + .builder() + .logicalTableName(ddbTableName) + .partitionKeyName("partition_key") + .sortKeyName("sort_key") + .attributeActionsOnEncrypt(attributeActionsOnEncrypt) + .keyring(rawEcdhKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + .build(); + tableConfigs.put(ddbTableName, config); + + // 6. Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient + .builder() + .overrideConfiguration( + ClientOverrideConfiguration + .builder() + .addExecutionInterceptor(encryptionInterceptor) + .build() + ) + .build(); + + // 8. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + final HashMap keyToGet = new HashMap<>(); + keyToGet.put( + "partition_key", + AttributeValue.builder().s("rawEcdhKeyringItem").build() + ); + keyToGet.put("sort_key", AttributeValue.builder().n("0").build()); + + final GetItemRequest getRequest = GetItemRequest + .builder() + .key(keyToGet) + .tableName(ddbTableName) + .build(); + + final GetItemResponse getResponse = ddb.getItem(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + assert 200 == getResponse.sdkHttpResponse().statusCode(); + final Map returnedItem = getResponse.item(); + assert returnedItem + .get("sensitive_data") + .s() + .equals("encrypt and sign me!"); + } + + public static void main(final String[] args) { + if (args.length <= 0) { + throw new IllegalArgumentException( + "To run this example, include the ddbTable in args" + ); + } + final String ddbTableName = args[0]; + + RawEcdhKeyringGetItemPutItem(ddbTableName, ECDHCurveSpec.ECC_NIST_P256); + } + + public static boolean shouldGenerateNewEccKeyPairs() { + File privateKeyFileSender = new File( + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + ); + File privateKeyFileRecipient = new File( + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + ); + File publicKeyFileRecipient = new File( + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + ); + + // If keys already exist: do not overwrite existing keys + if ( + privateKeyFileSender.exists() && + publicKeyFileRecipient.exists() && + privateKeyFileRecipient.exists() + ) { + return false; + } + + // If not all three keys are present; throw an exception + if ( + !privateKeyFileSender.exists() && + publicKeyFileRecipient.exists() && + privateKeyFileRecipient.exists() + ) { + throw new IllegalStateException( + "Missing private key sender file at " + + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + ); + } + if ( + privateKeyFileSender.exists() && + !publicKeyFileRecipient.exists() && + privateKeyFileRecipient.exists() + ) { + throw new IllegalStateException( + "Missing public key recipient file at " + + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + ); + } + if ( + privateKeyFileSender.exists() && + publicKeyFileRecipient.exists() && + !privateKeyFileRecipient.exists() + ) { + throw new IllegalStateException( + "Missing priavet key recipeitn file at " + + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + ); + } + + // If no keys are present, generate new keys + return true; + } + + public static void generateEccKeyPairs() { + File privateKeyFileSender = new File( + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + ); + File privateKeyFileRecipient = new File( + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + ); + File publicKeyFileRecipient = new File( + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + ); + + if ( + privateKeyFileSender.exists() || + publicKeyFileRecipient.exists() || + privateKeyFileRecipient.exists() + ) { + throw new IllegalStateException( + "generateEccKeyPairs will not overwrite existing PEM files" + ); + } + + // This code will generate new ECC key pairs for example use. + // The keys will be written to the files: + // - public_sender: EXAMPLE_ECC_PUBLIC_KEY_FILENAME_SENDER + // - private_sender: EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + // - public_recipient: EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // This example uses BouncyCastle's KeyPairGenerator to generate the key pairs. + // In practice, you should not generate this in your code, and should instead + // retrieve this key from a secure key management system (e.g. HSM). + // These examples only demonstrate using the P256 curve while the keyring accepts + // P256, P384, or P521. + // These keys are created here for example purposes only. + KeyPairGenerator keyGen; + try { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + keyGen = KeyPairGenerator.getInstance("EC", "BC"); + keyGen.initialize( + new ECGenParameterSpec("secp256r1"), + new SecureRandom() + ); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { + throw new RuntimeException("No such algorithm", e); + } catch (NoSuchProviderException e) { + throw new RuntimeException(e); + } + + final KeyPair senderKeyPair = keyGen.generateKeyPair(); + final KeyPair recipientKeyPair = keyGen.generateKeyPair(); + + writePrivateKey( + senderKeyPair.getPrivate(), + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + ); + writePrivateKey( + recipientKeyPair.getPrivate(), + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + ); + writePublicKey( + (ECPublicKey) recipientKeyPair.getPublic(), + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + ); + } + + private static void writePrivateKey( + PrivateKey aPrivate, + String exampleEccPrivateKeyFilenameSender + ) { + StringWriter privateKeySenderStringWriter = new StringWriter(); + PemWriter privateKeyPemWriter = new PemWriter(privateKeySenderStringWriter); + + try { + privateKeyPemWriter.writeObject( + new PemObject("PRIVATE KEY", aPrivate.getEncoded()) + ); + privateKeyPemWriter.close(); + } catch (IOException e) { + throw new RuntimeException( + "IOException while writing private key PEM", + e + ); + } + + ByteBuffer privateKeyUtf8EncodedPrivateKeyByteBuffer = + StandardCharsets.UTF_8.encode(privateKeySenderStringWriter.toString()); + + // Write UTF8 encoded PEM file + try { + FileChannel fc = new FileOutputStream(exampleEccPrivateKeyFilenameSender) + .getChannel(); + fc.write(privateKeyUtf8EncodedPrivateKeyByteBuffer); + fc.close(); + } catch (FileNotFoundException e) { + throw new RuntimeException( + "FileNotFoundException while opening private key FileChannel", + e + ); + } catch (IOException e) { + throw new RuntimeException( + "IOException while writing private key or closing FileChannel", + e + ); + } + } + + private static void writePublicKey(ECPublicKey aPublic, String fileName) { + StringWriter publicKeyStringWriter = new StringWriter(); + PemWriter publicKeySenderPemWriter = new PemWriter(publicKeyStringWriter); + X509EncodedKeySpec spec = new X509EncodedKeySpec(aPublic.getEncoded()); + + try { + publicKeySenderPemWriter.writeObject( + new PemObject("PUBLIC KEY", spec.getEncoded()) + ); + publicKeySenderPemWriter.close(); + } catch (IOException e) { + throw new RuntimeException("IOException while writing public key PEM", e); + } + + ByteBuffer publicKeyUtf8EncodedPublicKeyByteBuffer = + StandardCharsets.UTF_8.encode(publicKeyStringWriter.toString()); + + try { + FileChannel fc = new FileOutputStream(fileName).getChannel(); + fc.write(publicKeyUtf8EncodedPublicKeyByteBuffer); + fc.close(); + } catch (FileNotFoundException e) { + throw new RuntimeException( + "FileNotFoundException while opening private key FileChannel", + e + ); + } catch (IOException e) { + throw new RuntimeException( + "IOException while writing private key or closing FileChannel", + e + ); + } + } +} diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/TestUtils.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/TestUtils.java index 76fe7cbb8..c3f9fe308 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/TestUtils.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/TestUtils.java @@ -20,6 +20,10 @@ public class TestUtils { "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; public static final String TEST_MRK_REPLICA_KEY_ID_EU_WEST_1 = "arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; + public static final String TEST_KMS_ECDH_KEY_ID_P256_SENDER = + "arn:aws:kms:us-west-2:370957321024:key/eabdf483-6be2-4d2d-8ee4-8c2583d416e9"; + public static final String TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT = + "arn:aws:kms:us-west-2:370957321024:key/0265c8e9-5b6a-4055-8f70-63719e09fda5"; // Our tests require access to DDB Table with this name public static final String TEST_DDB_TABLE_NAME = diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestKmsEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestKmsEcdhKeyringExample.java new file mode 100644 index 000000000..42f9f51a8 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestKmsEcdhKeyringExample.java @@ -0,0 +1,47 @@ +package software.amazon.cryptography.examples.keyring; + +import static software.amazon.cryptography.examples.keyring.KmsEcdhKeyringExample.EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME; +import static software.amazon.cryptography.examples.keyring.KmsEcdhKeyringExample.EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME; +import static software.amazon.cryptography.examples.keyring.KmsEcdhKeyringExample.shouldGetNewPublicKeys; +import static software.amazon.cryptography.examples.keyring.KmsEcdhKeyringExample.writePublicKeyPemForEccKey; + +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.TestUtils; + +public class TestKmsEcdhKeyringExample { + + @Test + public void TestKmsEcdhKeyringExampleStatic() { + // You may provide your own ECC public keys at EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME + // and EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME. + // This must be the public key for the ECC key represented at eccKeyArn + // If this file is not present, this will write a UTF-8 encoded PEM file for you. + if (shouldGetNewPublicKeys()) { + writePublicKeyPemForEccKey( + TestUtils.TEST_KMS_ECDH_KEY_ID_P256_SENDER, + EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME + ); + writePublicKeyPemForEccKey( + TestUtils.TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT, + EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME + ); + } + + KmsEcdhKeyringExample.KmsEcdhKeyringGetItemPutItem( + TestUtils.TEST_DDB_TABLE_NAME, + TestUtils.TEST_KMS_ECDH_KEY_ID_P256_SENDER + ); + } + + @Test + public void TestKmsEcdhKeyringExampleDiscovery() { + // In this example you do not need to provide the recipient ECC Public Key. + // On initialization, the keyring will call KMS:getPublicKey on the configured + // recipientKmsIdentifier set on the keyring. This example uses the previous example + // to write an item meant for the recipient. + KmsEcdhKeyringExample.KmsEcdhDiscoveryGetItem( + TestUtils.TEST_DDB_TABLE_NAME, + TestUtils.TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT + ); + } +} diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestRawEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestRawEcdhKeyringExample.java new file mode 100644 index 000000000..a7f9cb252 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestRawEcdhKeyringExample.java @@ -0,0 +1,80 @@ +package software.amazon.cryptography.examples.keyring; + +import java.nio.ByteBuffer; +import java.security.spec.ECGenParameterSpec; +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.TestUtils; +import software.amazon.cryptography.primitives.model.ECDHCurveSpec; + +public class TestRawEcdhKeyringExample { + + @Test + public void TestStaticRawEcdhKeyringExample() { + // You may provide your own ECC Key pairs in the files located at + // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + // - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // If these files are not present, this will generate a pair for you. + // For this example we will use the curve P256. + if (RawEcdhKeyringExample.shouldGenerateNewEccKeyPairs()) { + RawEcdhKeyringExample.generateEccKeyPairs(); + } + + // Part of using these keyrings is knowing which curve the keys used in the key agreement + // lie on. The keyring will fail if the keys do not lie on the configured curve. + RawEcdhKeyringExample.RawEcdhKeyringGetItemPutItem( + TestUtils.TEST_DDB_TABLE_NAME, + ECDHCurveSpec.ECC_NIST_P256 + ); + } + + @Test + public void TestEphemeralRawEcdhKeyringExample() { + // You may provide your own ECC Public Key in the files located at + // - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // If these files are not present, this will generate a pair for you. + // For this example we will use the curve P256. + if (RawEcdhKeyringExample.shouldGenerateNewEccKeyPairs()) { + RawEcdhKeyringExample.generateEccKeyPairs(); + } + + // Part of using these keyrings is knowing which curve the keys used in the key agreement + // lie on. The keyring will fail if the keys do not lie on the configured curve. + RawEcdhKeyringExample.EphemeralRawEcdhKeyringPutItem( + TestUtils.TEST_DDB_TABLE_NAME, + ECDHCurveSpec.ECC_NIST_P256 + ); + } + + @Test + public void TestDiscoveryRawEcdhKeyringExample() { + // You may provide your own ECC Public Key in the files located at + // - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + // If these files are not present, this will generate a pair for you. + // For this example we will use the curve P256. + if (RawEcdhKeyringExample.shouldGenerateNewEccKeyPairs()) { + RawEcdhKeyringExample.generateEccKeyPairs(); + } + + // The discovery configuration is not allowed to encrypt + // To understand this example best, we will write a record with the ephemeral configuration + // in the previous example. This means that the recipient public key configured on + // both keyrings is the same. This means that the other party has the recipient public key + // and is writing messages meant only for the owner of the recipient public key to decrypt. + + // In this call we are writing a record that is written with an ephemeral sender key pair. + // The recipient will be able to decrypt the message + RawEcdhKeyringExample.EphemeralRawEcdhKeyringPutItem( + TestUtils.TEST_DDB_TABLE_NAME, + ECDHCurveSpec.ECC_NIST_P256 + ); + + // In this call we are reading a record that was written with the recipient's public key. + // It will use the recipient's private key and the sender's public key stored in the message to + // calculate the appropriate shared secret to successfully decrypt the message. + RawEcdhKeyringExample.DiscoveryRawEcdhKeyringGetItem( + TestUtils.TEST_DDB_TABLE_NAME, + ECDHCurveSpec.ECC_NIST_P256 + ); + } +} From 38ab8e625da5cee75a35d94c2f8689cf7e084e92 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 4 Nov 2024 15:03:49 -0800 Subject: [PATCH 02/12] cfn update --- cfn/CI.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/cfn/CI.yaml b/cfn/CI.yaml index 275d1dbf9..df70d257a 100644 --- a/cfn/CI.yaml +++ b/cfn/CI.yaml @@ -450,6 +450,7 @@ Resources: - Fn::ImportValue: "Polymorph-CA-GitHubCAReadPolicyArn" - "arn:aws:iam::370957321024:policy/ESDK-Dafny-DDB-ReadWriteDelete-us-west-2" - "arn:aws:iam::370957321024:policy/Hierarchical-GitHub-KMS-Key-Policy" + - "arn:aws:iam::370957321024:policy/Github-ECDH-KMS" - !Ref KMSUsage - !Ref DDBUsage AssumeRolePolicyDocument: !Sub | From 77d00002321d6deea7d1d8cb00ca7ae511d0aa04 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 4 Nov 2024 15:07:58 -0800 Subject: [PATCH 03/12] names are hard --- cfn/CI.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfn/CI.yaml b/cfn/CI.yaml index df70d257a..e816073d1 100644 --- a/cfn/CI.yaml +++ b/cfn/CI.yaml @@ -98,7 +98,7 @@ Resources: ProvisionedThroughput: ReadCapacityUnits: "5" WriteCapacityUnits: "5" - TableName: !Ref BasicTestDotnetTableName + TableName: !Ref BasicTestDotNetTableName SearchTestJavaTable: Type: AWS::DynamoDB::Table From b9a6692b61a5d53f3981995340e22f50bc05100b Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 4 Nov 2024 15:09:57 -0800 Subject: [PATCH 04/12] m --- cfn/CI.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfn/CI.yaml b/cfn/CI.yaml index e816073d1..37a46a7d3 100644 --- a/cfn/CI.yaml +++ b/cfn/CI.yaml @@ -384,7 +384,7 @@ Resources: - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${KeystoreTableName}" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${KeystoreTableName}/index/*" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BasicTestJavaTableName}" - - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BasicTestDotnetTableName}" + - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BasicTestDotNetTableName}" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SearchTestJavaTableName}" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SearchTestJavaTableName}/index/*" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SearchTestDotnetTableName}" From 6c80f5e9b3f540bcda8c3bc8661639953035fa6e Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 4 Nov 2024 15:19:00 -0800 Subject: [PATCH 05/12] m --- cfn/CI.yaml | 246 ++++++++++++++++++++++++++-------------------------- 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/cfn/CI.yaml b/cfn/CI.yaml index 37a46a7d3..a9728bc6d 100644 --- a/cfn/CI.yaml +++ b/cfn/CI.yaml @@ -64,131 +64,131 @@ Resources: WriteCapacityUnits: "5" TableName: !Ref TableName - BasicTestJavaTable: - Type: AWS::DynamoDB::Table - Properties: - AttributeDefinitions: - - AttributeName: "partition_key" - AttributeType: "S" - - AttributeName: "sort_key" - AttributeType: "N" - KeySchema: - - AttributeName: "partition_key" - KeyType: "HASH" - - AttributeName: "sort_key" - KeyType: "RANGE" - ProvisionedThroughput: - ReadCapacityUnits: "5" - WriteCapacityUnits: "5" - TableName: !Ref BasicTestJavaTableName + #BasicTestJavaTable: + # Type: AWS::DynamoDB::Table + # Properties: + # AttributeDefinitions: + # - AttributeName: "partition_key" + # AttributeType: "S" + # - AttributeName: "sort_key" + # AttributeType: "N" + # KeySchema: + # - AttributeName: "partition_key" + # KeyType: "HASH" + # - AttributeName: "sort_key" + # KeyType: "RANGE" + # ProvisionedThroughput: + # ReadCapacityUnits: "5" + # WriteCapacityUnits: "5" + # TableName: !Ref BasicTestJavaTableName - BasicTestDotnetTable: - Type: AWS::DynamoDB::Table - Properties: - AttributeDefinitions: - - AttributeName: "partition_key" - AttributeType: "S" - - AttributeName: "sort_key" - AttributeType: "N" - KeySchema: - - AttributeName: "partition_key" - KeyType: "HASH" - - AttributeName: "sort_key" - KeyType: "RANGE" - ProvisionedThroughput: - ReadCapacityUnits: "5" - WriteCapacityUnits: "5" - TableName: !Ref BasicTestDotNetTableName + #BasicTestDotnetTable: + # Type: AWS::DynamoDB::Table + # Properties: + # AttributeDefinitions: + # - AttributeName: "partition_key" + # AttributeType: "S" + # - AttributeName: "sort_key" + # AttributeType: "N" + # KeySchema: + # - AttributeName: "partition_key" + # KeyType: "HASH" + # - AttributeName: "sort_key" + # KeyType: "RANGE" + # ProvisionedThroughput: + # ReadCapacityUnits: "5" + # WriteCapacityUnits: "5" + # TableName: !Ref BasicTestDotnetTableName - SearchTestJavaTable: - Type: AWS::DynamoDB::Table - Properties: - AttributeDefinitions: - - AttributeName: "aws_dbe_b_inspector_id_last4" - AttributeType: "S" - - AttributeName: "aws_dbe_b_last4UnitCompound" - AttributeType: "S" - - AttributeName: "aws_dbe_b_unit" - AttributeType: "S" - - AttributeName: "inspection_date" - AttributeType: "S" - - AttributeName: "work_id" - AttributeType: "S" - KeySchema: - - AttributeName: "work_id" - KeyType: "HASH" - - AttributeName: "inspection_date" - KeyType: "RANGE" - ProvisionedThroughput: - ReadCapacityUnits: "5" - WriteCapacityUnits: "5" - TableName: !Ref SearchTestJavaTableName - GlobalSecondaryIndexes: - - IndexName: "last4-unit-index" - KeySchema: - - AttributeName: "aws_dbe_b_inspector_id_last4" - KeyType: "HASH" - - AttributeName: "aws_dbe_b_unit" - KeyType: "RANGE" - Projection: - ProjectionType: ALL - ProvisionedThroughput: - ReadCapacityUnits: "5" - WriteCapacityUnits: "5" - - IndexName: "last4UnitCompound-index" - KeySchema: - - AttributeName: "aws_dbe_b_last4UnitCompound" - KeyType: "HASH" - Projection: - ProjectionType: ALL - ProvisionedThroughput: - ReadCapacityUnits: "5" - WriteCapacityUnits: "5" + #SearchTestJavaTable: + # Type: AWS::DynamoDB::Table + # Properties: + # AttributeDefinitions: + # - AttributeName: "aws_dbe_b_inspector_id_last4" + # AttributeType: "S" + # - AttributeName: "aws_dbe_b_last4UnitCompound" + # AttributeType: "S" + # - AttributeName: "aws_dbe_b_unit" + # AttributeType: "S" + # - AttributeName: "inspection_date" + # AttributeType: "S" + # - AttributeName: "work_id" + # AttributeType: "S" + # KeySchema: + # - AttributeName: "work_id" + # KeyType: "HASH" + # - AttributeName: "inspection_date" + # KeyType: "RANGE" + # ProvisionedThroughput: + # ReadCapacityUnits: "5" + # WriteCapacityUnits: "5" + # TableName: !Ref SearchTestJavaTableName + # GlobalSecondaryIndexes: + # - IndexName: "last4-unit-index" + # KeySchema: + # - AttributeName: "aws_dbe_b_inspector_id_last4" + # KeyType: "HASH" + # - AttributeName: "aws_dbe_b_unit" + # KeyType: "RANGE" + # Projection: + # ProjectionType: ALL + # ProvisionedThroughput: + # ReadCapacityUnits: "5" + # WriteCapacityUnits: "5" + # - IndexName: "last4UnitCompound-index" + # KeySchema: + # - AttributeName: "aws_dbe_b_last4UnitCompound" + # KeyType: "HASH" + # Projection: + # ProjectionType: ALL + # ProvisionedThroughput: + # ReadCapacityUnits: "5" + # WriteCapacityUnits: "5" - SearchTestDotnetTable: - Type: AWS::DynamoDB::Table - Properties: - AttributeDefinitions: - - AttributeName: "aws_dbe_b_inspector_id_last4" - AttributeType: "S" - - AttributeName: "aws_dbe_b_last4UnitCompound" - AttributeType: "S" - - AttributeName: "aws_dbe_b_unit" - AttributeType: "S" - - AttributeName: "inspection_date" - AttributeType: "S" - - AttributeName: "work_id" - AttributeType: "S" - KeySchema: - - AttributeName: "work_id" - KeyType: "HASH" - - AttributeName: "inspection_date" - KeyType: "RANGE" - ProvisionedThroughput: - ReadCapacityUnits: "5" - WriteCapacityUnits: "5" - TableName: !Ref SearchTestDotnetTableName - GlobalSecondaryIndexes: - - IndexName: "last4-unit-index" - KeySchema: - - AttributeName: "aws_dbe_b_inspector_id_last4" - KeyType: "HASH" - - AttributeName: "aws_dbe_b_unit" - KeyType: "RANGE" - Projection: - ProjectionType: ALL - ProvisionedThroughput: - ReadCapacityUnits: "5" - WriteCapacityUnits: "5" - - IndexName: "last4UnitCompound-index" - KeySchema: - - AttributeName: "aws_dbe_b_last4UnitCompound" - KeyType: "HASH" - Projection: - ProjectionType: ALL - ProvisionedThroughput: - ReadCapacityUnits: "5" - WriteCapacityUnits: "5" + #SearchTestDotnetTable: + # Type: AWS::DynamoDB::Table + # Properties: + # AttributeDefinitions: + # - AttributeName: "aws_dbe_b_inspector_id_last4" + # AttributeType: "S" + # - AttributeName: "aws_dbe_b_last4UnitCompound" + # AttributeType: "S" + # - AttributeName: "aws_dbe_b_unit" + # AttributeType: "S" + # - AttributeName: "inspection_date" + # AttributeType: "S" + # - AttributeName: "work_id" + # AttributeType: "S" + # KeySchema: + # - AttributeName: "work_id" + # KeyType: "HASH" + # - AttributeName: "inspection_date" + # KeyType: "RANGE" + # ProvisionedThroughput: + # ReadCapacityUnits: "5" + # WriteCapacityUnits: "5" + # TableName: !Ref SearchTestDotnetTableName + # GlobalSecondaryIndexes: + # - IndexName: "last4-unit-index" + # KeySchema: + # - AttributeName: "aws_dbe_b_inspector_id_last4" + # KeyType: "HASH" + # - AttributeName: "aws_dbe_b_unit" + # KeyType: "RANGE" + # Projection: + # ProjectionType: ALL + # ProvisionedThroughput: + # ReadCapacityUnits: "5" + # WriteCapacityUnits: "5" + # - IndexName: "last4UnitCompound-index" + # KeySchema: + # - AttributeName: "aws_dbe_b_last4UnitCompound" + # KeyType: "HASH" + # Projection: + # ProjectionType: ALL + # ProvisionedThroughput: + # ReadCapacityUnits: "5" + # WriteCapacityUnits: "5" TestTableWithSimpleBeaconIndex: Type: AWS::DynamoDB::Table @@ -384,7 +384,7 @@ Resources: - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${KeystoreTableName}" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${KeystoreTableName}/index/*" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BasicTestJavaTableName}" - - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BasicTestDotNetTableName}" + - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BasicTestDotnetTableName}" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SearchTestJavaTableName}" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SearchTestJavaTableName}/index/*" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SearchTestDotnetTableName}" From 837c49106b63be6eb535566882134698fcdc4ce6 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 4 Nov 2024 15:21:20 -0800 Subject: [PATCH 06/12] names --- cfn/CI.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfn/CI.yaml b/cfn/CI.yaml index a9728bc6d..b273ca768 100644 --- a/cfn/CI.yaml +++ b/cfn/CI.yaml @@ -384,7 +384,7 @@ Resources: - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${KeystoreTableName}" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${KeystoreTableName}/index/*" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BasicTestJavaTableName}" - - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BasicTestDotnetTableName}" + - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BasicTestDotNetTableName}" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SearchTestJavaTableName}" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SearchTestJavaTableName}/index/*" - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${SearchTestDotnetTableName}" From 854c984a2a8e38f882ca72663284b806c9d722f8 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 4 Nov 2024 16:34:36 -0800 Subject: [PATCH 07/12] address typo --- .../cryptography/examples/keyring/RawEcdhKeyringExample.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java index f22c8d805..96a1bd4c0 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java @@ -66,7 +66,7 @@ This parameter takes in the sender's private key as a UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures), the recipient's DER-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), - and the Curve Specification where the keys lie. If this example + and the Curve Specification where the keys lie. This example encrypts a test item using the provided ECC keys and puts the encrypted item to the provided DynamoDb table. Then, it gets the @@ -726,7 +726,7 @@ public static boolean shouldGenerateNewEccKeyPairs() { !privateKeyFileRecipient.exists() ) { throw new IllegalStateException( - "Missing priavet key recipeitn file at " + + "Missing private key recipient file at " + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT ); } From cf4f403059c4d7f31dc2def843a1416ae055513e Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 4 Nov 2024 16:35:05 -0800 Subject: [PATCH 08/12] add net examples --- .../keyring/RawEcdhKeyringExample.java | 6 +- Examples/runtimes/net/src/Examples.cs | 7 +- Examples/runtimes/net/src/TestUtils.cs | 6 + .../net/src/keyring/KmsEcdhKeyringExample.cs | 378 ++++++++++ .../net/src/keyring/RawEcdhKeyringExample.cs | 678 ++++++++++++++++++ 5 files changed, 1070 insertions(+), 5 deletions(-) create mode 100644 Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs create mode 100644 Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java index 96a1bd4c0..30032bbbe 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java @@ -278,9 +278,9 @@ public static void DiscoveryRawEcdhKeyringGetItem( } // 2. Create the keyring. - // This keyring uses an ephemeral configuration. This configuration will always create a new - // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only - // encrypt data and CANNOT decrypt messages. + // This keyring uses a discovery configuration. This configuration will check on decrypt + // if it is meant to decrypt the message by checking if the configured public key is stored on the message. + // The discovery configuration can only decrypt messages and CANNOT encrypt messages. // The DynamoDb encryption client uses this to encrypt and decrypt items. final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput .builder() diff --git a/Examples/runtimes/net/src/Examples.cs b/Examples/runtimes/net/src/Examples.cs index c7f5b15f3..993b63bc3 100644 --- a/Examples/runtimes/net/src/Examples.cs +++ b/Examples/runtimes/net/src/Examples.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Examples.keyring; namespace Examples { @@ -10,7 +11,7 @@ class Program static async Task Main() { ItemEncryptDecryptExample.PutItemGetItem(); - + await BasicPutGetExample.PutItemGetItem(); await ScanErrorExample.ScanError(); await GetEncryptedDataKeyDescriptionExample.GetEncryptedDataKeyDescription(); @@ -22,6 +23,8 @@ static async Task Main() await MultiKeyringExample.MultiKeyringGetItemPutItem(); await RawRsaKeyringExample.RawRsaKeyringGetItemPutItem(); await KmsRsaKeyringExample.KmsRsaKeyringGetItemPutItem(); + await RawEcdhKeyringExample.RawEcdhKeyringExamples(); + await KmsEcdhKeyringExample.KmsEcdhKeyringExamples(); var keyId = CreateKeyStoreKeyExample.KeyStoreCreateKey(); var keyId2 = CreateKeyStoreKeyExample.KeyStoreCreateKey(); @@ -30,7 +33,7 @@ static async Task Main() Thread.Sleep(5000); await HierarchicalKeyringExample.HierarchicalKeyringGetItemPutItem(keyId, keyId2); - + await BasicSearchableEncryptionExample.PutItemQueryItemWithBeacon(keyId); await CompoundBeaconSearchableEncryptionExample.PutItemQueryItemWithCompoundBeacon(keyId); await VirtualBeaconSearchableEncryptionExample.PutItemQueryItemWithVirtualBeacon(keyId); diff --git a/Examples/runtimes/net/src/TestUtils.cs b/Examples/runtimes/net/src/TestUtils.cs index 2fd898553..406790e86 100644 --- a/Examples/runtimes/net/src/TestUtils.cs +++ b/Examples/runtimes/net/src/TestUtils.cs @@ -23,6 +23,12 @@ public class TestUtils public static readonly string TEST_KMS_RSA_KEY_ID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6"; + + public static readonly string TEST_KMS_ECDH_KEY_ID_P256_SENDER = + "arn:aws:kms:us-west-2:370957321024:key/eabdf483-6be2-4d2d-8ee4-8c2583d416e9"; + + public static readonly string TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT = + "arn:aws:kms:us-west-2:370957321024:key/0265c8e9-5b6a-4055-8f70-63719e09fda5"; public static readonly string TEST_MRK_REPLICA_KEY_ID_US_EAST_1 = "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; diff --git a/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs b/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs new file mode 100644 index 000000000..c5754e92e --- /dev/null +++ b/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs @@ -0,0 +1,378 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Amazon.DynamoDBv2.Model; +using Amazon.KeyManagementService; +using Amazon.KeyManagementService.Model; +using AWS.Cryptography.DbEncryptionSDK.DynamoDb; +using AWS.Cryptography.DbEncryptionSDK.StructuredEncryption; +using AWS.Cryptography.MaterialProviders; +using AWS.Cryptography.Primitives; +using Org.BouncyCastle.Utilities.IO.Pem; +using PemWriter = Org.BouncyCastle.OpenSsl.PemWriter; + +namespace Examples.keyring; + +public class KmsEcdhKeyringExample +{ + private static String EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME = + "KmsEccKeyringKeyringExamplePublicKeySender.pem"; + private static String EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME = + "KmsEccKeyringKeyringExamplePublicKeyRecipient.pem"; + + public static async Task KmsEcdhKeyringGetItemPutItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + var ecdhKeyArnSender = TestUtils.TEST_KMS_ECDH_KEY_ID_P256_SENDER; + + + // 1. Load public keys from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + MemoryStream publicKeySenderUtf8EncodedByteBuffer; + try + { + var publicKeyBytes = File.ReadAllText(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME); + PemReader pemReader = new PemReader(new StringReader(publicKeyBytes)); + publicKeySenderUtf8EncodedByteBuffer = new MemoryStream(pemReader.ReadPemObject().Content); + } + catch (IOException e) + { + throw new IOException("Exception while reading private key from file", e); + } + + MemoryStream publicKeyRecipientUtf8EncodedByteBuffer; + try + { + var publicKeyBytes = File.ReadAllText(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME); + PemReader pemReader = new PemReader(new StringReader(publicKeyBytes)); + publicKeyRecipientUtf8EncodedByteBuffer = new MemoryStream(pemReader.ReadPemObject().Content); + } + catch (IOException e) + { + throw new IOException("Exception while reading public key from file", e); + } + + // 2. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + var keyringInput = new CreateAwsKmsEcdhKeyringInput + { + // 2. Create a KMS ECDH keyring. + // This keyring takes in: + // - kmsClient + // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + // - senderPublicKey: A ByteBuffer of a UTF-8 encoded public + // key for the key passed into kmsKeyId in DER format + // - recipientPublicKey: A ByteBuffer of a UTF-8 encoded public key + // for the recipient public key. + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KmsClient = new AmazonKeyManagementServiceClient(), + KeyAgreementScheme = new KmsEcdhStaticConfigurations + { + KmsPrivateKeyToStaticPublicKey = new KmsPrivateKeyToStaticPublicKeyInput + { + SenderKmsIdentifier = ecdhKeyArnSender, + SenderPublicKey = publicKeySenderUtf8EncodedByteBuffer, + RecipientPublicKey = publicKeyRecipientUtf8EncodedByteBuffer + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var kmsEcdhKeyring = matProv.CreateAwsKmsEcdhKeyring(keyringInput); + + await PutItemGetItemWithKeyring(kmsEcdhKeyring, ddbTableName); + } + + private static async Task KmsEcdhKeyringDiscoveryGetItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + var ecdhKeyArnRecipient = TestUtils.TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT; + + // 1. Create a KMS ECDH keyring. + // This keyring takes in: + // - kmsClient + // - recipientKmsIdentifier: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + var keyringInput = new CreateAwsKmsEcdhKeyringInput + { + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KmsClient = new AmazonKeyManagementServiceClient(), + KeyAgreementScheme = new KmsEcdhStaticConfigurations + { + KmsPublicKeyDiscovery = new KmsPublicKeyDiscoveryInput + { + RecipientKmsIdentifier = ecdhKeyArnRecipient + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var kmsEcdhKeyring = matProv.CreateAwsKmsEcdhKeyring(keyringInput); + + await GetItemWithKeyring(kmsEcdhKeyring, ddbTableName); + } + + private static async Task GetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddbTableName) + { + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = kmsEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 5. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 6. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("kmsEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + var getResponse = await ddb.GetItemAsync(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK); + var returnedItem = getResponse.Item; + Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); + } + + private static async Task PutItemGetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddbTableName) + { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = kmsEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 6. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 7. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + var item = new Dictionary + { + ["partition_key"] = new AttributeValue("kmsEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" }, + ["sensitive_data"] = new AttributeValue("encrypt and sign me!") + }; + + var putRequest = new PutItemRequest + { + TableName = ddbTableName, + Item = item + }; + + var putResponse = await ddb.PutItemAsync(putRequest); + + // Demonstrate that PutItem succeeded + Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); + + // 8. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("kmsEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + var getResponse = await ddb.GetItemAsync(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK); + var returnedItem = getResponse.Item; + Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); + } + + public static async Task KmsEcdhKeyringExamples() + { + // You may provide your own ECC Keys in the files located at + // - EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME + // - EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME + // If these files are not present, this will get KMS ECC keys for you. + if (ShouldGetNewEccKeys()) + { + await WritePublicKeyPemForEccKey(TestUtils.TEST_KMS_ECDH_KEY_ID_P256_SENDER, EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME); + await WritePublicKeyPemForEccKey(TestUtils.TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT, EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME); + } + + await KmsEcdhKeyringGetItemPutItem(); + await KmsEcdhKeyringDiscoveryGetItem(); + } + + private static async Task WritePublicKeyPemForEccKey(string kmsEcdhKeyArn, string exampleEccPublicKeySenderFilename) + { + // Safety check: Validate file is not present + if (File.Exists(exampleEccPublicKeySenderFilename)) + { + throw new ApplicationException("WritePublicKeyPemForEccKey will not overwrite existing PEM files"); + } + // This code will call KMS to get the public key for the KMS ECC key. + // You must have kms:GetPublicKey permissions on the key for this to succeed. + // The public key will be written to the file exampleEccPublicKeySenderFilename. + var getterForPublicKey = new AmazonKeyManagementServiceClient(); + var response = await getterForPublicKey.GetPublicKeyAsync( + new GetPublicKeyRequest { KeyId = kmsEcdhKeyArn } + ); + var publicKeyByteArray = response.PublicKey.ToArray(); + + StringWriter publicKeyStringWriter = new StringWriter(); + PemWriter publicKeyPemWriter = new PemWriter(publicKeyStringWriter); + publicKeyPemWriter.WriteObject(new PemObject("PUBLIC KEY", publicKeyByteArray)); + + var publicKeyUtf8EncodedByteBuffer = Encoding.UTF8.GetBytes(publicKeyStringWriter.ToString()); + var fc = new FileStream(exampleEccPublicKeySenderFilename, FileMode.Create, FileAccess.Write); + fc.Write(publicKeyUtf8EncodedByteBuffer); + fc.Close(); + } + + private static bool ShouldGetNewEccKeys() + { + // If keys already exists; do not overwrite existing keys. + if (File.Exists(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME) + || File.Exists(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME)) + { + return false; + } + + if (!File.Exists(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME) + && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME)) + { + throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME); + } + + if (File.Exists(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME) + && !File.Exists(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME)) + { + throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME); + } + + return true; + } +} diff --git a/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs b/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs new file mode 100644 index 000000000..7aa06bebb --- /dev/null +++ b/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs @@ -0,0 +1,678 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using Amazon.DynamoDBv2.Model; +using AWS.Cryptography.DbEncryptionSDK.DynamoDb; +using AWS.Cryptography.DbEncryptionSDK.StructuredEncryption; +using AWS.Cryptography.MaterialProviders; +using ECDH; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.IO.Pem; +using Org.BouncyCastle.X509; +using software.amazon.cryptography.primitives.internaldafny.types; +using CreateRawEcdhKeyringInput = AWS.Cryptography.MaterialProviders.CreateRawEcdhKeyringInput; +using ECDHCurveSpec = AWS.Cryptography.Primitives.ECDHCurveSpec; +using PemWriter = Org.BouncyCastle.OpenSsl.PemWriter; +using RawEcdhStaticConfigurations = AWS.Cryptography.MaterialProviders.RawEcdhStaticConfigurations; + +namespace Examples.keyring; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + using the raw ECDH Keyring. This keyring takes in the sender's ECC + private key and the recipient's ECC Public Key to derive a shared secret. + The keyring uses the shared secret to derive a data key to protect the + data keys that encrypt and decrypt DynamoDb table items. + + This example takes in the sender's private key, the recipient's + public key, and the algorithm definition where the ECC keys lie. + This parameter takes in the sender's private key as a + UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures), the recipient's + DER-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + and the Curve Specification where the keys lie. + + This example encrypts a test item using the provided ECC keys and puts the + encrypted item to the provided DynamoDb table. Then, it gets the + item from the table and decrypts it. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + */ +public class RawEcdhKeyringExample +{ + private static readonly String EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER = "RawEcdhKeyringExamplePrivateKeySender.pem"; + private static readonly String EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT = "RawEcdhKeyringExamplePrivateKeyRecipient.pem"; + private static readonly String EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT = "RawEcdhKeyringExamplePublicKeyRecipient.pem"; + + private static async Task RawEcdhKeyringExampleGetItemPutItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + + // 1. Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + MemoryStream privateKeySenderUtf8EncodedByteBuffer; + try + { + privateKeySenderUtf8EncodedByteBuffer = new MemoryStream( + File.ReadAllBytes(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER)); + } + catch (IOException e) + { + throw new IOException("Exception while reading private key from file", e); + } + + MemoryStream publicKeyRecipientUtf8EncodedByteBuffer; + try + { + var publicKeyBytes = File.ReadAllText(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); + PemReader pemReader = new PemReader(new StringReader(publicKeyBytes)); + publicKeyRecipientUtf8EncodedByteBuffer = new MemoryStream(pemReader.ReadPemObject().Content); + } + catch (IOException e) + { + throw new IOException("Exception while reading public key from file", e); + } + + // 2. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + var keyringInput = new CreateRawEcdhKeyringInput + { + // This example uses keys that lie on the ECC Curve P256. + // The keyring supports curves P256, P384, and P521. + // On creation, the keyring verifies that all configured keys lie on the provided curve spec. + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KeyAgreementScheme = new RawEcdhStaticConfigurations + { + // This keyring is configured with a RawPrivateKeyToStaticPublicKey + // key agreement scheme. This means that both the sender's and recipient's + // keys are stored somewhere in memory. The recipient's public key MAY be + // a public key whose origin is an HSM like AWS KMS. + RawPrivateKeyToStaticPublicKey = new RawPrivateKeyToStaticPublicKeyInput + { + // Must be a UTF8 PEM-encoded private key + SenderStaticPrivateKey = privateKeySenderUtf8EncodedByteBuffer, + // Must be a UTF8 DER-encoded X.509 public key also known as SubjectPublicKeyInfo. + RecipientPublicKey = publicKeyRecipientUtf8EncodedByteBuffer + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); + + await PutGetExampleWithKeyring(rawEcdhKeyring, ddbTableName); + } + + private static async Task EphemeralRawEcdhKeyringPutItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + + // 1. Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the RawEcdhKeyringExamples method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + MemoryStream publicKeyRecipientUtf8EncodedByteBuffer; + try + { + var publicKeyBytes = File.ReadAllText(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); + PemReader pemReader = new PemReader(new StringReader(publicKeyBytes)); + publicKeyRecipientUtf8EncodedByteBuffer = new MemoryStream(pemReader.ReadPemObject().Content); + } + catch (IOException e) + { + throw new IOException("Exception while reading public key from file", e); + } + + // 2. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + var keyringInput = new CreateRawEcdhKeyringInput + { + // This example uses keys that lie on the ECC Curve P256. + // The keyring supports curves P256, P384, and P521. + // On creation, the keyring verifies that all configured keys lie on the provided curve spec. + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KeyAgreementScheme = new RawEcdhStaticConfigurations + { + // This keyring is configured with the EphemeralPrivateKeyToStaticPublicKey configuration. + // This configuration will always create a new + // key pair as the sender key pair for the key agreement operation. + // The ephemeral configuration can only encrypt data and CANNOT decrypt messages. + EphemeralPrivateKeyToStaticPublicKey = new EphemeralPrivateKeyToStaticPublicKeyInput + { + RecipientPublicKey = publicKeyRecipientUtf8EncodedByteBuffer + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); + + // A raw ecdh keyring with Ephemeral configuration cannot decrypt data since the key pair + // used as the sender is ephemeral. This means that at decrypt time it does not have + // the private key that corresponds to the public key that is stored on the message. + await PutExampleWithKeyring(rawEcdhKeyring, ddbTableName); + } + + private static async Task DiscoveryRawEcdhKeyringGetItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + + // 1. Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + MemoryStream privateKeyRecipientUtf8EncodedByteBuffer; + try + { + privateKeyRecipientUtf8EncodedByteBuffer = new MemoryStream( + File.ReadAllBytes(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)); + } + catch (IOException e) + { + throw new IOException("Exception while reading private key from file", e); + } + + // 2. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + var keyringInput = new CreateRawEcdhKeyringInput + { + // This example uses keys that lie on the ECC Curve P256. + // The keyring supports curves P256, P384, and P521. + // On creation, the keyring verifies that all configured keys lie on the provided curve spec. + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KeyAgreementScheme = new RawEcdhStaticConfigurations + { + // This keyring uses a discovery configuration. This configuration will check on decrypt + // if it is meant to decrypt the message by checking if the configured public key is stored on the message. + // The discovery configuration can only decrypt messages and CANNOT encrypt messages. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + PublicKeyDiscovery = new PublicKeyDiscoveryInput + { + // Must be a UTF8 PEM-encoded private key + RecipientStaticPrivateKey = privateKeyRecipientUtf8EncodedByteBuffer + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); + + await GetExampleWithKeyring(rawEcdhKeyring, ddbTableName); + } + + private static async Task PutGetExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) + { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = rawEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 6. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 7. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + var item = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" }, + ["sensitive_data"] = new AttributeValue("encrypt and sign me!") + }; + + var putRequest = new PutItemRequest + { + TableName = ddbTableName, + Item = item + }; + + var putResponse = await ddb.PutItemAsync(putRequest); + + // Demonstrate that PutItem succeeded + Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); + + // 8. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + var getResponse = await ddb.GetItemAsync(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK); + var returnedItem = getResponse.Item; + Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); + } + private static async Task PutExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) + { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = rawEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 6. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 7. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + var item = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" }, + ["sensitive_data"] = new AttributeValue("encrypt and sign me!") + }; + + var putRequest = new PutItemRequest + { + TableName = ddbTableName, + Item = item + }; + + var putResponse = await ddb.PutItemAsync(putRequest); + + // Demonstrate that PutItem succeeded + Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); + + // 8. Try to get the item and assert that the ephemeral keyring configuration + // cannot decrypt data. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + try + { + await ddb.GetItemAsync(getRequest); + } + catch (AwsCryptographicMaterialProvidersException e) + { + Debug.Assert(e.Message.Contains("EphemeralPrivateKeyToStaticPublicKey Key Agreement Scheme is forbidden on decrypt.")); + } + + } + + private static async Task GetExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) + { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = rawEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 6. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 7. Attempt to put an item into our table using the above client. + // Assert that Discovery configuration cannot encrypt data + var item = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" }, + ["sensitive_data"] = new AttributeValue("encrypt and sign me!") + }; + + var putRequest = new PutItemRequest + { + TableName = ddbTableName, + Item = item + }; + + try + { + await ddb.PutItemAsync(putRequest); + } + catch (AwsCryptographicMaterialProvidersException e) + { + Debug.Assert(e.Message.Contains("PublicKeyDiscovery Key Agreement Scheme is forbidden on encrypt.")); + } + + // 8. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + var getResponse = await ddb.GetItemAsync(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK); + var returnedItem = getResponse.Item; + Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); + } + + public static async Task RawEcdhKeyringExamples() + { + // You may provide your own ECC Keys in the files located at + // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + // - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // If these files are not present, this will generate keys for you. + if (ShouldGenerateNewEccKeys()) + { + GenerateEccKeyPairs(); + } + + await RawEcdhKeyringExampleGetItemPutItem(); + await EphemeralRawEcdhKeyringPutItem(); + await DiscoveryRawEcdhKeyringGetItem(); + } + + + private static bool ShouldGenerateNewEccKeys() + { + // If keys already exists; do not overwrite existing keys. + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + return false; + } + + // If only two keys are present; throw exception + if (!File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + throw new ApplicationException("Missing private key file at: " + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER); + } + + // If only two keys are present; throw exception + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + && !File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + throw new ApplicationException("Missing private key file at: " + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT); + } + + // If only two keys are present; throw exception + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + && !File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); + } + + return true; + } + + private static void GenerateEccKeyPairs() + { + // Safety check; Validate neither file is present + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + || File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + || File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + throw new ApplicationException("generateEccKeyPairs will not overwrite existing PEM files."); + } + // This code will generate new ECC Keys for example use. + // The keys will be written to the files: + // - private sender: EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + // - private recipient: EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + // - public recipient: EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // This example uses BouncyCastle's KeyPairGenerator to generate the key pair. + // In practice, you should not generate this in your code, and should instead + // retrieve this key from a secure key management system (e.g. HSM) + // These examples only demonstrate using the P256 curve while the keyring accepts + // P256, P384, or P521. + // These keys are created here for example purposes only. + ECKeyPairGenerator generator; + try + { + generator = new ECKeyPairGenerator(); + SecureRandom rng = new SecureRandom(); + X9ECParameters p = ECNamedCurveTable.GetByName("secp256r1"); + + var domainParameters = new ECDomainParameters(p.Curve, p.G, p.N, p.H); + generator.Init(new ECKeyGenerationParameters(domainParameters, rng)); + + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + AsymmetricCipherKeyPair senderKeyPair = generator.GenerateKeyPair(); + AsymmetricCipherKeyPair recipientKeyPair = generator.GenerateKeyPair(); + + WritePrivateKey(senderKeyPair.Private, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER); + WritePrivateKey(recipientKeyPair.Private, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT); + WritePublicKey(recipientKeyPair, "secp256r1", EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); + } + + private static void WritePrivateKey(AsymmetricKeyParameter privateKey, string fileName) + { + var privateKeyStringWriter = new StringWriter(); + var pemWriter = new PemWriter(privateKeyStringWriter); + pemWriter.WriteObject(privateKey); + + var privateKeyUtf8EncodedBytes = Encoding.UTF8.GetBytes(privateKeyStringWriter.ToString()); + var fc = new FileStream(fileName, FileMode.Create, FileAccess.Write); + fc.Write(privateKeyUtf8EncodedBytes); + fc.Close(); + } + + private static void WritePublicKey(AsymmetricCipherKeyPair publicKey, string curveName, string fileName) + { + var ecdhCurveSpecFromCurveName = ToEcdhCurveSpec(curveName); + var spki = KeyGeneration.SerializePublicKey(publicKey, ecdhCurveSpecFromCurveName).CloneAsArray(); + + var publicKeyStringWriter = new StringWriter(); + var publicKeyPemWriter = new PemWriter(publicKeyStringWriter); + publicKeyPemWriter.WriteObject(new PemObject("PUBLIC KEY", spki)); + var publicKeyUtf8EncodedBytes = Encoding.UTF8.GetBytes(publicKeyStringWriter.ToString()); + var fc = new FileStream(fileName, FileMode.Create, FileAccess.Write); + fc.Write(publicKeyUtf8EncodedBytes); + fc.Close(); + } + + private static _IECDHCurveSpec ToEcdhCurveSpec(string curveName) + { + switch (curveName) + { + case "secp256r1": return new ECDHCurveSpec_ECC__NIST__P256(); + case "secp384r1": return new ECDHCurveSpec_ECC__NIST__P384(); + case "secp521r1": return new ECDHCurveSpec_ECC__NIST__P521(); + } + throw new ApplicationException("Unknown curve: " + curveName); + } +} From fc6c9a9d1d7d0c5874def0767eeff7bd2d4cb7fa Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Wed, 6 Nov 2024 12:17:17 -0800 Subject: [PATCH 09/12] format --- Examples/runtimes/net/src/Examples.cs | 4 +- Examples/runtimes/net/src/TestUtils.cs | 8 +-- .../net/src/keyring/KmsEcdhKeyringExample.cs | 28 ++++---- .../net/src/keyring/RawEcdhKeyringExample.cs | 64 +++++++++---------- 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/Examples/runtimes/net/src/Examples.cs b/Examples/runtimes/net/src/Examples.cs index 993b63bc3..e06deb95d 100644 --- a/Examples/runtimes/net/src/Examples.cs +++ b/Examples/runtimes/net/src/Examples.cs @@ -11,7 +11,7 @@ class Program static async Task Main() { ItemEncryptDecryptExample.PutItemGetItem(); - + await BasicPutGetExample.PutItemGetItem(); await ScanErrorExample.ScanError(); await GetEncryptedDataKeyDescriptionExample.GetEncryptedDataKeyDescription(); @@ -33,7 +33,7 @@ static async Task Main() Thread.Sleep(5000); await HierarchicalKeyringExample.HierarchicalKeyringGetItemPutItem(keyId, keyId2); - + await BasicSearchableEncryptionExample.PutItemQueryItemWithBeacon(keyId); await CompoundBeaconSearchableEncryptionExample.PutItemQueryItemWithCompoundBeacon(keyId); await VirtualBeaconSearchableEncryptionExample.PutItemQueryItemWithVirtualBeacon(keyId); diff --git a/Examples/runtimes/net/src/TestUtils.cs b/Examples/runtimes/net/src/TestUtils.cs index 406790e86..8c1ea95d4 100644 --- a/Examples/runtimes/net/src/TestUtils.cs +++ b/Examples/runtimes/net/src/TestUtils.cs @@ -23,11 +23,11 @@ public class TestUtils public static readonly string TEST_KMS_RSA_KEY_ID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6"; - - public static readonly string TEST_KMS_ECDH_KEY_ID_P256_SENDER = + + public static readonly string TEST_KMS_ECDH_KEY_ID_P256_SENDER = "arn:aws:kms:us-west-2:370957321024:key/eabdf483-6be2-4d2d-8ee4-8c2583d416e9"; - - public static readonly string TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT = + + public static readonly string TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT = "arn:aws:kms:us-west-2:370957321024:key/0265c8e9-5b6a-4055-8f70-63719e09fda5"; public static readonly string TEST_MRK_REPLICA_KEY_ID_US_EAST_1 = diff --git a/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs b/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs index c5754e92e..42ccf9060 100644 --- a/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs +++ b/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs @@ -28,8 +28,8 @@ public static async Task KmsEcdhKeyringGetItemPutItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; var ecdhKeyArnSender = TestUtils.TEST_KMS_ECDH_KEY_ID_P256_SENDER; - - + + // 1. Load public keys from UTF-8 encoded PEM files. // You may provide your own PEM files to use here. // If you do not, the main method in this class will generate PEM @@ -45,7 +45,7 @@ public static async Task KmsEcdhKeyringGetItemPutItem() { throw new IOException("Exception while reading private key from file", e); } - + MemoryStream publicKeyRecipientUtf8EncodedByteBuffer; try { @@ -57,7 +57,7 @@ public static async Task KmsEcdhKeyringGetItemPutItem() { throw new IOException("Exception while reading public key from file", e); } - + // 2. Create the keyring. // The DynamoDb encryption client uses this to encrypt and decrypt items. var keyringInput = new CreateAwsKmsEcdhKeyringInput @@ -88,12 +88,12 @@ public static async Task KmsEcdhKeyringGetItemPutItem() await PutItemGetItemWithKeyring(kmsEcdhKeyring, ddbTableName); } - + private static async Task KmsEcdhKeyringDiscoveryGetItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; var ecdhKeyArnRecipient = TestUtils.TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT; - + // 1. Create a KMS ECDH keyring. // This keyring takes in: // - kmsClient @@ -202,7 +202,7 @@ private static async Task GetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddb var returnedItem = getResponse.Item; Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); } - + private static async Task PutItemGetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddbTableName) { // 3. Configure which attributes are encrypted and/or signed when writing new items. @@ -309,7 +309,7 @@ private static async Task PutItemGetItemWithKeyring(IKeyring kmsEcdhKeyring, str var returnedItem = getResponse.Item; Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); } - + public static async Task KmsEcdhKeyringExamples() { // You may provide your own ECC Keys in the files located at @@ -325,7 +325,7 @@ public static async Task KmsEcdhKeyringExamples() await KmsEcdhKeyringGetItemPutItem(); await KmsEcdhKeyringDiscoveryGetItem(); } - + private static async Task WritePublicKeyPemForEccKey(string kmsEcdhKeyArn, string exampleEccPublicKeySenderFilename) { // Safety check: Validate file is not present @@ -341,11 +341,11 @@ private static async Task WritePublicKeyPemForEccKey(string kmsEcdhKeyArn, strin new GetPublicKeyRequest { KeyId = kmsEcdhKeyArn } ); var publicKeyByteArray = response.PublicKey.ToArray(); - + StringWriter publicKeyStringWriter = new StringWriter(); PemWriter publicKeyPemWriter = new PemWriter(publicKeyStringWriter); publicKeyPemWriter.WriteObject(new PemObject("PUBLIC KEY", publicKeyByteArray)); - + var publicKeyUtf8EncodedByteBuffer = Encoding.UTF8.GetBytes(publicKeyStringWriter.ToString()); var fc = new FileStream(exampleEccPublicKeySenderFilename, FileMode.Create, FileAccess.Write); fc.Write(publicKeyUtf8EncodedByteBuffer); @@ -355,7 +355,7 @@ private static async Task WritePublicKeyPemForEccKey(string kmsEcdhKeyArn, strin private static bool ShouldGetNewEccKeys() { // If keys already exists; do not overwrite existing keys. - if (File.Exists(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME) + if (File.Exists(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME) || File.Exists(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME)) { return false; @@ -366,13 +366,13 @@ private static bool ShouldGetNewEccKeys() { throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME); } - + if (File.Exists(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME) && !File.Exists(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME)) { throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME); } - + return true; } } diff --git a/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs b/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs index 7aa06bebb..93691bfad 100644 --- a/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs +++ b/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs @@ -62,7 +62,7 @@ public class RawEcdhKeyringExample private static async Task RawEcdhKeyringExampleGetItemPutItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; - + // 1. Load key pair from UTF-8 encoded PEM files. // You may provide your own PEM files to use here. // If you do not, the main method in this class will generate PEM @@ -77,7 +77,7 @@ private static async Task RawEcdhKeyringExampleGetItemPutItem() { throw new IOException("Exception while reading private key from file", e); } - + MemoryStream publicKeyRecipientUtf8EncodedByteBuffer; try { @@ -89,7 +89,7 @@ private static async Task RawEcdhKeyringExampleGetItemPutItem() { throw new IOException("Exception while reading public key from file", e); } - + // 2. Create the keyring. // The DynamoDb encryption client uses this to encrypt and decrypt items. var keyringInput = new CreateRawEcdhKeyringInput @@ -108,21 +108,21 @@ private static async Task RawEcdhKeyringExampleGetItemPutItem() { // Must be a UTF8 PEM-encoded private key SenderStaticPrivateKey = privateKeySenderUtf8EncodedByteBuffer, - // Must be a UTF8 DER-encoded X.509 public key also known as SubjectPublicKeyInfo. + // Must be a UTF8 DER-encoded X.509 public key also known as SubjectPublicKeyInfo. RecipientPublicKey = publicKeyRecipientUtf8EncodedByteBuffer } } }; var matProv = new MaterialProviders(new MaterialProvidersConfig()); var rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); - + await PutGetExampleWithKeyring(rawEcdhKeyring, ddbTableName); } private static async Task EphemeralRawEcdhKeyringPutItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; - + // 1. Load key pair from UTF-8 encoded PEM files. // You may provide your own PEM files to use here. // If you do not, the RawEcdhKeyringExamples method in this class will generate PEM @@ -138,7 +138,7 @@ private static async Task EphemeralRawEcdhKeyringPutItem() { throw new IOException("Exception while reading public key from file", e); } - + // 2. Create the keyring. // The DynamoDb encryption client uses this to encrypt and decrypt items. var keyringInput = new CreateRawEcdhKeyringInput @@ -161,17 +161,17 @@ private static async Task EphemeralRawEcdhKeyringPutItem() }; var matProv = new MaterialProviders(new MaterialProvidersConfig()); var rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); - + // A raw ecdh keyring with Ephemeral configuration cannot decrypt data since the key pair // used as the sender is ephemeral. This means that at decrypt time it does not have // the private key that corresponds to the public key that is stored on the message. await PutExampleWithKeyring(rawEcdhKeyring, ddbTableName); } - + private static async Task DiscoveryRawEcdhKeyringGetItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; - + // 1. Load key pair from UTF-8 encoded PEM files. // You may provide your own PEM files to use here. // If you do not, the main method in this class will generate PEM @@ -186,7 +186,7 @@ private static async Task DiscoveryRawEcdhKeyringGetItem() { throw new IOException("Exception while reading private key from file", e); } - + // 2. Create the keyring. // The DynamoDb encryption client uses this to encrypt and decrypt items. var keyringInput = new CreateRawEcdhKeyringInput @@ -210,7 +210,7 @@ private static async Task DiscoveryRawEcdhKeyringGetItem() }; var matProv = new MaterialProviders(new MaterialProvidersConfig()); var rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); - + await GetExampleWithKeyring(rawEcdhKeyring, ddbTableName); } @@ -403,7 +403,7 @@ private static async Task PutExampleWithKeyring(IKeyring rawEcdhKeyring, string // Demonstrate that PutItem succeeded Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); - + // 8. Try to get the item and assert that the ephemeral keyring configuration // cannot decrypt data. var keyToGet = new Dictionary @@ -426,9 +426,9 @@ private static async Task PutExampleWithKeyring(IKeyring rawEcdhKeyring, string { Debug.Assert(e.Message.Contains("EphemeralPrivateKeyToStaticPublicKey Key Agreement Scheme is forbidden on decrypt.")); } - + } - + private static async Task GetExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) { // 3. Configure which attributes are encrypted and/or signed when writing new items. @@ -515,7 +515,7 @@ private static async Task GetExampleWithKeyring(IKeyring rawEcdhKeyring, string { Debug.Assert(e.Message.Contains("PublicKeyDiscovery Key Agreement Scheme is forbidden on encrypt.")); } - + // 8. Get the item back from our table using the same client. // The client will decrypt the item client-side, and return // back the original item. @@ -550,7 +550,7 @@ public static async Task RawEcdhKeyringExamples() { GenerateEccKeyPairs(); } - + await RawEcdhKeyringExampleGetItemPutItem(); await EphemeralRawEcdhKeyringPutItem(); await DiscoveryRawEcdhKeyringGetItem(); @@ -560,7 +560,7 @@ public static async Task RawEcdhKeyringExamples() private static bool ShouldGenerateNewEccKeys() { // If keys already exists; do not overwrite existing keys. - if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) { @@ -568,36 +568,36 @@ private static bool ShouldGenerateNewEccKeys() } // If only two keys are present; throw exception - if (!File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + if (!File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) { throw new ApplicationException("Missing private key file at: " + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER); } - + // If only two keys are present; throw exception - if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) && !File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) { throw new ApplicationException("Missing private key file at: " + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT); } - + // If only two keys are present; throw exception - if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) && !File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) { throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); } - + return true; } - + private static void GenerateEccKeyPairs() { // Safety check; Validate neither file is present - if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) || File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) || File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) { @@ -620,7 +620,7 @@ private static void GenerateEccKeyPairs() generator = new ECKeyPairGenerator(); SecureRandom rng = new SecureRandom(); X9ECParameters p = ECNamedCurveTable.GetByName("secp256r1"); - + var domainParameters = new ECDomainParameters(p.Curve, p.G, p.N, p.H); generator.Init(new ECKeyGenerationParameters(domainParameters, rng)); @@ -630,10 +630,10 @@ private static void GenerateEccKeyPairs() Console.WriteLine(e); throw; } - + AsymmetricCipherKeyPair senderKeyPair = generator.GenerateKeyPair(); AsymmetricCipherKeyPair recipientKeyPair = generator.GenerateKeyPair(); - + WritePrivateKey(senderKeyPair.Private, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER); WritePrivateKey(recipientKeyPair.Private, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT); WritePublicKey(recipientKeyPair, "secp256r1", EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); @@ -650,19 +650,19 @@ private static void WritePrivateKey(AsymmetricKeyParameter privateKey, string fi fc.Write(privateKeyUtf8EncodedBytes); fc.Close(); } - + private static void WritePublicKey(AsymmetricCipherKeyPair publicKey, string curveName, string fileName) { var ecdhCurveSpecFromCurveName = ToEcdhCurveSpec(curveName); var spki = KeyGeneration.SerializePublicKey(publicKey, ecdhCurveSpecFromCurveName).CloneAsArray(); - + var publicKeyStringWriter = new StringWriter(); var publicKeyPemWriter = new PemWriter(publicKeyStringWriter); publicKeyPemWriter.WriteObject(new PemObject("PUBLIC KEY", spki)); var publicKeyUtf8EncodedBytes = Encoding.UTF8.GetBytes(publicKeyStringWriter.ToString()); var fc = new FileStream(fileName, FileMode.Create, FileAccess.Write); fc.Write(publicKeyUtf8EncodedBytes); - fc.Close(); + fc.Close(); } private static _IECDHCurveSpec ToEcdhCurveSpec(string curveName) From c320fe5f245aef56c1bcf1b44bc64ae79080203c Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Fri, 8 Nov 2024 13:06:45 -0800 Subject: [PATCH 10/12] address feedback --- .../keyring/KmsEcdhKeyringExample.java | 318 +++++++++----- .../keyring/RawEcdhKeyringExample.java | 396 +++++++++--------- .../keyring/TestKmsEcdhKeyringExample.java | 6 +- .../keyring/TestRawEcdhKeyringExample.java | 3 + .../net/src/keyring/KmsEcdhKeyringExample.cs | 268 ++++++++---- .../net/src/keyring/RawEcdhKeyringExample.cs | 381 +++++++++-------- 6 files changed, 783 insertions(+), 589 deletions(-) diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java index 4bc016197..325b0aa19 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java @@ -50,6 +50,21 @@ import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; import software.amazon.cryptography.primitives.model.ECDHCurveSpec; +/* + These examples set up DynamoDb Encryption for the AWS SDK client + using the AWS KMS ECDH Keyring. This keyring, depending on its KeyAgreement scheme, + takes in the sender's KMS ECC Key ARN, and the recipient's ECC Public Key to derive a shared secret. + The keyring uses the shared secret to derive a data key to protect the + data keys that encrypt and decrypt DynamoDb table items. + + + Running these examples require access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + */ public class KmsEcdhKeyringExample { public static String EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME = @@ -57,17 +72,53 @@ public class KmsEcdhKeyringExample { public static String EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME = "KmsEccKeyringKeyringExamplePublicKeyRecipient.pem"; + /* + This example takes in the sender's KMS ECC key ARN, the sender's public key, + the recipient's public key, and the algorithm definition where the ECC keys lie. + The eccKeyArn parameter takes in the sender's KMS ECC key ARN, + the eccPublicKeySenderFileName parameter takes in the sender's public key that corresponds to the + eccKeyArn, the eccPublicKeyRecipientFileName parameter takes in the recipient's public key, + and the Curve Specification where the keys lie. + + Both public keys MUST be UTF8 PEM-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + + This example encrypts a test item using the provided ECC keys and puts the + encrypted item to the provided DynamoDb table. Then, it gets the + item from the table and decrypts it. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + This example also requires access to a KMS ECC key. + Our tests provide a KMS ECC Key ARN that anyone can use, but you + can also provide your own KMS ECC key. + To use your own KMS ECC key, you must have either: + - Its public key downloaded in a UTF-8 encoded PEM file + - kms:GetPublicKey permissions on that key. + If you do not have the public key downloaded, running this example + through its main method will download the public key for you + by calling kms:GetPublicKey. + You must also have kms:DeriveSharedSecret permissions on the KMS ECC key. + This example also requires a recipient ECC Public Key that lies on the same + curve as the sender public key. This examples uses another distinct + KMS ECC Public Key, it does not have to be a KMS key; it can be a + valid SubjectPublicKeyInfo (SPKI) Public Key. + */ public static void KmsEcdhKeyringGetItemPutItem( String ddbTableName, String eccKeyArn, String eccPublicKeySenderFileName, String eccPublicKeyRecipientFileName ) { - // 1. Load UTF-8 encoded public key PEM files as DER encoded bytes. - // You may have an ECC public key file already defined. - // If not, the main method in this class will call - // the KMS ECC key, retrieve its public key, and store it - // in a PEM file for example use. + // Load UTF-8 encoded public key PEM files as DER encoded bytes. + // You may provide your own PEM files to use here. If you provide this, it MUST + // be a key on curve P256. + // If not, the main method in this class will call + // the KMS ECC key, retrieve its public key, and store it + // in a PEM file for example use. ByteBuffer publicKeyRecipientByteBuffer = loadPublicKeyBytes( eccPublicKeyRecipientFileName ); @@ -75,19 +126,24 @@ public static void KmsEcdhKeyringGetItemPutItem( eccPublicKeySenderFileName ); - // 2. Create a KMS ECDH keyring. - // This keyring takes in: - // - kmsClient - // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement - // - curveSpec: The curve name where the public keys lie - // - senderPublicKey: A ByteBuffer of a UTF-8 encoded public - // key for the key passed into kmsKeyId in DER format - // - recipientPublicKey: A ByteBuffer of a UTF-8 encoded public key - // for the recipient public key. - final MaterialProviders matProv = MaterialProviders - .builder() - .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) - .build(); + // Create a KMS ECDH keyring. + // This keyring uses the KmsPrivateKeyToStaticPublicKey configuration. This configuration calls for both of + // the keys to be on the same curve (P256, P384, P521). + // On encrypt, the keyring calls AWS KMS to derive the shared from the sender's KMS ECC Key ARN and the recipient's public key. + // For this example, on decrypt, the keyring calls AWS KMS to derive the shared from the sender's KMS ECC Key ARN and the recipient's public key; + // however, on decrypt the recipient can construct a keyring such that the shared secret is calculated with + // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. + // For more information on this configuration see: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-create + // The DynamoDb encryption client uses this to encrypt and decrypt items. + // This keyring takes in: + // - kmsClient + // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + // - senderPublicKey: A ByteBuffer of a UTF-8 encoded public + // key for the key passed into kmsKeyId in DER format + // - recipientPublicKey: A ByteBuffer of a UTF-8 encoded public + // key for the key passed into kmsKeyId in DER format final CreateAwsKmsEcdhKeyringInput createAwsKmsEcdhKeyringInput = CreateAwsKmsEcdhKeyringInput .builder() @@ -100,13 +156,20 @@ public static void KmsEcdhKeyringGetItemPutItem( KmsPrivateKeyToStaticPublicKeyInput .builder() .senderKmsIdentifier(eccKeyArn) + // Must be a DER-encoded X.509 public key .senderPublicKey(publicKeySenderByteBuffer) + // Must be a DER-encoded X.509 public key .recipientPublicKey(publicKeyRecipientByteBuffer) .build() ) .build() ) .build(); + + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); IKeyring kmsEcdhKeyring = matProv.CreateAwsKmsEcdhKeyring( createAwsKmsEcdhKeyringInput ); @@ -114,19 +177,44 @@ public static void KmsEcdhKeyringGetItemPutItem( PutGetItemWithKeyring(kmsEcdhKeyring, ddbTableName); } + /* + This example takes in the recipient's KMS ECC key ARN, + and the algorithm definition where the ECC keys lie. + The eccRecipientKeyArn parameter takes in the sender's KMS ECC key ARN + + This example attempts to decrypt a test item using the provided eccRecipientKeyArn, + it does so by checking if the message header contains the recipient's public key. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + This example also requires access to a KMS ECC key. + Our tests provide a KMS ECC Key ARN that anyone can use, but you + can also provide your own KMS ECC key. + To use your own KMS ECC key, you must have: + - kms:GetPublicKey permissions on that key. + This example will call kms:GetPublicKey on keyring creation. + You must also have kms:DeriveSharedSecret permissions on the KMS ECC key. + */ public static void KmsEcdhDiscoveryGetItem( String ddbTableName, String eccRecipientKeyArn ) { - // 1. Create a KMS ECDH keyring. - // This keyring takes in: - // - kmsClient - // - recipientKmsIdentifier: Must be an ARN representing a KMS ECC key meant for KeyAgreement - // - curveSpec: The curve name where the public keys lie - final MaterialProviders matProv = MaterialProviders - .builder() - .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) - .build(); + // Create a KMS ECDH keyring. + // This keyring uses the KmsPublicKeyDiscovery configuration. + // On encrypt, the keyring will fail as it is not allowed to encrypt data under this configuration. + // On decrypt, the keyring will check if its corresponding public key is stored in the message header. It + // will AWS KMS to derive the shared from the recipient's KMS ECC Key ARN and the sender's public key; + // For more information on this configuration see: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-discovery + // The DynamoDb encryption client uses this to encrypt and decrypt items. + // This keyring takes in: + // - kmsClient + // - recipientKmsIdentifier: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie final CreateAwsKmsEcdhKeyringInput createAwsKmsEcdhKeyringInput = CreateAwsKmsEcdhKeyringInput .builder() @@ -144,6 +232,11 @@ public static void KmsEcdhDiscoveryGetItem( .build() ) .build(); + + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); IKeyring kmsEcdhKeyring = matProv.CreateAwsKmsEcdhKeyring( createAwsKmsEcdhKeyringInput ); @@ -155,50 +248,50 @@ private static void GetItemWithKeyring( IKeyring kmsEcdhKeyring, String ddbTableName ) { - // 2. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature final Map attributeActions = new HashMap<>(); attributeActions.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY attributeActions.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY attributeActions.put("sensitive_data", CryptoAction.ENCRYPT_AND_SIGN); - // 3. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActions` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActions` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // - // For this example, we currently authenticate all attributes. To make it easier to - // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. final String unsignAttrPrefix = ":"; - // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. - // Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite - // that does not use asymmetric signing. + // Create the DynamoDb Encryption configuration for the table we will be writing to. + // Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite + // that does not use asymmetric signing. final Map tableConfigs = new HashMap<>(); final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig @@ -218,7 +311,7 @@ private static void GetItemWithKeyring( .build(); tableConfigs.put(ddbTableName, config); - // 5. Create the DynamoDb Encryption Interceptor + // Create the DynamoDb Encryption Interceptor DynamoDbEncryptionInterceptor encryptionInterceptor = DynamoDbEncryptionInterceptor .builder() @@ -230,7 +323,7 @@ private static void GetItemWithKeyring( ) .build(); - // 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + // Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above final DynamoDbClient ddbClient = DynamoDbClient .builder() .overrideConfiguration( @@ -241,9 +334,9 @@ private static void GetItemWithKeyring( ) .build(); - // 7. Get the item back from our table using the client. - // The client will decrypt the item client-side using the ECDH keyring - // and return the original item. + // Get the item back from our table using the client. + // The client will decrypt the item client-side using the ECDH keyring + // and return the original item. final HashMap keyToGet = new HashMap<>(); keyToGet.put( "partition_key", @@ -272,50 +365,50 @@ private static void PutGetItemWithKeyring( IKeyring awsKmsEcdhKeyring, String ddbTableName ) { - // 3. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature final Map attributeActions = new HashMap<>(); attributeActions.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY attributeActions.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY attributeActions.put("sensitive_data", CryptoAction.ENCRYPT_AND_SIGN); - // 4. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActions` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActions` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // - // For this example, we currently authenticate all attributes. To make it easier to - // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. final String unsignAttrPrefix = ":"; - // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. - // Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite - // that does not use asymmetric signing. + // Create the DynamoDb Encryption configuration for the table we will be writing to. + // Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite + // that does not use asymmetric signing. final Map tableConfigs = new HashMap<>(); final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig @@ -335,7 +428,7 @@ private static void PutGetItemWithKeyring( .build(); tableConfigs.put(ddbTableName, config); - // 6. Create the DynamoDb Encryption Interceptor + // Create the DynamoDb Encryption Interceptor DynamoDbEncryptionInterceptor encryptionInterceptor = DynamoDbEncryptionInterceptor .builder() @@ -347,7 +440,7 @@ private static void PutGetItemWithKeyring( ) .build(); - // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + // Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above final DynamoDbClient ddbClient = DynamoDbClient .builder() .overrideConfiguration( @@ -358,9 +451,9 @@ private static void PutGetItemWithKeyring( ) .build(); - // 8. Put an item into our table using the above client. - // Before the item gets sent to DynamoDb, it will be encrypted - // client-side, according to our configuration. + // Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. final HashMap item = new HashMap<>(); item.put( "partition_key", @@ -383,9 +476,9 @@ private static void PutGetItemWithKeyring( // Demonstrate that PutItem succeeded assert 200 == putResponse.sdkHttpResponse().statusCode(); - // 9. Get the item back from our table using the client. - // The client will decrypt the item client-side using the RSA keyring - // and return the original item. + // Get the item back from our table using the client. + // The client will decrypt the item client-side using the RSA keyring + // and return the original item. final HashMap keyToGet = new HashMap<>(); keyToGet.put( "partition_key", @@ -460,6 +553,15 @@ public static void KmsEcdhKeyringGetItemPutItem( ); } + /* + To run the KmsEcdhKeyringGetItemPutItem as a standalone example you may provide + the following arguments in the following order. + - The DynamoDB Table Name you wish to read and write items to + - The sender's KMS ECC Key Arn, you MUST have kms:DeriveSharedSecret permission on the key. + - The sender's public key file name + - The recipient's public key file name + - The recipient's KMS ECC Key Arn, you must have kms:GetPublicKey permission on the key. + */ public static void main(final String[] args) { if (args.length <= 1) { throw new IllegalArgumentException( diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java index 30032bbbe..41a2a8204 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java @@ -55,24 +55,14 @@ import software.amazon.cryptography.primitives.model.ECDHCurveSpec; /* - This example sets up DynamoDb Encryption for the AWS SDK client - using the raw ECDH Keyring. This keyring takes in the sender's ECC - private key and the recipient's ECC Public Key to derive a shared secret. + These examples set up DynamoDb Encryption for the AWS SDK client + using the raw ECDH Keyring. This keyring, depending on its KeyAgreement scheme, + takes in the sender's ECC private key, and the recipient's ECC Public Key to derive a shared secret. The keyring uses the shared secret to derive a data key to protect the data keys that encrypt and decrypt DynamoDb table items. - This example takes in the sender's private key, the recipient's - public key, and the algorithm definition where the ECC keys lie. - This parameter takes in the sender's private key as a - UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures), the recipient's - DER-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), - and the Curve Specification where the keys lie. - This example encrypts a test item using the provided ECC keys and puts the - encrypted item to the provided DynamoDb table. Then, it gets the - item from the table and decrypts it. - - Running this example requires access to the DDB Table whose name + Running these examples require access to the DDB Table whose name is provided in CLI arguments. This table must be configured with the following primary key configuration: @@ -88,14 +78,36 @@ public class RawEcdhKeyringExample { public static String EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT = "RawEcdhKeyringExamplePublicKeyRecipient.pem"; + /* + This example takes in the sender's private key as a + UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures) + located at the file location defined in EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER, + the recipient's public key as a UTF8 PEM-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + located at the file location defined in EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT, + and the Curve Specification where the keys lie. + + This example encrypts a test item using the provided ECC keys and puts the + encrypted item to the provided DynamoDb table. Then, it gets the + item from the table and decrypts it. + + This examples creates a RawECDH keyring with the RawPrivateKeyToStaticPublicKey key agreement scheme. + For more information on this configuration see: + https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-RawPrivateKeyToStaticPublicKey + + On encrypt, the shared secret is derived from the sender's private key and the recipient's public key. + On decrypt, the shared secret is derived from the sender's private key and the recipient's public key; + however, on decrypt the recipient can construct a keyring such that the shared secret is calculated with + the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. + */ public static void RawEcdhKeyringGetItemPutItem( String ddbTableName, ECDHCurveSpec curveSpec ) { - // 1. Load key pair from UTF-8 encoded PEM files. - // You may provide your own PEM files to use here. - // If you do not, the main method in this class will generate PEM - // files for example use. Do not use these files for any other purpose. + // Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. If you provide this, it MUST + // be a key on curve P256. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. ByteBuffer privateKeyUtf8EncodedByteBuffer; try { privateKeyUtf8EncodedByteBuffer = @@ -145,10 +157,14 @@ public static void RawEcdhKeyringGetItemPutItem( throw new RuntimeException(e); } - // 2. Create the keyring. - // This keyring uses static sender and recipient keys. This configuration calls for the keys - // to be - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // Create the keyring. + // This keyring uses static sender and recipient keys. This configuration calls for both of + // the keys to be on the same curve (P256, P384, P521). + // On encrypt, the shared secret is derived from the sender's private key and the recipient's public key. + // For this example, on decrypt, the shared secret is derived from the sender's private key and the recipient's public key; + // however, on decrypt the recipient can construct a keyring such that the shared secret is calculated with + // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. + // The DynamoDb encryption client uses this to encrypt and decrypt items. final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput .builder() .curveSpec(curveSpec) @@ -177,14 +193,25 @@ public static void RawEcdhKeyringGetItemPutItem( PutGetExampleWithKeyring(rawEcdhKeyring, ddbTableName); } + /* + This example takes in the recipient's public key located at EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + as a UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures), and the Curve Specification where the key lies. + + This examples creates a RawECDH keyring with the EphemeralPrivateKeyToStaticPublicKey key agreement scheme. + This configuration will always create a new key pair as the sender key pair for the key agreement operation. + The ephemeral configuration can only encrypt data and CANNOT decrypt messages. + For more information on this configuration see: + https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-EphemeralPrivateKeyToStaticPublicKey + */ public static void EphemeralRawEcdhKeyringPutItem( String ddbTableName, ECDHCurveSpec ecdhCurveSpec ) { - // 1. Load public key from UTF-8 encoded PEM files into a DER encoded public key. - // You may provide your own PEM files to use here. - // If you do not, the main method in this class will generate PEM - // files for example use. Do not use these files for any other purpose. + // Load public key from UTF-8 encoded PEM files into a DER encoded public key. + // You may provide your own PEM files to use here. If you provide this, it MUST + // be a key on curve P256. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. ByteBuffer publicKeyByteBuffer; try { ByteBuffer publicKeyUtf8EncodedByteBuffer = ByteBuffer.wrap( @@ -221,11 +248,11 @@ public static void EphemeralRawEcdhKeyringPutItem( throw new RuntimeException(e); } - // 2. Create the keyring. - // This keyring uses an ephemeral configuration. This configuration will always create a new - // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only - // encrypt data and CANNOT decrypt messages. - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // Create the keyring. + // This keyring uses an ephemeral configuration. This configuration will always create a new + // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only + // encrypt data and CANNOT decrypt messages. + // The DynamoDb encryption client uses this to encrypt and decrypt items. final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput .builder() .curveSpec(ecdhCurveSpec) @@ -254,14 +281,25 @@ public static void EphemeralRawEcdhKeyringPutItem( PutExampleWithKeyring(rawEcdhKeyring, ddbTableName); } + /* + This example takes in the recipient's private key located at EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + as a UTF8 PEM-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + and the Curve Specification where the key lies. + + This examples creates a RawECDH keyring with the PublicKeyDiscovery key agreement scheme. + This scheme is only available on decrypt. + For more information on this configuration see: + https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-PublicKeyDiscovery + */ public static void DiscoveryRawEcdhKeyringGetItem( String ddbTableName, ECDHCurveSpec ecdhCurveSpec ) { - // 1. Load key pair from UTF-8 encoded PEM files. - // You may provide your own PEM files to use here. - // If you do not, the main method in this class will generate PEM - // files for example use. Do not use these files for any other purpose. + // Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. If you provide this, it MUST + // be a key on curve P256. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. ByteBuffer privateKeyUtf8EncodedByteBuffer; try { privateKeyUtf8EncodedByteBuffer = @@ -277,11 +315,11 @@ public static void DiscoveryRawEcdhKeyringGetItem( ); } - // 2. Create the keyring. - // This keyring uses a discovery configuration. This configuration will check on decrypt - // if it is meant to decrypt the message by checking if the configured public key is stored on the message. - // The discovery configuration can only decrypt messages and CANNOT encrypt messages. - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // Create the keyring. + // This keyring uses a discovery configuration. This configuration will check on decrypt + // if it is meant to decrypt the message by checking if the configured public key is stored on the message. + // The discovery configuration can only decrypt messages and CANNOT encrypt messages. + // The DynamoDb encryption client uses this to encrypt and decrypt items. final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput .builder() .curveSpec(ecdhCurveSpec) @@ -313,12 +351,12 @@ public static void PutGetExampleWithKeyring( IKeyring rawEcdhKeyring, String ddbTableName ) { - // 3. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature final Map attributeActionsOnEncrypt = new HashMap<>(); attributeActionsOnEncrypt.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY attributeActionsOnEncrypt.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY @@ -327,37 +365,37 @@ public static void PutGetExampleWithKeyring( CryptoAction.ENCRYPT_AND_SIGN ); - // 4. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActionsOnEncrypt` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // - // For this example, we currently authenticate all attributes. To make it easier to - // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. final String unsignAttrPrefix = ":"; - // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Create the DynamoDb Encryption configuration for the table we will be writing to. final Map tableConfigs = new HashMap<>(); final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig @@ -371,7 +409,7 @@ public static void PutGetExampleWithKeyring( .build(); tableConfigs.put(ddbTableName, config); - // 6. Create the DynamoDb Encryption Interceptor + // Create the DynamoDb Encryption Interceptor DynamoDbEncryptionInterceptor encryptionInterceptor = DynamoDbEncryptionInterceptor .builder() @@ -383,7 +421,7 @@ public static void PutGetExampleWithKeyring( ) .build(); - // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + // Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above final DynamoDbClient ddb = DynamoDbClient .builder() .overrideConfiguration( @@ -394,9 +432,9 @@ public static void PutGetExampleWithKeyring( ) .build(); - // 8. Put an item into our table using the above client. - // Before the item gets sent to DynamoDb, it will be encrypted - // client-side, according to our configuration. + // Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. final HashMap item = new HashMap<>(); item.put( "partition_key", @@ -419,9 +457,9 @@ public static void PutGetExampleWithKeyring( // Demonstrate that PutItem succeeded assert 200 == putResponse.sdkHttpResponse().statusCode(); - // 9. Get the item back from our table using the same client. - // The client will decrypt the item client-side, and return - // back the original item. + // Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. final HashMap keyToGet = new HashMap<>(); keyToGet.put( "partition_key", @@ -450,12 +488,12 @@ public static void PutExampleWithKeyring( IKeyring rawEcdhKeyring, String ddbTableName ) { - // 3. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature final Map attributeActionsOnEncrypt = new HashMap<>(); attributeActionsOnEncrypt.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY attributeActionsOnEncrypt.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY @@ -464,37 +502,37 @@ public static void PutExampleWithKeyring( CryptoAction.ENCRYPT_AND_SIGN ); - // 4. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActionsOnEncrypt` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // - // For this example, we currently authenticate all attributes. To make it easier to - // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. final String unsignAttrPrefix = ":"; - // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Create the DynamoDb Encryption configuration for the table we will be writing to. final Map tableConfigs = new HashMap<>(); final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig @@ -508,7 +546,7 @@ public static void PutExampleWithKeyring( .build(); tableConfigs.put(ddbTableName, config); - // 6. Create the DynamoDb Encryption Interceptor + // Create the DynamoDb Encryption Interceptor DynamoDbEncryptionInterceptor encryptionInterceptor = DynamoDbEncryptionInterceptor .builder() @@ -520,7 +558,7 @@ public static void PutExampleWithKeyring( ) .build(); - // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + // Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above final DynamoDbClient ddb = DynamoDbClient .builder() .overrideConfiguration( @@ -531,9 +569,9 @@ public static void PutExampleWithKeyring( ) .build(); - // 8. Put an item into our table using the above client. - // Before the item gets sent to DynamoDb, it will be encrypted - // client-side, according to our configuration. + // Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. final HashMap item = new HashMap<>(); item.put( "partition_key", @@ -561,12 +599,12 @@ public static void GetExampleWithKeyring( IKeyring rawEcdhKeyring, String ddbTableName ) { - // 3. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature final Map attributeActionsOnEncrypt = new HashMap<>(); attributeActionsOnEncrypt.put("partition_key", CryptoAction.SIGN_ONLY); // Our partition attribute must be SIGN_ONLY attributeActionsOnEncrypt.put("sort_key", CryptoAction.SIGN_ONLY); // Our sort attribute must be SIGN_ONLY @@ -574,37 +612,37 @@ public static void GetExampleWithKeyring( "sensitive_data", CryptoAction.ENCRYPT_AND_SIGN ); - // 4. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActionsOnEncrypt` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // - // For this example, we currently authenticate all attributes. To make it easier to - // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. final String unsignAttrPrefix = ":"; - // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Create the DynamoDb Encryption configuration for the table we will be writing to. final Map tableConfigs = new HashMap<>(); final DynamoDbTableEncryptionConfig config = DynamoDbTableEncryptionConfig @@ -618,7 +656,7 @@ public static void GetExampleWithKeyring( .build(); tableConfigs.put(ddbTableName, config); - // 6. Create the DynamoDb Encryption Interceptor + // Create the DynamoDb Encryption Interceptor DynamoDbEncryptionInterceptor encryptionInterceptor = DynamoDbEncryptionInterceptor .builder() @@ -630,7 +668,7 @@ public static void GetExampleWithKeyring( ) .build(); - // 7. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + // Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above final DynamoDbClient ddb = DynamoDbClient .builder() .overrideConfiguration( @@ -641,9 +679,9 @@ public static void GetExampleWithKeyring( ) .build(); - // 8. Get the item back from our table using the same client. - // The client will decrypt the item client-side, and return - // back the original item. + // Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. final HashMap keyToGet = new HashMap<>(); keyToGet.put( "partition_key", @@ -668,6 +706,12 @@ public static void GetExampleWithKeyring( .equals("encrypt and sign me!"); } + /* + To run this example standalone, you will need to supply your own ECC keys at: + - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + You will also need to provide the Dynamodb table name as part of the args. + */ public static void main(final String[] args) { if (args.length <= 0) { throw new IllegalArgumentException( @@ -691,48 +735,12 @@ public static boolean shouldGenerateNewEccKeyPairs() { ); // If keys already exist: do not overwrite existing keys - if ( - privateKeyFileSender.exists() && - publicKeyFileRecipient.exists() && - privateKeyFileRecipient.exists() - ) { - return false; - } - - // If not all three keys are present; throw an exception - if ( - !privateKeyFileSender.exists() && - publicKeyFileRecipient.exists() && - privateKeyFileRecipient.exists() - ) { - throw new IllegalStateException( - "Missing private key sender file at " + - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER - ); - } - if ( - privateKeyFileSender.exists() && - !publicKeyFileRecipient.exists() && - privateKeyFileRecipient.exists() - ) { - throw new IllegalStateException( - "Missing public key recipient file at " + - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT - ); - } - if ( - privateKeyFileSender.exists() && - publicKeyFileRecipient.exists() && + return ( + !privateKeyFileSender.exists() || + !publicKeyFileRecipient.exists() || !privateKeyFileRecipient.exists() - ) { - throw new IllegalStateException( - "Missing private key recipient file at " + - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT - ); - } - + ); // If no keys are present, generate new keys - return true; } public static void generateEccKeyPairs() { diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestKmsEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestKmsEcdhKeyringExample.java index 42f9f51a8..7495261af 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestKmsEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestKmsEcdhKeyringExample.java @@ -12,8 +12,10 @@ public class TestKmsEcdhKeyringExample { @Test public void TestKmsEcdhKeyringExampleStatic() { - // You may provide your own ECC public keys at EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME - // and EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME. + // You may provide your own ECC public keys at + // - EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME + // - EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME. + // If you provide these, the keys MUST be on curve P256 // This must be the public key for the ECC key represented at eccKeyArn // If this file is not present, this will write a UTF-8 encoded PEM file for you. if (shouldGetNewPublicKeys()) { diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestRawEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestRawEcdhKeyringExample.java index a7f9cb252..a6ee1aff6 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestRawEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestRawEcdhKeyringExample.java @@ -13,6 +13,7 @@ public void TestStaticRawEcdhKeyringExample() { // You may provide your own ECC Key pairs in the files located at // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER // - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // If you provide this, the keys MUST be on curve P256 // If these files are not present, this will generate a pair for you. // For this example we will use the curve P256. if (RawEcdhKeyringExample.shouldGenerateNewEccKeyPairs()) { @@ -31,6 +32,7 @@ public void TestStaticRawEcdhKeyringExample() { public void TestEphemeralRawEcdhKeyringExample() { // You may provide your own ECC Public Key in the files located at // - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // If you provide this, the keys MUST be on curve P256 // If these files are not present, this will generate a pair for you. // For this example we will use the curve P256. if (RawEcdhKeyringExample.shouldGenerateNewEccKeyPairs()) { @@ -50,6 +52,7 @@ public void TestDiscoveryRawEcdhKeyringExample() { // You may provide your own ECC Public Key in the files located at // - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + // If you provide this, the keys MUST be on curve P256 // If these files are not present, this will generate a pair for you. // For this example we will use the curve P256. if (RawEcdhKeyringExample.shouldGenerateNewEccKeyPairs()) { diff --git a/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs b/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs index 42ccf9060..ae3fc8bbe 100644 --- a/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs +++ b/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs @@ -17,6 +17,21 @@ namespace Examples.keyring; +/* + These examples set up DynamoDb Encryption for the AWS SDK client + using the AWS KMS ECDH Keyring. This keyring, depending on its KeyAgreement scheme, + takes in the sender's KMS ECC Key ARN, and the recipient's ECC Public Key to derive a shared secret. + The keyring uses the shared secret to derive a data key to protect the + data keys that encrypt and decrypt DynamoDb table items. + + + Running these examples require access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + */ public class KmsEcdhKeyringExample { private static String EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME = @@ -24,16 +39,46 @@ public class KmsEcdhKeyringExample private static String EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME = "KmsEccKeyringKeyringExamplePublicKeyRecipient.pem"; + // This example takes in the sender's KMS ECC key ARN, the sender's public key, + // the recipient's public key, and the algorithm definition where the ECC keys lie. + // Both public keys MUST be UTF8 PEM-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + // + // This example encrypts a test item using the provided ECC keys and puts the + // encrypted item to the provided DynamoDb table. Then, it gets the + // item from the table and decrypts it. + // + // Running this example requires access to the DDB Table whose name + // is provided through the TestUtils.TEST_DDB_TABLE_NAME variable. + // This table must be configured with the following + // primary key configuration: + // - Partition key is named "partition_key" with type (S) + // - Sort key is named "sort_key" with type (S) + // This example also requires access to a KMS ECC key. + // Our tests provide a KMS ECC Key ARN that anyone can use, but you + // can also provide your own KMS ECC key. + // To use your own KMS ECC key, you must have either: + // - Its public key downloaded in a UTF-8 encoded PEM file + // - kms:GetPublicKey permissions on that key. + // If you do not have the public key downloaded, running this example + // through its main method will download the public key for you + // by calling kms:GetPublicKey. + // You must also have kms:DeriveSharedSecret permissions on the KMS ECC key. + // This example also requires a recipient ECC Public Key that lies on the same + // curve as the sender public key. This examples uses another distinct + // KMS ECC Public Key, it does not have to be a KMS key; it can be a + // valid SubjectPublicKeyInfo (SPKI) Public Key. public static async Task KmsEcdhKeyringGetItemPutItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; var ecdhKeyArnSender = TestUtils.TEST_KMS_ECDH_KEY_ID_P256_SENDER; - // 1. Load public keys from UTF-8 encoded PEM files. - // You may provide your own PEM files to use here. - // If you do not, the main method in this class will generate PEM - // files for example use. Do not use these files for any other purpose. + // Load UTF-8 encoded public key PEM files as DER encoded bytes. + // You may provide your own PEM files to use here. If you provide this, it MUST + // be a key on curve P256. + // If not, the main method in this class will call + // the KMS ECC key, retrieve its public key, and store it + // in a PEM file for example use. MemoryStream publicKeySenderUtf8EncodedByteBuffer; try { @@ -58,19 +103,26 @@ public static async Task KmsEcdhKeyringGetItemPutItem() throw new IOException("Exception while reading public key from file", e); } - // 2. Create the keyring. - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // Create a KMS ECDH keyring. + // This keyring uses the KmsPrivateKeyToStaticPublicKey configuration. This configuration calls for both of + // the keys to be on the same curve (P256, P384, P521). + // On encrypt, the keyring calls AWS KMS to derive the shared from the sender's KMS ECC Key ARN and the recipient's public key. + // For this example, on decrypt, the keyring calls AWS KMS to derive the shared from the sender's KMS ECC Key ARN and the recipient's public key; + // however, on decrypt the recipient can construct a keyring such that the shared secret is calculated with + // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. + // For more information on this configuration see: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-create + // The DynamoDb encryption client uses this to encrypt and decrypt items. + // This keyring takes in: + // - kmsClient + // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + // - senderPublicKey: A ByteBuffer of a UTF-8 encoded public + // key for the key passed into kmsKeyId in DER format + // - recipientPublicKey: A ByteBuffer of a UTF-8 encoded public + // key for the key passed into kmsKeyId in DER format var keyringInput = new CreateAwsKmsEcdhKeyringInput { - // 2. Create a KMS ECDH keyring. - // This keyring takes in: - // - kmsClient - // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement - // - curveSpec: The curve name where the public keys lie - // - senderPublicKey: A ByteBuffer of a UTF-8 encoded public - // key for the key passed into kmsKeyId in DER format - // - recipientPublicKey: A ByteBuffer of a UTF-8 encoded public key - // for the recipient public key. CurveSpec = ECDHCurveSpec.ECC_NIST_P256, KmsClient = new AmazonKeyManagementServiceClient(), KeyAgreementScheme = new KmsEcdhStaticConfigurations @@ -89,16 +141,46 @@ public static async Task KmsEcdhKeyringGetItemPutItem() await PutItemGetItemWithKeyring(kmsEcdhKeyring, ddbTableName); } + // This example takes in the recipient's KMS ECC key ARN, + // and the algorithm definition where the ECC keys lie. + // + // This example attempts to decrypt a test item using the provided eccRecipientKeyArn, + // it does so by checking if the message header contains the recipient's public key. + // + // Running this example requires access to the DDB Table whose name + // is provided through the TestUtils.TEST_DDB_TABLE_NAME variable. + // + // This table must be configured with the following + // primary key configuration: + // - Partition key is named "partition_key" with type (S) + // - Sort key is named "sort_key" with type (S) + // + // This example also requires access to a KMS ECC key. + // Our tests provide a KMS ECC Key ARN that anyone can use, but you + // can also provide your own KMS ECC key. + // + // To use your own KMS ECC key, you must have: + // - kms:GetPublicKey permissions on that key. + // + // This example will call kms:GetPublicKey on keyring creation. + // You must also have kms:DeriveSharedSecret permissions on the KMS ECC key. private static async Task KmsEcdhKeyringDiscoveryGetItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; var ecdhKeyArnRecipient = TestUtils.TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT; - // 1. Create a KMS ECDH keyring. - // This keyring takes in: - // - kmsClient - // - recipientKmsIdentifier: Must be an ARN representing a KMS ECC key meant for KeyAgreement - // - curveSpec: The curve name where the public keys lie + // Create a KMS ECDH keyring. + // This keyring uses the KmsPublicKeyDiscovery configuration. + // On encrypt, the keyring will fail as it is not allowed to encrypt data under this configuration. + // On decrypt, the keyring will check if its corresponding public key is stored in the message header. It + // will AWS KMS to derive the shared from the recipient's KMS ECC Key ARN and the sender's public key; + // For more information on this configuration see: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-discovery + // The DynamoDb encryption client uses this to encrypt and decrypt items. + // This keyring takes in: + // - kmsClient + // - recipientKmsIdentifier: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie var keyringInput = new CreateAwsKmsEcdhKeyringInput { CurveSpec = ECDHCurveSpec.ECC_NIST_P256, @@ -119,12 +201,12 @@ private static async Task KmsEcdhKeyringDiscoveryGetItem() private static async Task GetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddbTableName) { - // 2. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature var attributeActionsOnEncrypt = new Dictionary { ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY @@ -132,37 +214,37 @@ private static async Task GetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddb ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN }; - // 3. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActionsOnEncrypt` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // // For this example, we currently authenticate all attributes. To make it easier to // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. const String unsignAttrPrefix = ":"; - // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Create the DynamoDb Encryption configuration for the table we will be writing to. var tableConfigs = new Dictionary { [ddbTableName] = new DynamoDbTableEncryptionConfig @@ -176,13 +258,13 @@ private static async Task GetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddb } }; - // 5. Create a new AWS SDK DynamoDb client using the config above + // Create a new AWS SDK DynamoDb client using the config above var ddb = new Client.DynamoDbClient( new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); - // 6. Get the item back from our table using the same client. - // The client will decrypt the item client-side, and return - // back the original item. + // Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. var keyToGet = new Dictionary { ["partition_key"] = new AttributeValue("kmsEcdhKeyringItem"), @@ -205,12 +287,12 @@ private static async Task GetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddb private static async Task PutItemGetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddbTableName) { - // 3. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature var attributeActionsOnEncrypt = new Dictionary { ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY @@ -218,37 +300,37 @@ private static async Task PutItemGetItemWithKeyring(IKeyring kmsEcdhKeyring, str ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN }; - // 4. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActionsOnEncrypt` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // - // For this example, we currently authenticate all attributes. To make it easier to - // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. const String unsignAttrPrefix = ":"; - // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Create the DynamoDb Encryption configuration for the table we will be writing to. var tableConfigs = new Dictionary { [ddbTableName] = new DynamoDbTableEncryptionConfig @@ -262,13 +344,13 @@ private static async Task PutItemGetItemWithKeyring(IKeyring kmsEcdhKeyring, str } }; - // 6. Create a new AWS SDK DynamoDb client using the config above + // Create a new AWS SDK DynamoDb client using the config above var ddb = new Client.DynamoDbClient( new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); - // 7. Put an item into our table using the above client. - // Before the item gets sent to DynamoDb, it will be encrypted - // client-side, according to our configuration. + // Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. var item = new Dictionary { ["partition_key"] = new AttributeValue("kmsEcdhKeyringItem"), @@ -287,9 +369,9 @@ private static async Task PutItemGetItemWithKeyring(IKeyring kmsEcdhKeyring, str // Demonstrate that PutItem succeeded Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); - // 8. Get the item back from our table using the same client. - // The client will decrypt the item client-side, and return - // back the original item. + // Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. var keyToGet = new Dictionary { ["partition_key"] = new AttributeValue("kmsEcdhKeyringItem"), diff --git a/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs b/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs index 93691bfad..be33ff5d4 100644 --- a/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs +++ b/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs @@ -28,45 +28,53 @@ namespace Examples.keyring; -/* - This example sets up DynamoDb Encryption for the AWS SDK client - using the raw ECDH Keyring. This keyring takes in the sender's ECC - private key and the recipient's ECC Public Key to derive a shared secret. - The keyring uses the shared secret to derive a data key to protect the - data keys that encrypt and decrypt DynamoDb table items. - - This example takes in the sender's private key, the recipient's - public key, and the algorithm definition where the ECC keys lie. - This parameter takes in the sender's private key as a - UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures), the recipient's - DER-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), - and the Curve Specification where the keys lie. - - This example encrypts a test item using the provided ECC keys and puts the - encrypted item to the provided DynamoDb table. Then, it gets the - item from the table and decrypts it. - - Running this example requires access to the DDB Table whose name - is provided in CLI arguments. - This table must be configured with the following - primary key configuration: - - Partition key is named "partition_key" with type (S) - - Sort key is named "sort_key" with type (S) - */ +// These examples set up DynamoDb Encryption for the AWS SDK client +// using the raw ECDH Keyring. This keyring, depending on its KeyAgreement scheme, +// takes in the sender's ECC private key, and the recipient's ECC Public Key to derive a shared secret. +// The keyring uses the shared secret to derive a data key to protect the +// data keys that encrypt and decrypt DynamoDb table items. +// +// +// Running these examples require access to the DDB Table whose name +// is provided in CLI arguments. +// This table must be configured with the following +// primary key configuration: +// - Partition key is named "partition_key" with type (S) +// - Sort key is named "sort_key" with type (S) public class RawEcdhKeyringExample { private static readonly String EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER = "RawEcdhKeyringExamplePrivateKeySender.pem"; private static readonly String EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT = "RawEcdhKeyringExamplePrivateKeyRecipient.pem"; private static readonly String EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT = "RawEcdhKeyringExamplePublicKeyRecipient.pem"; + // This example takes in the sender's private key as a + // UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures) + // located at the file location defined in EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER, + // the recipient's public key as a UTF8 PEM-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + // located at the file location defined in EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT, + // and the Curve Specification where the keys lie. + // + // This example encrypts a test item using the provided ECC keys and puts the + // encrypted item to the provided DynamoDb table. Then, it gets the + // item from the table and decrypts it. + // + // This examples creates a RawECDH keyring with the RawPrivateKeyToStaticPublicKey key agreement scheme. + // For more information on this configuration see: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-RawPrivateKeyToStaticPublicKey + // + // On encrypt, the shared secret is derived from the sender's private key and the recipient's public key. + // On decrypt, the shared secret is derived from the sender's private key and the recipient's public key; + // however, on decrypt the recipient can construct a keyring such that the shared secret is calculated with + // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. private static async Task RawEcdhKeyringExampleGetItemPutItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; - // 1. Load key pair from UTF-8 encoded PEM files. - // You may provide your own PEM files to use here. - // If you do not, the main method in this class will generate PEM - // files for example use. Do not use these files for any other purpose. + // Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. If you provide this, it MUST + // be a key on curve P256. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. MemoryStream privateKeySenderUtf8EncodedByteBuffer; try { @@ -90,13 +98,16 @@ private static async Task RawEcdhKeyringExampleGetItemPutItem() throw new IOException("Exception while reading public key from file", e); } - // 2. Create the keyring. - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // Create the keyring. + // This keyring uses static sender and recipient keys. This configuration calls for both of + // the keys to be on the same curve (P256, P384, P521). + // On encrypt, the shared secret is derived from the sender's private key and the recipient's public key. + // For this example, on decrypt, the shared secret is derived from the sender's private key and the recipient's public key; + // however, on decrypt the recipient can construct a keyring such that the shared secret is calculated with + // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. + // The DynamoDb encryption client uses this to encrypt and decrypt items. var keyringInput = new CreateRawEcdhKeyringInput { - // This example uses keys that lie on the ECC Curve P256. - // The keyring supports curves P256, P384, and P521. - // On creation, the keyring verifies that all configured keys lie on the provided curve spec. CurveSpec = ECDHCurveSpec.ECC_NIST_P256, KeyAgreementScheme = new RawEcdhStaticConfigurations { @@ -119,14 +130,23 @@ private static async Task RawEcdhKeyringExampleGetItemPutItem() await PutGetExampleWithKeyring(rawEcdhKeyring, ddbTableName); } + // This example takes in the recipient's public key located at EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // as a UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures), and the Curve Specification where the key lies. + // This examples creates a RawECDH keyring with the EphemeralPrivateKeyToStaticPublicKey key agreement scheme. + // This configuration will always create a new key pair as the sender key pair for the key agreement operation. + // The ephemeral configuration can only encrypt data and CANNOT decrypt messages. + // For more information on this configuration see: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-EphemeralPrivateKeyToStaticPublicKey + // private static async Task EphemeralRawEcdhKeyringPutItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; - // 1. Load key pair from UTF-8 encoded PEM files. - // You may provide your own PEM files to use here. - // If you do not, the RawEcdhKeyringExamples method in this class will generate PEM - // files for example use. Do not use these files for any other purpose. + // Load public key from UTF-8 encoded PEM files into a DER encoded public key. + // You may provide your own PEM files to use here. If you provide this, it MUST + // be a key on curve P256. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. MemoryStream publicKeyRecipientUtf8EncodedByteBuffer; try { @@ -139,8 +159,11 @@ private static async Task EphemeralRawEcdhKeyringPutItem() throw new IOException("Exception while reading public key from file", e); } - // 2. Create the keyring. - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // Create the keyring. + // This keyring uses an ephemeral configuration. This configuration will always create a new + // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only + // encrypt data and CANNOT decrypt messages. + // The DynamoDb encryption client uses this to encrypt and decrypt items. var keyringInput = new CreateRawEcdhKeyringInput { // This example uses keys that lie on the ECC Curve P256. @@ -149,10 +172,6 @@ private static async Task EphemeralRawEcdhKeyringPutItem() CurveSpec = ECDHCurveSpec.ECC_NIST_P256, KeyAgreementScheme = new RawEcdhStaticConfigurations { - // This keyring is configured with the EphemeralPrivateKeyToStaticPublicKey configuration. - // This configuration will always create a new - // key pair as the sender key pair for the key agreement operation. - // The ephemeral configuration can only encrypt data and CANNOT decrypt messages. EphemeralPrivateKeyToStaticPublicKey = new EphemeralPrivateKeyToStaticPublicKeyInput { RecipientPublicKey = publicKeyRecipientUtf8EncodedByteBuffer @@ -168,14 +187,22 @@ private static async Task EphemeralRawEcdhKeyringPutItem() await PutExampleWithKeyring(rawEcdhKeyring, ddbTableName); } + // This example takes in the recipient's private key located at EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + // as a UTF8 PEM-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + // and the Curve Specification where the key lies. + // This examples creates a RawECDH keyring with the PublicKeyDiscovery key agreement scheme. + // This scheme is only available on decrypt. + // For more information on this configuration see: + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-raw-ecdh-keyring.html#raw-ecdh-PublicKeyDiscovery private static async Task DiscoveryRawEcdhKeyringGetItem() { var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; - // 1. Load key pair from UTF-8 encoded PEM files. - // You may provide your own PEM files to use here. - // If you do not, the main method in this class will generate PEM - // files for example use. Do not use these files for any other purpose. + // Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. If you provide this, it MUST + // be a key on curve P256. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. MemoryStream privateKeyRecipientUtf8EncodedByteBuffer; try { @@ -187,8 +214,11 @@ private static async Task DiscoveryRawEcdhKeyringGetItem() throw new IOException("Exception while reading private key from file", e); } - // 2. Create the keyring. - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // Create the keyring. + // This keyring uses a discovery configuration. This configuration will check on decrypt + // if it is meant to decrypt the message by checking if the configured public key is stored on the message. + // The discovery configuration can only decrypt messages and CANNOT encrypt messages. + // The DynamoDb encryption client uses this to encrypt and decrypt items. var keyringInput = new CreateRawEcdhKeyringInput { // This example uses keys that lie on the ECC Curve P256. @@ -197,10 +227,6 @@ private static async Task DiscoveryRawEcdhKeyringGetItem() CurveSpec = ECDHCurveSpec.ECC_NIST_P256, KeyAgreementScheme = new RawEcdhStaticConfigurations { - // This keyring uses a discovery configuration. This configuration will check on decrypt - // if it is meant to decrypt the message by checking if the configured public key is stored on the message. - // The discovery configuration can only decrypt messages and CANNOT encrypt messages. - // The DynamoDb encryption client uses this to encrypt and decrypt items. PublicKeyDiscovery = new PublicKeyDiscoveryInput { // Must be a UTF8 PEM-encoded private key @@ -216,12 +242,12 @@ private static async Task DiscoveryRawEcdhKeyringGetItem() private static async Task PutGetExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) { - // 3. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature var attributeActionsOnEncrypt = new Dictionary { ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY @@ -229,37 +255,37 @@ private static async Task PutGetExampleWithKeyring(IKeyring rawEcdhKeyring, stri ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN }; - // 4. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActionsOnEncrypt` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // // For this example, we currently authenticate all attributes. To make it easier to // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. const String unsignAttrPrefix = ":"; - // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Create the DynamoDb Encryption configuration for the table we will be writing to. var tableConfigs = new Dictionary { [ddbTableName] = new DynamoDbTableEncryptionConfig @@ -273,13 +299,13 @@ private static async Task PutGetExampleWithKeyring(IKeyring rawEcdhKeyring, stri } }; - // 6. Create a new AWS SDK DynamoDb client using the config above + // Create a new AWS SDK DynamoDb client using the config above var ddb = new Client.DynamoDbClient( new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); - // 7. Put an item into our table using the above client. - // Before the item gets sent to DynamoDb, it will be encrypted - // client-side, according to our configuration. + // Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. var item = new Dictionary { ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), @@ -298,9 +324,9 @@ private static async Task PutGetExampleWithKeyring(IKeyring rawEcdhKeyring, stri // Demonstrate that PutItem succeeded Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); - // 8. Get the item back from our table using the same client. - // The client will decrypt the item client-side, and return - // back the original item. + // Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. var keyToGet = new Dictionary { ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), @@ -322,12 +348,12 @@ private static async Task PutGetExampleWithKeyring(IKeyring rawEcdhKeyring, stri } private static async Task PutExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) { - // 3. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature var attributeActionsOnEncrypt = new Dictionary { ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY @@ -335,37 +361,37 @@ private static async Task PutExampleWithKeyring(IKeyring rawEcdhKeyring, string ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN }; - // 4. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActionsOnEncrypt` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // // For this example, we currently authenticate all attributes. To make it easier to // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. const String unsignAttrPrefix = ":"; - // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Create the DynamoDb Encryption configuration for the table we will be writing to. var tableConfigs = new Dictionary { [ddbTableName] = new DynamoDbTableEncryptionConfig @@ -379,11 +405,11 @@ private static async Task PutExampleWithKeyring(IKeyring rawEcdhKeyring, string } }; - // 6. Create a new AWS SDK DynamoDb client using the config above + // Create a new AWS SDK DynamoDb client using the config above var ddb = new Client.DynamoDbClient( new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); - // 7. Put an item into our table using the above client. + // Put an item into our table using the above client. // Before the item gets sent to DynamoDb, it will be encrypted // client-side, according to our configuration. var item = new Dictionary @@ -404,7 +430,7 @@ private static async Task PutExampleWithKeyring(IKeyring rawEcdhKeyring, string // Demonstrate that PutItem succeeded Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); - // 8. Try to get the item and assert that the ephemeral keyring configuration + // Try to get the item and assert that the ephemeral keyring configuration // cannot decrypt data. var keyToGet = new Dictionary { @@ -431,12 +457,12 @@ private static async Task PutExampleWithKeyring(IKeyring rawEcdhKeyring, string private static async Task GetExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) { - // 3. Configure which attributes are encrypted and/or signed when writing new items. - // For each attribute that may exist on the items we plan to write to our DynamoDbTable, - // we must explicitly configure how they should be treated during item encryption: - // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature - // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature - // - DO_NOTHING: The attribute is not encrypted and not included in the signature + // Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature var attributeActionsOnEncrypt = new Dictionary { ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY @@ -444,37 +470,37 @@ private static async Task GetExampleWithKeyring(IKeyring rawEcdhKeyring, string ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN }; - // 4. Configure which attributes we expect to be included in the signature - // when reading items. There are two options for configuring this: + // Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: // - // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: - // When defining your DynamoDb schema and deciding on attribute names, - // choose a distinguishing prefix (such as ":") for all attributes that - // you do not want to include in the signature. - // This has two main benefits: - // - It is easier to reason about the security and authenticity of data within your item - // when all unauthenticated data is easily distinguishable by their attribute name. - // - If you need to add new unauthenticated attributes in the future, - // you can easily make the corresponding update to your `attributeActionsOnEncrypt` - // and immediately start writing to that new attribute, without - // any other configuration update needed. - // Once you configure this field, it is not safe to update it. + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. // - // - Configure `allowedUnsignedAttributes`: You may also explicitly list - // a set of attributes that should be considered unauthenticated when encountered - // on read. Be careful if you use this configuration. Do not remove an attribute - // name from this configuration, even if you are no longer writing with that attribute, - // as old items may still include this attribute, and our configuration needs to know - // to continue to exclude this attribute from the signature scope. - // If you add new attribute names to this field, you must first deploy the update to this - // field to all readers in your host fleet before deploying the update to start writing - // with that new attribute. + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. // // For this example, we currently authenticate all attributes. To make it easier to // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. const String unsignAttrPrefix = ":"; - // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + // Create the DynamoDb Encryption configuration for the table we will be writing to. var tableConfigs = new Dictionary { [ddbTableName] = new DynamoDbTableEncryptionConfig @@ -488,12 +514,12 @@ private static async Task GetExampleWithKeyring(IKeyring rawEcdhKeyring, string } }; - // 6. Create a new AWS SDK DynamoDb client using the config above + // Create a new AWS SDK DynamoDb client using the config above var ddb = new Client.DynamoDbClient( new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); - // 7. Attempt to put an item into our table using the above client. - // Assert that Discovery configuration cannot encrypt data + // Attempt to put an item into our table using the above client. + // Assert that Discovery configuration cannot encrypt data var item = new Dictionary { ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), @@ -516,9 +542,9 @@ private static async Task GetExampleWithKeyring(IKeyring rawEcdhKeyring, string Debug.Assert(e.Message.Contains("PublicKeyDiscovery Key Agreement Scheme is forbidden on encrypt.")); } - // 8. Get the item back from our table using the same client. - // The client will decrypt the item client-side, and return - // back the original item. + // Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. var keyToGet = new Dictionary { ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), @@ -560,38 +586,9 @@ public static async Task RawEcdhKeyringExamples() private static bool ShouldGenerateNewEccKeys() { // If keys already exists; do not overwrite existing keys. - if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) - && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) - && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) - { - return false; - } - - // If only two keys are present; throw exception - if (!File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) - && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) - && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) - { - throw new ApplicationException("Missing private key file at: " + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER); - } - - // If only two keys are present; throw exception - if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) - && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) - && !File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) - { - throw new ApplicationException("Missing private key file at: " + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT); - } - - // If only two keys are present; throw exception - if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) - && !File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) - && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) - { - throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); - } - - return true; + return !File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + || !File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + || !File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT); } private static void GenerateEccKeyPairs() From 351ed090c2d59e7743eec68aa8200c590e4f3688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Corella?= <39066999+josecorella@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:10:15 -0800 Subject: [PATCH 11/12] Apply suggestions from code review Co-authored-by: Lucas McDonald --- .../examples/keyring/KmsEcdhKeyringExample.java | 8 ++++---- .../examples/keyring/RawEcdhKeyringExample.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java index 325b0aa19..37b5d771b 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java @@ -129,13 +129,13 @@ public static void KmsEcdhKeyringGetItemPutItem( // Create a KMS ECDH keyring. // This keyring uses the KmsPrivateKeyToStaticPublicKey configuration. This configuration calls for both of // the keys to be on the same curve (P256, P384, P521). - // On encrypt, the keyring calls AWS KMS to derive the shared from the sender's KMS ECC Key ARN and the recipient's public key. - // For this example, on decrypt, the keyring calls AWS KMS to derive the shared from the sender's KMS ECC Key ARN and the recipient's public key; - // however, on decrypt the recipient can construct a keyring such that the shared secret is calculated with + // On encrypt, the keyring calls AWS KMS to derive the shared secret from the sender's KMS ECC Key ARN and the recipient's public key. + // For this example, on decrypt, the keyring calls AWS KMS to derive the shared secret from the sender's KMS ECC Key ARN and the recipient's public key; + // however, on decrypt, the recipient can construct a keyring such that the shared secret is calculated with // the recipient's private key and the sender's public key. In both scenarios the shared secret will be the same. // For more information on this configuration see: // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-ecdh-keyring.html#kms-ecdh-create - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // The DynamoDb encryption client uses this keyring to encrypt and decrypt items. // This keyring takes in: // - kmsClient // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java index 41a2a8204..fe235bc5d 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java @@ -736,8 +736,8 @@ public static boolean shouldGenerateNewEccKeyPairs() { // If keys already exist: do not overwrite existing keys return ( - !privateKeyFileSender.exists() || - !publicKeyFileRecipient.exists() || + !privateKeyFileSender.exists() && + !publicKeyFileRecipient.exists() && !privateKeyFileRecipient.exists() ); // If no keys are present, generate new keys From 0345d95ad44335682b674400f6db389c30b2e6f5 Mon Sep 17 00:00:00 2001 From: Jose Corella Date: Mon, 25 Nov 2024 10:45:10 -0800 Subject: [PATCH 12/12] address feddback --- .../keyring/KmsEcdhKeyringExample.java | 7 +- .../keyring/RawEcdhKeyringExample.java | 8 +- cfn/CI.yaml | 132 +----------------- 3 files changed, 15 insertions(+), 132 deletions(-) diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java index 37b5d771b..bb9978d4d 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java @@ -178,9 +178,8 @@ public static void KmsEcdhKeyringGetItemPutItem( } /* - This example takes in the recipient's KMS ECC key ARN, - and the algorithm definition where the ECC keys lie. - The eccRecipientKeyArn parameter takes in the sender's KMS ECC key ARN + This example takes in the recipient's KMS ECC key ARN via + the eccRecipientKeyArn parameter. This example attempts to decrypt a test item using the provided eccRecipientKeyArn, it does so by checking if the message header contains the recipient's public key. @@ -581,6 +580,8 @@ public static void main(final String[] args) { eccPublicKeyRecipientFilename = EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME; } if (args.length == 5) { + eccPublicKeySenderFilename = args[2]; + eccPublicKeyRecipientFilename = args[3]; eccRecipientKeyArn = args[4]; } else { eccRecipientKeyArn = null; diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java index fe235bc5d..97fbac6f0 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java @@ -195,7 +195,7 @@ public static void RawEcdhKeyringGetItemPutItem( /* This example takes in the recipient's public key located at EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT - as a UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures), and the Curve Specification where the key lies. + as a UTF8 PEM-encoded X.509 public key, and the Curve Specification where the key lies. This examples creates a RawECDH keyring with the EphemeralPrivateKeyToStaticPublicKey key agreement scheme. This configuration will always create a new key pair as the sender key pair for the key agreement operation. @@ -252,7 +252,7 @@ public static void EphemeralRawEcdhKeyringPutItem( // This keyring uses an ephemeral configuration. This configuration will always create a new // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only // encrypt data and CANNOT decrypt messages. - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // The DynamoDb encryption client uses this to encrypt items. final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput .builder() .curveSpec(ecdhCurveSpec) @@ -283,7 +283,7 @@ public static void EphemeralRawEcdhKeyringPutItem( /* This example takes in the recipient's private key located at EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT - as a UTF8 PEM-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + as a UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures) private key, and the Curve Specification where the key lies. This examples creates a RawECDH keyring with the PublicKeyDiscovery key agreement scheme. @@ -319,7 +319,7 @@ public static void DiscoveryRawEcdhKeyringGetItem( // This keyring uses a discovery configuration. This configuration will check on decrypt // if it is meant to decrypt the message by checking if the configured public key is stored on the message. // The discovery configuration can only decrypt messages and CANNOT encrypt messages. - // The DynamoDb encryption client uses this to encrypt and decrypt items. + // The DynamoDb encryption client uses this to decrypt items. final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput .builder() .curveSpec(ecdhCurveSpec) diff --git a/cfn/CI.yaml b/cfn/CI.yaml index b273ca768..3e7856fa8 100644 --- a/cfn/CI.yaml +++ b/cfn/CI.yaml @@ -64,131 +64,13 @@ Resources: WriteCapacityUnits: "5" TableName: !Ref TableName - #BasicTestJavaTable: - # Type: AWS::DynamoDB::Table - # Properties: - # AttributeDefinitions: - # - AttributeName: "partition_key" - # AttributeType: "S" - # - AttributeName: "sort_key" - # AttributeType: "N" - # KeySchema: - # - AttributeName: "partition_key" - # KeyType: "HASH" - # - AttributeName: "sort_key" - # KeyType: "RANGE" - # ProvisionedThroughput: - # ReadCapacityUnits: "5" - # WriteCapacityUnits: "5" - # TableName: !Ref BasicTestJavaTableName - - #BasicTestDotnetTable: - # Type: AWS::DynamoDB::Table - # Properties: - # AttributeDefinitions: - # - AttributeName: "partition_key" - # AttributeType: "S" - # - AttributeName: "sort_key" - # AttributeType: "N" - # KeySchema: - # - AttributeName: "partition_key" - # KeyType: "HASH" - # - AttributeName: "sort_key" - # KeyType: "RANGE" - # ProvisionedThroughput: - # ReadCapacityUnits: "5" - # WriteCapacityUnits: "5" - # TableName: !Ref BasicTestDotnetTableName - - #SearchTestJavaTable: - # Type: AWS::DynamoDB::Table - # Properties: - # AttributeDefinitions: - # - AttributeName: "aws_dbe_b_inspector_id_last4" - # AttributeType: "S" - # - AttributeName: "aws_dbe_b_last4UnitCompound" - # AttributeType: "S" - # - AttributeName: "aws_dbe_b_unit" - # AttributeType: "S" - # - AttributeName: "inspection_date" - # AttributeType: "S" - # - AttributeName: "work_id" - # AttributeType: "S" - # KeySchema: - # - AttributeName: "work_id" - # KeyType: "HASH" - # - AttributeName: "inspection_date" - # KeyType: "RANGE" - # ProvisionedThroughput: - # ReadCapacityUnits: "5" - # WriteCapacityUnits: "5" - # TableName: !Ref SearchTestJavaTableName - # GlobalSecondaryIndexes: - # - IndexName: "last4-unit-index" - # KeySchema: - # - AttributeName: "aws_dbe_b_inspector_id_last4" - # KeyType: "HASH" - # - AttributeName: "aws_dbe_b_unit" - # KeyType: "RANGE" - # Projection: - # ProjectionType: ALL - # ProvisionedThroughput: - # ReadCapacityUnits: "5" - # WriteCapacityUnits: "5" - # - IndexName: "last4UnitCompound-index" - # KeySchema: - # - AttributeName: "aws_dbe_b_last4UnitCompound" - # KeyType: "HASH" - # Projection: - # ProjectionType: ALL - # ProvisionedThroughput: - # ReadCapacityUnits: "5" - # WriteCapacityUnits: "5" - - #SearchTestDotnetTable: - # Type: AWS::DynamoDB::Table - # Properties: - # AttributeDefinitions: - # - AttributeName: "aws_dbe_b_inspector_id_last4" - # AttributeType: "S" - # - AttributeName: "aws_dbe_b_last4UnitCompound" - # AttributeType: "S" - # - AttributeName: "aws_dbe_b_unit" - # AttributeType: "S" - # - AttributeName: "inspection_date" - # AttributeType: "S" - # - AttributeName: "work_id" - # AttributeType: "S" - # KeySchema: - # - AttributeName: "work_id" - # KeyType: "HASH" - # - AttributeName: "inspection_date" - # KeyType: "RANGE" - # ProvisionedThroughput: - # ReadCapacityUnits: "5" - # WriteCapacityUnits: "5" - # TableName: !Ref SearchTestDotnetTableName - # GlobalSecondaryIndexes: - # - IndexName: "last4-unit-index" - # KeySchema: - # - AttributeName: "aws_dbe_b_inspector_id_last4" - # KeyType: "HASH" - # - AttributeName: "aws_dbe_b_unit" - # KeyType: "RANGE" - # Projection: - # ProjectionType: ALL - # ProvisionedThroughput: - # ReadCapacityUnits: "5" - # WriteCapacityUnits: "5" - # - IndexName: "last4UnitCompound-index" - # KeySchema: - # - AttributeName: "aws_dbe_b_last4UnitCompound" - # KeyType: "HASH" - # Projection: - # ProjectionType: ALL - # ProvisionedThroughput: - # ReadCapacityUnits: "5" - # WriteCapacityUnits: "5" + # These tables were manually created but not used in CI + # If we have to start using them in CI we just have to add + # them to the policy below. + # BasicTestJavaTable: + # BasicTestDotnetTable: + # SearchTestJavaTable: + # SearchTestDotnetTable: TestTableWithSimpleBeaconIndex: Type: AWS::DynamoDB::Table