Skip to content

chore(.NET): update hierarchy examples #618

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: mainline
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.DynamoDBv2;
using Amazon.KeyManagementService;
using AWS.Cryptography.EncryptionSDK;
Expand Down Expand Up @@ -34,9 +37,13 @@
/// or you can implement an interface that selects
/// the Branch Key based on the Encryption Context.
///
/// This example demonstrates configuring a Hierarchical Keyring
/// with a Branch Key ID Supplier to encrypt and decrypt data for
/// two separate tenants.
/// This example demonstrates how to:
/// - Create a key store
/// - Create a branch key
/// - Version a branch key
/// - Configure a Hierarchical Keyring
/// with a static branch key configuration to ensure we are restricting
/// access to a single tenant.
///
/// This example requires access to the DDB Table where you are storing the Branch Keys.
/// This table must be configured with the following
Expand All @@ -51,158 +58,121 @@
/// </summary>
public class AwsKmsHierarchicalKeyring
{
// THESE ARE PUBLIC RESOURCES DO NOT USE IN A PRODUCTION ENVIRONMENT
private static string branchKeyIdA = "43574aa0-de30-424e-bad4-0b06f6e89478";
private static string branchKeyIdB = "a2f4be37-15ec-489a-bcb5-dcce1f6bfe84";
private static void Run(MemoryStream plaintext)
private void Run(MemoryStream plaintext)
{
var kmsConfig = new KMSConfiguration { KmsKeyArn = ExampleUtils.ExampleUtils.GetBranchKeyArn() };
// Create an AWS KMS Configuration to use with your KeyStore.
// The KMS Configuration MUST have the right access to the resources in the KeyStore.
var keystoreConfig = new KeyStoreConfig
// 1. Configure your KeyStore resource.
// `ddbTableName` is the name you want for the DDB table that
// will back your keystore.
// `kmsKeyArn` is the KMS Key that will protect your branch keys and beacon keys
// when they are stored in your DDB table.
var keyStoreConfig = new KeyStoreConfig
{
// Client MUST have permissions to decrypt kmsConfig.KmsKeyArn
KmsClient = new AmazonKeyManagementServiceClient(),
KmsConfiguration = kmsConfig,
KmsConfiguration = new KMSConfiguration{ KmsKeyArn = ExampleUtils.ExampleUtils.GetBranchKeyArn() },
DdbTableName = ExampleUtils.ExampleUtils.GetKeyStoreName(),
DdbClient = new AmazonDynamoDBClient(),
LogicalKeyStoreName = ExampleUtils.ExampleUtils.GetLogicalKeyStoreName()
};
var materialProviders = new MaterialProviders(new MaterialProvidersConfig());
var keystore = new KeyStore(keystoreConfig);
var keyStore = new KeyStore(keyStoreConfig);

// 2. Create the DynamoDb table that will store the branch keys and beacon keys.
// This checks if the correct table already exists at `ddbTableName`
// by using the DescribeTable API. If no table exists,
// it will create one. If a table exists, it will verify
// the table's configuration and will error if the configuration is incorrect.
// It may take a couple minutes for the table to become ACTIVE,
// at which point it is ready to store branch and beacon keys.

// keyStore.CreateKeyStore(new CreateKeyStoreInput());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why should we not run this?

The Description of in 2 states that IF the table already exists,
it will validate the table.

Therefore, it SHOULD BE safe to run this,
as it will NOT create a table.

Though we may need to grant the CI Role Describe Table permission.


// We have already created a Table with the specified configuration.
// For testing purposes we will not create a table when we run this example.
Comment on lines +98 to +99
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// We have already created a Table with the specified configuration.
// For testing purposes we will not create a table when we run this example.


// Create a branch key supplier that maps the branch key id to a more readable format
var branchKeySupplier = new ExampleBranchKeySupplier(branchKeyIdA, branchKeyIdB);
// 3. Create a new branch key and beacon key in our KeyStore.
// Both the branch key and the beacon key will share an Id.
// This creation is eventually consistent. See the CreateBranchKey
// Example for how to populate this table.

// Create an AWS Hierarchical Keyring with the branch key id supplier
// var branchKeyId = CreateBranchKey.createBranchKey();

// For testing purposes we will not create a table when we run this example.
// We will use an existing branch key created using the above code to run this
// example.

// THIS IS A PUBLIC RESOURCE DO NOT USE IN A PRODUCTION ENVIRONMENT
var branchKeyId = "43574aa0-de30-424e-bad4-0b06f6e89478";

// 4. Create the Hierarchical Keyring, using the Branch Key ID Supplier above.
// With this configuration, the AWS SDK Client ultimately configured will be capable
// of encrypting or decrypting items for either tenant (assuming correct KMS access).
// If you want to restrict the client to only encrypt or decrypt for a single tenant,
// configure this Hierarchical Keyring using `BranchKeyId = tenant1BranchKeyId` instead
// of `BranchKeyIdSupplier = branchKeySupplier`.
var createKeyringInput = new CreateAwsKmsHierarchicalKeyringInput
{
KeyStore = keystore,
// This branchKeyId you have configured your keyring with MUST be decrypted by the
// KMS config in the keystore and therefore MUST have the right permissions.
BranchKeyIdSupplier = branchKeySupplier,
KeyStore = keyStore,
BranchKeyId = branchKeyId,
// The value provided to `EntryCapacity` dictates how many branch keys will be held locally
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Is this true?
I think that multiple of versions of the same Branch Key
count towards the Entry Capacity.

Suggested change
// The value provided to `EntryCapacity` dictates how many branch keys will be held locally
// The value provided to `EntryCapacity` dictates how many branch key versions will be held locally

Cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} },
// This dictates how often we call back to KMS to authorize use of the branch keys
TtlSeconds = 600
};
var materialProviders = new MaterialProviders(new MaterialProvidersConfig());
var keyring = materialProviders.CreateAwsKmsHierarchicalKeyring(createKeyringInput);

// The Branch Key Id supplier uses the encryption context to determine which branch key id
// will be used to encrypt data.
var encryptionContextA = new Dictionary<string, string>()

// 5. Create an encryption context
// Most encrypted data should have an associated encryption context
// to protect integrity. This sample uses placeholder values.
// For more information see:
// blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management
var encryptionContext = new Dictionary<string, string>()
{
// We will encrypt with TenantKeyA
{"tenant", "TenantA"},
{"encryption", "context"},
{"is not", "secret"},
{"but adds", "useful metadata"},
{"that can help you", "be confident that"},
{"the data you are handling", "is what you think it is"}
};
var encryptionContextB = new Dictionary<string, string>()
{
// We will encrypt with TenantKeyB
{"tenant", "TenantB"},
{"encryption", "context"},
{"is not", "secret"},
{"but adds", "useful metadata"},
{"that can help you", "be confident that"},
{"the data you are handling", "is what you think it is"}
};

var encryptionSDK = new ESDK(new AwsEncryptionSdkConfig());
var encryptionSdk = new ESDK(new AwsEncryptionSdkConfig());

var encryptInputA = new EncryptInput
var encryptInput = new EncryptInput
{
Plaintext = plaintext,
Keyring = keyring,
// We will encrypt with TenantKeyA
EncryptionContext = encryptionContextA
};

var encryptInputB = new EncryptInput{
Plaintext = plaintext,
Keyring = keyring,
// We will encrypt with TenantKeyB
EncryptionContext = encryptionContextB
EncryptionContext = encryptionContext
};

var encryptOutputA = encryptionSDK.Encrypt(encryptInputA);
var encryptOutputB = encryptionSDK.Encrypt(encryptInputB);
// 6. Encrypt the Data
var encryptOutput = encryptionSdk.Encrypt(encryptInput);

// To attest that TenantKeyB cannot decrypt a message written by TenantKeyA
// let's construct more restrictive hierarchical keyrings.

var createHierarchicalKeyringA = new CreateAwsKmsHierarchicalKeyringInput()
{
KeyStore = keystore,
BranchKeyId = branchKeyIdA,
Cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} },
TtlSeconds = 600
};
var hierarchicalKeyringA = materialProviders.CreateAwsKmsHierarchicalKeyring(createHierarchicalKeyringA);
// Demonstrate that the ciphertext and plaintext are different.
Assert.NotEqual(encryptOutput.Ciphertext.ToArray(), plaintext.ToArray());

var createHierarchicalKeyringB = new CreateAwsKmsHierarchicalKeyringInput()
var decryptInput = new DecryptInput
{
KeyStore = keystore,
BranchKeyId = branchKeyIdB,
Cache = new CacheType { Default = new DefaultCache{EntryCapacity = 100} },
TtlSeconds = 600
Ciphertext = encryptOutput.Ciphertext,
EncryptionContext = encryptionContext,
Keyring = keyring
};
var hierarchicalKeyringB = materialProviders.CreateAwsKmsHierarchicalKeyring(createHierarchicalKeyringB);

var decryptFailed = false;
try
{
// Try to use keyring for Tenant B to decrypt a message encrypted with Tenant A's key
// Expected to fail.
var decryptInput = new DecryptInput
{
Ciphertext = encryptOutputA.Ciphertext,
Keyring = hierarchicalKeyringB,
};
encryptionSDK.Decrypt(decryptInput);
}
catch (AwsCryptographicMaterialProvidersException)
{
decryptFailed = true;
}
Assert.True(decryptFailed);
// 7. Decrypt the Data
var decryptOutput = encryptionSdk.Decrypt(decryptInput);

decryptFailed = false;
try
{
// Try to use keyring for Tenant A to decrypt a message encrypted with Tenant B's key
// Expected to fail.
var decryptInput = new DecryptInput
{
Ciphertext = encryptOutputB.Ciphertext,
Keyring = hierarchicalKeyringA,
};
encryptionSDK.Decrypt(decryptInput);
}
catch (AwsCryptographicMaterialProvidersException)
{
decryptFailed = true;
}
Assert.True(decryptFailed);

// Decrypt your encrypted data using the same keyring you used on encrypt.
encryptionSDK.Decrypt(new DecryptInput {
Ciphertext = encryptOutputA.Ciphertext,
Keyring = keyring }
);
encryptionSDK.Decrypt(new DecryptInput {
Ciphertext = encryptOutputB.Ciphertext,
Keyring = keyring }
);

// Demonstrate that the decrypted ciphertext and plaintext are the same
Assert.Equal(decryptOutput.Plaintext.ToArray(), plaintext.ToArray());

// 8. Version the Branch Key in our KeyStore.
// Only the branch key will be rotated.
// This rotation is eventually consistent. See the VersionBranchKey
// Example for how to version a branch key.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: Can you describe Version as Rotate?
It is implied here, but not explicitly said.


// For testing purposes we will not version this key when we run this example.
// VersionBranchKey.versionBranchKey(branchKeyId);
}

// We test examples to ensure they remain up-to-date.
[Fact]
public void TestAwsKmsHierarchicalKeyringExample()

public void TestAwsKmsHierarchicalKeyring()
{
Run(ExampleUtils.ExampleUtils.GetPlaintextStream());
}
Expand Down
Loading