Skip to content

docs: add keyring examples #221

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 38 commits into from
Mar 31, 2020
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
558563f
docs: add raw RSA keyring examples
mattsb42-aws Mar 11, 2020
7b6b920
fix: fix type signatures in raw RSA keyring
mattsb42-aws Mar 11, 2020
05266a8
docs: add raw AES keyring example
mattsb42-aws Mar 11, 2020
f368f09
chore: move single-CMK example to new location
mattsb42-aws Mar 11, 2020
4709409
docs: update single-CMK example to match new template
mattsb42-aws Mar 11, 2020
5fb2d3e
fix: fix docstring in raw AES keyring example
mattsb42-aws Mar 11, 2020
f6af7ee
chore: move multi-region KMS example to new location
mattsb42-aws Mar 11, 2020
52f8312
chore: update multi-region AWS keyring example to new format
mattsb42-aws Mar 11, 2020
0047ddf
docs: add multi-keyring example
mattsb42-aws Mar 11, 2020
5925b77
docs: add custom KMS client config keyring example
mattsb42-aws Mar 11, 2020
a6519d2
docs: add multi-partition client supplier example
mattsb42-aws Mar 12, 2020
2fb99d0
docs: clarify custom client supplier example intro
mattsb42-aws Mar 12, 2020
154ce5d
docs: add reference to in-region-discovery example
mattsb42-aws Mar 12, 2020
3fc892a
docs: add basic KMS discovery keyring example
mattsb42-aws Mar 12, 2020
47351a7
docs: add examples showing region-scoped discovery keyrings
mattsb42-aws Mar 12, 2020
7770186
docs: add listing of keyring examples
mattsb42-aws Mar 12, 2020
0186e61
chore: remove reminder to write more because I already did
mattsb42-aws Mar 12, 2020
23c3766
docs: clean up multikeyring description
mattsb42-aws Mar 12, 2020
59b09df
docs: rearrange examples readme
mattsb42-aws Mar 12, 2020
9234c01
fix: set AWS_DEFAULT_REGION to region of primary CMK for examples tets
mattsb42-aws Mar 12, 2020
def80a5
Apply suggestions from code review
mattsb42-aws Mar 13, 2020
d0cca29
fix: fix examples test runner for Windows
mattsb42-aws Mar 16, 2020
ee23080
docs: update examples docs and comments with feedback
mattsb42-aws Mar 18, 2020
edc57ca
docs: re-order operations in RSA example for clarity
mattsb42-aws Mar 18, 2020
341c5b9
docs: add framing for examples in readme
mattsb42-aws Mar 18, 2020
5a364b9
docs: adjust wording
mattsb42-aws Mar 19, 2020
ad25893
docs: remove reference to Java
mattsb42-aws Mar 19, 2020
8e44484
docs: derive allowed discovery region from ARN rather than hard-codin…
mattsb42-aws Mar 19, 2020
810eff2
Merge branch 'keyring' into moar-examples
mattsb42-aws Mar 23, 2020
9acd33d
docs: change examples to talk about "KMS discovery keyring" rather th…
mattsb42-aws Mar 24, 2020
b8fdf96
docs: add link to NIST docs explaining RSA key size and fix typos
mattsb42-aws Mar 24, 2020
dd59a36
docs: apply suggestions from code review
mattsb42-aws Mar 25, 2020
739ed47
docs: carrying over copyedits
mattsb42-aws Mar 25, 2020
5ea4e81
Merge branch 'keyring' into moar-examples
mattsb42-aws Mar 26, 2020
8b28bee
feat: update KMS discovery keyring examples to use new discovery conf…
mattsb42-aws Mar 26, 2020
18007ed
Merge branch 'keyring' into moar-examples
mattsb42-aws Mar 31, 2020
c4aeaf7
docs: fix typo
mattsb42-aws Mar 31, 2020
dd63564
docs: remove trailing whitespace
mattsb42-aws Mar 31, 2020
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
40 changes: 36 additions & 4 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,45 @@ and streaming APIs.
You can find examples that demonstrate these APIs
in the [`examples/src/`](./src) directory.

* [How to encrypt and decrypt](./src/onestep_defaults.py)
* [How to change the algorithm suite](./src/onestep_unsigned.py)
* [How to encrypt and decrypt data streams in memory](./src/in_memory_streaming_defaults.py)
* [How to encrypt and decrypt data streamed between files](./src/file_streaming_defaults.py)

## Configuration

To use the library APIs,
you need to describe how you want the library to protect your data keys.
You can do this using
[keyrings][#keyrings] or [cryptographic materials managers][#cryptographic-materials-managers],
or using [master key providers][#master-key-providers].
These examples will show you how.
[keyrings](#keyrings) or [cryptographic materials managers](#cryptographic-materials-managers),
or using [master key providers](#master-key-providers).
These examples will show you how to use the configuration tools that we include for you
as well as how to create some of your own.
We start with AWS KMS examples, then show how to use other wrapping keys.

* Using AWS Key Management Service (AWS KMS)
* How to use a single AWS KMS CMK
* [with keyrings](./src/keyring/aws_kms/single_cmk.py)
* How to use multiple AWS KMS CMKs in different regions
* [with keyrings](./src/keyring/aws_kms/multiple_regions.py)
* How to decrypt when you don't know the CMK
* [with keyrings](./src/keyring/aws_kms/discovery_decrypt.py)
* How to decrypt within a region
* [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)
* Using raw wrapping keys
* How to use a raw AES wrapping key
* [with keyrings](./src/keyring/raw_aes/raw_aes.py)
* How to use a raw RSA wrapping key
* [with keyrings](./src/keyring/raw_rsa/private_key_only.py)
* How to use a raw RSA wrapping key when the key is PEM or DER encoded
* [with keyrings](./src/keyring/raw_rsa/private_key_only_from_pem.py)
* How to encrypt with a raw RSA public key wrapping key without access to the private key
* [with keyrings](./src/keyring/raw_rsa/public_private_key_separate.py)
* Combining wrapping keys
* How to combine AWS KMS with an offline escrow key
* [with keyrings](./src/keyring/multi/aws_kms_with_escrow.py)

### Keyrings

Expand Down Expand Up @@ -56,7 +87,8 @@ you can find these examples in [`examples/src/master_key_provider`](./src/master

## Legacy

This section includes older examples, including examples of using master keys and master key providers in Java and Python.
This section includes older examples,
including examples of using master keys and master key providers.
You can use them as a reference,
but we recommend looking at the newer examples, which explain the preferred ways of using this library.
You can find these examples in [`examples/src/legacy`](./src/legacy).
Expand Down
4 changes: 2 additions & 2 deletions examples/src/file_streaming_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def run(aws_kms_cmk, source_plaintext_filename):
for segment in encryptor:
ciphertext.write(segment)

# Verify that the ciphertext and plaintext are different.
# Demonstrate that the ciphertext and plaintext are different.
assert not filecmp.cmp(source_plaintext_filename, ciphertext_filename)

# Open the files you want to work with.
Expand All @@ -78,5 +78,5 @@ def run(aws_kms_cmk, source_plaintext_filename):
for segment in decryptor:
decrypted.write(segment)

# Verify that the decrypted plaintext is identical to the original plaintext.
# Demonstrate that the decrypted plaintext is identical to the original plaintext.
assert filecmp.cmp(source_plaintext_filename, decrypted_filename)
4 changes: 2 additions & 2 deletions examples/src/in_memory_streaming_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def run(aws_kms_cmk, source_plaintext):
for segment in encryptor:
ciphertext.write(segment)

# Verify that the ciphertext and plaintext are different.
# Demonstrate that the ciphertext and plaintext are different.
assert ciphertext.getvalue() != source_plaintext

# Reset the ciphertext stream position so that we can read from the beginning.
Expand All @@ -75,5 +75,5 @@ def run(aws_kms_cmk, source_plaintext):
for segment in decryptor:
decrypted.write(segment)

# Verify that the decrypted plaintext is identical to the original plaintext.
# Demonstrate that the decrypted plaintext is identical to the original plaintext.
assert decrypted.getvalue() == source_plaintext
7 changes: 7 additions & 0 deletions examples/src/keyring/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Keyring examples.

These examples show how to use keyrings.
"""
7 changes: 7 additions & 0 deletions examples/src/keyring/aws_kms/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
AWS KMS keyring examples.

These examples show how to use the KMS keyring.
"""
115 changes: 115 additions & 0 deletions examples/src/keyring/aws_kms/custom_client_supplier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
By default, the KMS keyring uses a client supplier that
supplies a client with the same configuration for every region.
If you need different behavior, you can write your own client supplier.

One use-case where you might need this is
if you need different credentials to talk to different AWS regions.
This might be because you are crossing partitions (ex: ``aws`` and ``aws-cn``)
or if you are working with regions that have separate authentication silos
like ``ap-east-1`` and ``me-south-1``.

This example shows how to create a client supplier
that will supply KMS clients with valid credentials for the target region
even when working with regions that need different credentials.

https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring

For an example of how to use the KMS keyring with CMKs in multiple regions,
see the ``keyring/aws_kms/multiple_regions`` example.

For another example of how to use the KMS keyring with a custom client configuration,
see the ``keyring/aws_kms/custom_kms_client_config`` example.

For examples of how to use the KMS keyring in discovery mode on decrypt,
see the ``keyring/aws_kms/discovery_decrypt``,
``keyring/aws_kms/discovery_decrypt_in_region_only``,
and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples.
"""
from botocore.client import BaseClient
from botocore.session import Session

import aws_encryption_sdk
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring
from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import ClientSupplier, DefaultClientSupplier

try: # Python 3.5.0 and 3.5.1 have incompatible typing modules
from typing import Union # noqa pylint: disable=unused-import
except ImportError: # pragma: no cover
# We only actually need these imports when running the mypy checks
pass


class MultiPartitionClientSupplier(ClientSupplier):
"""Client supplier that supplies clients across AWS partitions and identity silos."""

def __init__(self):
"""Set up default client suppliers for identity silos."""
self._china_supplier = DefaultClientSupplier(botocore_session=Session(profile="china"))
self._middle_east_supplier = DefaultClientSupplier(botocore_session=Session(profile="middle-east"))
self._hong_kong_supplier = DefaultClientSupplier(botocore_session=Session(profile="hong-kong"))
self._default_supplier = DefaultClientSupplier()

def __call__(self, region_name):
# type: (Union[None, str]) -> BaseClient
"""Return a client for the requested region.

:rtype: BaseClient
"""
if region_name.startswith("cn-"):
return self._china_supplier(region_name)

if region_name.startswith("me-"):
return self._middle_east_supplier(region_name)

if region_name == "ap-east-1":
return self._hong_kong_supplier(region_name)

return self._default_supplier(region_name)


def run(aws_kms_cmk, source_plaintext):
# type: (str, bytes) -> None
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with a custom client supplier.
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
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with a custom client supplier.
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with a custom client supplier.

I ran into the same dilemma in the docs. Does the "custom" before client supplier imply a configured standard client supplier, or a different implementation of the client supplier interface?

Copy link
Member Author

Choose a reason for hiding this comment

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

I would assume that "custom" means "something that is not provided out of the box". A configuration of a built-in feature does not say "custom" to me


: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",
}

# Create the keyring that determines how your data keys are protected.
keyring = KmsKeyring(generator_key_id=aws_kms_cmk, client_supplier=MultiPartitionClientSupplier())

# 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.
#
# We do not need to specify the encryption context on decrypt
# because the header 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())
88 changes: 88 additions & 0 deletions examples/src/keyring/aws_kms/custom_kms_client_config.py
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
"""
By default, the KMS keyring will use the default configurations
for all KMS clients and will use the default discoverable credentials.
If you need to change these configurations,
you can do that using the client supplier.

This example shows how to use custom-configured clients with the KMS keyring.

https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring

For an example of how to use the KMS keyring with CMKs in multiple regions,
see the ``keyring/aws_kms/multiple_regions`` example.

For another example of how to use the KMS keyring with custom client configuration,
see the ``keyring/aws_kms/custom_client_supplier`` example.

For examples of how to use the KMS keyring in discovery mode on decrypt,
see the ``keyring/aws_kms/discovery_decrypt``,
``keyring/aws_kms/discovery_decrypt_in_region_only``,
and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples.
"""
from botocore.config import Config
from botocore.session import Session

import aws_encryption_sdk
from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring
from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import DefaultClientSupplier


def run(aws_kms_cmk, source_plaintext):
# type: (str, bytes) -> None
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with custom KMS client configuration.

: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",
}

# Prepare your custom configuration values.
#
# Set your custom connection timeout value.
# https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html
custom_client_config = Config(connect_timeout=10.0, user_agent_extra=USER_AGENT_SUFFIX)
# For this example we will just use the default botocore session configuration
# but if you need to, you can set custom credentials in the botocore session.
custom_session = Session()

# Use your custom configuration values to configure your client supplier.
client_supplier = DefaultClientSupplier(botocore_session=custom_session, client_config=custom_client_config)

# Create the keyring that determines how your data keys are protected,
# providing the client supplier that you created.
keyring = KmsKeyring(generator_key_id=aws_kms_cmk, client_supplier=client_supplier)

# 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.
#
# We do not need to specify the encryption context on decrypt
# because the header 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())
78 changes: 78 additions & 0 deletions examples/src/keyring/aws_kms/discovery_decrypt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
When you give the KMS keyring specific key IDs it will use those CMKs and nothing else.
This is true both on encrypt and on decrypt.
However, sometimes you need more flexibility on decrypt,
especially if you might not know beforehand which CMK was used to encrypt a message.
To address this need, you can use a KMS discovery keyring.
The KMS discovery keyring will do nothing on encrypt
but will attempt to decrypt *any* data keys that were encrypted under a KMS CMK.

This example shows how to configure and use a KMS keyring in discovery mode.

https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring

For an example of how to use the KMS keyring with CMKs in multiple regions,
see the ``keyring/aws_kms/multiple_regions`` example.

For examples of how to use the KMS keyring with custom client configurations,
see the ``keyring/aws_kms/custom_client_supplier``
and ``keyring/aws_kms/custom_kms_client_config`` examples.

For examples of how to use the KMS keyring in discovery mode on decrypt,
see the ``keyring/aws_kms/discovery_decrypt_in_region_only``
and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples.
"""
import aws_encryption_sdk
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring


def run(aws_kms_cmk, source_plaintext):
# type: (str, bytes) -> None
"""Demonstrate configuring a KMS keyring to use discovery mode for decryption.

: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",
}

# Create the keyring that determines how your data keys are protected.
encrypt_keyring = KmsKeyring(generator_key_id=aws_kms_cmk)

# Create the KMS discovery keyring that we will use on decrypt.
#
# Because we do not specify any key IDs, this keyring is created in discovery mode.
decrypt_keyring = KmsKeyring()

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

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

# Decrypt your encrypted data using the KMS discovery keyring.
#
# We do not need to specify the encryption context on decrypt
# because the header message includes the encryption context.
decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=decrypt_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())
Loading