diff --git a/.gitignore b/.gitignore index fd0875aed..4c01b8aed 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ test/**/Output/* specification_compliance_report.html /compliance /.smithy.lsp.log + +# logs +*.log \ No newline at end of file diff --git a/DynamoDbEncryption/codegen-patches/DynamoDbEncryption/java/dafny-4.8.0.patch b/DynamoDbEncryption/codegen-patches/DynamoDbEncryption/java/dafny-4.8.0.patch new file mode 100644 index 000000000..5916d97ab --- /dev/null +++ b/DynamoDbEncryption/codegen-patches/DynamoDbEncryption/java/dafny-4.8.0.patch @@ -0,0 +1,27 @@ +diff --git b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/CollectionOfErrors.java a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/CollectionOfErrors.java +index c6f44188..c2af7d32 100644 +--- b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/CollectionOfErrors.java ++++ a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/CollectionOfErrors.java +@@ -4,6 +4,7 @@ + package software.amazon.cryptography.dbencryptionsdk.dynamodb.model; + + import java.util.List; ++import java.util.stream.Collectors; + + public class CollectionOfErrors extends RuntimeException { + +@@ -134,6 +135,14 @@ public class CollectionOfErrors extends RuntimeException { + } + + public CollectionOfErrors build() { ++ if (!(this.list == null || this.list.isEmpty())) { ++ this.message = ++ this.message + ++ " String representation of Exceptions in list.\n" + ++ this.list.stream() ++ .map(ex -> ex.getClass().getSimpleName() + ": " + ex.getMessage()) ++ .collect(Collectors.joining("\n")); ++ } + return new CollectionOfErrors(this); + } + } diff --git a/DynamoDbEncryption/codegen-patches/DynamoDbEncryptionTransforms/java/dafny-4.8.0.patch b/DynamoDbEncryption/codegen-patches/DynamoDbEncryptionTransforms/java/dafny-4.8.0.patch new file mode 100644 index 000000000..88642ebc3 --- /dev/null +++ b/DynamoDbEncryption/codegen-patches/DynamoDbEncryptionTransforms/java/dafny-4.8.0.patch @@ -0,0 +1,27 @@ +diff --git b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/CollectionOfErrors.java a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/CollectionOfErrors.java +index 7a2ca9b0..db01d961 100644 +--- b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/CollectionOfErrors.java ++++ a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/CollectionOfErrors.java +@@ -4,6 +4,7 @@ + package software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model; + + import java.util.List; ++import java.util.stream.Collectors; + + public class CollectionOfErrors extends RuntimeException { + +@@ -134,6 +135,14 @@ public class CollectionOfErrors extends RuntimeException { + } + + public CollectionOfErrors build() { ++ if (!(this.list == null || this.list.isEmpty())) { ++ this.message = ++ this.message + ++ " String representation of Exceptions in list.\n" + ++ this.list.stream() ++ .map(ex -> ex.getClass().getSimpleName() + ": " + ex.getMessage()) ++ .collect(Collectors.joining("\n")); ++ } + return new CollectionOfErrors(this); + } + } diff --git a/DynamoDbEncryption/codegen-patches/DynamoDbItemEncryptor/java/dafny-4.8.0.patch b/DynamoDbEncryption/codegen-patches/DynamoDbItemEncryptor/java/dafny-4.8.0.patch new file mode 100644 index 000000000..5dc88b460 --- /dev/null +++ b/DynamoDbEncryption/codegen-patches/DynamoDbItemEncryptor/java/dafny-4.8.0.patch @@ -0,0 +1,27 @@ +diff --git b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/CollectionOfErrors.java a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/CollectionOfErrors.java +index 1bb59f8f..19928c23 100644 +--- b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/CollectionOfErrors.java ++++ a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/CollectionOfErrors.java +@@ -4,6 +4,7 @@ + package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model; + + import java.util.List; ++import java.util.stream.Collectors; + + public class CollectionOfErrors extends RuntimeException { + +@@ -134,6 +135,14 @@ public class CollectionOfErrors extends RuntimeException { + } + + public CollectionOfErrors build() { ++ if (!(this.list == null || this.list.isEmpty())) { ++ this.message = ++ this.message + ++ " String representation of Exceptions in list.\n" + ++ this.list.stream() ++ .map(ex -> ex.getClass().getSimpleName() + ": " + ex.getMessage()) ++ .collect(Collectors.joining("\n")); ++ } + return new CollectionOfErrors(this); + } + } diff --git a/DynamoDbEncryption/codegen-patches/StructuredEncryption/java/dafny-4.8.0.patch b/DynamoDbEncryption/codegen-patches/StructuredEncryption/java/dafny-4.8.0.patch new file mode 100644 index 000000000..ab2ae6e07 --- /dev/null +++ b/DynamoDbEncryption/codegen-patches/StructuredEncryption/java/dafny-4.8.0.patch @@ -0,0 +1,27 @@ +diff --git b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CollectionOfErrors.java a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CollectionOfErrors.java +index a4d45a55..a6c2fc43 100644 +--- b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CollectionOfErrors.java ++++ a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CollectionOfErrors.java +@@ -4,6 +4,7 @@ + package software.amazon.cryptography.dbencryptionsdk.structuredencryption.model; + + import java.util.List; ++import java.util.stream.Collectors; + + public class CollectionOfErrors extends RuntimeException { + +@@ -134,6 +135,14 @@ public class CollectionOfErrors extends RuntimeException { + } + + public CollectionOfErrors build() { ++ if (!(this.list == null || this.list.isEmpty())) { ++ this.message = ++ this.message + ++ " String representation of Exceptions in list.\n" + ++ this.list.stream() ++ .map(ex -> ex.getClass().getSimpleName() + ": " + ex.getMessage()) ++ .collect(Collectors.joining("\n")); ++ } + return new CollectionOfErrors(this); + } + } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/CollectionOfErrors.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/CollectionOfErrors.java index 1bb59f8f4..19928c232 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/CollectionOfErrors.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/CollectionOfErrors.java @@ -4,6 +4,7 @@ package software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.model; import java.util.List; +import java.util.stream.Collectors; public class CollectionOfErrors extends RuntimeException { @@ -134,6 +135,14 @@ public List list() { } public CollectionOfErrors build() { + if (!(this.list == null || this.list.isEmpty())) { + this.message = + this.message + + " String representation of Exceptions in list.\n" + + this.list.stream() + .map(ex -> ex.getClass().getSimpleName() + ": " + ex.getMessage()) + .collect(Collectors.joining("\n")); + } return new CollectionOfErrors(this); } } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/CollectionOfErrors.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/CollectionOfErrors.java index c6f441887..c2af7d327 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/CollectionOfErrors.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/model/CollectionOfErrors.java @@ -4,6 +4,7 @@ package software.amazon.cryptography.dbencryptionsdk.dynamodb.model; import java.util.List; +import java.util.stream.Collectors; public class CollectionOfErrors extends RuntimeException { @@ -134,6 +135,14 @@ public List list() { } public CollectionOfErrors build() { + if (!(this.list == null || this.list.isEmpty())) { + this.message = + this.message + + " String representation of Exceptions in list.\n" + + this.list.stream() + .map(ex -> ex.getClass().getSimpleName() + ": " + ex.getMessage()) + .collect(Collectors.joining("\n")); + } return new CollectionOfErrors(this); } } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/CollectionOfErrors.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/CollectionOfErrors.java index 7a2ca9b05..db01d9610 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/CollectionOfErrors.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/model/CollectionOfErrors.java @@ -4,6 +4,7 @@ package software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.model; import java.util.List; +import java.util.stream.Collectors; public class CollectionOfErrors extends RuntimeException { @@ -134,6 +135,14 @@ public List list() { } public CollectionOfErrors build() { + if (!(this.list == null || this.list.isEmpty())) { + this.message = + this.message + + " String representation of Exceptions in list.\n" + + this.list.stream() + .map(ex -> ex.getClass().getSimpleName() + ": " + ex.getMessage()) + .collect(Collectors.joining("\n")); + } return new CollectionOfErrors(this); } } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CollectionOfErrors.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CollectionOfErrors.java index a4d45a553..a6c2fc436 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CollectionOfErrors.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CollectionOfErrors.java @@ -4,6 +4,7 @@ package software.amazon.cryptography.dbencryptionsdk.structuredencryption.model; import java.util.List; +import java.util.stream.Collectors; public class CollectionOfErrors extends RuntimeException { @@ -134,6 +135,14 @@ public List list() { } public CollectionOfErrors build() { + if (!(this.list == null || this.list.isEmpty())) { + this.message = + this.message + + " String representation of Exceptions in list.\n" + + this.list.stream() + .map(ex -> ex.getClass().getSimpleName() + ": " + ex.getMessage()) + .collect(Collectors.joining("\n")); + } return new CollectionOfErrors(this); } } 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..bb9978d4d --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/KmsEcdhKeyringExample.java @@ -0,0 +1,694 @@ +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; + +/* + 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 = + "KmsEccKeyringKeyringExamplePublicKeySender.pem"; + 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 + ) { + // 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 + ); + ByteBuffer publicKeySenderByteBuffer = loadPublicKeyBytes( + eccPublicKeySenderFileName + ); + + // 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 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 keyring 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() + .kmsClient(KmsClient.create()) + .curveSpec(ECDHCurveSpec.ECC_NIST_P256) + .KeyAgreementScheme( + KmsEcdhStaticConfigurations + .builder() + .KmsPrivateKeyToStaticPublicKey( + 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 + ); + + PutGetItemWithKeyring(kmsEcdhKeyring, ddbTableName); + } + + /* + 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. + + 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 + ) { + // 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() + .kmsClient(KmsClient.create()) + .curveSpec(ECDHCurveSpec.ECC_NIST_P256) + .KeyAgreementScheme( + KmsEcdhStaticConfigurations + .builder() + .KmsPublicKeyDiscovery( + KmsPublicKeyDiscoveryInput + .builder() + .recipientKmsIdentifier(eccRecipientKeyArn) + .build() + ) + .build() + ) + .build(); + + final MaterialProviders matProv = MaterialProviders + .builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + IKeyring kmsEcdhKeyring = matProv.CreateAwsKmsEcdhKeyring( + createAwsKmsEcdhKeyringInput + ); + + GetItemWithKeyring(kmsEcdhKeyring, ddbTableName); + } + + private static void GetItemWithKeyring( + IKeyring kmsEcdhKeyring, + String ddbTableName + ) { + // 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); + + // 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 = ":"; + + // 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); + + // Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 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(); + + // 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 + ) { + // 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); + + // 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 = ":"; + + // 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); + + // Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 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(); + + // 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(); + + // 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 + ); + } + + /* + 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( + "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) { + eccPublicKeySenderFilename = args[2]; + eccPublicKeyRecipientFilename = args[3]; + 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..97fbac6f0 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java @@ -0,0 +1,885 @@ +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; + +/* + 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 { + + 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"; + + /* + 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 + ) { + // 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 = + 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); + } + + // 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) + .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); + } + + /* + This example takes in the recipient's public key located at EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + 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. + 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 + ) { + // 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( + 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); + } + + // 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 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); + } + + /* + This example takes in the recipient's private key located at EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + 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. + 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 + ) { + // 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 = + 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 + ); + } + + // 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 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 + ) { + // 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 + ); + + // 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 = ":"; + + // 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); + + // Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 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(); + + // 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(); + + // 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 + ) { + // 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 + ); + + // 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 = ":"; + + // 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); + + // Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 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(); + + // 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 + ) { + // 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 + ); + // 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 = ":"; + + // 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); + + // Create the DynamoDb Encryption Interceptor + DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEncryptionInterceptor + .builder() + .config( + DynamoDbTablesEncryptionConfig + .builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ) + .build(); + + // 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(); + + // 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!"); + } + + /* + 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( + "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 + return ( + !privateKeyFileSender.exists() && + !publicKeyFileRecipient.exists() && + !privateKeyFileRecipient.exists() + ); + // If no keys are present, generate new keys + } + + 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..7495261af --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestKmsEcdhKeyringExample.java @@ -0,0 +1,49 @@ +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 + // - 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()) { + 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..a6ee1aff6 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/keyring/TestRawEcdhKeyringExample.java @@ -0,0 +1,83 @@ +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 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()) { + 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 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()) { + 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 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()) { + 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 + ); + } +} diff --git a/Examples/runtimes/net/src/Examples.cs b/Examples/runtimes/net/src/Examples.cs index c7f5b15f3..e06deb95d 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 { @@ -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(); diff --git a/Examples/runtimes/net/src/TestUtils.cs b/Examples/runtimes/net/src/TestUtils.cs index 2fd898553..8c1ea95d4 100644 --- a/Examples/runtimes/net/src/TestUtils.cs +++ b/Examples/runtimes/net/src/TestUtils.cs @@ -24,6 +24,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..ae3fc8bbe --- /dev/null +++ b/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs @@ -0,0 +1,460 @@ +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; + +/* + 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 = + "KmsEccKeyringKeyringExamplePublicKeySender.pem"; + 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; + + + // 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 + { + 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); + } + + // 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 + { + 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); + } + + // 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; + + // 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, + 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) + { + // 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 + }; + + // 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 = ":"; + + // 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 + } + }; + + // Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 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) + { + // 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 + }; + + // 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 = ":"; + + // 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 + } + }; + + // Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 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); + + // 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..be33ff5d4 --- /dev/null +++ b/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs @@ -0,0 +1,675 @@ +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; + +// 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; + + // 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 + { + 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); + } + + // 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 + { + 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); + } + + // 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; + + // 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 + { + 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); + } + + // 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. + // 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 + { + 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); + } + + // 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; + + // 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 + { + 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); + } + + // 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. + // 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 + { + 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) + { + // 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 + }; + + // 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 = ":"; + + // 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 + } + }; + + // Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 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); + + // 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) + { + // 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 + }; + + // 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 = ":"; + + // 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 + } + }; + + // Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 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); + + // 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) + { + // 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 + }; + + // 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 = ":"; + + // 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 + } + }; + + // Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 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.")); + } + + // 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. + 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() + { + // 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); + } +} diff --git a/cfn/CI.yaml b/cfn/CI.yaml index 275d1dbf9..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 @@ -384,7 +266,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}" @@ -450,6 +332,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 |