diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java index 96a1bd4c0..30032bbbe 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/RawEcdhKeyringExample.java @@ -278,9 +278,9 @@ public static void DiscoveryRawEcdhKeyringGetItem( } // 2. Create the keyring. - // This keyring uses an ephemeral configuration. This configuration will always create a new - // key pair as the sender key pair for the key agreement operation. The ephemeral configuration can only - // encrypt data and CANNOT decrypt messages. + // This keyring uses a discovery configuration. This configuration will check on decrypt + // if it is meant to decrypt the message by checking if the configured public key is stored on the message. + // The discovery configuration can only decrypt messages and CANNOT encrypt messages. // The DynamoDb encryption client uses this to encrypt and decrypt items. final CreateRawEcdhKeyringInput keyringInput = CreateRawEcdhKeyringInput .builder() diff --git a/Examples/runtimes/net/src/Examples.cs b/Examples/runtimes/net/src/Examples.cs index c7f5b15f3..993b63bc3 100644 --- a/Examples/runtimes/net/src/Examples.cs +++ b/Examples/runtimes/net/src/Examples.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Examples.keyring; namespace Examples { @@ -10,7 +11,7 @@ class Program static async Task Main() { ItemEncryptDecryptExample.PutItemGetItem(); - + await BasicPutGetExample.PutItemGetItem(); await ScanErrorExample.ScanError(); await GetEncryptedDataKeyDescriptionExample.GetEncryptedDataKeyDescription(); @@ -22,6 +23,8 @@ static async Task Main() await MultiKeyringExample.MultiKeyringGetItemPutItem(); await RawRsaKeyringExample.RawRsaKeyringGetItemPutItem(); await KmsRsaKeyringExample.KmsRsaKeyringGetItemPutItem(); + await RawEcdhKeyringExample.RawEcdhKeyringExamples(); + await KmsEcdhKeyringExample.KmsEcdhKeyringExamples(); var keyId = CreateKeyStoreKeyExample.KeyStoreCreateKey(); var keyId2 = CreateKeyStoreKeyExample.KeyStoreCreateKey(); @@ -30,7 +33,7 @@ static async Task Main() Thread.Sleep(5000); await HierarchicalKeyringExample.HierarchicalKeyringGetItemPutItem(keyId, keyId2); - + await BasicSearchableEncryptionExample.PutItemQueryItemWithBeacon(keyId); await CompoundBeaconSearchableEncryptionExample.PutItemQueryItemWithCompoundBeacon(keyId); await VirtualBeaconSearchableEncryptionExample.PutItemQueryItemWithVirtualBeacon(keyId); diff --git a/Examples/runtimes/net/src/TestUtils.cs b/Examples/runtimes/net/src/TestUtils.cs index 2fd898553..406790e86 100644 --- a/Examples/runtimes/net/src/TestUtils.cs +++ b/Examples/runtimes/net/src/TestUtils.cs @@ -23,6 +23,12 @@ public class TestUtils public static readonly string TEST_KMS_RSA_KEY_ID = "arn:aws:kms:us-west-2:658956600833:key/8b432da4-dde4-4bc3-a794-c7d68cbab5a6"; + + public static readonly string TEST_KMS_ECDH_KEY_ID_P256_SENDER = + "arn:aws:kms:us-west-2:370957321024:key/eabdf483-6be2-4d2d-8ee4-8c2583d416e9"; + + public static readonly string TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT = + "arn:aws:kms:us-west-2:370957321024:key/0265c8e9-5b6a-4055-8f70-63719e09fda5"; public static readonly string TEST_MRK_REPLICA_KEY_ID_US_EAST_1 = "arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"; diff --git a/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs b/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs new file mode 100644 index 000000000..c5754e92e --- /dev/null +++ b/Examples/runtimes/net/src/keyring/KmsEcdhKeyringExample.cs @@ -0,0 +1,378 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Amazon.DynamoDBv2.Model; +using Amazon.KeyManagementService; +using Amazon.KeyManagementService.Model; +using AWS.Cryptography.DbEncryptionSDK.DynamoDb; +using AWS.Cryptography.DbEncryptionSDK.StructuredEncryption; +using AWS.Cryptography.MaterialProviders; +using AWS.Cryptography.Primitives; +using Org.BouncyCastle.Utilities.IO.Pem; +using PemWriter = Org.BouncyCastle.OpenSsl.PemWriter; + +namespace Examples.keyring; + +public class KmsEcdhKeyringExample +{ + private static String EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME = + "KmsEccKeyringKeyringExamplePublicKeySender.pem"; + private static String EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME = + "KmsEccKeyringKeyringExamplePublicKeyRecipient.pem"; + + public static async Task KmsEcdhKeyringGetItemPutItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + var ecdhKeyArnSender = TestUtils.TEST_KMS_ECDH_KEY_ID_P256_SENDER; + + + // 1. Load public keys from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + MemoryStream publicKeySenderUtf8EncodedByteBuffer; + try + { + var publicKeyBytes = File.ReadAllText(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME); + PemReader pemReader = new PemReader(new StringReader(publicKeyBytes)); + publicKeySenderUtf8EncodedByteBuffer = new MemoryStream(pemReader.ReadPemObject().Content); + } + catch (IOException e) + { + throw new IOException("Exception while reading private key from file", e); + } + + MemoryStream publicKeyRecipientUtf8EncodedByteBuffer; + try + { + var publicKeyBytes = File.ReadAllText(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME); + PemReader pemReader = new PemReader(new StringReader(publicKeyBytes)); + publicKeyRecipientUtf8EncodedByteBuffer = new MemoryStream(pemReader.ReadPemObject().Content); + } + catch (IOException e) + { + throw new IOException("Exception while reading public key from file", e); + } + + // 2. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + var keyringInput = new CreateAwsKmsEcdhKeyringInput + { + // 2. Create a KMS ECDH keyring. + // This keyring takes in: + // - kmsClient + // - kmsKeyId: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + // - senderPublicKey: A ByteBuffer of a UTF-8 encoded public + // key for the key passed into kmsKeyId in DER format + // - recipientPublicKey: A ByteBuffer of a UTF-8 encoded public key + // for the recipient public key. + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KmsClient = new AmazonKeyManagementServiceClient(), + KeyAgreementScheme = new KmsEcdhStaticConfigurations + { + KmsPrivateKeyToStaticPublicKey = new KmsPrivateKeyToStaticPublicKeyInput + { + SenderKmsIdentifier = ecdhKeyArnSender, + SenderPublicKey = publicKeySenderUtf8EncodedByteBuffer, + RecipientPublicKey = publicKeyRecipientUtf8EncodedByteBuffer + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var kmsEcdhKeyring = matProv.CreateAwsKmsEcdhKeyring(keyringInput); + + await PutItemGetItemWithKeyring(kmsEcdhKeyring, ddbTableName); + } + + private static async Task KmsEcdhKeyringDiscoveryGetItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + var ecdhKeyArnRecipient = TestUtils.TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT; + + // 1. Create a KMS ECDH keyring. + // This keyring takes in: + // - kmsClient + // - recipientKmsIdentifier: Must be an ARN representing a KMS ECC key meant for KeyAgreement + // - curveSpec: The curve name where the public keys lie + var keyringInput = new CreateAwsKmsEcdhKeyringInput + { + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KmsClient = new AmazonKeyManagementServiceClient(), + KeyAgreementScheme = new KmsEcdhStaticConfigurations + { + KmsPublicKeyDiscovery = new KmsPublicKeyDiscoveryInput + { + RecipientKmsIdentifier = ecdhKeyArnRecipient + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var kmsEcdhKeyring = matProv.CreateAwsKmsEcdhKeyring(keyringInput); + + await GetItemWithKeyring(kmsEcdhKeyring, ddbTableName); + } + + private static async Task GetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddbTableName) + { + // 2. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = kmsEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 5. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 6. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("kmsEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + var getResponse = await ddb.GetItemAsync(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK); + var returnedItem = getResponse.Item; + Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); + } + + private static async Task PutItemGetItemWithKeyring(IKeyring kmsEcdhKeyring, string ddbTableName) + { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = kmsEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 6. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 7. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + var item = new Dictionary + { + ["partition_key"] = new AttributeValue("kmsEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" }, + ["sensitive_data"] = new AttributeValue("encrypt and sign me!") + }; + + var putRequest = new PutItemRequest + { + TableName = ddbTableName, + Item = item + }; + + var putResponse = await ddb.PutItemAsync(putRequest); + + // Demonstrate that PutItem succeeded + Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); + + // 8. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("kmsEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + var getResponse = await ddb.GetItemAsync(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK); + var returnedItem = getResponse.Item; + Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); + } + + public static async Task KmsEcdhKeyringExamples() + { + // You may provide your own ECC Keys in the files located at + // - EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME + // - EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME + // If these files are not present, this will get KMS ECC keys for you. + if (ShouldGetNewEccKeys()) + { + await WritePublicKeyPemForEccKey(TestUtils.TEST_KMS_ECDH_KEY_ID_P256_SENDER, EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME); + await WritePublicKeyPemForEccKey(TestUtils.TEST_KMS_ECDH_KEY_ID_P256_RECIPIENT, EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME); + } + + await KmsEcdhKeyringGetItemPutItem(); + await KmsEcdhKeyringDiscoveryGetItem(); + } + + private static async Task WritePublicKeyPemForEccKey(string kmsEcdhKeyArn, string exampleEccPublicKeySenderFilename) + { + // Safety check: Validate file is not present + if (File.Exists(exampleEccPublicKeySenderFilename)) + { + throw new ApplicationException("WritePublicKeyPemForEccKey will not overwrite existing PEM files"); + } + // This code will call KMS to get the public key for the KMS ECC key. + // You must have kms:GetPublicKey permissions on the key for this to succeed. + // The public key will be written to the file exampleEccPublicKeySenderFilename. + var getterForPublicKey = new AmazonKeyManagementServiceClient(); + var response = await getterForPublicKey.GetPublicKeyAsync( + new GetPublicKeyRequest { KeyId = kmsEcdhKeyArn } + ); + var publicKeyByteArray = response.PublicKey.ToArray(); + + StringWriter publicKeyStringWriter = new StringWriter(); + PemWriter publicKeyPemWriter = new PemWriter(publicKeyStringWriter); + publicKeyPemWriter.WriteObject(new PemObject("PUBLIC KEY", publicKeyByteArray)); + + var publicKeyUtf8EncodedByteBuffer = Encoding.UTF8.GetBytes(publicKeyStringWriter.ToString()); + var fc = new FileStream(exampleEccPublicKeySenderFilename, FileMode.Create, FileAccess.Write); + fc.Write(publicKeyUtf8EncodedByteBuffer); + fc.Close(); + } + + private static bool ShouldGetNewEccKeys() + { + // If keys already exists; do not overwrite existing keys. + if (File.Exists(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME) + || File.Exists(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME)) + { + return false; + } + + if (!File.Exists(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME) + && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME)) + { + throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME); + } + + if (File.Exists(EXAMPLE_ECC_PUBLIC_KEY_SENDER_FILENAME) + && !File.Exists(EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME)) + { + throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_RECIPIENT_FILENAME); + } + + return true; + } +} diff --git a/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs b/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs new file mode 100644 index 000000000..7aa06bebb --- /dev/null +++ b/Examples/runtimes/net/src/keyring/RawEcdhKeyringExample.cs @@ -0,0 +1,678 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using Amazon.DynamoDBv2.Model; +using AWS.Cryptography.DbEncryptionSDK.DynamoDb; +using AWS.Cryptography.DbEncryptionSDK.StructuredEncryption; +using AWS.Cryptography.MaterialProviders; +using ECDH; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.IO.Pem; +using Org.BouncyCastle.X509; +using software.amazon.cryptography.primitives.internaldafny.types; +using CreateRawEcdhKeyringInput = AWS.Cryptography.MaterialProviders.CreateRawEcdhKeyringInput; +using ECDHCurveSpec = AWS.Cryptography.Primitives.ECDHCurveSpec; +using PemWriter = Org.BouncyCastle.OpenSsl.PemWriter; +using RawEcdhStaticConfigurations = AWS.Cryptography.MaterialProviders.RawEcdhStaticConfigurations; + +namespace Examples.keyring; + +/* + This example sets up DynamoDb Encryption for the AWS SDK client + using the raw ECDH Keyring. This keyring takes in the sender's ECC + private key and the recipient's ECC Public Key to derive a shared secret. + The keyring uses the shared secret to derive a data key to protect the + data keys that encrypt and decrypt DynamoDb table items. + + This example takes in the sender's private key, the recipient's + public key, and the algorithm definition where the ECC keys lie. + This parameter takes in the sender's private key as a + UTF8 PEM-encoded (PKCS #8 PrivateKeyInfo structures), the recipient's + DER-encoded X.509 public key, also known as SubjectPublicKeyInfo (SPKI), + and the Curve Specification where the keys lie. + + This example encrypts a test item using the provided ECC keys and puts the + encrypted item to the provided DynamoDb table. Then, it gets the + item from the table and decrypts it. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + */ +public class RawEcdhKeyringExample +{ + private static readonly String EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER = "RawEcdhKeyringExamplePrivateKeySender.pem"; + private static readonly String EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT = "RawEcdhKeyringExamplePrivateKeyRecipient.pem"; + private static readonly String EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT = "RawEcdhKeyringExamplePublicKeyRecipient.pem"; + + private static async Task RawEcdhKeyringExampleGetItemPutItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + + // 1. Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + MemoryStream privateKeySenderUtf8EncodedByteBuffer; + try + { + privateKeySenderUtf8EncodedByteBuffer = new MemoryStream( + File.ReadAllBytes(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER)); + } + catch (IOException e) + { + throw new IOException("Exception while reading private key from file", e); + } + + MemoryStream publicKeyRecipientUtf8EncodedByteBuffer; + try + { + var publicKeyBytes = File.ReadAllText(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); + PemReader pemReader = new PemReader(new StringReader(publicKeyBytes)); + publicKeyRecipientUtf8EncodedByteBuffer = new MemoryStream(pemReader.ReadPemObject().Content); + } + catch (IOException e) + { + throw new IOException("Exception while reading public key from file", e); + } + + // 2. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + var keyringInput = new CreateRawEcdhKeyringInput + { + // This example uses keys that lie on the ECC Curve P256. + // The keyring supports curves P256, P384, and P521. + // On creation, the keyring verifies that all configured keys lie on the provided curve spec. + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KeyAgreementScheme = new RawEcdhStaticConfigurations + { + // This keyring is configured with a RawPrivateKeyToStaticPublicKey + // key agreement scheme. This means that both the sender's and recipient's + // keys are stored somewhere in memory. The recipient's public key MAY be + // a public key whose origin is an HSM like AWS KMS. + RawPrivateKeyToStaticPublicKey = new RawPrivateKeyToStaticPublicKeyInput + { + // Must be a UTF8 PEM-encoded private key + SenderStaticPrivateKey = privateKeySenderUtf8EncodedByteBuffer, + // Must be a UTF8 DER-encoded X.509 public key also known as SubjectPublicKeyInfo. + RecipientPublicKey = publicKeyRecipientUtf8EncodedByteBuffer + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); + + await PutGetExampleWithKeyring(rawEcdhKeyring, ddbTableName); + } + + private static async Task EphemeralRawEcdhKeyringPutItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + + // 1. Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the RawEcdhKeyringExamples method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + MemoryStream publicKeyRecipientUtf8EncodedByteBuffer; + try + { + var publicKeyBytes = File.ReadAllText(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); + PemReader pemReader = new PemReader(new StringReader(publicKeyBytes)); + publicKeyRecipientUtf8EncodedByteBuffer = new MemoryStream(pemReader.ReadPemObject().Content); + } + catch (IOException e) + { + throw new IOException("Exception while reading public key from file", e); + } + + // 2. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + var keyringInput = new CreateRawEcdhKeyringInput + { + // This example uses keys that lie on the ECC Curve P256. + // The keyring supports curves P256, P384, and P521. + // On creation, the keyring verifies that all configured keys lie on the provided curve spec. + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KeyAgreementScheme = new RawEcdhStaticConfigurations + { + // This keyring is configured with the EphemeralPrivateKeyToStaticPublicKey configuration. + // This configuration will always create a new + // key pair as the sender key pair for the key agreement operation. + // The ephemeral configuration can only encrypt data and CANNOT decrypt messages. + EphemeralPrivateKeyToStaticPublicKey = new EphemeralPrivateKeyToStaticPublicKeyInput + { + RecipientPublicKey = publicKeyRecipientUtf8EncodedByteBuffer + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); + + // A raw ecdh keyring with Ephemeral configuration cannot decrypt data since the key pair + // used as the sender is ephemeral. This means that at decrypt time it does not have + // the private key that corresponds to the public key that is stored on the message. + await PutExampleWithKeyring(rawEcdhKeyring, ddbTableName); + } + + private static async Task DiscoveryRawEcdhKeyringGetItem() + { + var ddbTableName = TestUtils.TEST_DDB_TABLE_NAME; + + // 1. Load key pair from UTF-8 encoded PEM files. + // You may provide your own PEM files to use here. + // If you do not, the main method in this class will generate PEM + // files for example use. Do not use these files for any other purpose. + MemoryStream privateKeyRecipientUtf8EncodedByteBuffer; + try + { + privateKeyRecipientUtf8EncodedByteBuffer = new MemoryStream( + File.ReadAllBytes(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)); + } + catch (IOException e) + { + throw new IOException("Exception while reading private key from file", e); + } + + // 2. Create the keyring. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + var keyringInput = new CreateRawEcdhKeyringInput + { + // This example uses keys that lie on the ECC Curve P256. + // The keyring supports curves P256, P384, and P521. + // On creation, the keyring verifies that all configured keys lie on the provided curve spec. + CurveSpec = ECDHCurveSpec.ECC_NIST_P256, + KeyAgreementScheme = new RawEcdhStaticConfigurations + { + // This keyring uses a discovery configuration. This configuration will check on decrypt + // if it is meant to decrypt the message by checking if the configured public key is stored on the message. + // The discovery configuration can only decrypt messages and CANNOT encrypt messages. + // The DynamoDb encryption client uses this to encrypt and decrypt items. + PublicKeyDiscovery = new PublicKeyDiscoveryInput + { + // Must be a UTF8 PEM-encoded private key + RecipientStaticPrivateKey = privateKeyRecipientUtf8EncodedByteBuffer + } + } + }; + var matProv = new MaterialProviders(new MaterialProvidersConfig()); + var rawEcdhKeyring = matProv.CreateRawEcdhKeyring(keyringInput); + + await GetExampleWithKeyring(rawEcdhKeyring, ddbTableName); + } + + private static async Task PutGetExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) + { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = rawEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 6. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 7. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + var item = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" }, + ["sensitive_data"] = new AttributeValue("encrypt and sign me!") + }; + + var putRequest = new PutItemRequest + { + TableName = ddbTableName, + Item = item + }; + + var putResponse = await ddb.PutItemAsync(putRequest); + + // Demonstrate that PutItem succeeded + Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); + + // 8. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + var getResponse = await ddb.GetItemAsync(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK); + var returnedItem = getResponse.Item; + Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); + } + private static async Task PutExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) + { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = rawEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 6. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 7. Put an item into our table using the above client. + // Before the item gets sent to DynamoDb, it will be encrypted + // client-side, according to our configuration. + var item = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" }, + ["sensitive_data"] = new AttributeValue("encrypt and sign me!") + }; + + var putRequest = new PutItemRequest + { + TableName = ddbTableName, + Item = item + }; + + var putResponse = await ddb.PutItemAsync(putRequest); + + // Demonstrate that PutItem succeeded + Debug.Assert(putResponse.HttpStatusCode == HttpStatusCode.OK); + + // 8. Try to get the item and assert that the ephemeral keyring configuration + // cannot decrypt data. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + try + { + await ddb.GetItemAsync(getRequest); + } + catch (AwsCryptographicMaterialProvidersException e) + { + Debug.Assert(e.Message.Contains("EphemeralPrivateKeyToStaticPublicKey Key Agreement Scheme is forbidden on decrypt.")); + } + + } + + private static async Task GetExampleWithKeyring(IKeyring rawEcdhKeyring, string ddbTableName) + { + // 3. Configure which attributes are encrypted and/or signed when writing new items. + // For each attribute that may exist on the items we plan to write to our DynamoDbTable, + // we must explicitly configure how they should be treated during item encryption: + // - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature + // - SIGN_ONLY: The attribute not encrypted, but is still included in the signature + // - DO_NOTHING: The attribute is not encrypted and not included in the signature + var attributeActionsOnEncrypt = new Dictionary + { + ["partition_key"] = CryptoAction.SIGN_ONLY, // Our partition attribute must be SIGN_ONLY + ["sort_key"] = CryptoAction.SIGN_ONLY, // Our sort attribute must be SIGN_ONLY + ["sensitive_data"] = CryptoAction.ENCRYPT_AND_SIGN + }; + + // 4. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can easily make the corresponding update to your `attributeActionsOnEncrypt` + // and immediately start writing to that new attribute, without + // any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we currently authenticate all attributes. To make it easier to + // add unauthenticated attributes in the future, we define a prefix ":" for such attributes. + const String unsignAttrPrefix = ":"; + + // 5. Create the DynamoDb Encryption configuration for the table we will be writing to. + var tableConfigs = new Dictionary + { + [ddbTableName] = new DynamoDbTableEncryptionConfig + { + LogicalTableName = ddbTableName, + PartitionKeyName = "partition_key", + SortKeyName = "sort_key", + AttributeActionsOnEncrypt = attributeActionsOnEncrypt, + Keyring = rawEcdhKeyring, + AllowedUnsignedAttributePrefix = unsignAttrPrefix + } + }; + + // 6. Create a new AWS SDK DynamoDb client using the config above + var ddb = new Client.DynamoDbClient( + new DynamoDbTablesEncryptionConfig { TableEncryptionConfigs = tableConfigs }); + + // 7. Attempt to put an item into our table using the above client. + // Assert that Discovery configuration cannot encrypt data + var item = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" }, + ["sensitive_data"] = new AttributeValue("encrypt and sign me!") + }; + + var putRequest = new PutItemRequest + { + TableName = ddbTableName, + Item = item + }; + + try + { + await ddb.PutItemAsync(putRequest); + } + catch (AwsCryptographicMaterialProvidersException e) + { + Debug.Assert(e.Message.Contains("PublicKeyDiscovery Key Agreement Scheme is forbidden on encrypt.")); + } + + // 8. Get the item back from our table using the same client. + // The client will decrypt the item client-side, and return + // back the original item. + var keyToGet = new Dictionary + { + ["partition_key"] = new AttributeValue("rawEcdhKeyringItem"), + ["sort_key"] = new AttributeValue { N = "0" } + }; + + var getRequest = new GetItemRequest + { + Key = keyToGet, + TableName = ddbTableName + }; + + var getResponse = await ddb.GetItemAsync(getRequest); + + // Demonstrate that GetItem succeeded and returned the decrypted item + Debug.Assert(getResponse.HttpStatusCode == HttpStatusCode.OK); + var returnedItem = getResponse.Item; + Debug.Assert(returnedItem["sensitive_data"].S.Equals("encrypt and sign me!")); + } + + public static async Task RawEcdhKeyringExamples() + { + // You may provide your own ECC Keys in the files located at + // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + // - EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + // - EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // If these files are not present, this will generate keys for you. + if (ShouldGenerateNewEccKeys()) + { + GenerateEccKeyPairs(); + } + + await RawEcdhKeyringExampleGetItemPutItem(); + await EphemeralRawEcdhKeyringPutItem(); + await DiscoveryRawEcdhKeyringGetItem(); + } + + + private static bool ShouldGenerateNewEccKeys() + { + // If keys already exists; do not overwrite existing keys. + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + return false; + } + + // If only two keys are present; throw exception + if (!File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + throw new ApplicationException("Missing private key file at: " + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER); + } + + // If only two keys are present; throw exception + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + && File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + && !File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + throw new ApplicationException("Missing private key file at: " + EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT); + } + + // If only two keys are present; throw exception + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + && !File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + && File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + throw new ApplicationException("Missing public key file at: " + EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); + } + + return true; + } + + private static void GenerateEccKeyPairs() + { + // Safety check; Validate neither file is present + if (File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER) + || File.Exists(EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT) + || File.Exists(EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT)) + { + throw new ApplicationException("generateEccKeyPairs will not overwrite existing PEM files."); + } + // This code will generate new ECC Keys for example use. + // The keys will be written to the files: + // - private sender: EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER + // - private recipient: EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT + // - public recipient: EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT + // This example uses BouncyCastle's KeyPairGenerator to generate the key pair. + // In practice, you should not generate this in your code, and should instead + // retrieve this key from a secure key management system (e.g. HSM) + // These examples only demonstrate using the P256 curve while the keyring accepts + // P256, P384, or P521. + // These keys are created here for example purposes only. + ECKeyPairGenerator generator; + try + { + generator = new ECKeyPairGenerator(); + SecureRandom rng = new SecureRandom(); + X9ECParameters p = ECNamedCurveTable.GetByName("secp256r1"); + + var domainParameters = new ECDomainParameters(p.Curve, p.G, p.N, p.H); + generator.Init(new ECKeyGenerationParameters(domainParameters, rng)); + + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + AsymmetricCipherKeyPair senderKeyPair = generator.GenerateKeyPair(); + AsymmetricCipherKeyPair recipientKeyPair = generator.GenerateKeyPair(); + + WritePrivateKey(senderKeyPair.Private, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_SENDER); + WritePrivateKey(recipientKeyPair.Private, EXAMPLE_ECC_PRIVATE_KEY_FILENAME_RECIPIENT); + WritePublicKey(recipientKeyPair, "secp256r1", EXAMPLE_ECC_PUBLIC_KEY_FILENAME_RECIPIENT); + } + + private static void WritePrivateKey(AsymmetricKeyParameter privateKey, string fileName) + { + var privateKeyStringWriter = new StringWriter(); + var pemWriter = new PemWriter(privateKeyStringWriter); + pemWriter.WriteObject(privateKey); + + var privateKeyUtf8EncodedBytes = Encoding.UTF8.GetBytes(privateKeyStringWriter.ToString()); + var fc = new FileStream(fileName, FileMode.Create, FileAccess.Write); + fc.Write(privateKeyUtf8EncodedBytes); + fc.Close(); + } + + private static void WritePublicKey(AsymmetricCipherKeyPair publicKey, string curveName, string fileName) + { + var ecdhCurveSpecFromCurveName = ToEcdhCurveSpec(curveName); + var spki = KeyGeneration.SerializePublicKey(publicKey, ecdhCurveSpecFromCurveName).CloneAsArray(); + + var publicKeyStringWriter = new StringWriter(); + var publicKeyPemWriter = new PemWriter(publicKeyStringWriter); + publicKeyPemWriter.WriteObject(new PemObject("PUBLIC KEY", spki)); + var publicKeyUtf8EncodedBytes = Encoding.UTF8.GetBytes(publicKeyStringWriter.ToString()); + var fc = new FileStream(fileName, FileMode.Create, FileAccess.Write); + fc.Write(publicKeyUtf8EncodedBytes); + fc.Close(); + } + + private static _IECDHCurveSpec ToEcdhCurveSpec(string curveName) + { + switch (curveName) + { + case "secp256r1": return new ECDHCurveSpec_ECC__NIST__P256(); + case "secp384r1": return new ECDHCurveSpec_ECC__NIST__P384(); + case "secp521r1": return new ECDHCurveSpec_ECC__NIST__P521(); + } + throw new ApplicationException("Unknown curve: " + curveName); + } +}