Example #1
0
def test_aws_kms_single_cmk_keyring_on_decrypt_multiple_cmk(fake_generator_and_child):
    generator, child = fake_generator_and_child

    encrypting_keyring = AwsKmsKeyring(generator_key_id=generator, key_ids=(child,))
    decrypting_keyring = _AwsKmsSingleCmkKeyring(key_id=child, client_supplier=DefaultClientSupplier())

    initial_encryption_materials = EncryptionMaterials(algorithm=ALGORITHM, encryption_context={})

    encryption_materials = encrypting_keyring.on_encrypt(initial_encryption_materials)

    initial_decryption_materials = DecryptionMaterials(
        algorithm=encryption_materials.algorithm, encryption_context=encryption_materials.encryption_context
    )

    result_materials = decrypting_keyring.on_decrypt(
        decryption_materials=initial_decryption_materials, encrypted_data_keys=encryption_materials.encrypted_data_keys
    )

    generator_flags = _matching_flags(
        MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=generator), result_materials.keyring_trace
    )
    assert len(generator_flags) == 0

    child_flags = _matching_flags(
        MasterKeyInfo(provider_id=KEY_NAMESPACE, key_info=child), result_materials.keyring_trace
    )

    assert KeyringTraceFlag.DECRYPTED_DATA_KEY in child_flags
    assert KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT in child_flags
def run(aws_kms_cmk, source_plaintext):
    # type: (str, bytes) -> None
    """Demonstrate configuring an AWS KMS discovery keyring to only work within a single region.

    :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 = AwsKmsKeyring(generator_key_id=aws_kms_cmk)

    # Extract the region from the CMK ARN.
    decrypt_region = aws_kms_cmk.split(":", 4)[3]

    # Create the AWS KMS discovery keyring that we will use on decrypt.
    #
    # The client supplier that we specify here will only supply clients for the specified region.
    # The keyring only attempts to decrypt data keys if it can get a client for that region,
    # so this keyring will now ignore any data keys that were encrypted under a CMK in another region.
    decrypt_keyring = AwsKmsKeyring(is_discovery=True,
                                    client_supplier=AllowRegionsClientSupplier(
                                        allowed_regions=[decrypt_region]))

    # 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 AWS KMS discovery keyring.
    #
    # 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=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())
def test_kms_keyring_inner_keyring_on_encrypt(mocker):
    mock_keyring = mocker.Mock()

    keyring = AwsKmsKeyring(is_discovery=True)
    keyring._inner_keyring = mock_keyring

    test = keyring.on_encrypt(
        encryption_materials=mocker.sentinel.encryption_materials)

    # on_encrypt MUST be a straight passthrough to the inner keyring
    assert mock_keyring.on_encrypt.called_once_with(
        encryption_materials=mocker.sentinel.encryption_materials)
    assert test is mock_keyring.on_encrypt.return_value
def run(aws_kms_cmk, source_plaintext):
    # type: (str, bytes) -> None
    """Demonstrate configuring an AWS KMS discovery keyring 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.
    # Remember that your encryption context is NOT SECRET.
    # 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 = AwsKmsKeyring(generator_key_id=aws_kms_cmk)

    # Create an AWS KMS discovery keyring to use on decrypt.
    decrypt_keyring = AwsKmsKeyring(is_discovery=True)

    # 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 AWS KMS discovery keyring.
    #
    # 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=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())
def test_kms_keyring_builds_correct_inner_keyring_multikeyring_no_generator():
    test = AwsKmsKeyring(key_ids=("bar", "baz"))

    # We specified child IDs, so the inner keyring MUST be a multikeyring
    assert isinstance(test._inner_keyring, MultiKeyring)

    # We did not specify a generator ID, so the generator MUST NOT be set
    assert test._inner_keyring.generator is None

    # We specified two child IDs, so there MUST be exactly two children
    assert len(test._inner_keyring.children) == 2
def test_kms_keyring_builds_correct_inner_keyring_multikeyring_no_children():
    test = AwsKmsKeyring(generator_key_id="foo")

    # We specified a generator ID, so the inner keyring MUST be a multikeyring
    assert isinstance(test._inner_keyring, MultiKeyring)

    # We specified a generator ID, so the generator MUST be set
    assert test._inner_keyring.generator is not None

    # We did not specify any child IDs, so the multikeyring MUST NOT contain any children
    assert len(test._inner_keyring.children) == 0
def run(aws_kms_cmk, source_plaintext):
    # type: (str, bytes) -> None
    """Demonstrate requesting a specific algorithm suite through the one-step encrypt/decrypt APIs.

    :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 = AwsKmsKeyring(generator_key_id=aws_kms_cmk)

    # Encrypt your plaintext data.
    ciphertext, _encrypt_header = aws_encryption_sdk.encrypt(
        source=source_plaintext,
        encryption_context=encryption_context,
        keyring=keyring,
        # Here we can specify the algorithm suite that we want to use.
        algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA256,
    )

    # 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.
    #
    # You do not need to specify the algorithm suite on decrypt
    # because the header message includes the algorithm suite identifier.
    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())
def test_kms_keyring_builds_correct_inner_keyring_discovery():
    grants = ("asdf", "fdas")
    supplier = DefaultClientSupplier()

    test = AwsKmsKeyring(is_discovery=True,
                         grant_tokens=grants,
                         client_supplier=supplier)

    # We specified neither a generator nor children, so the inner keyring MUST be a discovery keyring
    assert isinstance(test._inner_keyring, _AwsKmsDiscoveryKeyring)

    # Verify that the discovery keyring is configured correctly
    assert test._inner_keyring._grant_tokens == grants
    assert test._inner_keyring._client_supplier is supplier
def build_aws_kms_keyring(generate=True, cache=True):
    """Build an AWS KMS keyring."""
    global _KMS_KEYRING  # pylint: disable=global-statement
    if cache and _KMS_KEYRING is not None:
        return _KMS_KEYRING

    cmk_arn = get_cmk_arn()

    if generate:
        kwargs = dict(generator_key_id=cmk_arn)
    else:
        kwargs = dict(key_ids=[cmk_arn])

    keyring = AwsKmsKeyring(**kwargs)

    if cache:
        _KMS_KEYRING = keyring

    return keyring
def test_kms_keyring_builds_correct_inner_keyring_multikeyring():
    generator_id = "foo"
    child_id_1 = "bar"
    child_id_2 = "baz"
    grants = ("asdf", "fdsa")
    supplier = DefaultClientSupplier()

    test = AwsKmsKeyring(
        generator_key_id=generator_id,
        key_ids=(child_id_1, child_id_2),
        grant_tokens=grants,
        client_supplier=supplier,
    )

    # We specified a generator and child IDs, so the inner keyring MUST be a multikeyring
    assert isinstance(test._inner_keyring, MultiKeyring)

    # Verify that the generator is configured correctly
    assert isinstance(test._inner_keyring.generator, _AwsKmsSingleCmkKeyring)
    assert test._inner_keyring.generator._key_id == generator_id
    assert test._inner_keyring.generator._grant_tokens == grants
    assert test._inner_keyring.generator._client_supplier is supplier

    # We specified two child IDs, so there MUST be exactly two children
    assert len(test._inner_keyring.children) == 2

    # Verify that the first child is configured correctly
    assert isinstance(test._inner_keyring.children[0], _AwsKmsSingleCmkKeyring)
    assert test._inner_keyring.children[0]._key_id == child_id_1
    assert test._inner_keyring.children[0]._grant_tokens == grants
    assert test._inner_keyring.children[0]._client_supplier is supplier

    # Verify that the second child is configured correctly
    assert isinstance(test._inner_keyring.children[1], _AwsKmsSingleCmkKeyring)
    assert test._inner_keyring.children[1]._key_id == child_id_2
    assert test._inner_keyring.children[1]._grant_tokens == grants
    assert test._inner_keyring.children[1]._client_supplier is supplier
Example #11
0
def run(aws_kms_cmk, source_plaintext):
    # type: (str, bytes) -> None
    """Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with custom AWS 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.
    # Remember that your encryption context is NOT SECRET.
    # 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 = AwsKmsKeyring(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.
    #
    # 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())
Example #12
0
def run(aws_kms_cmk, source_plaintext_filename):
    # type: (str, str) -> None
    """Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs with files.

    :param str aws_kms_cmk: The ARN of an AWS KMS CMK that protects data keys
    :param str source_plaintext_filename: Path to plaintext file to encrypt
    """
    # We assume that you can also write to the directory containing the plaintext file,
    # so that is where we will put all of the results.
    ciphertext_filename = source_plaintext_filename + ".encrypted"
    decrypted_filename = ciphertext_filename + ".decrypted"

    # Prepare your encryption context.
    # Remember that your encryption context is NOT SECRET.
    # 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 = AwsKmsKeyring(generator_key_id=aws_kms_cmk)

    # Open the files you want to work with.
    with open(source_plaintext_filename,
              "rb") as plaintext, open(ciphertext_filename,
                                       "wb") as ciphertext:
        # The streaming API provides a context manager.
        # You can read from it just as you read from a file.
        with aws_encryption_sdk.stream(mode="encrypt",
                                       source=plaintext,
                                       encryption_context=encryption_context,
                                       keyring=keyring) as encryptor:
            # Iterate through the segments in the context manager
            # and write the results to the ciphertext.
            for segment in encryptor:
                ciphertext.write(segment)

    # 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.
    with open(ciphertext_filename,
              "rb") as ciphertext, open(decrypted_filename, "wb") as decrypted:
        # 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.
        with aws_encryption_sdk.stream(mode="decrypt",
                                       source=ciphertext,
                                       keyring=keyring) as decryptor:
            # Check the encryption context in the header before we start decrypting.
            #
            # 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(
                decryptor.header.encryption_context.items())

            # Now that we are more confident that we will decrypt the right message,
            # we can start decrypting.
            for segment in decryptor:
                decrypted.write(segment)

    # Demonstrate that the decrypted plaintext is identical to the original plaintext.
    assert filecmp.cmp(source_plaintext_filename, decrypted_filename)
Example #13
0
def run(aws_kms_generator_cmk, aws_kms_additional_cmks, source_plaintext):
    # type: (str, Sequence[str], bytes) -> None
    """Demonstrate an encrypt/decrypt cycle using an AWS KMS keyring with CMKs in multiple regions.

    :param str aws_kms_generator_cmk: The ARN of the primary AWS KMS CMK
    :param List[str] aws_kms_additional_cmks: Additional ARNs of secondary AWS KMS CMKs
    :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 will encrypt your data keys under all requested CMKs.
    many_cmks_keyring = AwsKmsKeyring(generator_key_id=aws_kms_generator_cmk,
                                      key_ids=aws_kms_additional_cmks)

    # Create keyrings that each only use one of the CMKs.
    # We will use these later to demonstrate that any of the CMKs can be used to decrypt the message.
    #
    # We provide these in "key_ids" rather than "generator_key_id"
    # so that these keyrings cannot be used to generate a new data key.
    # We will only be using them on decrypt.
    single_cmk_keyring_that_generated = AwsKmsKeyring(
        key_ids=[aws_kms_generator_cmk])
    single_cmk_keyring_that_encrypted = AwsKmsKeyring(
        key_ids=[aws_kms_additional_cmks[0]])

    # Encrypt your plaintext data using the keyring that uses all requests CMKs.
    ciphertext, encrypt_header = aws_encryption_sdk.encrypt(
        source=source_plaintext,
        encryption_context=encryption_context,
        keyring=many_cmks_keyring)

    # Verify that the header contains the expected number of encrypted data keys (EDKs).
    # It should contain one EDK for each CMK.
    assert len(
        encrypt_header.encrypted_data_keys) == len(aws_kms_additional_cmks) + 1

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

    # Decrypt your encrypted data separately using the single-CMK keyrings.
    #
    # You do not need to specify the encryption context on decrypt
    # because the header of the encrypted message includes the encryption context.
    decrypted_1, decrypt_header_1 = aws_encryption_sdk.decrypt(
        source=ciphertext, keyring=single_cmk_keyring_that_generated)
    decrypted_2, decrypt_header_2 = aws_encryption_sdk.decrypt(
        source=ciphertext, keyring=single_cmk_keyring_that_encrypted)

    # Demonstrate that the decrypted plaintext is identical to the original plaintext.
    assert decrypted_1 == source_plaintext
    assert decrypted_2 == 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_1.encryption_context.items())
    assert set(encryption_context.items()) <= set(
        decrypt_header_2.encryption_context.items())
def run(aws_kms_cmk, source_plaintext):
    # type: (str, bytes) -> None
    """Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs in-memory.

    :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 = AwsKmsKeyring(generator_key_id=aws_kms_cmk)

    ciphertext = io.BytesIO()

    # The streaming API provides a context manager.
    # You can read from it just as you read from a file.
    with aws_encryption_sdk.stream(mode="encrypt",
                                   source=source_plaintext,
                                   encryption_context=encryption_context,
                                   keyring=keyring) as encryptor:
        # Iterate through the segments in the context manager
        # and write the results to the ciphertext.
        for segment in encryptor:
            ciphertext.write(segment)

    # 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.
    ciphertext.seek(0)

    # 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 = io.BytesIO()
    with aws_encryption_sdk.stream(mode="decrypt",
                                   source=ciphertext,
                                   keyring=keyring) as decryptor:
        # Check the encryption context in the header before we start decrypting.
        #
        # 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(
            decryptor.header.encryption_context.items())

        # Now that we are more confident that we will decrypt the right message,
        # we can start decrypting.
        for segment in decryptor:
            decrypted.write(segment)

    # Demonstrate that the decrypted plaintext is identical to the original plaintext.
    assert decrypted.getvalue() == source_plaintext
Example #15
0
def run(aws_kms_cmk, source_plaintext):
    # type: (str, bytes) -> None
    """Demonstrate configuring an AWS KMS discovery-like keyring a particular AWS region and failover to others.

    :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.
    # Remember that your encryption context is NOT SECRET.
    # 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 = AwsKmsKeyring(generator_key_id=aws_kms_cmk)

    # To create our decrypt keyring, we need to know our current default AWS region.
    #
    # Create a throw-away boto3 session to discover the default region.
    local_region = Session().region_name

    # Now, use that region name to create two AWS KMS discovery keyrings:
    #
    # One that only works in the local region
    local_region_decrypt_keyring = AwsKmsKeyring(
        is_discovery=True, client_supplier=AllowRegionsClientSupplier(allowed_regions=[local_region])
    )
    # and one that will work in any other region but NOT the local region.
    other_regions_decrypt_keyring = AwsKmsKeyring(
        is_discovery=True, client_supplier=DenyRegionsClientSupplier(denied_regions=[local_region])
    )

    # Finally, combine those two keyrings into a multi-keyring.
    #
    # The multi-keyring steps through its member keyrings in the order that you provide them,
    # attempting to decrypt every encrypted data key with each keyring before moving on to the next keyring.
    # Because of this, other_regions_decrypt_keyring will not be called
    # unless local_region_decrypt_keyring fails to decrypt every encrypted data key.
    decrypt_keyring = MultiKeyring(children=[local_region_decrypt_keyring, other_regions_decrypt_keyring])

    # 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 multi-keyring.
    #
    # 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=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())
def test_kms_keyring_invalid_parameters(kwargs):
    with pytest.raises(TypeError):
        AwsKmsKeyring(**kwargs)
Example #17
0
def run(aws_kms_cmk, source_plaintext):
    # type: (str, bytes) -> None
    """Demonstrate an encrypt/decrypt cycle using a custom cryptographic materials manager that filters requests.

    :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.
    # Remember that your encryption context is NOT SECRET.
    # 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 = AwsKmsKeyring(generator_key_id=aws_kms_cmk)

    # Create the classification requiring cryptographic materials manager using your keyring.
    cmm = ClassificationRequiringCryptoMaterialsManager(keyring=keyring)

    # Demonstrate that the classification requiring CMM will not let you encrypt without a classification identifier.
    try:
        aws_encryption_sdk.encrypt(
            source=source_plaintext, encryption_context=encryption_context, materials_manager=cmm,
        )
    except MissingClassificationError:
        # Your encryption context did not contain a classification identifier.
        # Reaching this point means everything is working as expected.
        pass
    else:
        # The classification requiring CMM keeps this from happening.
        raise AssertionError("The classification requiring CMM does not let this happen!")

    # Encrypt your plaintext data.
    classified_ciphertext, _encrypt_header = aws_encryption_sdk.encrypt(
        source=source_plaintext,
        encryption_context=dict(classification="secret", **encryption_context),
        materials_manager=cmm,
    )

    # Demonstrate that the ciphertext and plaintext are different.
    assert classified_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=classified_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())

    # Now demonstrate the decrypt path of the classification requiring cryptographic materials manager.

    # Encrypt your plaintext using the keyring and do not include a classification identifier.
    unclassified_ciphertext, encrypt_header = aws_encryption_sdk.encrypt(
        source=source_plaintext, encryption_context=encryption_context, keyring=keyring
    )

    assert "classification" not in encrypt_header.encryption_context

    # Demonstrate that the classification requiring CMM
    # will not let you decrypt messages without classification identifiers.
    try:
        aws_encryption_sdk.decrypt(source=unclassified_ciphertext, materials_manager=cmm)
    except MissingClassificationError:
        # Your encryption context did not contain a classification identifier.
        # Reaching this point means everything is working as expected.
        pass
    else:
        # The classification requiring CMM keeps this from happening.
        raise AssertionError("The classification requiring CMM does not let this happen!")
def run(aws_kms_cmk, source_plaintext):
    # type: (str, bytes) -> None
    """Demonstrate configuring a keyring to use an AWS KMS CMK and an RSA wrapping key.

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

    # Generate an RSA private key to use with your keyring.
    # In practice, you should get this key from a secure key management system such as an HSM.
    #
    # The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA.
    # https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths
    #
    # Why did we use this public exponent?
    # https://crypto.stanford.edu/~dabo/pubs/papers/RSA-survey.pdf
    private_key = rsa.generate_private_key(public_exponent=65537,
                                           key_size=4096,
                                           backend=default_backend())

    # Collect the public key from the private key.
    public_key = private_key.public_key()

    # Create the encrypt keyring that only has access to the public key.
    escrow_encrypt_keyring = RawRSAKeyring(
        # The key namespace and key name are defined by you
        # and are used by the raw RSA keyring
        # to determine whether it should attempt to decrypt
        # an encrypted data key.
        #
        # https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring
        key_namespace="some managed raw keys",
        key_name=b"my RSA wrapping key",
        public_wrapping_key=public_key,
        # The wrapping algorithm tells the raw RSA keyring
        # how to use your wrapping key to encrypt data keys.
        #
        # We recommend using RSA_OAEP_SHA256_MGF1.
        # You should not use RSA_PKCS1 unless you require it for backwards compatibility.
        wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1,
    )

    # Create the decrypt keyring that has access to the private key.
    escrow_decrypt_keyring = RawRSAKeyring(
        # The key namespace and key name MUST match the encrypt keyring.
        key_namespace="some managed raw keys",
        key_name=b"my RSA wrapping key",
        private_wrapping_key=private_key,
        # The wrapping algorithm MUST match the encrypt keyring.
        wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1,
    )

    # Create the AWS KMS keyring that you will use for decryption during normal operations.
    kms_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk)

    # Combine the AWS KMS keyring and the escrow encrypt keyring using the multi-keyring.
    encrypt_keyring = MultiKeyring(generator=kms_keyring,
                                   children=[escrow_encrypt_keyring])

    # Encrypt your plaintext data using the multi-keyring.
    ciphertext, encrypt_header = aws_encryption_sdk.encrypt(
        source=source_plaintext,
        encryption_context=encryption_context,
        keyring=encrypt_keyring)

    # Verify that the header contains the expected number of encrypted data keys (EDKs).
    # It should contain one EDK for AWS KMS and one for the escrow key.
    assert len(encrypt_header.encrypted_data_keys) == 2

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

    # Decrypt your encrypted data separately using the AWS KMS keyring and the escrow decrypt keyring.
    #
    # You do not need to specify the encryption context on decrypt
    # because the header of the encrypted message includes the encryption context.
    decrypted_kms, decrypt_header_kms = aws_encryption_sdk.decrypt(
        source=ciphertext, keyring=kms_keyring)
    decrypted_escrow, decrypt_header_escrow = aws_encryption_sdk.decrypt(
        source=ciphertext, keyring=escrow_decrypt_keyring)

    # Demonstrate that the decrypted plaintext is identical to the original plaintext.
    assert decrypted_kms == source_plaintext
    assert decrypted_escrow == 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_kms.encryption_context.items())
    assert set(encryption_context.items()) <= set(
        decrypt_header_escrow.encryption_context.items())
Example #19
0
def run(aws_kms_cmk, aws_kms_additional_cmks, source_plaintext):
    # type: (str, Sequence[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 List[str] aws_kms_additional_cmks: Additional ARNs of secondary AWS KMS CMKs
    :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 reproduce.
    #
    # When encrypting, this master key provider generates the data key using the first CMK in the list
    # and encrypts the data key using all specified CMKs.
    # However, when decrypting, this master key provider attempts to decrypt
    # any data keys that were encrypted under an AWS KMS CMK.
    master_key_provider_cmks = [aws_kms_cmk] + aws_kms_additional_cmks
    _master_key_provider_to_replicate = KMSMasterKeyProvider(  # noqa: intentionally never used
        key_ids=master_key_provider_cmks, )

    # Create a CMK keyring that encrypts and decrypts using the specified AWS KMS CMKs.
    #
    # This keyring reproduces the encryption behavior of the AWS KMS master key provider.
    #
    # The AWS KMS keyring requires that you explicitly identify the CMK
    # that you want the keyring to use to generate the data key.
    cmk_keyring = AwsKmsKeyring(generator_key_id=aws_kms_cmk,
                                key_ids=aws_kms_additional_cmks)

    # 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 CMK and discovery keyrings
    # to create a keyring that behaves like an AWS KMS master key provider.
    #
    # The CMK keyring reproduces the encryption behavior
    # and the discovery keyring reproduces the decryption behavior.
    # This also means that it does not matter if the CMK keyring fails to decrypt.
    # For example, if you configured the CMK keyring with aliases,
    # it works on encrypt but fails to match any encrypted data keys on decrypt
    # because the serialized key name is the resulting CMK ARN rather than the alias name.
    # However, because the discovery keyring attempts to decrypt any AWS KMS-encrypted
    # data keys that it finds, the message still decrypts successfully.
    keyring = MultiKeyring(generator=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())
Example #20
0
def run(aws_kms_cmk, source_plaintext):
    # type: (str, bytes) -> None
    """Demonstrate an encrypt/decrypt cycle using the caching cryptographic materials manager.

    :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.
    # Remember that your encryption context is NOT SECRET.
    # 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 = AwsKmsKeyring(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())