Skip to content

Commit e47e441

Browse files
authored
chore(examples): added mrk discovery keyring examples (#675)
1 parent 52ad465 commit e47e441

7 files changed

+423
-2
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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 MRK (multi-region key) Discovery Keyring
5+
6+
The AWS KMS discovery keyring is an AWS KMS keyring that doesn't specify any wrapping keys.
7+
8+
When decrypting, an MRK discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt
9+
any encrypted data key by using the AWS KMS MRK that encrypted it, regardless of who owns or
10+
has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt
11+
permission on the AWS KMS MRK.
12+
13+
The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring
14+
for AWS KMS multi-Region keys. Because it doesn't specify any wrapping keys, a discovery keyring
15+
can't encrypt data. If you use a discovery keyring to encrypt data, alone or in a multi-keyring,
16+
the encrypt operation fails.
17+
18+
The AWS Key Management Service (AWS KMS) MRK keyring interacts with AWS KMS to
19+
create, encrypt, and decrypt data keys with multi-region AWS KMS keys (MRKs).
20+
This example creates a KMS MRK Keyring and then encrypts a custom input EXAMPLE_DATA
21+
with an encryption context. This encrypted ciphertext is then decrypted using an
22+
MRK Discovery keyring. This example also includes some sanity checks for demonstration:
23+
1. Ciphertext and plaintext data are not the same
24+
2. Encryption context is correct in the decrypted message header
25+
3. Decrypted plaintext value matches EXAMPLE_DATA
26+
These sanity checks are for demonstration in the example only. You do not need these in your code.
27+
28+
For information about using multi-Region keys with the AWS Encryption SDK, see
29+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks
30+
31+
For more info on KMS MRKs (multi-region keys), see the KMS documentation:
32+
https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html
33+
34+
For more information on how to use KMS Discovery keyrings, see
35+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery
36+
"""
37+
import sys
38+
39+
import boto3
40+
from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders
41+
from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig
42+
from aws_cryptographic_materialproviders.mpl.models import (
43+
CreateAwsKmsMrkDiscoveryKeyringInput,
44+
CreateAwsKmsMrkKeyringInput,
45+
DiscoveryFilter,
46+
)
47+
from aws_cryptographic_materialproviders.mpl.references import IKeyring
48+
from typing import Dict
49+
50+
import aws_encryption_sdk
51+
from aws_encryption_sdk import CommitmentPolicy
52+
53+
# TODO-MPL: Remove this as part of removing PYTHONPATH hacks.
54+
MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1])
55+
56+
sys.path.append(MODULE_ROOT_DIR)
57+
58+
EXAMPLE_DATA: bytes = b"Hello World"
59+
60+
61+
def encrypt_and_decrypt_with_keyring(
62+
mrk_key_id_encrypt: str,
63+
aws_account_id: str,
64+
mrk_encrypt_region: str,
65+
mrk_replica_decrypt_region: str
66+
):
67+
"""Demonstrate decryption using an AWS KMS MRK Discovery keyring.
68+
69+
Since discovery keyrings cannot be used to encrypt, we use KMS MRK keyring for encryption
70+
Usage: encrypt_and_decrypt_with_keyring(mrk_key_id_encrypt,
71+
aws_account_id,
72+
mrk_encrypt_region,
73+
mrk_replica_decrypt_region)
74+
:param mrk_key_id_encrypt: KMS Key identifier for the KMS key located in your
75+
default region, which you want to use for encryption of your data keys
76+
:type mrk_key_id_encrypt: string
77+
:param aws_account_id: AWS Account ID to use in the discovery filter
78+
:type aws_account_id: string
79+
:param mrk_encrypt_region: AWS Region for encryption of your data keys. This should
80+
be the region of the mrk_key_id_encrypt.
81+
:type mrk_encrypt_region: string
82+
:param mrk_replica_decrypt_region: AWS Region for decryption of your data keys.
83+
This example assumes you have already replicated your mrk_key_id_encrypt to the
84+
region mrk_replica_decrypt_region. Therefore, this mrk_replica_decrypt_region should
85+
be the region of the MRK replica. However, since we are using a discovery keyring,
86+
we don't need to provide the replica MRK ID.
87+
:type mrk_replica_decrypt_region: string
88+
89+
For more information on KMS Key identifiers for multi-region keys, see
90+
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id
91+
"""
92+
# 1. Instantiate the encryption SDK client.
93+
# This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
94+
# which enforces that this client only encrypts using committing algorithm suites and enforces
95+
# that this client will only decrypt encrypted messages that were created with a committing
96+
# algorithm suite.
97+
# This is the default commitment policy if you were to build the client as
98+
# `client = aws_encryption_sdk.EncryptionSDKClient()`.
99+
client = aws_encryption_sdk.EncryptionSDKClient(
100+
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
101+
)
102+
103+
# 2. Create encryption context.
104+
# Remember that your encryption context is NOT SECRET.
105+
# For more information, see
106+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
107+
encryption_context: Dict[str, str] = {
108+
"encryption": "context",
109+
"is not": "secret",
110+
"but adds": "useful metadata",
111+
"that can help you": "be confident that",
112+
"the data you are handling": "is what you think it is",
113+
}
114+
115+
# 3. Create the keyring that determines how your data keys are protected.
116+
# Although this example highlights Discovery keyrings, Discovery keyrings cannot
117+
# be used to encrypt, so for encryption we create an MRK keyring without discovery mode.
118+
119+
# Create a keyring that will encrypt your data, using a KMS MRK in the first region.
120+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
121+
config=MaterialProvidersConfig()
122+
)
123+
124+
# Create a boto3 client for KMS in the first region.
125+
encrypt_kms_client = boto3.client('kms', region_name=mrk_encrypt_region)
126+
127+
encrypt_keyring_input: CreateAwsKmsMrkKeyringInput = CreateAwsKmsMrkKeyringInput(
128+
kms_key_id=mrk_key_id_encrypt,
129+
kms_client=encrypt_kms_client
130+
)
131+
132+
encrypt_keyring: IKeyring = mat_prov.create_aws_kms_mrk_keyring(
133+
input=encrypt_keyring_input
134+
)
135+
136+
# 4. Encrypt the data with the encryptionContext using the encrypt_keyring.
137+
ciphertext, _ = client.encrypt(
138+
source=EXAMPLE_DATA,
139+
keyring=encrypt_keyring,
140+
encryption_context=encryption_context
141+
)
142+
143+
# 5. Demonstrate that the ciphertext and plaintext are different.
144+
# (This is an example for demonstration; you do not need to do this in your own code.)
145+
assert ciphertext != EXAMPLE_DATA, \
146+
"Ciphertext and plaintext data are the same. Invalid encryption"
147+
148+
# 6. Now create a Discovery keyring to use for decryption.
149+
# In order to illustrate the MRK behavior of this keyring, we configure
150+
# the keyring to use the second KMS region where the MRK (mrk_key_id_encrypt) is replicated to.
151+
# This example assumes you have already replicated your key, but since we
152+
# are using a discovery keyring, we don't need to provide the mrk replica key id
153+
154+
# Create a boto3 client for KMS in the second region.
155+
decrypt_kms_client = boto3.client('kms', region_name=mrk_replica_decrypt_region)
156+
157+
decrypt_discovery_keyring_input: CreateAwsKmsMrkDiscoveryKeyringInput = \
158+
CreateAwsKmsMrkDiscoveryKeyringInput(
159+
kms_client=decrypt_kms_client,
160+
region=mrk_replica_decrypt_region,
161+
discovery_filter=DiscoveryFilter(
162+
account_ids=[aws_account_id],
163+
partition="aws"
164+
)
165+
)
166+
167+
decrypt_discovery_keyring: IKeyring = mat_prov.create_aws_kms_mrk_discovery_keyring(
168+
input=decrypt_discovery_keyring_input
169+
)
170+
171+
# 7. Decrypt your encrypted data using the discovery keyring.
172+
plaintext_bytes, dec_header = client.decrypt(
173+
source=ciphertext,
174+
keyring=decrypt_discovery_keyring
175+
)
176+
177+
# 8. Demonstrate that the encryption context is correct in the decrypted message header
178+
# (This is an example for demonstration; you do not need to do this in your own code.)
179+
for k, v in encryption_context.items():
180+
assert v == dec_header.encryption_context[k], \
181+
"Encryption context does not match expected values"
182+
183+
# 9. Demonstrate that the decrypted plaintext is identical to the original plaintext.
184+
# (This is an example for demonstration; you do not need to do this in your own code.)
185+
assert plaintext_bytes == EXAMPLE_DATA
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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 MRK (multi-region key) Discovery Multi Keyring
5+
6+
AWS KMS MRK Discovery Multi Keyring is composed of multiple MRK discovery keyrings.
7+
8+
The AWS KMS discovery keyring is an AWS KMS keyring that doesn't specify any wrapping keys.
9+
10+
When decrypting, an MRK discovery keyring allows the AWS Encryption SDK to ask AWS KMS to decrypt
11+
any encrypted data key by using the AWS KMS MRK that encrypted it, regardless of who owns or
12+
has access to that AWS KMS key. The call succeeds only when the caller has kms:Decrypt
13+
permission on the AWS KMS MRK.
14+
15+
The AWS Encryption SDK provides a standard AWS KMS discovery keyring and a discovery keyring
16+
for AWS KMS multi-Region keys. Because it doesn't specify any wrapping keys, a discovery keyring
17+
can't encrypt data. If you use a discovery keyring to encrypt data, alone or in a multi-keyring,
18+
the encrypt operation fails.
19+
20+
The AWS Key Management Service (AWS KMS) MRK keyring interacts with AWS KMS to
21+
create, encrypt, and decrypt data keys with multi-region AWS KMS keys (MRKs).
22+
This example creates a KMS MRK Keyring and then encrypts a custom input EXAMPLE_DATA
23+
with an encryption context. This encrypted ciphertext is then decrypted using an
24+
MRK Discovery Multi keyring. 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+
These sanity checks are for demonstration in the example only. You do not need these in your code.
29+
30+
For information about using multi-Region keys with the AWS Encryption SDK, see
31+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/configure.html#config-mrks
32+
33+
For more info on KMS MRKs (multi-region keys), see the KMS documentation:
34+
https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html
35+
36+
For more information on how to use KMS Discovery keyrings, see
37+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html#kms-keyring-discovery
38+
"""
39+
import sys
40+
41+
import boto3
42+
from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders
43+
from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig
44+
from aws_cryptographic_materialproviders.mpl.models import (
45+
CreateAwsKmsMrkDiscoveryMultiKeyringInput,
46+
CreateAwsKmsMrkKeyringInput,
47+
DiscoveryFilter,
48+
)
49+
from aws_cryptographic_materialproviders.mpl.references import IKeyring
50+
from typing import Dict
51+
52+
import aws_encryption_sdk
53+
from aws_encryption_sdk import CommitmentPolicy
54+
55+
# TODO-MPL: Remove this as part of removing PYTHONPATH hacks.
56+
MODULE_ROOT_DIR = '/'.join(__file__.split("/")[:-1])
57+
58+
sys.path.append(MODULE_ROOT_DIR)
59+
60+
EXAMPLE_DATA: bytes = b"Hello World"
61+
62+
63+
def encrypt_and_decrypt_with_keyring(
64+
mrk_key_id_encrypt: str,
65+
mrk_encrypt_region: str,
66+
aws_account_id: str,
67+
aws_regions: list[str]
68+
):
69+
"""Demonstrate decryption using an AWS KMS MRK Discovery Multi keyring.
70+
71+
Since discovery keyrings cannot be used to encrypt, we use KMS MRK keyring for encryption
72+
Usage: encrypt_and_decrypt_with_keyring(mrk_key_id_encrypt,
73+
mrk_encrypt_region,
74+
aws_account_id,
75+
aws_regions)
76+
:param mrk_key_id_encrypt: KMS Key identifier for the KMS key located in your
77+
default region, which you want to use for encryption of your data keys
78+
:type mrk_key_id_encrypt: string
79+
:param mrk_encrypt_region: AWS Region for encryption of your data keys. This should
80+
be the region of the mrk_key_id_encrypt
81+
:type mrk_encrypt_region: string
82+
:param aws_account_id: AWS Account ID to use in the discovery filter
83+
:type aws_account_id: string
84+
:param aws_regions: AWS Regions to use in the the discovery filter
85+
:type aws_regions: list[string]
86+
87+
For more information on KMS Key identifiers for multi-region keys, see
88+
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id
89+
"""
90+
# 1. Instantiate the encryption SDK client.
91+
# This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
92+
# which enforces that this client only encrypts using committing algorithm suites and enforces
93+
# that this client will only decrypt encrypted messages that were created with a committing
94+
# algorithm suite.
95+
# This is the default commitment policy if you were to build the client as
96+
# `client = aws_encryption_sdk.EncryptionSDKClient()`.
97+
client = aws_encryption_sdk.EncryptionSDKClient(
98+
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
99+
)
100+
101+
# 2. Create encryption context.
102+
# Remember that your encryption context is NOT SECRET.
103+
# For more information, see
104+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
105+
encryption_context: Dict[str, str] = {
106+
"encryption": "context",
107+
"is not": "secret",
108+
"but adds": "useful metadata",
109+
"that can help you": "be confident that",
110+
"the data you are handling": "is what you think it is",
111+
}
112+
113+
# 3. Create the keyring that determines how your data keys are protected.
114+
# Although this example highlights Discovery keyrings, Discovery keyrings cannot
115+
# be used to encrypt, so for encryption we create an MRK keyring without discovery mode.
116+
117+
# Create a keyring that will encrypt your data, using a KMS MRK in the first region.
118+
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
119+
config=MaterialProvidersConfig()
120+
)
121+
122+
# Create a boto3 client for KMS in the first region.
123+
encrypt_kms_client = boto3.client('kms', region_name=mrk_encrypt_region)
124+
125+
encrypt_keyring_input: CreateAwsKmsMrkKeyringInput = CreateAwsKmsMrkKeyringInput(
126+
kms_key_id=mrk_key_id_encrypt,
127+
kms_client=encrypt_kms_client
128+
)
129+
130+
encrypt_keyring: IKeyring = mat_prov.create_aws_kms_mrk_keyring(
131+
input=encrypt_keyring_input
132+
)
133+
134+
# 4. Encrypt the data with the encryptionContext using the encrypt_keyring.
135+
ciphertext, _ = client.encrypt(
136+
source=EXAMPLE_DATA,
137+
keyring=encrypt_keyring,
138+
encryption_context=encryption_context
139+
)
140+
141+
# 5. Demonstrate that the ciphertext and plaintext are different.
142+
# (This is an example for demonstration; you do not need to do this in your own code.)
143+
assert ciphertext != EXAMPLE_DATA, \
144+
"Ciphertext and plaintext data are the same. Invalid encryption"
145+
146+
# 6. Now create a MRK Discovery Multi Keyring to use for decryption.
147+
# We'll add a discovery filter to limit the set of encrypted data keys
148+
# we are willing to decrypt to only ones created by KMS keys in select
149+
# accounts and the partition `aws`.
150+
# MRK Discovery keyrings also filter encrypted data keys by the region
151+
# the keyring is created with.
152+
decrypt_discovery_multi_keyring_input: CreateAwsKmsMrkDiscoveryMultiKeyringInput = \
153+
CreateAwsKmsMrkDiscoveryMultiKeyringInput(
154+
regions=aws_regions,
155+
discovery_filter=DiscoveryFilter(
156+
account_ids=[aws_account_id],
157+
partition="aws"
158+
)
159+
)
160+
161+
# This is a Multi Keyring composed of Discovery Keyrings.
162+
# There is a keyring for every region in `regions`.
163+
# All the keyrings have the same Discovery Filter.
164+
# Each keyring has its own KMS Client, which is created for the keyring's region.
165+
decrypt_discovery_keyring: IKeyring = mat_prov.create_aws_kms_mrk_discovery_multi_keyring(
166+
input=decrypt_discovery_multi_keyring_input
167+
)
168+
169+
# 7. Decrypt your encrypted data using the discovery multi keyring.
170+
# On Decrypt, the header of the encrypted message (ciphertext) will be parsed.
171+
# The header contains the Encrypted Data Keys (EDKs), which, if the EDK
172+
# was encrypted by a KMS Keyring, includes the KMS Key ARN.
173+
# For each member of the Multi Keyring, every EDK will try to be decrypted until a decryption
174+
# is successful.
175+
# Since every member of the Multi Keyring is a Discovery Keyring:
176+
# Each Keyring will filter the EDKs by the Discovery Filter and the Keyring's region.
177+
# For each filtered EDK, the keyring will attempt decryption with the keyring's client.
178+
# All of this is done serially, until a success occurs or all keyrings have failed
179+
# all (filtered) EDKs. KMS MRK Discovery Keyrings will attempt to decrypt
180+
# Multi Region Keys (MRKs) and regular KMS Keys.
181+
plaintext_bytes, dec_header = client.decrypt(
182+
source=ciphertext,
183+
keyring=decrypt_discovery_keyring
184+
)
185+
186+
# 8. Demonstrate that the encryption context is correct in the decrypted message header
187+
# (This is an example for demonstration; you do not need to do this in your own code.)
188+
for k, v in encryption_context.items():
189+
assert v == dec_header.encryption_context[k], \
190+
"Encryption context does not match expected values"
191+
192+
# 9. Demonstrate that the decrypted plaintext is identical to the original plaintext.
193+
# (This is an example for demonstration; you do not need to do this in your own code.)
194+
assert plaintext_bytes == EXAMPLE_DATA

examples/src/keyrings/hierarchical_keyring.py renamed to examples/src/keyrings/hierarchical_keyring_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
from aws_encryption_sdk import CommitmentPolicy
5454
from aws_encryption_sdk.exceptions import AWSEncryptionSDKClientError
5555

56-
from .example_branch_key_id_supplier import ExampleBranchKeyIdSupplier
56+
from .branch_key_id_supplier_example import ExampleBranchKeyIdSupplier
5757

5858
# TODO-MPL: Remove this as part of removing PYTHONPATH hacks.
5959
module_root_dir = '/'.join(__file__.split("/")[:-1])

0 commit comments

Comments
 (0)