Skip to content

docs: add CMM examples #239

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 12 commits into from
Apr 9, 2020
7 changes: 7 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ We start with AWS KMS examples, then show how to use other wrapping keys.
* How to combine AWS KMS with an offline escrow key
* [with keyrings](./src/keyring/multi/aws_kms_with_escrow.py)
* [with master key providers](./src/master_key_provider/multi/aws_kms_with_escrow.py)
* How to reuse data keys across multiple messages
* [with the caching cryptographic materials manager](./src/crypto_materials_manager/caching/simple_cache.py)
* How to restrict algorithm suites
* [with a custom cryptographic materials manager](src/crypto_materials_manager/custom/algorithm_suite_enforcement.py)
* How to require encryption context fields
* [with a custom cryptographic materials manager](src/crypto_materials_manager/custom/requiring_encryption_context_fields.py)
* [How to explicitly use the default cryptographic materials manager](./src/crypto_materials_manager/explicit_default.py)

### Keyrings

Expand Down
7 changes: 7 additions & 0 deletions examples/src/crypto_materials_manager/__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
"""
Cryptographic materials manager examples.

These examples show how to create and use cryptographic materials managers.
"""
7 changes: 7 additions & 0 deletions examples/src/crypto_materials_manager/caching/__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
"""
Caching cryptographic materials manager examples.

These examples show how to configure and use the caching cryptographic materials manager.
"""
94 changes: 94 additions & 0 deletions examples/src/crypto_materials_manager/caching/simple_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
The default cryptographic materials manager (CMM)
creates new encryption and decryption materials
on every call.
This means every encrypted message is protected by a unique data key,
but it also means that you need to interact with your key management system
in order to process any message.
If this causes performance, operations, or cost issues for you,
you might benefit from data key caching.

https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/data-key-caching.html

This examples shows how to configure the caching CMM
to reuse data keys across multiple encrypted messages.

In this example, we use an AWS KMS customer master key (CMK),
but you can use other key management options with the AWS Encryption SDK.
For examples that demonstrate how to use other key management configurations,
see the ``keyring`` and ``master_key_provider`` directories.

In this example, we use the one-step encrypt and decrypt APIs.
"""
import aws_encryption_sdk
from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring
from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager


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

: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)

# Create the caching cryptographic materials manager using your keyring.
cmm = CachingCryptoMaterialsManager(
keyring=keyring,
# The cache is where the caching CMM stores the materials.
#
# LocalCryptoMaterialsCache gives you a local, in-memory, cache.
cache=LocalCryptoMaterialsCache(capacity=100),
# max_age determines how long the caching CMM will reuse materials.
#
# This example uses two minutes.
# In production, always choose as small a value as possible
# that works for your requirements.
max_age=120.0,
# max_messages_encrypted determines how many messages
# the caching CMM will protect with the same materials.
#
# In production, always choose as small a value as possible
# that works for your requirements.
max_messages_encrypted=10,
)

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

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

# Decrypt your encrypted data using the same cryptographic materials manager 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, materials_manager=cmm)

# 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())
10 changes: 10 additions & 0 deletions examples/src/crypto_materials_manager/custom/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Cuystom cryptographic materials manager (CMM) examples.

The AWS Encryption SDK includes CMMs for common use cases,
but you might need to do something else.

These examples show how you could create your own CMM for some specific requirements.
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
The AWS Encryption SDK supports several different algorithm suites
that offer different security properties.

https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/supported-algorithms.html

By default, the AWS Encryption SDK will let you use any of these,
but you might want to restrict that further.
We do not recommend using the algorithm suites without key derivation,
so for this example we will show how to make a custom CMM
that will not allow you to use those algorithm suites.
"""
import aws_encryption_sdk
from aws_encryption_sdk.identifiers import AlgorithmSuite, KDFSuite
from aws_encryption_sdk.keyrings.aws_kms import KmsKeyring
from aws_encryption_sdk.keyrings.base import Keyring
from aws_encryption_sdk.materials_managers import (
DecryptionMaterials,
DecryptionMaterialsRequest,
EncryptionMaterials,
EncryptionMaterialsRequest,
)
from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager
from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager


class UnsupportedAlgorithmSuite(Exception):
"""Indicate that an unsupported algorithm suite was requested."""


class OnlyKdfAlgorithmSuitesCryptoMaterialsManager(CryptoMaterialsManager):
"""Only allow encryption requests for algorithm suites with a KDF."""

def __init__(self, keyring):
# type: (Keyring) -> None
"""Set up the inner cryptographic materials manager using the provided keyring.

:param Keyring keyring: Keyring to use in the inner cryptographic materials manager
"""
self._cmm = DefaultCryptoMaterialsManager(keyring=keyring)

def get_encryption_materials(self, request):
# type: (EncryptionMaterialsRequest) -> EncryptionMaterials
"""Block any requests that include an algorithm suite without a KDF."""
if request.algorithm is not None and request.algorithm.kdf is KDFSuite.NONE:
Copy link
Contributor

Choose a reason for hiding this comment

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

There isn't a particularly elegant way to do this in the Java ESDK. It would be something like:

if(!request.getRequestedAlgorithm().getDataKeyAlgo().contains("Hkdf"))

I wouldn't normally recommend someone write code that depends on a string in that way. Alternatively, both Java and Python both have the isSafeToCache parameter, could this make use of that instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe. If we want to go that route I want to re-frame what the example is about: changing it from specifically barring non-KDF suites to instead making sure that you can cache the request.

Another option would be changing the requirement to be one of our two recommended (default or default - signing).

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, I'd say let's do the second option. The example doesn't have to do anything particularly interesting or innovative to serve its purpose, that would show a basic validation so the user can get a sense of whats possible.

raise UnsupportedAlgorithmSuite("Non-KDF algorithm suites are not allowed!")

return self._cmm.get_encryption_materials(request)

def decrypt_materials(self, request):
# type: (DecryptionMaterialsRequest) -> DecryptionMaterials
"""Be more permissive on decrypt and just pass through."""
return self._cmm.decrypt_materials(request)


def run(aws_kms_cmk, source_plaintext):
# type: (str, bytes) -> None
"""Demonstrate an encrypt/decrypt cycle using a KMS keyring with a single CMK.
Copy link
Contributor

Choose a reason for hiding this comment

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

Update


: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)

# Create the filtering cryptographic materials manager using your keyring.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the first time you used the terminology "filtering CMM". Do you want to introduce that at the top?

Copy link
Member Author

Choose a reason for hiding this comment

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

This was just shorthand I was using, but you're right; I'll just replace these with references back to the actual custom CMM.

cmm = OnlyKdfAlgorithmSuitesCryptoMaterialsManager(keyring=keyring)

# Demonstrate that the filtering CMM will not let you use non-KDF algorithm suites.
try:
aws_encryption_sdk.encrypt(
source=source_plaintext,
encryption_context=encryption_context,
materials_manager=cmm,
algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16,
)
except UnsupportedAlgorithmSuite:
# You asked for a non-KDF algorithm suite.
# Reaching this point means everything is working as expected.
pass
else:
# The filtering CMM keeps this from happening.
raise AssertionError("The filtering CMM does not let this happen!")

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

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

# Decrypt your encrypted data using the same cryptographic materials manager 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, materials_manager=cmm)

# 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