Skip to content

Commit 32c44f4

Browse files
committed
chore(python): examples for keyrings
1 parent d501f40 commit 32c44f4

21 files changed

+1856
-8
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

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

0 commit comments

Comments
 (0)