Skip to content

Commit 428fe95

Browse files
authored
chore: added keyring examples (kms_rsa, multi, discovery) (#671)
1 parent 88e6d58 commit 428fe95

13 files changed

+1060
-30
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
This example sets up the AWS KMS Discovery Keyring
5+
6+
AWS KMS discovery keyring is an AWS KMS keyring that doesn't specify any wrapping keys.
7+
8+
The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring
9+
for AWS KMS multi-Region keys. For information about using multi-Region keys with the
10+
AWS Encryption SDK, see
11+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks
12+
13+
Because it doesn't specify any wrapping keys, a discovery keyring can't encrypt data.
14+
If you use a discovery keyring to encrypt data, alone or in a multi-keyring, the encrypt
15+
operation fails.
16+
17+
When decrypting, a discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt
18+
any encrypted data key by using the AWS KMS key that encrypted it, regardless of who owns or
19+
has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt
20+
permission on the AWS KMS key.
21+
22+
This example creates a KMS Keyring and then encrypts a custom input EXAMPLE_DATA
23+
with an encryption context. This encrypted ciphertext is then decrypted using the Discovery keyring.
24+
This example also includes some sanity checks for demonstration:
25+
1. Ciphertext and plaintext data are not the same
26+
2. Encryption context is correct in the decrypted message header
27+
3. Decrypted plaintext value matches EXAMPLE_DATA
28+
4. Decryption is only possible if the Discovery Keyring contains the correct AWS Account ID's to
29+
which the KMS key used for encryption belongs
30+
These sanity checks are for demonstration in the example only. You do not need these in your code.
31+
32+
For more information on how to use KMS Discovery keyrings, see
33+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery
34+
"""
35+
import sys
36+
37+
import boto3
38+
from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders
39+
from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig
40+
from aws_cryptographic_materialproviders.mpl.models import (
41+
CreateAwsKmsDiscoveryKeyringInput,
42+
CreateAwsKmsKeyringInput,
43+
DiscoveryFilter,
44+
)
45+
from aws_cryptographic_materialproviders.mpl.references import IKeyring
46+
from typing import Dict
47+
48+
import aws_encryption_sdk
49+
from aws_encryption_sdk import CommitmentPolicy
50+
from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError
51+
52+
# TODO-MPL: Remove this as part of removing PYTHONPATH hacks.
53+
MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1])
54+
55+
sys.path.append(MODULE_ROOT_DIR)
56+
57+
EXAMPLE_DATA: bytes = b"Hello World"
58+
59+
60+
def encrypt_and_decrypt_with_keyring(
61+
kms_key_id: str,
62+
aws_account_id: str,
63+
aws_region: str
64+
):
65+
"""Demonstrate an encrypt/decrypt cycle using an AWS KMS Discovery Keyring.
66+
67+
Usage: encrypt_and_decrypt_with_keyring(kms_key_id, aws_account_id)
68+
:param kms_key_id: KMS Key identifier for the KMS key you want to use for creating
69+
the kms_keyring used for encryption
70+
:type kms_key_id: string
71+
:param aws_account_id: AWS Account ID to use in the discovery filter
72+
:type aws_account_id: string
73+
:param aws_region: AWS Region to use for the kms client
74+
:type aws_region: string
75+
76+
For more information on KMS Key identifiers, see
77+
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id
78+
"""
79+
# 1. Instantiate the encryption SDK client.
80+
# This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
81+
# which enforces that this client only encrypts using committing algorithm suites and enforces
82+
# that this client will only decrypt encrypted messages that were created with a committing
83+
# algorithm suite.
84+
# This is the default commitment policy if you were to build the client as
85+
# `client = aws_encryption_sdk.EncryptionSDKClient()`.
86+
client = aws_encryption_sdk.EncryptionSDKClient(
87+
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
88+
)
89+
90+
# 2. Create a boto3 client for KMS.
91+
kms_client = boto3.client('kms', region_name=aws_region)
92+
93+
# 3. Create encryption context.
94+
# Remember that your encryption context is NOT SECRET.
95+
# For more information, see
96+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
97+
encryption_context: Dict[str, str] = {
98+
"encryption": "context",
99+
"is not": "secret",
100+
"but adds": "useful metadata",
101+
"that can help you": "be confident that",
102+
"the data you are handling": "is what you think it is",
103+
}
104+
105+
# 4. Create the keyring that determines how your data keys are protected.
106+
# Although this example highlights Discovery keyrings, Discovery keyrings cannot
107+
# be used to encrypt, so for encryption we create a KMS keyring without discovery mode.
108+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
109+
config=MaterialProvidersConfig()
110+
)
111+
112+
kms_keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput(
113+
kms_key_id=kms_key_id,
114+
kms_client=kms_client
115+
)
116+
117+
encrypt_kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring(
118+
input=kms_keyring_input
119+
)
120+
121+
# 5. Encrypt the data with the encryptionContext
122+
ciphertext, _ = client.encrypt(
123+
source=EXAMPLE_DATA,
124+
keyring=encrypt_kms_keyring,
125+
encryption_context=encryption_context
126+
)
127+
128+
# 6. Demonstrate that the ciphertext and plaintext are different.
129+
# (This is an example for demonstration; you do not need to do this in your own code.)
130+
assert ciphertext != EXAMPLE_DATA, \
131+
"Ciphertext and plaintext data are the same. Invalid encryption"
132+
133+
# 7. Now create a Discovery keyring to use for decryption. We'll add a discovery filter
134+
# so that we limit the set of ciphertexts we are willing to decrypt to only ones
135+
# created by KMS keys in our account and partition.
136+
137+
discovery_keyring_input: CreateAwsKmsDiscoveryKeyringInput = CreateAwsKmsDiscoveryKeyringInput(
138+
kms_client=kms_client,
139+
discovery_filter=DiscoveryFilter(
140+
account_ids=[aws_account_id],
141+
partition="aws"
142+
)
143+
)
144+
145+
discovery_keyring: IKeyring = mat_prov.create_aws_kms_discovery_keyring(
146+
input=discovery_keyring_input
147+
)
148+
149+
# 8. Decrypt your encrypted data using the discovery keyring.
150+
# On Decrypt, the header of the encrypted message (ciphertext) will be parsed.
151+
# The header contains the Encrypted Data Keys (EDKs), which, if the EDK
152+
# was encrypted by a KMS Keyring, includes the KMS Key ARN.
153+
# The Discovery Keyring filters these EDKs for
154+
# EDKs encrypted by Single Region OR Multi Region KMS Keys.
155+
# If a Discovery Filter is present, these KMS Keys must belong
156+
# to an AWS Account ID in the discovery filter's AccountIds and
157+
# must be from the discovery filter's partition.
158+
# Finally, KMS is called to decrypt each filtered EDK until an EDK is
159+
# successfully decrypted. The resulting data key is used to decrypt the
160+
# ciphertext's message.
161+
# If all calls to KMS fail, the decryption fails.
162+
plaintext_bytes, dec_header = client.decrypt(
163+
source=ciphertext,
164+
keyring=discovery_keyring
165+
)
166+
167+
# 9. Demonstrate that the encryption context is correct in the decrypted message header
168+
# (This is an example for demonstration; you do not need to do this in your own code.)
169+
for k, v in encryption_context.items():
170+
assert v == dec_header.encryption_context[k], \
171+
"Encryption context does not match expected values"
172+
173+
# 10. Demonstrate that the decrypted plaintext is identical to the original plaintext.
174+
# (This is an example for demonstration; you do not need to do this in your own code.)
175+
assert plaintext_bytes == EXAMPLE_DATA
176+
177+
# 11. Demonstrate that if a discovery keyring (Bob's) doesn't have the correct AWS Account ID's,
178+
# the decrypt will fail with an error message
179+
# Note that this assumes Account ID used here ('888888888888') is different than the one used
180+
# during encryption
181+
discovery_keyring_input_bob: CreateAwsKmsDiscoveryKeyringInput = \
182+
CreateAwsKmsDiscoveryKeyringInput(
183+
kms_client=kms_client,
184+
discovery_filter=DiscoveryFilter(
185+
account_ids=["888888888888"],
186+
partition="aws"
187+
)
188+
)
189+
190+
discovery_keyring_bob: IKeyring = mat_prov.create_aws_kms_discovery_keyring(
191+
input=discovery_keyring_input_bob
192+
)
193+
194+
# Decrypt the ciphertext using Bob's discovery keyring which doesn't contain the required
195+
# Account ID's for the KMS keyring used for encryption.
196+
# This should throw an AWSEncryptionSDKClientError exception
197+
try:
198+
plaintext_bytes, _ = client.decrypt(
199+
source=ciphertext,
200+
keyring=discovery_keyring_bob
201+
)
202+
203+
raise AssertionError("Decrypt using discovery keyring with wrong AWS Account ID should"
204+
+ "raise AWSEncryptionSDKClientError")
205+
except AWSEncryptionSDKClientError:
206+
pass
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
This example sets up the AWS KMS Discovery Multi Keyring and demonstrates decryption
5+
using a Multi-Keyring containing multiple AWS KMS Discovery Keyrings.
6+
7+
The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring
8+
for AWS KMS multi-Region keys. For information about using multi-Region keys with the
9+
AWS Encryption SDK, see
10+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks
11+
12+
Because it doesn't specify any wrapping keys, a discovery keyring can't encrypt data.
13+
If you use a discovery keyring to encrypt data, alone or in a multi-keyring, the encrypt
14+
operation fails.
15+
16+
When decrypting, a discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt
17+
any encrypted data key by using the AWS KMS key that encrypted it, regardless of who owns or
18+
has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt
19+
permission on the AWS KMS key.
20+
21+
This example creates a KMS Keyring and then encrypts a custom input EXAMPLE_DATA
22+
with an encryption context. This encrypted ciphertext is then decrypted using the Discovery Multi
23+
keyring. This example also includes some sanity checks for demonstration:
24+
1. Ciphertext and plaintext data are not the same
25+
2. Encryption context is correct in the decrypted message header
26+
3. Decrypted plaintext value matches EXAMPLE_DATA
27+
These sanity checks are for demonstration in the example only. You do not need these in your code.
28+
29+
For more information on how to use KMS Discovery keyrings, see
30+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery
31+
"""
32+
import sys
33+
34+
import boto3
35+
from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders
36+
from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig
37+
from aws_cryptographic_materialproviders.mpl.models import (
38+
CreateAwsKmsDiscoveryMultiKeyringInput,
39+
CreateAwsKmsKeyringInput,
40+
DiscoveryFilter,
41+
)
42+
from aws_cryptographic_materialproviders.mpl.references import IKeyring
43+
from typing import Dict
44+
45+
import aws_encryption_sdk
46+
from aws_encryption_sdk import CommitmentPolicy
47+
48+
# TODO-MPL: Remove this as part of removing PYTHONPATH hacks.
49+
MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1])
50+
51+
sys.path.append(MODULE_ROOT_DIR)
52+
53+
EXAMPLE_DATA: bytes = b"Hello World"
54+
55+
56+
def encrypt_and_decrypt_with_keyring(
57+
kms_key_id: str,
58+
aws_account_id: str,
59+
aws_regions: list[str]
60+
):
61+
"""Demonstrate an encrypt/decrypt cycle using an AWS KMS Discovery Multi Keyring.
62+
63+
Usage: encrypt_and_decrypt_with_keyring(kms_key_id, aws_account_id, aws_regions)
64+
:param kms_key_id: KMS Key identifier for the KMS key you want to use for creating
65+
the kms_keyring used for encryption
66+
:type kms_key_id: string
67+
:param aws_account_id: AWS Account ID to use in the discovery filter
68+
:type aws_account_id: string
69+
:param aws_regions: List of AWS Regions to use for creating the discovery multi keyring
70+
:type aws_regions: list[string]
71+
72+
For more information on KMS Key identifiers, see
73+
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id
74+
"""
75+
# 1. Instantiate the encryption SDK client.
76+
# This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
77+
# which enforces that this client only encrypts using committing algorithm suites and enforces
78+
# that this client will only decrypt encrypted messages that were created with a committing
79+
# algorithm suite.
80+
# This is the default commitment policy if you were to build the client as
81+
# `client = aws_encryption_sdk.EncryptionSDKClient()`.
82+
client = aws_encryption_sdk.EncryptionSDKClient(
83+
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
84+
)
85+
86+
# 2. Create a boto3 client for KMS.
87+
kms_client = boto3.client('kms', region_name="us-west-2")
88+
89+
# 3. Create encryption context.
90+
# Remember that your encryption context is NOT SECRET.
91+
# For more information, see
92+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
93+
encryption_context: Dict[str, str] = {
94+
"encryption": "context",
95+
"is not": "secret",
96+
"but adds": "useful metadata",
97+
"that can help you": "be confident that",
98+
"the data you are handling": "is what you think it is",
99+
}
100+
101+
# 4. Create the keyring that determines how your data keys are protected.
102+
# Although this example highlights Discovery keyrings, Discovery keyrings cannot
103+
# be used to encrypt, so for encryption we create a KMS keyring without discovery mode.
104+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
105+
config=MaterialProvidersConfig()
106+
)
107+
108+
kms_keyring_input: CreateAwsKmsKeyringInput = CreateAwsKmsKeyringInput(
109+
kms_key_id=kms_key_id,
110+
kms_client=kms_client
111+
)
112+
113+
encrypt_kms_keyring: IKeyring = mat_prov.create_aws_kms_keyring(
114+
input=kms_keyring_input
115+
)
116+
117+
# 5. Encrypt the data with the encryptionContext
118+
ciphertext, _ = client.encrypt(
119+
source=EXAMPLE_DATA,
120+
keyring=encrypt_kms_keyring,
121+
encryption_context=encryption_context
122+
)
123+
124+
# 6. Demonstrate that the ciphertext and plaintext are different.
125+
# (This is an example for demonstration; you do not need to do this in your own code.)
126+
assert ciphertext != EXAMPLE_DATA, \
127+
"Ciphertext and plaintext data are the same. Invalid encryption"
128+
129+
# 7. Now create a Discovery Multi keyring to use for decryption. We'll add a discovery filter
130+
# so that we limit the set of ciphertexts we are willing to decrypt to only ones
131+
# created by KMS keys in our account and partition.
132+
discovery_multi_keyring_input: CreateAwsKmsDiscoveryMultiKeyringInput = \
133+
CreateAwsKmsDiscoveryMultiKeyringInput(
134+
regions=aws_regions,
135+
discovery_filter=DiscoveryFilter(
136+
account_ids=[aws_account_id],
137+
partition="aws"
138+
)
139+
)
140+
141+
# This is a Multi Keyring composed of Discovery Keyrings.
142+
# There is a keyring for every region in `regions`.
143+
# All the keyrings have the same Discovery Filter.
144+
# Each keyring has its own KMS Client, which is created for the keyring's region.
145+
discovery_multi_keyring: IKeyring = mat_prov.create_aws_kms_discovery_multi_keyring(
146+
input=discovery_multi_keyring_input
147+
)
148+
149+
# 8. On Decrypt, the header of the encrypted message (ciphertext) will be parsed.
150+
# The header contains the Encrypted Data Keys (EDKs), which, if the EDK
151+
# was encrypted by a KMS Keyring, includes the KMS Key ARN.
152+
# For each member of the Multi Keyring, every EDK will try to be decrypted until a decryption
153+
# is successful.
154+
# Since every member of the Multi Keyring is a Discovery Keyring:
155+
# Each Keyring will filter the EDKs by the Discovery Filter
156+
# For the filtered EDKs, the keyring will try to decrypt it with the keyring's client.
157+
# All of this is done serially, until a success occurs or all keyrings have
158+
# failed all (filtered) EDKs.
159+
# KMS Discovery Keyrings will attempt to decrypt Multi Region Keys (MRKs) and regular KMS Keys.
160+
plaintext_bytes, dec_header = client.decrypt(
161+
source=ciphertext,
162+
keyring=discovery_multi_keyring
163+
)
164+
165+
# 9. Demonstrate that the encryption context is correct in the decrypted message header
166+
# (This is an example for demonstration; you do not need to do this in your own code.)
167+
for k, v in encryption_context.items():
168+
assert v == dec_header.encryption_context[k], \
169+
"Encryption context does not match expected values"
170+
171+
# 10. Demonstrate that the decrypted plaintext is identical to the original plaintext.
172+
# (This is an example for demonstration; you do not need to do this in your own code.)
173+
assert plaintext_bytes == EXAMPLE_DATA

0 commit comments

Comments
 (0)