Skip to content

Commit 8d84ff5

Browse files
mattsb42-awsjunebWesleyRosenblum
authored
docs: add keyring examples (#221)
* docs: add raw RSA keyring examples * fix: fix type signatures in raw RSA keyring * docs: add raw AES keyring example * chore: move single-CMK example to new location * docs: update single-CMK example to match new template * fix: fix docstring in raw AES keyring example * chore: move multi-region KMS example to new location * chore: update multi-region AWS keyring example to new format * docs: add multi-keyring example * docs: add custom KMS client config keyring example * docs: add multi-partition client supplier example * docs: clarify custom client supplier example intro * docs: add reference to in-region-discovery example * docs: add basic KMS discovery keyring example * docs: add examples showing region-scoped discovery keyrings * docs: add listing of keyring examples * chore: remove reminder to write more because I already did * docs: clean up multikeyring description * docs: rearrange examples readme * fix: set AWS_DEFAULT_REGION to region of primary CMK for examples tets * Apply suggestions from code review Co-Authored-By: June Blender <[email protected]> Co-Authored-By: Wesley Rosenblum <[email protected]> * fix: fix examples test runner for Windows * docs: update examples docs and comments with feedback * docs: re-order operations in RSA example for clarity * docs: add framing for examples in readme * docs: adjust wording * docs: remove reference to Java * docs: derive allowed discovery region from ARN rather than hard-coding it * docs: change examples to talk about "KMS discovery keyring" rather than "KMS keyring in discovery mode" * docs: add link to NIST docs explaining RSA key size and fix typos * docs: apply suggestions from code review Co-Authored-By: June Blender <[email protected]> * docs: carrying over copyedits * feat: update KMS discovery keyring examples to use new discovery configuration * docs: fix typo * docs: remove trailing whitespace Co-authored-by: June Blender <[email protected]> Co-authored-by: Wesley Rosenblum <[email protected]>
1 parent daed717 commit 8d84ff5

27 files changed

+1263
-135
lines changed

examples/README.md

+38-6
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,45 @@ and streaming APIs.
1414
You can find examples that demonstrate these APIs
1515
in the [`examples/src/`](./src) directory.
1616

17+
* [How to encrypt and decrypt](./src/onestep_defaults.py)
18+
* [How to change the algorithm suite](./src/onestep_unsigned.py)
19+
* [How to encrypt and decrypt data streams in memory](./src/in_memory_streaming_defaults.py)
20+
* [How to encrypt and decrypt data streamed between files](./src/file_streaming_defaults.py)
21+
1722
## Configuration
1823

19-
To use the library APIs,
24+
To use the encryption and decryption APIs,
2025
you need to describe how you want the library to protect your data keys.
21-
You can do this using
22-
[keyrings][#keyrings] or [cryptographic materials managers][#cryptographic-materials-managers],
23-
or using [master key providers][#master-key-providers].
24-
These examples will show you how.
26+
You can do this by configuring
27+
[keyrings](#keyrings) or [cryptographic materials managers](#cryptographic-materials-managers),
28+
or by configuring [master key providers](#master-key-providers).
29+
These examples will show you how to use the configuration tools that we include for you
30+
and how to create some of your own.
31+
We start with AWS KMS examples, then show how to use other wrapping keys.
32+
33+
* Using AWS Key Management Service (AWS KMS)
34+
* How to use one AWS KMS CMK
35+
* [with keyrings](./src/keyring/aws_kms/single_cmk.py)
36+
* How to use multiple AWS KMS CMKs in different regions
37+
* [with keyrings](./src/keyring/aws_kms/multiple_regions.py)
38+
* How to decrypt when you don't know the CMK
39+
* [with keyrings](./src/keyring/aws_kms/discovery_decrypt.py)
40+
* How to decrypt within a region
41+
* [with keyrings](./src/keyring/aws_kms/discovery_decrypt_in_region_only.py)
42+
* How to decrypt with a preferred region but failover to others
43+
* [with keyrings](./src/keyring/aws_kms/discovery_decrypt_with_preferred_regions.py)
44+
* Using raw wrapping keys
45+
* How to use a raw AES wrapping key
46+
* [with keyrings](./src/keyring/raw_aes/raw_aes.py)
47+
* How to use a raw RSA wrapping key
48+
* [with keyrings](./src/keyring/raw_rsa/private_key_only.py)
49+
* How to use a raw RSA wrapping key when the key is PEM or DER encoded
50+
* [with keyrings](./src/keyring/raw_rsa/private_key_only_from_pem.py)
51+
* How to encrypt with a raw RSA public key wrapping key without access to the private key
52+
* [with keyrings](./src/keyring/raw_rsa/public_private_key_separate.py)
53+
* Combining wrapping keys
54+
* How to combine AWS KMS with an offline escrow key
55+
* [with keyrings](./src/keyring/multi/aws_kms_with_escrow.py)
2556

2657
### Keyrings
2758

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

5788
## Legacy
5889

59-
This section includes older examples, including examples of using master keys and master key providers in Java and Python.
90+
This section includes older examples,
91+
including examples of using master keys and master key providers.
6092
You can use them as a reference,
6193
but we recommend looking at the newer examples, which explain the preferred ways of using this library.
6294
You can find these examples in [`examples/src/legacy`](./src/legacy).

examples/src/file_streaming_defaults.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ def run(aws_kms_cmk, source_plaintext_filename):
5454
for segment in encryptor:
5555
ciphertext.write(segment)
5656

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

6060
# Open the files you want to work with.
6161
with open(ciphertext_filename, "rb") as ciphertext, open(decrypted_filename, "wb") as decrypted:
6262
# Decrypt your encrypted data using the same keyring you used on encrypt.
6363
#
64-
# We do not need to specify the encryption context on decrypt
65-
# because the message header includes the encryption context.
64+
# You do not need to specify the encryption context on decrypt
65+
# because the header of the encrypted message includes the encryption context.
6666
with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, keyring=keyring) as decryptor:
6767
# Check the encryption context in the header before we start decrypting.
6868
#
@@ -78,5 +78,5 @@ def run(aws_kms_cmk, source_plaintext_filename):
7878
for segment in decryptor:
7979
decrypted.write(segment)
8080

81-
# Verify that the decrypted plaintext is identical to the original plaintext.
81+
# Demonstrate that the decrypted plaintext is identical to the original plaintext.
8282
assert filecmp.cmp(source_plaintext_filename, decrypted_filename)

examples/src/in_memory_streaming_defaults.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
In this example, we use an AWS KMS customer master key (CMK),
1010
but you can use other key management options with the AWS Encryption SDK.
1111
For examples that demonstrate how to use other key management configurations,
12-
see the ``keyring`` and ``mater_key_provider`` directories.
12+
see the ``keyring`` and ``master_key_provider`` directories.
1313
"""
1414
import io
1515

@@ -49,16 +49,16 @@ def run(aws_kms_cmk, source_plaintext):
4949
for segment in encryptor:
5050
ciphertext.write(segment)
5151

52-
# Verify that the ciphertext and plaintext are different.
52+
# Demonstrate that the ciphertext and plaintext are different.
5353
assert ciphertext.getvalue() != source_plaintext
5454

5555
# Reset the ciphertext stream position so that we can read from the beginning.
5656
ciphertext.seek(0)
5757

5858
# Decrypt your encrypted data using the same keyring you used on encrypt.
5959
#
60-
# We do not need to specify the encryption context on decrypt
61-
# because the header message includes the encryption context.
60+
# You do not need to specify the encryption context on decrypt
61+
# because the header of the encrypted message includes the encryption context.
6262
decrypted = io.BytesIO()
6363
with aws_encryption_sdk.stream(mode="decrypt", source=ciphertext, keyring=keyring) as decryptor:
6464
# Check the encryption context in the header before we start decrypting.
@@ -75,5 +75,5 @@ def run(aws_kms_cmk, source_plaintext):
7575
for segment in decryptor:
7676
decrypted.write(segment)
7777

78-
# Verify that the decrypted plaintext is identical to the original plaintext.
78+
# Demonstrate that the decrypted plaintext is identical to the original plaintext.
7979
assert decrypted.getvalue() == source_plaintext

examples/src/keyring/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
Keyring examples.
5+
6+
These examples show how to use keyrings.
7+
"""
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
AWS KMS keyring examples.
5+
6+
These examples show how to use the KMS keyring.
7+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
By default, the KMS keyring uses a client supplier that
5+
supplies a client with the same configuration for every region.
6+
If you need different behavior, you can write your own client supplier.
7+
8+
You might use this
9+
if you need different credentials in different AWS regions.
10+
This might be because you are crossing partitions (ex: ``aws`` and ``aws-cn``)
11+
or if you are working with regions that have separate authentication silos
12+
like ``ap-east-1`` and ``me-south-1``.
13+
14+
This example shows how to create a client supplier
15+
that will supply KMS clients with valid credentials for the target region
16+
even when working with regions that need different credentials.
17+
18+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring
19+
20+
For an example of how to use the KMS keyring with CMKs in multiple regions,
21+
see the ``keyring/aws_kms/multiple_regions`` example.
22+
23+
For another example of how to use the KMS keyring with a custom client configuration,
24+
see the ``keyring/aws_kms/custom_kms_client_config`` example.
25+
26+
For examples of how to use the KMS keyring in discovery mode on decrypt,
27+
see the ``keyring/aws_kms/discovery_decrypt``,
28+
``keyring/aws_kms/discovery_decrypt_in_region_only``,
29+
and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples.
30+
"""
31+
from botocore.client import BaseClient
32+
from botocore.session import Session
33+
34+
import aws_encryption_sdk
35+
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring
36+
from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import ClientSupplier, DefaultClientSupplier
37+
38+
try: # Python 3.5.0 and 3.5.1 have incompatible typing modules
39+
from typing import Union # noqa pylint: disable=unused-import
40+
except ImportError: # pragma: no cover
41+
# We only need these imports when running the mypy checks
42+
pass
43+
44+
45+
class MultiPartitionClientSupplier(ClientSupplier):
46+
"""Client supplier that supplies clients across AWS partitions and identity silos."""
47+
48+
def __init__(self):
49+
"""Set up default client suppliers for identity silos."""
50+
self._china_supplier = DefaultClientSupplier(botocore_session=Session(profile="china"))
51+
self._middle_east_supplier = DefaultClientSupplier(botocore_session=Session(profile="middle-east"))
52+
self._hong_kong_supplier = DefaultClientSupplier(botocore_session=Session(profile="hong-kong"))
53+
self._default_supplier = DefaultClientSupplier()
54+
55+
def __call__(self, region_name):
56+
# type: (Union[None, str]) -> BaseClient
57+
"""Return a client for the requested region.
58+
59+
:rtype: BaseClient
60+
"""
61+
if region_name.startswith("cn-"):
62+
return self._china_supplier(region_name)
63+
64+
if region_name.startswith("me-"):
65+
return self._middle_east_supplier(region_name)
66+
67+
if region_name == "ap-east-1":
68+
return self._hong_kong_supplier(region_name)
69+
70+
return self._default_supplier(region_name)
71+
72+
73+
def run(aws_kms_cmk, source_plaintext):
74+
# type: (str, bytes) -> None
75+
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with a custom client supplier.
76+
77+
:param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys
78+
:param bytes source_plaintext: Plaintext to encrypt
79+
"""
80+
# Prepare your encryption context.
81+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
82+
encryption_context = {
83+
"encryption": "context",
84+
"is not": "secret",
85+
"but adds": "useful metadata",
86+
"that can help you": "be confident that",
87+
"the data you are handling": "is what you think it is",
88+
}
89+
90+
# Create the keyring that determines how your data keys are protected.
91+
keyring = KmsKeyring(generator_key_id=aws_kms_cmk, client_supplier=MultiPartitionClientSupplier())
92+
93+
# Encrypt your plaintext data.
94+
ciphertext, _encrypt_header = aws_encryption_sdk.encrypt(
95+
source=source_plaintext, encryption_context=encryption_context, keyring=keyring
96+
)
97+
98+
# Demonstrate that the ciphertext and plaintext are different.
99+
assert ciphertext != source_plaintext
100+
101+
# Decrypt your encrypted data using the same keyring you used on encrypt.
102+
#
103+
# You do not need to specify the encryption context on decrypt
104+
# because the header of the encrypted message includes the encryption context.
105+
decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring)
106+
107+
# Demonstrate that the decrypted plaintext is identical to the original plaintext.
108+
assert decrypted == source_plaintext
109+
110+
# Verify that the encryption context used in the decrypt operation includes
111+
# the encryption context that you specified when encrypting.
112+
# The AWS Encryption SDK can add pairs, so don't require an exact match.
113+
#
114+
# In production, always use a meaningful encryption context.
115+
assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
By default, the KMS keyring uses the default configurations
5+
for all KMS clients and uses the default discoverable credentials.
6+
If you need to change this configuration,
7+
you can configure the client supplier.
8+
9+
This example shows how to use custom-configured clients with the KMS keyring.
10+
11+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring
12+
13+
For an example of how to use the KMS keyring with CMKs in multiple regions,
14+
see the ``keyring/aws_kms/multiple_regions`` example.
15+
16+
For another example of how to use the KMS keyring with custom client configuration,
17+
see the ``keyring/aws_kms/custom_client_supplier`` example.
18+
19+
For examples of how to use the KMS keyring in discovery mode on decrypt,
20+
see the ``keyring/aws_kms/discovery_decrypt``,
21+
``keyring/aws_kms/discovery_decrypt_in_region_only``,
22+
and ``keyring/aws_kms/discovery_decrypt_with_preferred_region`` examples.
23+
"""
24+
from botocore.config import Config
25+
from botocore.session import Session
26+
27+
import aws_encryption_sdk
28+
from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX
29+
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring
30+
from aws_encryption_sdk.keyrings.aws_kms.client_suppliers import DefaultClientSupplier
31+
32+
33+
def run(aws_kms_cmk, source_plaintext):
34+
# type: (str, bytes) -> None
35+
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with custom KMS client configuration.
36+
37+
:param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys
38+
:param bytes source_plaintext: Plaintext to encrypt
39+
"""
40+
# Prepare your encryption context.
41+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
42+
encryption_context = {
43+
"encryption": "context",
44+
"is not": "secret",
45+
"but adds": "useful metadata",
46+
"that can help you": "be confident that",
47+
"the data you are handling": "is what you think it is",
48+
}
49+
50+
# Prepare your custom configuration values.
51+
#
52+
# Set your custom connection timeout value.
53+
# https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html
54+
custom_client_config = Config(connect_timeout=10.0, user_agent_extra=USER_AGENT_SUFFIX)
55+
# For this example we will just use the default botocore session configuration
56+
# but if you need to, you can set custom credentials in the botocore session.
57+
custom_session = Session()
58+
59+
# Use your custom configuration values to configure your client supplier.
60+
client_supplier = DefaultClientSupplier(botocore_session=custom_session, client_config=custom_client_config)
61+
62+
# Create the keyring that determines how your data keys are protected,
63+
# providing the client supplier that you created.
64+
keyring = KmsKeyring(generator_key_id=aws_kms_cmk, client_supplier=client_supplier)
65+
66+
# Encrypt your plaintext data.
67+
ciphertext, _encrypt_header = aws_encryption_sdk.encrypt(
68+
source=source_plaintext, encryption_context=encryption_context, keyring=keyring
69+
)
70+
71+
# Demonstrate that the ciphertext and plaintext are different.
72+
assert ciphertext != source_plaintext
73+
74+
# Decrypt your encrypted data using the same keyring you used on encrypt.
75+
#
76+
# You do not need to specify the encryption context on decrypt
77+
# because the header of the encrypted message includes the encryption context.
78+
decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, keyring=keyring)
79+
80+
# Demonstrate that the decrypted plaintext is identical to the original plaintext.
81+
assert decrypted == source_plaintext
82+
83+
# Verify that the encryption context used in the decrypt operation includes
84+
# the encryption context that you specified when encrypting.
85+
# The AWS Encryption SDK can add pairs, so don't require an exact match.
86+
#
87+
# In production, always use a meaningful encryption context.
88+
assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items())

0 commit comments

Comments
 (0)