Skip to content

Commit a44ef3f

Browse files
committed
chore(python): examples for keyrings
1 parent 68ceeb3 commit a44ef3f

22 files changed

+1673
-19
lines changed

Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/keyring/SharedCacheAcrossHierarchicalKeyringsExample.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(
180180
final IKeyring hierarchicalKeyring1 =
181181
matProv.CreateAwsKmsHierarchicalKeyring(keyringInput1);
182182

183-
// 4. Configure which attributes are encrypted and/or signed when writing new items.
183+
// 5. Configure which attributes are encrypted and/or signed when writing new items.
184184
// For each attribute that may exist on the items we plan to write to our DynamoDbTable,
185185
// we must explicitly configure how they should be treated during item encryption:
186186
// - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
@@ -194,14 +194,14 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(
194194
CryptoAction.ENCRYPT_AND_SIGN
195195
);
196196

197-
// 5. Get the DDB Client for Hierarchical Keyring 1.
197+
// 6. Get the DDB Client for Hierarchical Keyring 1.
198198
final DynamoDbClient ddbClient1 = GetDdbClient(
199199
ddbTableName,
200200
hierarchicalKeyring1,
201201
attributeActionsOnEncrypt
202202
);
203203

204-
// 6. Encrypt Decrypt roundtrip with ddbClient1
204+
// 7. Encrypt Decrypt roundtrip with ddbClient1
205205
PutGetItems(ddbTableName, ddbClient1);
206206

207207
// Through the above encrypt and decrypt roundtrip, the cache will be populated and
@@ -210,7 +210,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(
210210
// - Same Logical Key Store Name of the Key Store for the Hierarchical Keyring
211211
// - Same Branch Key ID
212212

213-
// 7. Configure your KeyStore resource keystore2.
213+
// 8. Configure your KeyStore resource keystore2.
214214
// This SHOULD be the same configuration that you used
215215
// to initially create and populate your physical KeyStore.
216216
// Note that keyStoreTableName is the physical Key Store,
@@ -243,7 +243,7 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(
243243
)
244244
.build();
245245

246-
// 8. Create the Hierarchical Keyring HK2 with Key Store instance K2, the shared Cache
246+
// 9. Create the Hierarchical Keyring HK2 with Key Store instance K2, the shared Cache
247247
// and the same partitionId and BranchKeyId used in HK1 because we want to share cache entries
248248
// (and experience cache HITS).
249249

@@ -262,14 +262,14 @@ public static void SharedCacheAcrossHierarchicalKeyringsGetItemPutItem(
262262
final IKeyring hierarchicalKeyring2 =
263263
matProv.CreateAwsKmsHierarchicalKeyring(keyringInput2);
264264

265-
// 9. Get the DDB Client for Hierarchical Keyring 2.
265+
// 10. Get the DDB Client for Hierarchical Keyring 2.
266266
final DynamoDbClient ddbClient2 = GetDdbClient(
267267
ddbTableName,
268268
hierarchicalKeyring2,
269269
attributeActionsOnEncrypt
270270
);
271271

272-
// 10. Encrypt Decrypt roundtrip with ddbClient2
272+
// 11. Encrypt Decrypt roundtrip with ddbClient2
273273
PutGetItems(ddbTableName, ddbClient2);
274274
}
275275

Examples/runtimes/python/DynamoDBEncryption/src/create_keystore_key_example.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,18 @@
33
"""
44
Example for creating a new key in a KeyStore.
55
6-
The Hierarchical Keyring Example and Searchable Encryption Examples
7-
rely on the existence of a DDB-backed key store with pre-existing
8-
branch key material or beacon key material.
6+
The Hierarchical Keyring Example and Searchable Encryption Examples rely on the
7+
existence of a DDB-backed key store with pre-existing branch key material or
8+
beacon key material.
99
10-
See the "Create KeyStore Table Example" for how to first set up
11-
the DDB Table that will back this KeyStore.
10+
See the "Create KeyStore Table Example" for how to first set up the DDB Table
11+
that will back this KeyStore.
1212
13-
This example demonstrates configuring a KeyStore and then
14-
using a helper method to create a branch key and beacon key
15-
that share the same Id, then return that Id.
16-
We will always create a new beacon key alongside a new branch key,
17-
even if you are not using searchable encryption.
13+
Demonstrates configuring a KeyStore and using a helper method to create a branch
14+
key and beacon key that share the same Id. A new beacon key is always created
15+
alongside a new branch key, even if searchable encryption is not being used.
1816
19-
This key creation should occur within your control plane.
17+
Note: This key creation should occur within your control plane.
2018
"""
2119

2220
import boto3
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
Example for creating a DynamoDB table for use as a KeyStore.
5+
6+
The Hierarchical Keyring Example and Searchable Encryption Examples rely on the
7+
existence of a DDB-backed key store with pre-existing branch key material or
8+
beacon key material.
9+
10+
Shows how to configure a KeyStore and use a helper method to create the DDB table
11+
that will be used to persist branch keys and beacons keys for this KeyStore.
12+
13+
This table creation should occur within your control plane and only needs to occur
14+
once. While not demonstrated in this example, you should additionally use the
15+
`VersionKey` API on the KeyStore to periodically rotate your branch key material.
16+
"""
17+
18+
import boto3
19+
from aws_cryptographic_material_providers.keystore.client import KeyStore
20+
from aws_cryptographic_material_providers.keystore.config import KeyStoreConfig
21+
from aws_cryptographic_material_providers.keystore.models import (
22+
CreateKeyStoreInput,
23+
KMSConfigurationKmsKeyArn,
24+
)
25+
26+
27+
def keystore_create_table(keystore_table_name: str, logical_keystore_name: str, kms_key_arn: str):
28+
"""
29+
Create KeyStore Table Example.
30+
31+
:param keystore_table_name: The name of the DynamoDB table to create
32+
:param logical_keystore_name: The logical name for this keystore
33+
:param kms_key_arn: The ARN of the KMS key to use for protecting branch keys
34+
"""
35+
# 1. Configure your KeyStore resource.
36+
# `ddb_table_name` is the name you want for the DDB table that
37+
# will back your keystore.
38+
# `kms_key_arn` is the KMS Key that will protect your branch keys and beacon keys
39+
# when they are stored in your DDB table.
40+
keystore = KeyStore(
41+
config=KeyStoreConfig(
42+
ddb_client=boto3.client("dynamodb"),
43+
ddb_table_name=keystore_table_name,
44+
logical_key_store_name=logical_keystore_name,
45+
kms_client=boto3.client("kms"),
46+
kms_configuration=KMSConfigurationKmsKeyArn(kms_key_arn),
47+
)
48+
)
49+
50+
# 2. Create the DynamoDb table that will store the branch keys and beacon keys.
51+
# This checks if the correct table already exists at `ddb_table_name`
52+
# by using the DescribeTable API. If no table exists,
53+
# it will create one. If a table exists, it will verify
54+
# the table's configuration and will error if the configuration is incorrect.
55+
keystore.create_key_store(input=CreateKeyStoreInput())
56+
# It may take a couple of minutes for the table to become ACTIVE,
57+
# at which point it is ready to store branch and beacon keys.
58+
# See the Create KeyStore Key Example for how to populate
59+
# this table.

Examples/runtimes/python/DynamoDBEncryption/src/item_encryptor/encrypt_decrypt_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
)
4646

4747

48-
def encrypt_decrypt_example(kms_key_id: str, ddb_table_name: str) -> None:
48+
def encrypt_decrypt_example(kms_key_id: str, ddb_table_name: str):
4949
"""Encrypt and decrypt an item with an ItemEncryptor."""
5050
# 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data.
5151
# For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Stub to allow relative imports of examples from tests."""
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
Example demonstrating DynamoDb Encryption using a KMS RSA Keyring.
5+
6+
The KMS RSA Keyring uses a KMS RSA key pair to encrypt and decrypt records. The client
7+
uses the downloaded public key to encrypt items it adds to the table. The keyring
8+
uses the private key to decrypt existing table items it retrieves by calling
9+
KMS' decrypt API.
10+
11+
Running this example requires access to the DDB Table whose name is provided
12+
in CLI arguments. This table must be configured with the following primary key
13+
configuration:
14+
- Partition key is named "partition_key" with type (S)
15+
- Sort key is named "sort_key" with type (S)
16+
17+
The example also requires access to a KMS RSA key. Our tests provide a KMS RSA
18+
ARN that anyone can use, but you can also provide your own KMS RSA key.
19+
To use your own KMS RSA key, you must have either:
20+
- Its public key downloaded in a UTF-8 encoded PEM file
21+
- kms:GetPublicKey permissions on that key
22+
23+
If you do not have the public key downloaded, running this example through its
24+
main method will download the public key for you by calling kms:GetPublicKey.
25+
You must also have kms:Decrypt permissions on the KMS RSA key.
26+
"""
27+
28+
import os
29+
30+
import boto3
31+
from aws_cryptographic_material_providers.mpl import AwsCryptographicMaterialProviders
32+
from aws_cryptographic_material_providers.mpl.config import MaterialProvidersConfig
33+
from aws_cryptographic_material_providers.mpl.models import (
34+
CreateAwsKmsRsaKeyringInput,
35+
DBEAlgorithmSuiteId,
36+
)
37+
from aws_dbesdk_dynamodb.encrypted.client import EncryptedClient
38+
from aws_dbesdk_dynamodb.structures.dynamodb import (
39+
DynamoDbTableEncryptionConfig,
40+
DynamoDbTablesEncryptionConfig,
41+
)
42+
from aws_dbesdk_dynamodb.structures.structured_encryption import (
43+
CryptoAction,
44+
)
45+
from cryptography.hazmat.primitives import serialization
46+
47+
DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME = "KmsRsaKeyringExamplePublicKey.pem"
48+
49+
50+
def kms_rsa_keyring_example(
51+
ddb_table_name: str, rsa_key_arn: str, rsa_public_key_filename: str = DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME
52+
):
53+
"""
54+
Create a KMS RSA keyring and use it to encrypt/decrypt DynamoDB items.
55+
56+
:param ddb_table_name: The name of the DynamoDB table
57+
:param rsa_key_arn: ARN of the KMS RSA key
58+
:param rsa_public_key_filename: Path to the public key PEM file
59+
"""
60+
# 1. Load UTF-8 encoded public key PEM file.
61+
# You may have an RSA public key file already defined.
62+
# If not, the main method in this class will call
63+
# the KMS RSA key, retrieve its public key, and store it
64+
# in a PEM file for example use.
65+
try:
66+
with open(rsa_public_key_filename, "rb") as f:
67+
public_key_utf8_encoded = f.read()
68+
except IOError as e:
69+
raise RuntimeError("IOError while reading public key from file") from e
70+
71+
# 2. Create a KMS RSA keyring.
72+
# This keyring takes in:
73+
# - kms_client
74+
# - kms_key_id: Must be an ARN representing a KMS RSA key
75+
# - public_key: A ByteBuffer of a UTF-8 encoded PEM file representing the public
76+
# key for the key passed into kms_key_id
77+
# - encryption_algorithm: Must be either RSAES_OAEP_SHA_256 or RSAES_OAEP_SHA_1
78+
mat_prov = AwsCryptographicMaterialProviders(config=MaterialProvidersConfig())
79+
80+
keyring_input = CreateAwsKmsRsaKeyringInput(
81+
kms_key_id=rsa_key_arn,
82+
kms_client=boto3.client("kms"),
83+
public_key=public_key_utf8_encoded,
84+
encryption_algorithm="RSAES_OAEP_SHA_256",
85+
)
86+
87+
kms_rsa_keyring = mat_prov.create_aws_kms_rsa_keyring(input=keyring_input)
88+
89+
# 3. Configure which attributes are encrypted and/or signed when writing new items.
90+
# For each attribute that may exist on the items we plan to write to our DynamoDbTable,
91+
# we must explicitly configure how they should be treated during item encryption:
92+
# - ENCRYPT_AND_SIGN: The attribute is encrypted and included in the signature
93+
# - SIGN_ONLY: The attribute not encrypted, but is still included in the signature
94+
# - DO_NOTHING: The attribute is not encrypted and not included in the signature
95+
attribute_actions = {
96+
"partition_key": CryptoAction.SIGN_ONLY, # Our partition attribute must be SIGN_ONLY
97+
"sort_key": CryptoAction.SIGN_ONLY, # Our sort attribute must be SIGN_ONLY
98+
"sensitive_data": CryptoAction.ENCRYPT_AND_SIGN,
99+
}
100+
101+
# 4. Configure which attributes we expect to be included in the signature
102+
# when reading items. There are two options for configuring this:
103+
#
104+
# - (Recommended) Configure `allowed_unsigned_attribute_prefix`:
105+
# When defining your DynamoDb schema and deciding on attribute names,
106+
# choose a distinguishing prefix (such as ":") for all attributes that
107+
# you do not want to include in the signature.
108+
# This has two main benefits:
109+
# - It is easier to reason about the security and authenticity of data within your item
110+
# when all unauthenticated data is easily distinguishable by their attribute name.
111+
# - If you need to add new unauthenticated attributes in the future,
112+
# you can easily make the corresponding update to your `attribute_actions`
113+
# and immediately start writing to that new attribute, without
114+
# any other configuration update needed.
115+
# Once you configure this field, it is not safe to update it.
116+
#
117+
# - Configure `allowed_unsigned_attributes`: You may also explicitly list
118+
# a set of attributes that should be considered unauthenticated when encountered
119+
# on read. Be careful if you use this configuration. Do not remove an attribute
120+
# name from this configuration, even if you are no longer writing with that attribute,
121+
# as old items may still include this attribute, and our configuration needs to know
122+
# to continue to exclude this attribute from the signature scope.
123+
# If you add new attribute names to this field, you must first deploy the update to this
124+
# field to all readers in your host fleet before deploying the update to start writing
125+
# with that new attribute.
126+
#
127+
# For this example, we currently authenticate all attributes. To make it easier to
128+
# add unauthenticated attributes in the future, we define a prefix ":" for such attributes.
129+
unsign_attr_prefix = ":"
130+
131+
# 5. Create the DynamoDb Encryption configuration for the table we will be writing to.
132+
# Note: To use the KMS RSA keyring, your table config must specify an algorithmSuite
133+
# that does not use asymmetric signing.
134+
table_config = DynamoDbTableEncryptionConfig(
135+
logical_table_name=ddb_table_name,
136+
partition_key_name="partition_key",
137+
sort_key_name="sort_key",
138+
attribute_actions_on_encrypt=attribute_actions,
139+
keyring=kms_rsa_keyring,
140+
allowed_unsigned_attribute_prefix=unsign_attr_prefix,
141+
# Specify algorithmSuite without asymmetric signing here
142+
# As of v3.0.0, the only supported algorithmSuite without asymmetric signing is
143+
# ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384.
144+
algorithm_suite_id=DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384,
145+
)
146+
147+
table_configs = {ddb_table_name: table_config}
148+
tables_config = DynamoDbTablesEncryptionConfig(table_encryption_configs=table_configs)
149+
150+
# 6. Create the EncryptedClient
151+
ddb_client = boto3.client("dynamodb")
152+
encrypted_ddb_client = EncryptedClient(client=ddb_client, encryption_config=tables_config)
153+
154+
# 7. Put an item into our table using the above client.
155+
# Before the item gets sent to DynamoDb, it will be encrypted
156+
# client-side using the KMS RSA keyring.
157+
item = {
158+
"partition_key": {"S": "awsKmsRsaKeyringItem"},
159+
"sort_key": {"N": "0"},
160+
"sensitive_data": {"S": "encrypt and sign me!"},
161+
}
162+
163+
put_response = encrypted_ddb_client.put_item(TableName=ddb_table_name, Item=item)
164+
165+
# Demonstrate that PutItem succeeded
166+
assert put_response["ResponseMetadata"]["HTTPStatusCode"] == 200
167+
168+
# 8. Get the item back from our table using the client.
169+
# The client will decrypt the item client-side using the RSA keyring
170+
# and return the original item.
171+
key_to_get = {"partition_key": {"S": "awsKmsRsaKeyringItem"}, "sort_key": {"N": "0"}}
172+
173+
get_response = encrypted_ddb_client.get_item(TableName=ddb_table_name, Key=key_to_get)
174+
175+
# Demonstrate that GetItem succeeded and returned the decrypted item
176+
assert get_response["ResponseMetadata"]["HTTPStatusCode"] == 200
177+
returned_item = get_response["Item"]
178+
assert returned_item["sensitive_data"]["S"] == "encrypt and sign me!"
179+
180+
181+
def should_get_new_public_key(rsa_public_key_filename: str = DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME) -> bool:
182+
"""
183+
Check if we need to get a new public key.
184+
185+
:param rsa_public_key_filename: Path to the public key PEM file
186+
:return: True if we need to get a new public key, False otherwise
187+
"""
188+
# Check if a public key file already exists
189+
public_key_file = os.path.exists(rsa_public_key_filename)
190+
191+
# If a public key file already exists: do not overwrite existing file
192+
if public_key_file:
193+
return False
194+
195+
# If file is not present, generate a new key pair
196+
return True
197+
198+
199+
def write_public_key_pem_for_rsa_key(
200+
rsa_key_arn: str, rsa_public_key_filename: str = DEFAULT_EXAMPLE_RSA_PUBLIC_KEY_FILENAME
201+
):
202+
"""
203+
Get the public key from KMS and write it to a PEM file.
204+
205+
:param rsa_key_arn: The ARN of the KMS RSA key
206+
:param rsa_public_key_filename: Path to write the public key PEM file
207+
"""
208+
# Safety check: Validate file is not present
209+
if os.path.exists(rsa_public_key_filename):
210+
raise RuntimeError("getRsaPublicKey will not overwrite existing PEM files")
211+
212+
# This code will call KMS to get the public key for the KMS RSA key.
213+
# You must have kms:GetPublicKey permissions on the key for this to succeed.
214+
# The public key will be written to the file EXAMPLE_RSA_PUBLIC_KEY_FILENAME.
215+
kms_client = boto3.client("kms")
216+
response = kms_client.get_public_key(KeyId=rsa_key_arn)
217+
public_key_bytes = response["PublicKey"]
218+
219+
# Convert the public key to PEM format
220+
public_key = serialization.load_der_public_key(public_key_bytes)
221+
pem_data = public_key.public_bytes(
222+
encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo
223+
)
224+
225+
# Write the PEM file
226+
try:
227+
with open(rsa_public_key_filename, "wb") as f:
228+
f.write(pem_data)
229+
except IOError as e:
230+
raise RuntimeError("IOError while writing public key PEM") from e

0 commit comments

Comments
 (0)