Skip to content

Commit a3d09a0

Browse files
committed
added mrk discovery keyrings
1 parent 42def7a commit a3d09a0

File tree

4 files changed

+411
-0
lines changed

4 files changed

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

0 commit comments

Comments
 (0)