Skip to content

chore(examples): Added mrk and mrk_multi keyring examples #673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions examples/src/keyrings/aws_kms_mrk_keyring_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
This example sets up the KMS MRK (multi-region key) Keyring

The AWS Key Management Service (AWS KMS) MRK keyring interacts with AWS KMS to
create, encrypt, and decrypt data keys with multi-region AWS KMS keys (MRKs).
This example creates a KMS MRK Keyring and then encrypts a custom input EXAMPLE_DATA
with an encryption context. This example also includes some sanity checks for demonstration:
1. Ciphertext and plaintext data are not the same
2. Encryption context is correct in the decrypted message header
3. Decrypted plaintext value matches EXAMPLE_DATA
These sanity checks are for demonstration in the example only. You do not need these in your code.

AWS KMS MRK keyrings can be used independently or in a multi-keyring with other keyrings
of the same or a different type.

For more information on how to use KMS keyrings, see
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html

For more info on KMS MRK (multi-region keys), see the KMS documentation:
https://docs.aws.amazon.com/kms/latest/developerguide/multi-region-keys-overview.html
"""
import sys

import boto3
from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders
from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig
from aws_cryptographic_materialproviders.mpl.models import CreateAwsKmsMrkKeyringInput
from aws_cryptographic_materialproviders.mpl.references import IKeyring
from typing import Dict

import aws_encryption_sdk
from aws_encryption_sdk import CommitmentPolicy

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

sys.path.append(MODULE_ROOT_DIR)

EXAMPLE_DATA: bytes = b"Hello World"


def encrypt_and_decrypt_with_keyring(
mrk_key_id_encrypt: str,
mrk_replica_key_id_decrypt: str,
default_region: str,
second_region: str
):
"""Demonstrate an encrypt/decrypt cycle using an AWS KMS MRK keyring.

Usage: encrypt_and_decrypt_with_keyring(mrk_key_id_encrypt,
mrk_replica_key_id_decrypt,
default_region,
second_region)
:param mrk_key_id_encrypt: KMS Key identifier for the KMS key located in your
default region, which you want to use for encryption of your data keys
:type mrk_key_id_encrypt: string
:param mrk_replica_key_id_decrypt: KMS Key identifier for the KMS key KMS Key
that is a replica of the `mrk_key_id_encrypt` in a second region, which you
want to use for decryption of your data keys
:type mrk_replica_key_id_decrypt: string
:param default_region: AWS Region for encryption of your data keys
:type default_region: string
:param second_region: AWS Region for decryption of your data keys
:type second_region: string

For more information on KMS Key identifiers, see
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id
"""
# 1. Instantiate the encryption SDK client.
# This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
# which enforces that this client only encrypts using committing algorithm suites and enforces
# that this client will only decrypt encrypted messages that were created with a committing
# algorithm suite.
# This is the default commitment policy if you were to build the client as
# `client = aws_encryption_sdk.EncryptionSDKClient()`.
client = aws_encryption_sdk.EncryptionSDKClient(
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)

# 2. Create encryption context.
# Remember that your encryption context is NOT SECRET.
# For more information, see
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
encryption_context: Dict[str, str] = {
"encryption": "context",
"is not": "secret",
"but adds": "useful metadata",
"that can help you": "be confident that",
"the data you are handling": "is what you think it is",
}

# 3. Create a keyring that will encrypt your data, using a KMS MRK key in the first region.
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
config=MaterialProvidersConfig()
)

# Create a boto3 client for KMS in the first region.
encrypt_kms_client = boto3.client('kms', region_name=default_region)

encrypt_keyring_input: CreateAwsKmsMrkKeyringInput = CreateAwsKmsMrkKeyringInput(
kms_key_id=mrk_key_id_encrypt,
kms_client=encrypt_kms_client
)

encrypt_keyring: IKeyring = mat_prov.create_aws_kms_mrk_keyring(
input=encrypt_keyring_input
)

# 4. Encrypt the data with the encryptionContext using the encrypt_keyring.
ciphertext, _ = client.encrypt(
source=EXAMPLE_DATA,
keyring=encrypt_keyring,
encryption_context=encryption_context
)

# 5. Demonstrate that the ciphertext and plaintext are different.
# (This is an example for demonstration; you do not need to do this in your own code.)
assert ciphertext != EXAMPLE_DATA, \
"Ciphertext and plaintext data are the same. Invalid encryption"

# 6. Create a keyring that will decrypt your data, using the same KMS MRK key replicated
# to the second region. This example assumes you have already replicated your key

# Create a boto3 client for KMS in the second region.
decrypt_kms_client = boto3.client('kms', region_name=second_region)

decrypt_keyring_input: CreateAwsKmsMrkKeyringInput = CreateAwsKmsMrkKeyringInput(
kms_key_id=mrk_replica_key_id_decrypt,
kms_client=decrypt_kms_client
)

decrypt_keyring: IKeyring = mat_prov.create_aws_kms_mrk_keyring(
input=decrypt_keyring_input
)

# 7. Decrypt your encrypted data using the same keyring you used on encrypt.
plaintext_bytes, dec_header = client.decrypt(
source=ciphertext,
keyring=decrypt_keyring
)

# 8. Demonstrate that the encryption context is correct in the decrypted message header
# (This is an example for demonstration; you do not need to do this in your own code.)
for k, v in encryption_context.items():
assert v == dec_header.encryption_context[k], \
"Encryption context does not match expected values"

# 9. Demonstrate that the decrypted plaintext is identical to the original plaintext.
# (This is an example for demonstration; you do not need to do this in your own code.)
assert plaintext_bytes == EXAMPLE_DATA
175 changes: 175 additions & 0 deletions examples/src/keyrings/aws_kms_mrk_multi_keyring_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
This example sets up the KMS MRK Multi Keyring

The AWS Key Management Service (AWS KMS) MRK keyring interacts with AWS KMS to
create, encrypt, and decrypt data keys with multi-region AWS KMS keys (MRKs).
This example creates a KMS MRK Multi Keyring using an mrk_key_id (generator) and
a kms_key_id, and then encrypts a custom input EXAMPLE_DATA with an encryption context.
This example also includes some sanity checks for demonstration:
1. Ciphertext and plaintext data are not the same
2. Encryption context is correct in the decrypted message header
3. Decrypted plaintext value matches EXAMPLE_DATA
4. Ciphertext can be decrypted using an AwsKmsMrkKeyring containing a replica of the
MRK key (from the multi-keyring used for encryption) copied from the first region into
the second region
These sanity checks are for demonstration in the example only. You do not need these in your code.

For more information on how to use KMS keyrings, see
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/use-kms-keyring.html
"""
import sys

import boto3
from aws_cryptographic_materialproviders.mpl import AwsCryptographicMaterialProviders
from aws_cryptographic_materialproviders.mpl.config import MaterialProvidersConfig
from aws_cryptographic_materialproviders.mpl.models import CreateAwsKmsMrkKeyringInput, CreateAwsKmsMrkMultiKeyringInput
from aws_cryptographic_materialproviders.mpl.references import IKeyring
from typing import Dict

import aws_encryption_sdk
from aws_encryption_sdk import CommitmentPolicy

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

sys.path.append(MODULE_ROOT_DIR)

EXAMPLE_DATA: bytes = b"Hello World"


def encrypt_and_decrypt_with_keyring(
mrk_key_id: str,
kms_key_id: str,
mrk_replica_key_id: str,
second_region: str
):
"""Demonstrate an encrypt/decrypt cycle using a Multi-Keyring made
up of multiple AWS KMS MRK Keyrings

Usage: encrypt_and_decrypt_with_keyring(mrk_key_id,
kms_key_id,
mrk_replica_key_id,
second_region)
:param mrk_key_id: KMS Key identifier for an AWS KMS multi-region key (MRK) located in your
default region
:type mrk_key_id: string
:param kms_key_id: KMS Key identifier for a KMS key, possibly located in a different region
than the MRK key
:type kms_key_id: string
:param mrk_replica_key_id: KMS Key identifier for an MRK that is a replica of the
`mrk_key_id` in a second region.
:type mrk_replica_key_id: string
:param second_region: The second region where the MRK replica is located
:type second_region: string

For more information on KMS Key identifiers, see
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id
"""
# 1. Instantiate the encryption SDK client.
# This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
# which enforces that this client only encrypts using committing algorithm suites and enforces
# that this client will only decrypt encrypted messages that were created with a committing
# algorithm suite.
# This is the default commitment policy if you were to build the client as
# `client = aws_encryption_sdk.EncryptionSDKClient()`.
client = aws_encryption_sdk.EncryptionSDKClient(
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)

# 2. Create encryption context.
# Remember that your encryption context is NOT SECRET.
# For more information, see
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
encryption_context: Dict[str, str] = {
"encryption": "context",
"is not": "secret",
"but adds": "useful metadata",
"that can help you": "be confident that",
"the data you are handling": "is what you think it is",
}

# 3. Create an AwsKmsMrkMultiKeyring that protects your data under two different KMS Keys.
# The Keys can either be regular KMS keys or MRKs.
# Either KMS Key individually is capable of decrypting data encrypted under this keyring.
mat_prov: AwsCryptographicMaterialProviders = AwsCryptographicMaterialProviders(
config=MaterialProvidersConfig()
)

kms_mrk_multi_keyring_input: CreateAwsKmsMrkMultiKeyringInput =\
CreateAwsKmsMrkMultiKeyringInput(
generator=mrk_key_id,
kms_key_ids=[kms_key_id]
)

kms_mrk_multi_keyring: IKeyring = mat_prov.create_aws_kms_mrk_multi_keyring(
input=kms_mrk_multi_keyring_input
)

# 4. Encrypt the data with the encryptionContext using the kms_mrk_multi_keyring.
ciphertext, _ = client.encrypt(
source=EXAMPLE_DATA,
keyring=kms_mrk_multi_keyring,
encryption_context=encryption_context
)

# 5. Demonstrate that the ciphertext and plaintext are different.
# (This is an example for demonstration; you do not need to do this in your own code.)
assert ciphertext != EXAMPLE_DATA, \
"Ciphertext and plaintext data are the same. Invalid encryption"

# 6. Decrypt your encrypted data using the same AwsKmsMrkMultiKeyring you used on encrypt.
# It will decrypt the data using the generator KMS MRK key since that is the first available
# KMS key on the keyring that is capable of decrypting the data.
plaintext_bytes, dec_header = client.decrypt(
source=ciphertext,
keyring=kms_mrk_multi_keyring
)

# 7. Demonstrate that the encryption context is correct in the decrypted message header
# (This is an example for demonstration; you do not need to do this in your own code.)
for k, v in encryption_context.items():
assert v == dec_header.encryption_context[k], \
"Encryption context does not match expected values"

# 8. Demonstrate that the decrypted plaintext is identical to the original plaintext.
# (This is an example for demonstration; you do not need to do this in your own code.)
assert plaintext_bytes == EXAMPLE_DATA

# Demonstrate that a single AwsKmsMrkKeyring configured with a replica of the MRK from the
# multi-keyring used to encrypt the data is also capable of decrypting the data.
# (This is an example for demonstration; you do not need to do this in your own code.)

# 9. Create a single AwsKmsMrkKeyring with the replica KMS MRK from the second region.

# Create a boto3 client for KMS in the second region.
second_region_kms_client = boto3.client('kms', region_name=second_region)

second_region_mrk_keyring_input: CreateAwsKmsMrkKeyringInput = CreateAwsKmsMrkKeyringInput(
kms_key_id=mrk_replica_key_id,
kms_client=second_region_kms_client
)

second_region_mrk_keyring: IKeyring = mat_prov.create_aws_kms_mrk_keyring(
input=second_region_mrk_keyring_input
)

# 10. Decrypt your encrypted data using the second region AwsKmsMrkKeyring
plaintext_bytes_second_region, dec_header_second_region = client.decrypt(
source=ciphertext,
keyring=second_region_mrk_keyring
)

# 11. Demonstrate that the encryption context is correct in the decrypted message header
# (This is an example for demonstration; you do not need to do this in your own code.)
for k, v in encryption_context.items():
assert v == dec_header_second_region.encryption_context[k], \
"Encryption context does not match expected values"

# 12. Demonstrate that the decrypted plaintext is identical to the original plaintext.
# (This is an example for demonstration; you do not need to do this in your own code.)
assert plaintext_bytes_second_region == EXAMPLE_DATA

# Not shown in this example: A KMS Keyring created with `kms_key_id` could also
# decrypt this message.
22 changes: 22 additions & 0 deletions examples/test/keyrings/test_i_aws_kms_mrk_keyring_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""Test suite for the AWS KMS MRK keyring example."""
import pytest

from ...src.keyrings.aws_kms_mrk_keyring_example import encrypt_and_decrypt_with_keyring

pytestmark = [pytest.mark.examples]


def test_encrypt_and_decrypt_with_keyring():
"""Test function for encrypt and decrypt using the AWS KMS MRK Keyring example."""
mrk_key_id_encrypt = \
"arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"
mrk_replica_key_id_decrypt = \
"arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"
default_region = "us-east-1"
second_region = "eu-west-1"
encrypt_and_decrypt_with_keyring(mrk_key_id_encrypt,
mrk_replica_key_id_decrypt,
default_region,
second_region)
20 changes: 20 additions & 0 deletions examples/test/keyrings/test_i_aws_kms_mrk_multi_keyring_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""Test suite for the AWS KMS MRK Multi keyring example."""
import pytest

from ...src.keyrings.aws_kms_mrk_multi_keyring_example import encrypt_and_decrypt_with_keyring

pytestmark = [pytest.mark.examples]


def test_encrypt_and_decrypt_with_keyring():
"""Test function for encrypt and decrypt using the AWS KMS MRK Multi Keyring example."""
mrk_key_id = \
"arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"
kms_key_id = \
"arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"
mrk_replica_key_id = \
"arn:aws:kms:eu-west-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7"
second_region = "eu-west-1"
encrypt_and_decrypt_with_keyring(mrk_key_id, kms_key_id, mrk_replica_key_id, second_region)
Loading