Skip to content

Commit f0951f7

Browse files
committed
docs: add CMM examples
1 parent 5abbf9c commit f0951f7

File tree

7 files changed

+454
-0
lines changed

7 files changed

+454
-0
lines changed
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+
Cryptographic materials manager examples.
5+
6+
These examples show how to create and use cryptographic materials managers.
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+
Caching cryptographic materials manager examples.
5+
6+
These examples show how to configure and use the caching cryptographic materials manager.
7+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
The default cryptographic materials manager (CMM)
5+
creates new encryption and decryption materials
6+
on every call.
7+
This means every encrypted message is protected by a unique data key,
8+
but it also means that you need to interact with your key management system
9+
in order to process any message.
10+
If this causes performance, operations, or cost issues for you,
11+
you might benefit from data key caching.
12+
13+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/data-key-caching.html
14+
15+
This examples shows how to configure the caching CMM
16+
to reuse data keys across multiple encrypted messages.
17+
18+
In this example, we use an AWS KMS customer master key (CMK),
19+
but you can use other key management options with the AWS Encryption SDK.
20+
For examples that demonstrate how to use other key management configurations,
21+
see the ``keyring`` and ``master_key_provider`` directories.
22+
23+
In this example, we use the one-step encrypt and decrypt APIs.
24+
"""
25+
import aws_encryption_sdk
26+
from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache
27+
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring
28+
from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager
29+
30+
31+
def run(aws_kms_cmk, source_plaintext):
32+
# type: (str, bytes) -> None
33+
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with a single CMK.
34+
35+
:param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys
36+
:param bytes source_plaintext: Plaintext to encrypt
37+
"""
38+
# Prepare your encryption context.
39+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
40+
encryption_context = {
41+
"encryption": "context",
42+
"is not": "secret",
43+
"but adds": "useful metadata",
44+
"that can help you": "be confident that",
45+
"the data you are handling": "is what you think it is",
46+
}
47+
48+
# Create the keyring that determines how your data keys are protected.
49+
keyring = KmsKeyring(generator_key_id=aws_kms_cmk)
50+
51+
# Create the caching cryptographic materials manager using your keyring.
52+
cmm = CachingCryptoMaterialsManager(
53+
keyring=keyring,
54+
# The cache is where the caching CMM stores the materials.
55+
#
56+
# LocalCryptoMaterialsCache gives you a local, in-memory, cache.
57+
cache=LocalCryptoMaterialsCache(capacity=100),
58+
# max_age determines how long the caching CMM will reuse materials.
59+
#
60+
# This example uses two minutes.
61+
# In production, always choose as small a value as possible
62+
# that works for your requirements.
63+
max_age=120.0,
64+
# max_messages_encrypted determines how many messages
65+
# the caching CMM will protect with the same materials.
66+
#
67+
# In production, always choose as small a value as possible
68+
# that works for your requirements.
69+
max_messages_encrypted=10,
70+
)
71+
72+
# Encrypt your plaintext data.
73+
ciphertext, _encrypt_header = aws_encryption_sdk.encrypt(
74+
source=source_plaintext, encryption_context=encryption_context, materials_manager=cmm
75+
)
76+
77+
# Demonstrate that the ciphertext and plaintext are different.
78+
assert ciphertext != source_plaintext
79+
80+
# Decrypt your encrypted data using the same cryptographic materials manager you used on encrypt.
81+
#
82+
# You do not need to specify the encryption context on decrypt
83+
# because the header of the encrypted message includes the encryption context.
84+
decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, materials_manager=cmm)
85+
86+
# Demonstrate that the decrypted plaintext is identical to the original plaintext.
87+
assert decrypted == source_plaintext
88+
89+
# Verify that the encryption context used in the decrypt operation includes
90+
# the encryption context that you specified when encrypting.
91+
# The AWS Encryption SDK can add pairs, so don't require an exact match.
92+
#
93+
# In production, always use a meaningful encryption context.
94+
assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
Cuystom cryptographic materials manager (CMM) examples.
5+
6+
The AWS Encryption SDK includes CMMs for common use cases,
7+
but you might need to do something else.
8+
9+
These examples show how you could create your own CMM for some specific requirements.
10+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
The AWS Encryption SDK supports several different algorithm suites
5+
that offer different security properties.
6+
7+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/supported-algorithms.html
8+
9+
By default, the AWS Encryption SDK will let you use any of these,
10+
but you might want to restrict that further.
11+
We do not recommend using the algorithm suites without key derivation,
12+
so for this example we will show how to make a custom CMM
13+
that will not allow you to use those algorithm suites.
14+
"""
15+
import aws_encryption_sdk
16+
from aws_encryption_sdk.identifiers import AlgorithmSuite, KDFSuite
17+
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring
18+
from aws_encryption_sdk.keyrings.base import Keyring
19+
from aws_encryption_sdk.materials_managers import (
20+
DecryptionMaterials,
21+
DecryptionMaterialsRequest,
22+
EncryptionMaterials,
23+
EncryptionMaterialsRequest,
24+
)
25+
from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager
26+
from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager
27+
28+
29+
class UnsupportedAlgorithmSuite(Exception):
30+
"""Indicate that an unsupported algorithm suite was requested."""
31+
32+
33+
class OnlyKdfAlgorithmSuitesCryptoMaterialsManager(CryptoMaterialsManager):
34+
"""Only allow encryption requests for algorithm suites with a KDF."""
35+
36+
def __init__(self, keyring):
37+
# type: (Keyring) -> None
38+
"""Set up the inner cryptographic materials manager using the provided keyring.
39+
40+
:param Keyring keyring: Keyring to use in the inner cryptographic materials manager
41+
"""
42+
self._cmm = DefaultCryptoMaterialsManager(keyring=keyring)
43+
44+
def get_encryption_materials(self, request):
45+
# type: (EncryptionMaterialsRequest) -> EncryptionMaterials
46+
"""Block any requests that include an algorithm suite without a KDF."""
47+
if request.algorithm is not None and request.algorithm.kdf is KDFSuite.NONE:
48+
raise UnsupportedAlgorithmSuite("Non-KDF algorithm suites are not allowed!")
49+
50+
return self._cmm.get_encryption_materials(request)
51+
52+
def decrypt_materials(self, request):
53+
# type: (DecryptionMaterialsRequest) -> DecryptionMaterials
54+
"""Be more permissive on decrypt and just pass through."""
55+
return self._cmm.decrypt_materials(request)
56+
57+
58+
def run(aws_kms_cmk, source_plaintext):
59+
# type: (str, bytes) -> None
60+
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with a single CMK.
61+
62+
:param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys
63+
:param bytes source_plaintext: Plaintext to encrypt
64+
"""
65+
# Prepare your encryption context.
66+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
67+
encryption_context = {
68+
"encryption": "context",
69+
"is not": "secret",
70+
"but adds": "useful metadata",
71+
"that can help you": "be confident that",
72+
"the data you are handling": "is what you think it is",
73+
}
74+
75+
# Create the keyring that determines how your data keys are protected.
76+
keyring = KmsKeyring(generator_key_id=aws_kms_cmk)
77+
78+
# Create the filtering cryptographic materials manager using your keyring.
79+
cmm = OnlyKdfAlgorithmSuitesCryptoMaterialsManager(keyring=keyring)
80+
81+
# Demonstrate that the filtering CMM will not let you use non-KDF algorithm suites.
82+
try:
83+
aws_encryption_sdk.encrypt(
84+
source=source_plaintext,
85+
encryption_context=encryption_context,
86+
materials_manager=cmm,
87+
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16,
88+
)
89+
except UnsupportedAlgorithmSuite:
90+
# You asked for a non-KDF algorithm suite.
91+
# Reaching this point means everything is working as expected.
92+
pass
93+
else:
94+
# The filtering CMM keeps this from happening.
95+
raise AssertionError("The filtering CMM does not let this happen!")
96+
97+
# Encrypt your plaintext data.
98+
ciphertext, _encrypt_header = aws_encryption_sdk.encrypt(
99+
source=source_plaintext, encryption_context=encryption_context, materials_manager=cmm
100+
)
101+
102+
# Demonstrate that the ciphertext and plaintext are different.
103+
assert ciphertext != source_plaintext
104+
105+
# Decrypt your encrypted data using the same cryptographic materials manager you used on encrypt.
106+
#
107+
# You do not need to specify the encryption context on decrypt
108+
# because the header of the encrypted message includes the encryption context.
109+
decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=ciphertext, materials_manager=cmm)
110+
111+
# Demonstrate that the decrypted plaintext is identical to the original plaintext.
112+
assert decrypted == source_plaintext
113+
114+
# Verify that the encryption context used in the decrypt operation includes
115+
# the encryption context that you specified when encrypting.
116+
# The AWS Encryption SDK can add pairs, so don't require an exact match.
117+
#
118+
# In production, always use a meaningful encryption context.
119+
assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""
4+
Encryption context is a powerful tool for access and audit controls
5+
because it lets you tie *non-secret* metadata about a plaintext value to the encrypted message.
6+
This is especially powerful when you use the AWS Encryption SDK with AWS KMS,
7+
but within the context of the AWS Encryption SDK alone
8+
you can use cryptographic materials managers to analyse the encryption context
9+
to provide logical controls and additional metadata.
10+
11+
https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
12+
https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context
13+
14+
This example shows how to create a custom cryptographic materials manager (CMM)
15+
that requires a particular field in any encryption context.
16+
"""
17+
import aws_encryption_sdk
18+
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring
19+
from aws_encryption_sdk.keyrings.base import Keyring
20+
from aws_encryption_sdk.materials_managers import (
21+
DecryptionMaterials,
22+
DecryptionMaterialsRequest,
23+
EncryptionMaterials,
24+
EncryptionMaterialsRequest,
25+
)
26+
from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager
27+
from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager
28+
29+
30+
class MissingClassificationError(Exception):
31+
"""Indicates that an encryption context was found that lacked a classification identifier."""
32+
33+
34+
class ClassificationRequiringCryptoMaterialsManager(CryptoMaterialsManager):
35+
"""Only allow requests when the encryption context contains a classification identifier."""
36+
37+
def __init__(self, keyring):
38+
# type: (Keyring) -> None
39+
"""Set up the inner cryptographic materials manager using the provided keyring.
40+
41+
:param Keyring keyring: Keyring to use in the inner cryptographic materials manager
42+
"""
43+
self._classification_field = "classification"
44+
self._classification_error = MissingClassificationError("Encryption context does not contain classification!")
45+
self._cmm = DefaultCryptoMaterialsManager(keyring=keyring)
46+
47+
def get_encryption_materials(self, request):
48+
# type: (EncryptionMaterialsRequest) -> EncryptionMaterials
49+
"""Block any requests that do not contain a classification identifier in the encryption context."""
50+
if self._classification_field not in request.encryption_context:
51+
raise self._classification_error
52+
53+
return self._cmm.get_encryption_materials(request)
54+
55+
def decrypt_materials(self, request):
56+
# type: (DecryptionMaterialsRequest) -> DecryptionMaterials
57+
"""Block any requests that do not contain a classification identifier in the encryption context."""
58+
if self._classification_field not in request.encryption_context:
59+
raise self._classification_error
60+
61+
return self._cmm.decrypt_materials(request)
62+
63+
64+
def run(aws_kms_cmk, source_plaintext):
65+
# type: (str, bytes) -> None
66+
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with a single CMK.
67+
68+
:param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys
69+
:param bytes source_plaintext: Plaintext to encrypt
70+
"""
71+
# Prepare your encryption context.
72+
# https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
73+
encryption_context = {
74+
"encryption": "context",
75+
"is not": "secret",
76+
"but adds": "useful metadata",
77+
"that can help you": "be confident that",
78+
"the data you are handling": "is what you think it is",
79+
}
80+
81+
# Create the keyring that determines how your data keys are protected.
82+
keyring = KmsKeyring(generator_key_id=aws_kms_cmk)
83+
84+
# Create the filtering cryptographic materials manager using your keyring.
85+
cmm = ClassificationRequiringCryptoMaterialsManager(keyring=keyring)
86+
87+
# Demonstrate that the filtering CMM will not let you encrypt without a classification identifier.
88+
try:
89+
aws_encryption_sdk.encrypt(
90+
source=source_plaintext, encryption_context=encryption_context, materials_manager=cmm,
91+
)
92+
except MissingClassificationError:
93+
# Your encryption context did not contain a classification identifier.
94+
# Reaching this point means everything is working as expected.
95+
pass
96+
else:
97+
# The filtering CMM keeps this from happening.
98+
raise AssertionError("The filtering CMM does not let this happen!")
99+
100+
# Encrypt your plaintext data.
101+
classified_ciphertext, _encrypt_header = aws_encryption_sdk.encrypt(
102+
source=source_plaintext,
103+
encryption_context=dict(classification="secret", **encryption_context),
104+
materials_manager=cmm,
105+
)
106+
107+
# Demonstrate that the ciphertext and plaintext are different.
108+
assert classified_ciphertext != source_plaintext
109+
110+
# Decrypt your encrypted data using the same cryptographic materials manager you used on encrypt.
111+
#
112+
# You do not need to specify the encryption context on decrypt
113+
# because the header of the encrypted message includes the encryption context.
114+
decrypted, decrypt_header = aws_encryption_sdk.decrypt(source=classified_ciphertext, materials_manager=cmm)
115+
116+
# Demonstrate that the decrypted plaintext is identical to the original plaintext.
117+
assert decrypted == source_plaintext
118+
119+
# Verify that the encryption context used in the decrypt operation includes
120+
# the encryption context that you specified when encrypting.
121+
# The AWS Encryption SDK can add pairs, so don't require an exact match.
122+
#
123+
# In production, always use a meaningful encryption context.
124+
assert set(encryption_context.items()) <= set(decrypt_header.encryption_context.items())
125+
126+
# Now demonstrate the decrypt path of the filtering cryptographic materials manager.
127+
128+
# Encrypt your plaintext using the keyring and do not include a classification identifier.
129+
unclassified_ciphertext, encrypt_header = aws_encryption_sdk.encrypt(
130+
source=source_plaintext, encryption_context=encryption_context, keyring=keyring
131+
)
132+
133+
assert "classification" not in encrypt_header.encryption_context
134+
135+
# Demonstrate that the filtering CMM will not let you decrypt messages without classification identifiers.
136+
try:
137+
aws_encryption_sdk.decrypt(source=unclassified_ciphertext, materials_manager=cmm)
138+
except MissingClassificationError:
139+
# Your encryption context did not contain a classification identifier.
140+
# Reaching this point means everything is working as expected.
141+
pass
142+
else:
143+
# The filtering CMM keeps this from happening.
144+
raise AssertionError("The filtering CMM does not let this happen!")

0 commit comments

Comments
 (0)