Skip to content

docs: add example to replicate AWS KMS MKP behavior with AWS KMS keyring #255

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 18 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ We start with AWS KMS examples, then show how to use other wrapping keys.
* [with keyrings](./src/keyring/aws_kms/discovery_decrypt_in_region_only.py)
* How to decrypt with a preferred region but failover to others
* [with keyrings](./src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py)
* How to replicate the behavior of an AWS KMS master key provider
Copy link
Contributor

@juneb juneb Apr 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use "reproduce" instead of "replicate?" The KMS is using "replicate" and "replica" for other purposes. Although most people won't notice, I can envisions someone with limited English language skills googling this and becoming hopelessly confused.

* [with keyrings](./src/keyring/aws_kms/act_like_aws_kms_master_key_provider.py)
* Using raw wrapping keys
* How to use a raw AES wrapping key
* [with keyrings](./src/keyring/raw_aes/raw_aes.py)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Before there were keyrings, there were master key providers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once upon a time ... :)

Master key providers were the original configuration structure
that we provided for defining how you want to protect your data keys.

The AWS KMS master key provider was the tool that we provided for interacting with AWS KMS.
Like the AWS KMS keyring,
the AWS KMS master key provider encrypts with all CMKs that you identify,
but unlike the AWS KMS keyring,
the AWS KMS master key provider always attempts to decrypt
*any* data keys that were encrypted under an AWS KMS CMK.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
the AWS KMS master key provider always attempts to decrypt
*any* data keys that were encrypted under an AWS KMS CMK.
the AWS KMS master key provider can decrypt
*any* data key that was encrypted under an AWS KMS CMK.

-or-

the AWS KMS master key provider can use any AWS KMS CMK that encrypted
a data key to decrypt it. 

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not quite what I was getting at. Whether or not it can decrypt depends on the KMS/IAM permissions. Just like the discovery keyring, the point I was trying to make is that it does in fact reach out to the KMS service for every KMS-encrypted EDK whether or not it succeeds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difficulty here is the combination of "always" and "any." Does the ESDK always attempt to decrypt all of the encrypted data keys in an encrypted message? Or, it can attempt to decrypt any of them?

We have found that separating these two behaviors
makes it more clear what behavior to expect,
so that is what we did with the AWS KMS keyring and the AWS KMS discovery keyring.
However, as you migrate away from master key providers to keyrings,
you might need to replicate the behavior of the AWS KMS master key provider.

This example shows how to configure a keyring that behaves like an AWS KMS master key provider.

For more examples of how to use the AWS KMS keyring,
see the ``keyring/aws_kms`` directory.
"""
import aws_encryption_sdk
from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider
from aws_encryption_sdk.keyrings.aws_kms import AwsKmsKeyring
from aws_encryption_sdk.keyrings.multi import MultiKeyring


def run(aws_kms_cmk, source_plaintext):
# type: (str, bytes) -> None
"""Demonstrate how to create a keyring that behaves like an AWS KMS master key provider.

:param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys
:param bytes source_plaintext: Plaintext to encrypt
"""
# Prepare your encryption context.
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
encryption_context = {
"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",
}

# This is the master key provider whose behavior we want to replicate.
#
# On encrypt, this master key provider only uses the single target AWS KMS CMK.
# However, on decrypt, this master key provider attempts to decrypt
# any data keys that were encrypted under an AWS KMS CMK.
_master_key_provider_to_replicate = KMSMasterKeyProvider(key_ids=[aws_kms_cmk]) # noqa: intentionally never used

# Create a keyring that encrypts and decrypts using a single AWS KMS CMK.
single_cmk_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk)

# Create an AWS KMS discovery keyring that will attempt to decrypt
# any data keys that were encrypted under an AWS KMS CMK.
discovery_keyring = AwsKmsKeyring(is_discovery=True)

# Combine the single-CMK and discovery keyrings
# to create a keyring that behaves like an AWS KMS master key provider.
keyring = MultiKeyring(generator=single_cmk_keyring, children=[discovery_keyring])

# Encrypt your plaintext data.
ciphertext, _encrypt_header = aws_encryption_sdk.encrypt(
source=source_plaintext, encryption_context=encryption_context, keyring=keyring
)

# Demonstrate that the ciphertext and plaintext are different.
assert ciphertext != source_plaintext

# Decrypt your encrypted data using the same keyring you used on encrypt.
#
# You do not need to specify the encryption context on decrypt
# because the header of the encrypted message includes the encryption context.
decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring)

# Demonstrate that the decrypted plaintext is identical to the original plaintext.
assert decrypted == source_plaintext

# Verify that the encryption context used in the decrypt operation includes
# the encryption context that you specified when encrypting.
# The AWS Encryption SDK can add pairs, so don't require an exact match.
#
# In production, always use a meaningful encryption context.
assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items())