diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d9bca1f73..49cbd8486 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog ********* +1.7.0 -- 2020-09-24 +=================== + +TODO + 1.4.1 -- 2019-09-20 =================== diff --git a/README.rst b/README.rst index 7bc8038ea..c8cb8340e 100644 --- a/README.rst +++ b/README.rst @@ -92,76 +92,121 @@ uses a key derivation function, the data key is used to generate the key that di ***** Usage ***** -To use this client, you (the caller) must provide an instance of either a master key provider -or a CMM. The examples in this readme use the ``KMSMasterKeyProvider`` class. -KMSMasterKeyProvider -==================== -Because the ``KMSMasterKeyProvider`` uses the `boto3 SDK`_ to interact with `AWS KMS`_, it requires AWS Credentials. -To provide these credentials, use the `standard means by which boto3 locates credentials`_ or provide a -pre-existing instance of a ``botocore session`` to the ``KMSMasterKeyProvider``. -This latter option can be useful if you have an alternate way to store your AWS credentials or -you want to reuse an existing instance of a botocore session in order to decrease startup costs. +EncryptionSDKClient +=================== +To use this module, you (the caller) must first create an instance of the ``EncryptionSDKClient`` class. +The constructor to this class requires a single keyword argument, ``commitment_policy``. There is +currently only one valid value for this argument: ``FORBID_ENCRYPT_ALLOW_DECRYPT``. .. code:: python import aws_encryption_sdk - import botocore.session + from aws_encryption_sdk.identifiers import CommitmentPolicy + + + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + ) + - kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider() +You must then create an instance of either a master key provider or a CMM. The examples in this +readme use the ``StrictAwsKmsMasterKeyProvider`` class. - existing_botocore_session = botocore.session.Session() - kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(botocore_session=existing_botocore_session) +StrictAwsKmsMasterKeyProvider +============================= +A ``StrictAwsKmsMasterKeyProvider`` is configured with an explicit list of AWS KMS +CMKs with which to encrypt and decrypt data. On encryption, it encrypts the plaintext with all +configured CMKs. On decryption, it only attempts to decrypt ciphertexts that have been wrapped +with one of the configured CMKs. -You can pre-load the ``KMSMasterKeyProvider`` with one or more CMKs. -To encrypt data, you must configure the ``KMSMasterKeyProvider`` with as least one CMK. -If you configure the the ``KMSMasterKeyProvider`` with multiple CMKs, the `final message`_ +Because the ``StrictAwsKmsMasterKeyProvider`` uses the `boto3 SDK`_ to interact with `AWS KMS`_, +it requires AWS Credentials. +To provide these credentials, use the `standard means by which boto3 locates credentials`_ or provide a +pre-existing instance of a ``botocore session`` to the ``StrictAwsKmsMasterKeyProvider``. +This latter option can be useful if you have an alternate way to store your AWS credentials or +you want to reuse an existing instance of a botocore session in order to decrease startup costs. + +To create a ``StrictAwsKmsMasterKeyProvider`` you must provide one or more CMKs. +If you configure the the ``StrictAwsKmsMasterKeyProvider`` with multiple CMKs, the `final message`_ will include a copy of the data key encrypted by each configured CMK. .. code:: python import aws_encryption_sdk - kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ]) -You can add CMKs from multiple regions to the ``KMSMasterKeyProvider``. +You can add CMKs from multiple regions to the ``StrictAwsKmsMasterKeyProvider``. .. code:: python import aws_encryption_sdk - kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-west-2:3333333333333:key/33333333-3333-3333-3333-333333333333', 'arn:aws:kms:ap-northeast-1:4444444444444:key/44444444-4444-4444-4444-444444444444' ]) +DiscoveryAwsKmsMasterKeyProvider +================================ +We recommend using a ``StrictAwsKmsMasterKeyProvider`` in order to ensure that you can only +encrypt and decrypt data using the AWS KMS CMKs you expect. However, if you are unable to +explicitly identify the AWS KMS CMKs that should be used for decryption, you can instead +use a ``DiscoveryAwsKmsMasterKeyProvider`` for decryption operations. This provider +attempts decryption of any ciphertexts as long as they match a ``DiscoveryFilter`` that +you configure. A ``DiscoveryFilter`` consists of a list of AWS account ids and an AWS +partition. + +.. code:: python + + import aws_encryption_sdk + from aws_encryption_sdk.key_providers.kms import DiscoveryFilter + + discovery_filter = DiscoveryFilter( + account_ids=['222222222222', '333333333333'], + partition='aws' + ) + kms_key_provider = aws_encryption_sdk.DiscoveryAwsKmsMasterKeyProvider( + discovery_filter=discovery_filter + ) + +If you do not want to filter the set of allowed accounts, you can also omit the ``discovery_filter`` argument. + +Note that a ``DiscoveryAwsKmsMasterKeyProvider`` cannot be used for encryption operations. + Encryption and Decryption ========================= -After you create an instance of a ``MasterKeyProvider``, you can use either of the two -high-level ``encrypt``/``decrypt`` functions to encrypt and decrypt your data. +After you create an instance of an ``EncryptionSDKClient`` and a ``MasterKeyProvider``, you can use either of +the client's two ``encrypt``/``decrypt`` functions to encrypt and decrypt your data. .. code:: python import aws_encryption_sdk + from aws_encryption_sdk.identifiers import CommitmentPolicy - kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + ) + + kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ]) my_plaintext = b'This is some super secret data! Yup, sure is!' - my_ciphertext, encryptor_header = aws_encryption_sdk.encrypt( + my_ciphertext, encryptor_header = client.encrypt( source=my_plaintext, key_provider=kms_key_provider ) - decrypted_plaintext, decryptor_header = aws_encryption_sdk.decrypt( + decrypted_plaintext, decryptor_header = client.decrypt( source=my_ciphertext, key_provider=kms_key_provider ) @@ -174,14 +219,19 @@ You can provide an `encryption context`_: a form of additional authenticating in .. code:: python import aws_encryption_sdk + from aws_encryption_sdk.identifiers import CommitmentPolicy + + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + ) - kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ]) my_plaintext = b'This is some super secret data! Yup, sure is!' - my_ciphertext, encryptor_header = aws_encryption_sdk.encrypt( + my_ciphertext, encryptor_header = client.encrypt( source=my_plaintext, key_provider=kms_key_provider, encryption_context={ @@ -190,7 +240,7 @@ You can provide an `encryption context`_: a form of additional authenticating in } ) - decrypted_plaintext, decryptor_header = aws_encryption_sdk.decrypt( + decrypted_plaintext, decryptor_header = client.decrypt( source=my_ciphertext, key_provider=kms_key_provider ) @@ -209,9 +259,14 @@ offering context manager and iteration support. .. code:: python import aws_encryption_sdk + from aws_encryption_sdk.identifiers import CommitmentPolicy import filecmp - kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + client = aws_encryption_sdk.EncryptionSDKClient( + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + ) + + kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ]) @@ -219,7 +274,7 @@ offering context manager and iteration support. ciphertext_filename = 'my-encrypted-data.ct' with open(plaintext_filename, 'rb') as pt_file, open(ciphertext_filename, 'wb') as ct_file: - with aws_encryption_sdk.stream( + with client.stream( mode='e', source=pt_file, key_provider=kms_key_provider @@ -230,7 +285,7 @@ offering context manager and iteration support. new_plaintext_filename = 'my-decrypted-data.dat' with open(ciphertext_filename, 'rb') as ct_file, open(new_plaintext_filename, 'wb') as pt_file: - with aws_encryption_sdk.stream( + with client.stream( mode='d', source=ct_file, key_provider=kms_key_provider diff --git a/decrypt_oracle/test/integration/integration_test_utils.py b/decrypt_oracle/test/integration/integration_test_utils.py index 1fd08fc08..610a930d6 100644 --- a/decrypt_oracle/test/integration/integration_test_utils.py +++ b/decrypt_oracle/test/integration/integration_test_utils.py @@ -77,8 +77,7 @@ def kms_master_key_provider(cache: Optional[bool] = True): return _KMS_MKP cmk_arn = get_cmk_arn() - _kms_master_key_provider = KMSMasterKeyProvider() - _kms_master_key_provider.add_master_key(cmk_arn) + _kms_master_key_provider = KMSMasterKeyProvider(key_ids=[cmk_arn]) if cache: _KMS_MKP = _kms_master_key_provider diff --git a/examples/src/basic_encryption.py b/examples/src/basic_encryption.py index 6c194e45d..2233848a8 100644 --- a/examples/src/basic_encryption.py +++ b/examples/src/basic_encryption.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. """Example showing basic encryption and decryption of a value already in memory.""" import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy def cycle_string(key_arn, source_plaintext, botocore_session=None): @@ -22,17 +23,20 @@ def cycle_string(key_arn, source_plaintext, botocore_session=None): :param botocore_session: existing botocore session instance :type botocore_session: botocore.session.Session """ + # Set up an encryption client with an explicit commitment policy + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + # Create a KMS master key provider kms_kwargs = dict(key_ids=[key_arn]) if botocore_session is not None: kms_kwargs["botocore_session"] = botocore_session - master_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(**kms_kwargs) + master_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kms_kwargs) # Encrypt the plaintext source data - ciphertext, encryptor_header = aws_encryption_sdk.encrypt(source=source_plaintext, key_provider=master_key_provider) + ciphertext, encryptor_header = client.encrypt(source=source_plaintext, key_provider=master_key_provider) # Decrypt the ciphertext - cycled_plaintext, decrypted_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=master_key_provider) + cycled_plaintext, decrypted_header = client.decrypt(source=ciphertext, key_provider=master_key_provider) # Verify that the "cycled" (encrypted, then decrypted) plaintext is identical to the source plaintext assert cycled_plaintext == source_plaintext diff --git a/examples/src/basic_file_encryption_with_multiple_providers.py b/examples/src/basic_file_encryption_with_multiple_providers.py index e60b4f6c6..aaf4ae285 100644 --- a/examples/src/basic_file_encryption_with_multiple_providers.py +++ b/examples/src/basic_file_encryption_with_multiple_providers.py @@ -22,7 +22,7 @@ from cryptography.hazmat.primitives.asymmetric import rsa import aws_encryption_sdk -from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.identifiers import CommitmentPolicy, EncryptionKeyType, WrappingAlgorithm from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider @@ -76,11 +76,14 @@ def cycle_file(key_arn, source_plaintext_filename, botocore_session=None): cycled_kms_plaintext_filename = source_plaintext_filename + ".kms.decrypted" cycled_static_plaintext_filename = source_plaintext_filename + ".static.decrypted" + # Set up an encryption client with an explicit commitment policy + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + # Create a KMS master key provider kms_kwargs = dict(key_ids=[key_arn]) if botocore_session is not None: kms_kwargs["botocore_session"] = botocore_session - kms_master_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(**kms_kwargs) + kms_master_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kms_kwargs) # Create a static master key provider and add a master key to it static_key_id = os.urandom(8) @@ -94,23 +97,21 @@ def cycle_file(key_arn, source_plaintext_filename, botocore_session=None): # Encrypt plaintext with both AWS KMS and static master keys with open(source_plaintext_filename, "rb") as plaintext, open(ciphertext_filename, "wb") as ciphertext: - with aws_encryption_sdk.stream(source=plaintext, mode="e", key_provider=kms_master_key_provider) as encryptor: + with client.stream(source=plaintext, mode="e", key_provider=kms_master_key_provider) as encryptor: for chunk in encryptor: ciphertext.write(chunk) # Decrypt the ciphertext with only the AWS KMS master key with open(ciphertext_filename, "rb") as ciphertext, open(cycled_kms_plaintext_filename, "wb") as plaintext: - with aws_encryption_sdk.stream( - source=ciphertext, mode="d", key_provider=aws_encryption_sdk.KMSMasterKeyProvider(**kms_kwargs) + with client.stream( + source=ciphertext, mode="d", key_provider=aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kms_kwargs) ) as kms_decryptor: for chunk in kms_decryptor: plaintext.write(chunk) # Decrypt the ciphertext with only the static master key with open(ciphertext_filename, "rb") as ciphertext, open(cycled_static_plaintext_filename, "wb") as plaintext: - with aws_encryption_sdk.stream( - source=ciphertext, mode="d", key_provider=static_master_key_provider - ) as static_decryptor: + with client.stream(source=ciphertext, mode="d", key_provider=static_master_key_provider) as static_decryptor: for chunk in static_decryptor: plaintext.write(chunk) diff --git a/examples/src/basic_file_encryption_with_raw_key_provider.py b/examples/src/basic_file_encryption_with_raw_key_provider.py index 91e2a7e9a..25045359e 100644 --- a/examples/src/basic_file_encryption_with_raw_key_provider.py +++ b/examples/src/basic_file_encryption_with_raw_key_provider.py @@ -15,7 +15,7 @@ import os import aws_encryption_sdk -from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.identifiers import CommitmentPolicy, EncryptionKeyType, WrappingAlgorithm from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider @@ -53,6 +53,9 @@ def cycle_file(source_plaintext_filename): :param str source_plaintext_filename: Filename of file to encrypt """ + # Set up an encryption client with an explicit commitment policy + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + # Create a static random master key provider key_id = os.urandom(8) master_key_provider = StaticRandomMasterKeyProvider() @@ -63,13 +66,13 @@ def cycle_file(source_plaintext_filename): # Encrypt the plaintext source data with open(source_plaintext_filename, "rb") as plaintext, open(ciphertext_filename, "wb") as ciphertext: - with aws_encryption_sdk.stream(mode="e", source=plaintext, key_provider=master_key_provider) as encryptor: + with client.stream(mode="e", source=plaintext, key_provider=master_key_provider) as encryptor: for chunk in encryptor: ciphertext.write(chunk) # Decrypt the ciphertext with open(ciphertext_filename, "rb") as ciphertext, open(cycled_plaintext_filename, "wb") as plaintext: - with aws_encryption_sdk.stream(mode="d", source=ciphertext, key_provider=master_key_provider) as decryptor: + with client.stream(mode="d", source=ciphertext, key_provider=master_key_provider) as decryptor: for chunk in decryptor: plaintext.write(chunk) diff --git a/examples/src/data_key_caching_basic.py b/examples/src/data_key_caching_basic.py index 1d5445615..dc6250ab7 100644 --- a/examples/src/data_key_caching_basic.py +++ b/examples/src/data_key_caching_basic.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. """Example of encryption with data key caching.""" import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy def encrypt_with_caching(kms_cmk_arn, max_age_in_cache, cache_capacity): @@ -31,8 +32,11 @@ def encrypt_with_caching(kms_cmk_arn, max_age_in_cache, cache_capacity): # Create an encryption context encryption_context = {"purpose": "test"} + # Set up an encryption client with an explicit commitment policy + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + # Create a master key provider for the KMS customer master key (CMK) - key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[kms_cmk_arn]) + key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[kms_cmk_arn]) # Create a local cache cache = aws_encryption_sdk.LocalCryptoMaterialsCache(cache_capacity) @@ -48,7 +52,7 @@ def encrypt_with_caching(kms_cmk_arn, max_age_in_cache, cache_capacity): # When the call to encrypt data specifies a caching CMM, # the encryption operation uses the data key cache specified # in the caching CMM - encrypted_message, _header = aws_encryption_sdk.encrypt( + encrypted_message, _header = client.encrypt( source=my_data, materials_manager=caching_cmm, encryption_context=encryption_context ) diff --git a/examples/src/discovery_kms_provider.py b/examples/src/discovery_kms_provider.py new file mode 100644 index 000000000..0fd05bf14 --- /dev/null +++ b/examples/src/discovery_kms_provider.py @@ -0,0 +1,63 @@ +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Example showing encryption of a value already in memory using one KMS CMK, then decryption of the ciphertext using +a DiscoveryKMSMasterKeyProvider. +""" +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +from aws_encryption_sdk.internal.arn import arn_from_str +from aws_encryption_sdk.key_providers.kms import DiscoveryFilter + + +def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): + """Encrypts a string under one KMS customer master key (CMK), then decrypts it using discovery mode. + + :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK + :param bytes source_plaintext: Data to encrypt + :param botocore_session: existing botocore session instance + :type botocore_session: botocore.session.Session + """ + encrypt_kwargs = dict(key_ids=[key_arn]) + + if botocore_session is not None: + encrypt_kwargs["botocore_session"] = botocore_session + + # Set up an encryption client with an explicit commitment policy + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + + # Create strict master key provider that is only allowed to encrypt and decrypt using the ARN of the provided key. + strict_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**encrypt_kwargs) + + # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header + ciphertext, encrypted_message_header = client.encrypt(source=source_plaintext, key_provider=strict_key_provider) + + # Create a second master key provider in discovery mode that does not explicitly list the key used to encrypt. + # Note: The discovery_filter argument is optional; if you omit this, the AWS Encryption SDK attempts to + # decrypt any ciphertext it receives. + arn = arn_from_str(key_arn) + decrypt_kwargs = dict(discovery_filter=DiscoveryFilter(account_ids=[arn.account_id], partition=arn.partition)) + if botocore_session is not None: + encrypt_kwargs["botocore_session"] = botocore_session + discovery_key_provider = aws_encryption_sdk.DiscoveryAwsKmsMasterKeyProvider(**decrypt_kwargs) + + # Decrypt the encrypted message using the AWS Encryption SDK. It returns the decrypted message and the header. + plaintext, decrypted_message_header = client.decrypt(source=ciphertext, key_provider=discovery_key_provider) + + # Verify that the original message and the decrypted message are the same + assert source_plaintext == plaintext + + # Verify that the encryption context of the encrypted message and decrypted message match + assert all( + pair in encrypted_message_header.encryption_context.items() + for pair in decrypted_message_header.encryption_context.items() + ) diff --git a/examples/src/multiple_kms_cmk.py b/examples/src/multiple_kms_cmk.py new file mode 100644 index 000000000..a8d458a75 --- /dev/null +++ b/examples/src/multiple_kms_cmk.py @@ -0,0 +1,62 @@ +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Example showing basic encryption and decryption of a value already in memory using one KMS CMK.""" +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy + + +def encrypt_decrypt(key_arns, source_plaintext, botocore_session=None): + """Encrypts and then decrypts a string under two KMS customer master keys (CMK). + + :param str key_arns: Amazon Resource Names (ARNs) of the KMS CMKs + :param bytes source_plaintext: Data to encrypt + :param botocore_session: existing botocore session instance + :type botocore_session: botocore.session.Session + """ + encrypt_kwargs = dict(key_ids=key_arns) + + if botocore_session is not None: + encrypt_kwargs["botocore_session"] = botocore_session + + # Set up an encryption client with an explicit commitment policy + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + + # Create strict master key provider that is only allowed to encrypt and decrypt using the ARN of the provided key. + strict_encrypt_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**encrypt_kwargs) + + # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header + ciphertext, encrypted_message_header = client.encrypt( + source=source_plaintext, key_provider=strict_encrypt_key_provider + ) + + # For each original master key, create a strict key provider that only lists that key and decrypt the encrypted + # message using that provider. Note: in order for decrypt to succeed, the key_ids value must be the key ARN of the + # CMK. + for key_arn in key_arns: + decrypt_kwargs = dict(key_ids=[key_arn]) + if botocore_session is not None: + encrypt_kwargs["botocore_session"] = botocore_session + + strict_decrypt_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**decrypt_kwargs) + plaintext, decrypted_message_header = client.decrypt( + source=ciphertext, key_provider=strict_decrypt_key_provider + ) + + # Verify that the original message and the decrypted message are the same + assert source_plaintext == plaintext + + # Verify that the encryption context of the encrypted message and decrypted message match + assert all( + pair in encrypted_message_header.encryption_context.items() + for pair in decrypted_message_header.encryption_context.items() + ) diff --git a/examples/src/one_kms_cmk.py b/examples/src/one_kms_cmk.py index 1ba1d869f..03686f5f1 100644 --- a/examples/src/one_kms_cmk.py +++ b/examples/src/one_kms_cmk.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. """Example showing basic encryption and decryption of a value already in memory using one KMS CMK.""" import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): @@ -27,16 +28,18 @@ def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): if botocore_session is not None: kwargs["botocore_session"] = botocore_session + # Set up an encryption client with an explicit commitment policy + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + # Create master key provider using the ARN of the key and the session (botocore_session) - kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(**kwargs) + kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kwargs) - # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header - ciphertext, encrypted_message_header = aws_encryption_sdk.encrypt( - source=source_plaintext, key_provider=kms_key_provider - ) + # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header. Note: in + # order for decrypt to succeed, the key_ids value must be the key ARN of the CMK. + ciphertext, encrypted_message_header = client.encrypt(source=source_plaintext, key_provider=kms_key_provider) # Decrypt the encrypted message using the AWS Encryption SDK. It returns the decrypted message and the header - plaintext, decrypted_message_header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=kms_key_provider) + plaintext, decrypted_message_header = client.decrypt(source=ciphertext, key_provider=kms_key_provider) # Check if the original message and the decrypted message are the same assert source_plaintext == plaintext diff --git a/examples/src/one_kms_cmk_streaming_data.py b/examples/src/one_kms_cmk_streaming_data.py index d45e653ff..7972883dd 100644 --- a/examples/src/one_kms_cmk_streaming_data.py +++ b/examples/src/one_kms_cmk_streaming_data.py @@ -1,4 +1,3 @@ - # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You @@ -15,6 +14,7 @@ import filecmp import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy def encrypt_decrypt_stream(key_arn, source_plaintext_filename, botocore_session=None): @@ -32,21 +32,24 @@ def encrypt_decrypt_stream(key_arn, source_plaintext_filename, botocore_session= if botocore_session is not None: kwargs["botocore_session"] = botocore_session + # Set up an encryption client with an explicit commitment policy + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + # Create master key provider using the ARN of the key and the session (botocore_session) - kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(**kwargs) + kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kwargs) ciphertext_filename = source_plaintext_filename + ".encrypted" decrypted_text_filename = source_plaintext_filename + ".decrypted" # Encrypt the plaintext using the AWS Encryption SDK. with open(source_plaintext_filename, "rb") as plaintext, open(ciphertext_filename, "wb") as ciphertext: - with aws_encryption_sdk.stream(source=plaintext, mode="e", key_provider=kms_key_provider) as encryptor: + with client.stream(source=plaintext, mode="e", key_provider=kms_key_provider) as encryptor: for chunk in encryptor: ciphertext.write(chunk) # Decrypt the encrypted message using the AWS Encryption SDK. with open(ciphertext_filename, "rb") as ciphertext, open(decrypted_text_filename, "wb") as plaintext: - with aws_encryption_sdk.stream(source=ciphertext, mode="d", key_provider=kms_key_provider) as decryptor: + with client.stream(source=ciphertext, mode="d", key_provider=kms_key_provider) as decryptor: for chunk in decryptor: plaintext.write(chunk) diff --git a/examples/src/one_kms_cmk_unsigned.py b/examples/src/one_kms_cmk_unsigned.py index 783e640b4..8c53431cc 100644 --- a/examples/src/one_kms_cmk_unsigned.py +++ b/examples/src/one_kms_cmk_unsigned.py @@ -13,8 +13,9 @@ """Example showing basic encryption and decryption of a value already in memory using one AWS KMS CMK with an unsigned algorithm. """ -from aws_encryption_sdk import KMSMasterKeyProvider, decrypt, encrypt -from aws_encryption_sdk.identifiers import Algorithm +import aws_encryption_sdk +from aws_encryption_sdk import StrictAwsKmsMasterKeyProvider +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): @@ -30,16 +31,19 @@ def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): if botocore_session is not None: kwargs["botocore_session"] = botocore_session + # Set up an encryption client with an explicit commitment policy + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + # Create master key provider using the ARN of the key and the session (botocore_session) - kms_key_provider = KMSMasterKeyProvider(**kwargs) + kms_key_provider = StrictAwsKmsMasterKeyProvider(**kwargs) # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header - ciphertext, encrypted_message_header = encrypt( + ciphertext, encrypted_message_header = client.encrypt( algorithm=Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA256, source=source_plaintext, key_provider=kms_key_provider ) # Decrypt the encrypted message using the AWS Encryption SDK. It returns the decrypted message and the header - plaintext, decrypted_message_header = decrypt(source=ciphertext, key_provider=kms_key_provider) + plaintext, decrypted_message_header = client.decrypt(source=ciphertext, key_provider=kms_key_provider) # Check if the original message and the decrypted message are the same assert source_plaintext == plaintext diff --git a/examples/src/set_commitment.py b/examples/src/set_commitment.py new file mode 100644 index 000000000..7d58461c9 --- /dev/null +++ b/examples/src/set_commitment.py @@ -0,0 +1,56 @@ +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Example showing how to disable commitment. + +Note: This configuration should only be used as part of a migration from version 1.x to 2.x, or for advanced users +with specialized requirements. We recommend that AWS Encryption SDK users enable commitment whenever possible. +""" +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy + + +def encrypt_decrypt(key_arn, source_plaintext, botocore_session=None): + """Encrypts and then decrypts a string under one KMS customer master key (CMK). + + :param str key_arn: Amazon Resource Name (ARN) of the KMS CMK + :param bytes source_plaintext: Data to encrypt + :param botocore_session: existing botocore session instance + :type botocore_session: botocore.session.Session + """ + kwargs = dict(key_ids=[key_arn]) + + if botocore_session is not None: + kwargs["botocore_session"] = botocore_session + + # Set up an encryption client with an explicit commitment policy disallowing encryption with algorithms that + # provide commitment + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + + # Create master key provider using the ARN of the key and the session (botocore_session) + kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(**kwargs) + + # Encrypt the plaintext using the AWS Encryption SDK. It returns the encrypted message and the header. Note: in + # order for decrypt to succeed, the key_ids value must be the key ARN of the CMK. + ciphertext, encrypted_message_header = client.encrypt(source=source_plaintext, key_provider=kms_key_provider) + + # Decrypt the encrypted message using the AWS Encryption SDK. It returns the decrypted message and the header + plaintext, decrypted_message_header = client.decrypt(source=ciphertext, key_provider=kms_key_provider) + + # Verify that the original message and the decrypted message are the same + assert source_plaintext == plaintext + + # Verify that the encryption context of the encrypted message and decrypted message match + assert all( + pair in encrypted_message_header.encryption_context.items() + for pair in decrypted_message_header.encryption_context.items() + ) diff --git a/examples/test/examples_test_utils.py b/examples/test/examples_test_utils.py index 0984ee684..6844ae0f4 100644 --- a/examples/test/examples_test_utils.py +++ b/examples/test/examples_test_utils.py @@ -47,4 +47,4 @@ ) -from integration_test_utils import get_cmk_arn # noqa pylint: disable=unused-import,import-error +from integration_test_utils import get_cmk_arn, get_second_cmk_arn # noqa pylint: disable=unused-import,import-error diff --git a/examples/test/test_i_discovery_kms_provider.py b/examples/test/test_i_discovery_kms_provider.py new file mode 100644 index 000000000..e9a1c6e71 --- /dev/null +++ b/examples/test/test_i_discovery_kms_provider.py @@ -0,0 +1,29 @@ +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for the encryption and decryption using one KMS CMK example.""" + +import botocore.session +import pytest + +from ..src.discovery_kms_provider import encrypt_decrypt +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + + +pytestmark = [pytest.mark.examples] + + +def test_discovery_kms_provider(): + plaintext = static_plaintext + cmk_arn = get_cmk_arn() + encrypt_decrypt(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/examples/test/test_i_multiple_kms_cmk.py b/examples/test/test_i_multiple_kms_cmk.py new file mode 100644 index 000000000..39369cbc6 --- /dev/null +++ b/examples/test/test_i_multiple_kms_cmk.py @@ -0,0 +1,29 @@ +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for the encryption and decryption using one KMS CMK example.""" + +import botocore.session +import pytest + +from ..src.multiple_kms_cmk import encrypt_decrypt +from .examples_test_utils import get_cmk_arn, get_second_cmk_arn +from .examples_test_utils import static_plaintext + + +pytestmark = [pytest.mark.examples] + + +def test_one_kms_cmk(): + plaintext = static_plaintext + cmk_arns = [get_cmk_arn(), get_second_cmk_arn()] + encrypt_decrypt(key_arns=cmk_arns, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/examples/test/test_i_set_commitment.py b/examples/test/test_i_set_commitment.py new file mode 100644 index 000000000..96247334b --- /dev/null +++ b/examples/test/test_i_set_commitment.py @@ -0,0 +1,29 @@ +# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for the encryption and decryption using one KMS CMK example.""" + +import botocore.session +import pytest + +from ..src.set_commitment import encrypt_decrypt +from .examples_test_utils import get_cmk_arn +from .examples_test_utils import static_plaintext + + +pytestmark = [pytest.mark.examples] + + +def test_disable_commitment(): + plaintext = static_plaintext + cmk_arn = get_cmk_arn() + encrypt_decrypt(key_arn=cmk_arn, source_plaintext=plaintext, botocore_session=botocore.session.Session()) diff --git a/src/aws_encryption_sdk/__init__.py b/src/aws_encryption_sdk/__init__.py index 3f6d86e2e..12a5af531 100644 --- a/src/aws_encryption_sdk/__init__.py +++ b/src/aws_encryption_sdk/__init__.py @@ -12,10 +12,18 @@ # language governing permissions and limitations under the License. """High level AWS Encryption SDK client functions.""" # Below are imported for ease of use by implementors +import warnings + +import attr + from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache # noqa from aws_encryption_sdk.caches.null import NullCryptoMaterialsCache # noqa -from aws_encryption_sdk.identifiers import Algorithm, __version__ # noqa -from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider, KMSMasterKeyProviderConfig # noqa +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy, __version__ # noqa +from aws_encryption_sdk.key_providers.kms import ( # noqa + DiscoveryAwsKmsMasterKeyProvider, + KMSMasterKeyProviderConfig, + StrictAwsKmsMasterKeyProvider, +) from aws_encryption_sdk.materials_managers.caching import CachingCryptoMaterialsManager # noqa from aws_encryption_sdk.materials_managers.default import DefaultCryptoMaterialsManager # noqa from aws_encryption_sdk.streaming_client import ( # noqa @@ -26,6 +34,203 @@ ) +@attr.s(hash=True) +class EncryptionSDKClientConfig(object): + """Configuration object for EncryptionSDKClients + + :param commitment_policy: The commitment policy to apply to encryption and decryption requests + :type commitment_policy: aws_encryption_sdk.materials_manager.identifiers.CommitmentPolicy + """ + + commitment_policy = attr.ib(hash=True, validator=attr.validators.instance_of(CommitmentPolicy)) + + +class EncryptionSDKClient(object): + """A client providing high level AWS Encryption SDK client methods.""" + + _config_class = EncryptionSDKClientConfig + + def __new__(cls, **kwargs): + """Constructs a new EncryptionSDKClient instance.""" + instance = super(EncryptionSDKClient, cls).__new__(cls) + + config = kwargs.pop("config", None) + if not isinstance(config, instance._config_class): # pylint: disable=protected-access + config = instance._config_class(**kwargs) # pylint: disable=protected-access + instance.config = config + return instance + + def encrypt(self, **kwargs): + """Encrypts and serializes provided plaintext. + + .. note:: + When using this function, the entire ciphertext message is encrypted into memory before returning + any data. If streaming is desired, see :class:`aws_encryption_sdk.stream`. + + .. code:: python + + >>> import aws_encryption_sdk + >>> client = aws_encryption_sdk.EncryptionSDKClient( + >>> commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + >>> ) + >>> kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ + ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ... ]) + >>> my_ciphertext, encryptor_header = client.encrypt( + ... source=my_plaintext, + ... key_provider=kms_key_provider + ... ) + + :param config: Client configuration object (config or individual parameters required) + :type config: aws_encryption_sdk.streaming_client.EncryptorConfig + :param source: Source data to encrypt or decrypt + :type source: str, bytes, io.IOBase, or file + :param materials_manager: `CryptoMaterialsManager` that returns cryptographic materials + (requires either `materials_manager` or `key_provider`) + :type materials_manager: aws_encryption_sdk.materials_managers.base.CryptoMaterialsManager + :param key_provider: `MasterKeyProvider` that returns data keys for encryption + (requires either `materials_manager` or `key_provider`) + :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider + :param int source_length: Length of source data (optional) + + .. note:: + If source_length is not provided and unframed message is being written or read() is called, + will attempt to seek() to the end of the stream and tell() to find the length of source data. + + .. note:: + If `source_length` and `materials_manager` are both provided, the total plaintext bytes + encrypted will not be allowed to exceed `source_length`. To maintain backwards compatibility, + this is not enforced if a `key_provider` is provided. + + :param dict encryption_context: Dictionary defining encryption context + :param algorithm: Algorithm to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param int frame_length: Frame length in bytes + :returns: Tuple containing the encrypted ciphertext and the message header object + :rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader` + """ + kwargs["commitment_policy"] = self.config.commitment_policy + with StreamEncryptor(**kwargs) as encryptor: + ciphertext = encryptor.read() + return ciphertext, encryptor.header + + def decrypt(self, **kwargs): + """Deserializes and decrypts provided ciphertext. + + .. note:: + When using this function, the entire ciphertext message is decrypted into memory before returning + any data. If streaming is desired, see :class:`aws_encryption_sdk.stream`. + + .. code:: python + + >>> import aws_encryption_sdk + >>> client = aws_encryption_sdk.EncryptionSDKClient( + >>> commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + >>> ) + >>> kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ + ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ... ]) + >>> my_ciphertext, encryptor_header = client.decrypt( + ... source=my_ciphertext, + ... key_provider=kms_key_provider + ... ) + + :param config: Client configuration object (config or individual parameters required) + :type config: aws_encryption_sdk.streaming_client.DecryptorConfig + :param source: Source data to encrypt or decrypt + :type source: str, bytes, io.IOBase, or file + :param materials_manager: `CryptoMaterialsManager` that returns cryptographic materials + (requires either `materials_manager` or `key_provider`) + :type materials_manager: aws_encryption_sdk.materials_managers.base.CryptoMaterialsManager + :param key_provider: `MasterKeyProvider` that returns data keys for decryption + (requires either `materials_manager` or `key_provider`) + :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider + :param int source_length: Length of source data (optional) + + .. note:: + If source_length is not provided and read() is called, will attempt to seek() + to the end of the stream and tell() to find the length of source data. + + :param int max_body_length: Maximum frame size (or content length for non-framed messages) + in bytes to read from ciphertext message. + :returns: Tuple containing the decrypted plaintext and the message header object + :rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader` + """ + kwargs["commitment_policy"] = self.config.commitment_policy + with StreamDecryptor(**kwargs) as decryptor: + plaintext = decryptor.read() + return plaintext, decryptor.header + + def stream(self, **kwargs): + """Provides an :py:func:`open`-like interface to the streaming encryptor/decryptor classes. + + .. warning:: + Take care when decrypting framed messages with large frame length and large non-framed + messages. In order to protect the authenticity of the encrypted data, no plaintext + is returned until it has been authenticated. Because of this, potentially large amounts + of data may be read into memory. In the case of framed messages, the entire contents + of each frame are read into memory and authenticated before returning any plaintext. + In the case of non-framed messages, the entire message is read into memory and + authenticated before returning any plaintext. The authenticated plaintext is held in + memory until it is requested. + + .. note:: + Consequently, keep the above decrypting consideration in mind when encrypting messages + to ensure that issues are not encountered when decrypting those messages. + + .. code:: python + + >>> import aws_encryption_sdk + >>> client = aws_encryption_sdk.EncryptionSDKClient( + >>> commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + >>> ) + >>> kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ + ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ... ]) + >>> plaintext_filename = 'my-secret-data.dat' + >>> ciphertext_filename = 'my-encrypted-data.ct' + >>> with open(plaintext_filename, 'rb') as pt_file, open(ciphertext_filename, 'wb') as ct_file: + ... with client.stream( + ... mode='e', + ... source=pt_file, + ... key_provider=kms_key_provider + ... ) as encryptor: + ... for chunk in encryptor: + ... ct_file.write(chunk) + >>> new_plaintext_filename = 'my-decrypted-data.dat' + >>> with open(ciphertext_filename, 'rb') as ct_file, open(new_plaintext_filename, 'wb') as pt_file: + ... with client.stream( + ... mode='d', + ... source=ct_file, + ... key_provider=kms_key_provider + ... ) as decryptor: + ... for chunk in decryptor: + ... pt_file.write(chunk) + + :param str mode: Type of streaming client to return (e/encrypt: encryptor, d/decrypt: decryptor) + :param **kwargs: All other parameters provided are passed to the appropriate Streaming client + :returns: Streaming Encryptor or Decryptor, as requested + :rtype: :class:`aws_encryption_sdk.streaming_client.StreamEncryptor` + or :class:`aws_encryption_sdk.streaming_client.StreamDecryptor` + :raises ValueError: if supplied with an unsupported mode value + """ + kwargs["commitment_policy"] = self.config.commitment_policy + mode = kwargs.pop("mode") + _stream_map = { + "e": StreamEncryptor, + "encrypt": StreamEncryptor, + "d": StreamDecryptor, + "decrypt": StreamDecryptor, + } + try: + return _stream_map[mode.lower()](**kwargs) + except KeyError: + raise ValueError("Unsupported mode: {}".format(mode)) + + def encrypt(**kwargs): """Encrypts and serializes provided plaintext. @@ -36,7 +241,7 @@ def encrypt(**kwargs): .. code:: python >>> import aws_encryption_sdk - >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + >>> kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ... ]) @@ -75,6 +280,11 @@ def encrypt(**kwargs): :returns: Tuple containing the encrypted ciphertext and the message header object :rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader` """ + warnings.warn( + "This method is deprecated and will be removed in a future version. Please construct an EncryptionSDKClient " + "object instead.", + DeprecationWarning, + ) with StreamEncryptor(**kwargs) as encryptor: ciphertext = encryptor.read() return ciphertext, encryptor.header @@ -90,7 +300,7 @@ def decrypt(**kwargs): .. code:: python >>> import aws_encryption_sdk - >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + >>> kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ... ]) @@ -120,6 +330,11 @@ def decrypt(**kwargs): :returns: Tuple containing the decrypted plaintext and the message header object :rtype: tuple of bytes and :class:`aws_encryption_sdk.structures.MessageHeader` """ + warnings.warn( + "This method is deprecated and will be removed in a future version. Please construct an EncryptionSDKClient " + "object instead.", + DeprecationWarning, + ) with StreamDecryptor(**kwargs) as decryptor: plaintext = decryptor.read() return plaintext, decryptor.header @@ -145,7 +360,7 @@ def stream(**kwargs): .. code:: python >>> import aws_encryption_sdk - >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + >>> kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' ... ]) @@ -176,6 +391,11 @@ def stream(**kwargs): or :class:`aws_encryption_sdk.streaming_client.StreamDecryptor` :raises ValueError: if supplied with an unsupported mode value """ + warnings.warn( + "This method is deprecated and will be removed in a future version. Please construct an EncryptionSDKClient " + "object instead.", + DeprecationWarning, + ) mode = kwargs.pop("mode") _stream_map = {"e": StreamEncryptor, "encrypt": StreamEncryptor, "d": StreamDecryptor, "decrypt": StreamDecryptor} try: diff --git a/src/aws_encryption_sdk/exceptions.py b/src/aws_encryption_sdk/exceptions.py index a71d414c0..98eb69e8e 100644 --- a/src/aws_encryption_sdk/exceptions.py +++ b/src/aws_encryption_sdk/exceptions.py @@ -85,6 +85,10 @@ class UnknownRegionError(AWSEncryptionSDKClientError): """Exception class for errors encountered when attempting to process unknown regions or region names.""" +class MalformedArnError(AWSEncryptionSDKClientError): + """Exception class for errors encountered when attempting to parse an Amazon Resource Name (ARN).""" + + class CacheError(AWSEncryptionSDKClientError): """General exception class for materials caches. diff --git a/src/aws_encryption_sdk/identifiers.py b/src/aws_encryption_sdk/identifiers.py index e3c13c1ea..cbc511dc2 100644 --- a/src/aws_encryption_sdk/identifiers.py +++ b/src/aws_encryption_sdk/identifiers.py @@ -27,7 +27,7 @@ # We only actually need these imports when running the mypy checks pass -__version__ = "1.4.1" +__version__ = "1.7.0" USER_AGENT_SUFFIX = "AwsEncryptionSdkPython/{}".format(__version__) @@ -93,6 +93,7 @@ class KDFSuite(Enum): NONE = (None, None, None) HKDF_SHA256 = (hkdf.HKDF, None, hashes.SHA256) HKDF_SHA384 = (hkdf.HKDF, None, hashes.SHA384) + HKDF_SHA512 = (hkdf.HKDF, None, hashes.SHA512) def __init__(self, algorithm, input_length, hash_algorithm): """Prepare a new KDFSuite.""" @@ -154,35 +155,47 @@ class AlgorithmSuite(Enum): # pylint: disable=too-many-instance-attributes __rlookup__ = {} # algorithm_id -> AlgorithmSuite - AES_128_GCM_IV12_TAG16 = (0x0014, EncryptionSuite.AES_128_GCM_IV12_TAG16) - AES_192_GCM_IV12_TAG16 = (0x0046, EncryptionSuite.AES_192_GCM_IV12_TAG16) - AES_256_GCM_IV12_TAG16 = (0x0078, EncryptionSuite.AES_256_GCM_IV12_TAG16) - AES_128_GCM_IV12_TAG16_HKDF_SHA256 = (0x0114, EncryptionSuite.AES_128_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256) - AES_192_GCM_IV12_TAG16_HKDF_SHA256 = (0x0146, EncryptionSuite.AES_192_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256) - AES_256_GCM_IV12_TAG16_HKDF_SHA256 = (0x0178, EncryptionSuite.AES_256_GCM_IV12_TAG16, KDFSuite.HKDF_SHA256) + AES_128_GCM_IV12_TAG16 = (0x0014, EncryptionSuite.AES_128_GCM_IV12_TAG16, 0x01) + AES_192_GCM_IV12_TAG16 = (0x0046, EncryptionSuite.AES_192_GCM_IV12_TAG16, 0x01) + AES_256_GCM_IV12_TAG16 = (0x0078, EncryptionSuite.AES_256_GCM_IV12_TAG16, 0x01) + AES_128_GCM_IV12_TAG16_HKDF_SHA256 = (0x0114, EncryptionSuite.AES_128_GCM_IV12_TAG16, 0x01, KDFSuite.HKDF_SHA256) + AES_192_GCM_IV12_TAG16_HKDF_SHA256 = (0x0146, EncryptionSuite.AES_192_GCM_IV12_TAG16, 0x01, KDFSuite.HKDF_SHA256) + AES_256_GCM_IV12_TAG16_HKDF_SHA256 = (0x0178, EncryptionSuite.AES_256_GCM_IV12_TAG16, 0x01, KDFSuite.HKDF_SHA256) AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256 = ( 0x0214, EncryptionSuite.AES_128_GCM_IV12_TAG16, + 0x01, KDFSuite.HKDF_SHA256, AuthenticationSuite.SHA256_ECDSA_P256, ) AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 = ( 0x0346, EncryptionSuite.AES_192_GCM_IV12_TAG16, + 0x01, KDFSuite.HKDF_SHA384, AuthenticationSuite.SHA256_ECDSA_P384, ) AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 = ( 0x0378, EncryptionSuite.AES_256_GCM_IV12_TAG16, + 0x01, KDFSuite.HKDF_SHA384, AuthenticationSuite.SHA256_ECDSA_P384, ) + AES_256_GCM_HKDF_SHA512_COMMIT_KEY = (0x0478, EncryptionSuite.AES_256_GCM_IV12_TAG16, 0x02, KDFSuite.HKDF_SHA512) + AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384 = ( + 0x0578, + EncryptionSuite.AES_256_GCM_IV12_TAG16, + 0x02, + KDFSuite.HKDF_SHA512, + AuthenticationSuite.SHA256_ECDSA_P384, + ) def __init__( self, algorithm_id, # type: int encryption, # type: EncryptionSuite + message_format_version, # type: int kdf=KDFSuite.NONE, # type: Optional[KDFSuite] authentication=AuthenticationSuite.NONE, # type: Optional[AuthenticationSuite] allowed=True, # type: bool @@ -191,6 +204,7 @@ def __init__( """Prepare a new AlgorithmSuite.""" self.algorithm_id = algorithm_id self.encryption = encryption + self.message_format_version = message_format_version self.encryption.valid_kdf(kdf) self.kdf = kdf self.authentication = authentication @@ -212,6 +226,7 @@ def __init__( self.signing_algorithm_info = self.authentication.algorithm self.signing_hash_type = self.authentication.hash_algorithm self.signature_len = self.authentication.signature_length + self.header_auth_iv = b"\x00" * self.iv_len self.__rlookup__[algorithm_id] = self @@ -233,12 +248,34 @@ def get_by_id(cls, algorithm_id): def id_as_bytes(self): """Return the algorithm suite ID as a 2-byte array""" - return struct.pack(">H", self.algorithm_id) + b = bytearray() + b.extend(struct.pack(">H", self.algorithm_id)) + return b def safe_to_cache(self): """Determine whether encryption materials for this algorithm suite should be cached.""" return self.kdf is not KDFSuite.NONE + def is_committing(self): + """Determine whether this algorithm suite offers key commitment.""" + upper_bytes = self.id_as_bytes()[0] + return upper_bytes in (0x04, 0x05) + + def message_id_length(self): + """Returns the size of the message id.""" + if self.message_format_version == 0x01: + return 16 + else: + return 32 + + def algorithm_suite_data_length(self): + """Returns the length of the Algorithm Suite Data field.""" + upper_bytes = self.id_as_bytes()[0] + if upper_bytes in (0x04, 0x05): + return 32 + else: + return 0 + Algorithm = AlgorithmSuite @@ -313,6 +350,7 @@ class SerializationVersion(Enum): """Valid Versions of AWS Encryption SDK message format.""" V1 = 1 # pylint: disable=invalid-name + V2 = 2 # pylint: disable=invalid-name class ContentType(Enum): @@ -328,3 +366,9 @@ class ContentAADString(Enum): FRAME_STRING_ID = b"AWSKMSEncryptionClient Frame" FINAL_FRAME_STRING_ID = b"AWSKMSEncryptionClient Final Frame" NON_FRAMED_STRING_ID = b"AWSKMSEncryptionClient Single Block" + + +class CommitmentPolicy(Enum): + """Controls algorithm suites that can be used on encryption and decryption.""" + + FORBID_ENCRYPT_ALLOW_DECRYPT = 0 diff --git a/src/aws_encryption_sdk/internal/arn.py b/src/aws_encryption_sdk/internal/arn.py new file mode 100644 index 000000000..ea2fc3ad7 --- /dev/null +++ b/src/aws_encryption_sdk/internal/arn.py @@ -0,0 +1,66 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Utility class for processing Amazon Resource Names (ARNs)""" + +from aws_encryption_sdk.exceptions import MalformedArnError + + +class Arn(object): + """Arn to identify AWS resources. See https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html + for details. + + :param str partition: The AWS partition of the resource, e.g. 'aws' + :param str service: The service of the resource, e.g. 'kms' + :param str region: The region to which the resource belongs, e.g. 'us-east-1' + :param str account_id: The account containing the resource, e.g. '123456789012' + :param str resource_type: The type of the resource, e.g. 'key' + :param resource_id: The id for the resource, e.g. 'aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb' + """ + + def __init__(self, partition, service, region, account_id, resource_type, resource_id): + """Initializes an ARN with all required fields.""" + self.partition = partition + self.service = service + self.region = region + self.account_id = account_id + self.resource_type = resource_type + self.resource_id = resource_id + + +def arn_from_str(arn_str): + """Parses an input string as an ARN. + + :param str arn_str: The string to parse. + :returns: An ARN object representing the input string. + :rtype: aws_encryption_sdk.internal.arn.Arn + :raises MalformedArnError: if the string cannot be parsed as an ARN. + """ + elements = arn_str.split(":", 5) + + if elements[0] != "arn": + raise MalformedArnError("Resource {} could not be parsed as an ARN".format(arn_str)) + + try: + partition = elements[1] + service = elements[2] + region = elements[3] + account = elements[4] + + resource = elements[5] + resource_elements = resource.split("/", 1) + resource_type = resource_elements[0] + resource_id = resource_elements[1] + + return Arn(partition, service, region, account, resource_type, resource_id) + except IndexError: + raise MalformedArnError("Resource {} could not be parsed as an ARN".format(arn_str)) diff --git a/src/aws_encryption_sdk/internal/crypto/data_keys.py b/src/aws_encryption_sdk/internal/crypto/data_keys.py index f16873106..13a3bc2b7 100644 --- a/src/aws_encryption_sdk/internal/crypto/data_keys.py +++ b/src/aws_encryption_sdk/internal/crypto/data_keys.py @@ -18,6 +18,15 @@ _LOGGER = logging.getLogger(__name__) +# Used in SerializationVersion.V2 to calculate the derived key +KEY_LABEL = b"DERIVEKEY" + +# Used in SerializationVersion.V2 to calculate the commitment key +COMMIT_LABEL = b"COMMITKEY" + +# Used in SerializationVersion.V2 to calculate the commitment key +L_C = 32 + def derive_data_encryption_key(source_key, algorithm, message_id): """Derives the data encryption key using the defined algorithm. @@ -31,11 +40,40 @@ def derive_data_encryption_key(source_key, algorithm, message_id): """ key = source_key if algorithm.kdf_type is not None: - key = algorithm.kdf_type( - algorithm=algorithm.kdf_hash_type(), - length=algorithm.data_key_len, - salt=None, - info=struct.pack(">H16s", algorithm.algorithm_id, message_id), - backend=default_backend(), - ).derive(source_key) + if algorithm.is_committing(): + key = algorithm.kdf_type( + algorithm=algorithm.kdf_hash_type(), + length=algorithm.data_key_len, + salt=message_id, + info=struct.pack(">H9s", algorithm.algorithm_id, KEY_LABEL), + backend=default_backend(), + ).derive(source_key) + else: + key = algorithm.kdf_type( + algorithm=algorithm.kdf_hash_type(), + length=algorithm.data_key_len, + salt=None, + info=struct.pack(">H16s", algorithm.algorithm_id, message_id), + backend=default_backend(), + ).derive(source_key) + return key + + +def calculate_commitment_key(source_key, algorithm, message_id): + """Calculates the commitment value. + + :param bytes source_key: Raw source key + :param algorithm: Algorithm used to encrypt this body + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes message_id: Message ID + :returns: Derived data encryption key + :rtype: bytes + """ + key = algorithm.kdf_type( + algorithm=algorithm.kdf_hash_type(), + length=L_C, + salt=message_id, + info=struct.pack(">9s", COMMIT_LABEL), + backend=default_backend(), + ).derive(source_key) return key diff --git a/src/aws_encryption_sdk/internal/formatting/__init__.py b/src/aws_encryption_sdk/internal/formatting/__init__.py index a98ee1574..9dd59a29f 100644 --- a/src/aws_encryption_sdk/internal/formatting/__init__.py +++ b/src/aws_encryption_sdk/internal/formatting/__init__.py @@ -24,7 +24,8 @@ def header_length(header): # Because encrypted data key lengths may not be knowable until the ciphertext # is received from the providers, just serialize the header directly. header_length = len(serialize_header(header)) - header_length += header.algorithm.iv_len # Header Authentication IV + if header.algorithm.message_format_version == 0x01: + header_length += header.algorithm.iv_len # Header Authentication IV header_length += header.algorithm.auth_len # Header Authentication Tag return header_length diff --git a/src/aws_encryption_sdk/internal/formatting/deserialize.py b/src/aws_encryption_sdk/internal/formatting/deserialize.py index 894205eb5..d18ae60a7 100644 --- a/src/aws_encryption_sdk/internal/formatting/deserialize.py +++ b/src/aws_encryption_sdk/internal/formatting/deserialize.py @@ -226,24 +226,23 @@ def _verified_frame_length(frame_length, content_type): return frame_length -def deserialize_header(stream): +def _deserialize_header_v1(header, tee_stream): # type: (IO) -> MessageHeader - """Deserializes the header from a source stream + """Deserializes the header from a source stream in SerializationVersion.V1. - :param stream: Source data stream - :type stream: io.BytesIO + :param header: A dictionary in which to store deserialized values + :type header: dict + :param tee_stream: The stream from which to read bytes + :type tee_stream: aws_encryption_sdk.internal.utils.streams.TeeStream :returns: Deserialized MessageHeader object - :rtype: :class:`aws_encryption_sdk.structures.MessageHeader` and bytes + :rtype: :class:`aws_encryption_sdk.structures.MessageHeader` :raises NotSupportedError: if unsupported data types are found :raises UnknownIdentityError: if unknown data types are found :raises SerializationError: if IV length does not match algorithm """ - _LOGGER.debug("Starting header deserialization") - tee = io.BytesIO() - tee_stream = TeeStream(stream, tee) - version_id, message_type_id = unpack_values(">BB", tee_stream) - header = dict() - header["version"] = _verified_version_from_id(version_id) + _LOGGER.debug("Deserializing header in version V1") + + (message_type_id,) = unpack_values(">B", tee_stream) header["type"] = _verified_message_type_from_id(message_type_id) algorithm_id, message_id, ser_encryption_context_length = unpack_values(">H16sH", tee_stream) @@ -267,11 +266,77 @@ def deserialize_header(stream): (frame_length,) = unpack_values(">I", tee_stream) header["frame_length"] = _verified_frame_length(frame_length, header["content_type"]) - return MessageHeader(**header), tee.getvalue() + return MessageHeader(**header) -def deserialize_header_auth(stream, algorithm, verifier=None): - """Deserializes a MessageHeaderAuthentication object from a source stream. +def _deserialize_header_v2(header, tee_stream): + # type: (IO) -> MessageHeader + """Deserializes the header from a source stream in SerializationVersion.V2. + + :param header: A dictionary in which to store deserialized values + :type header: dict + :param tee_stream: The stream from which to read bytes + :type tee_stream: aws_encryption_sdk.internal.utils.streams.TeeStream + :returns: Deserialized MessageHeader object + :rtype: :class:`aws_encryption_sdk.structures.MessageHeader` + :raises NotSupportedError: if unsupported data types are found + :raises UnknownIdentityError: if unknown data types are found + :raises SerializationError: if IV length does not match algorithm + """ + _LOGGER.debug("Deserializing header in version V2") + + algorithm_id, message_id, ser_encryption_context_length = unpack_values(">H32sH", tee_stream) + + header["algorithm"] = _verified_algorithm_from_id(algorithm_id) + header["message_id"] = message_id + + header["encryption_context"] = deserialize_encryption_context(tee_stream.read(ser_encryption_context_length)) + + header["encrypted_data_keys"] = _deserialize_encrypted_data_keys(tee_stream) + + (content_type_id,) = unpack_values(">B", tee_stream) + header["content_type"] = _verified_content_type_from_id(content_type_id) + + (frame_length,) = unpack_values(">I", tee_stream) + header["frame_length"] = _verified_frame_length(frame_length, header["content_type"]) + + algorithm_suite_data_length = header["algorithm"].algorithm_suite_data_length() + (algorithm_suite_data,) = unpack_values(">{}s".format(algorithm_suite_data_length), tee_stream) + header["commitment_key"] = algorithm_suite_data + + return MessageHeader(**header) + + +def deserialize_header(stream): + # type: (IO) -> MessageHeader + """Deserializes the header from a source stream + + :param stream: Source data stream + :type stream: io.BytesIO + :returns: Deserialized MessageHeader object + :rtype: :class:`aws_encryption_sdk.structures.MessageHeader` and bytes + :raises NotSupportedError: if unsupported data types are found + :raises UnknownIdentityError: if unknown data types are found + :raises SerializationError: if IV length does not match algorithm + """ + _LOGGER.debug("Starting header deserialization") + tee = io.BytesIO() + tee_stream = TeeStream(stream, tee) + (version_id,) = unpack_values(">B", tee_stream) + version = _verified_version_from_id(version_id) + header = dict() + header["version"] = version + + if version == SerializationVersion.V1: + return _deserialize_header_v1(header, tee_stream), tee.getvalue() + elif version == SerializationVersion.V2: + return _deserialize_header_v2(header, tee_stream), tee.getvalue() + else: + raise NotSupportedError("Unrecognized message format version: {}".format(version)) + + +def _deserialize_header_auth_v1(stream, algorithm, verifier=None): + """Deserializes a MessageHeaderAuthentication object from a source stream in serialization version V1. :param stream: Source data stream :type stream: io.BytesIO @@ -282,11 +347,51 @@ def deserialize_header_auth(stream, algorithm, verifier=None): :returns: Deserialized MessageHeaderAuthentication object :rtype: aws_encryption_sdk.internal.structures.MessageHeaderAuthentication """ - _LOGGER.debug("Starting header auth deserialization") format_string = ">{iv_len}s{tag_len}s".format(iv_len=algorithm.iv_len, tag_len=algorithm.tag_len) return MessageHeaderAuthentication(*unpack_values(format_string, stream, verifier)) +def _deserialize_header_auth_v2(stream, algorithm, verifier=None): + """Deserializes a MessageHeaderAuthentication object from a source stream in serialization version V1. + + :param stream: Source data stream + :type stream: io.BytesIO + :param algorithm: The AlgorithmSuite object type contained in the header + :type algorith: aws_encryption_sdk.identifiers.AlgorithmSuite + :param verifier: Signature verifier object (optional) + :type verifier: aws_encryption_sdk.internal.crypto.Verifier + :returns: Deserialized MessageHeaderAuthentication object + :rtype: aws_encryption_sdk.internal.structures.MessageHeaderAuthentication + """ + format_string = ">{tag_len}s".format(tag_len=algorithm.tag_len) + (tag,) = unpack_values(format_string, stream, verifier) + iv = algorithm.header_auth_iv + return MessageHeaderAuthentication(tag=tag, iv=iv) + + +def deserialize_header_auth(version, stream, algorithm, verifier=None): + """Deserializes a MessageHeaderAuthentication object from a source stream. + + :param version: The serialization version of the message + :type version: int + :param stream: Source data stream + :type stream: io.BytesIO + :param algorithm: The AlgorithmSuite object type contained in the header + :type algorith: aws_encryption_sdk.identifiers.AlgorithmSuite + :param verifier: Signature verifier object (optional) + :type verifier: aws_encryption_sdk.internal.crypto.Verifier + :returns: Deserialized MessageHeaderAuthentication object + :rtype: aws_encryption_sdk.internal.structures.MessageHeaderAuthentication + """ + _LOGGER.debug("Starting header auth deserialization") + if version == SerializationVersion.V1: + return _deserialize_header_auth_v1(stream, algorithm, verifier) + elif version == SerializationVersion.V2: + return _deserialize_header_auth_v2(stream, algorithm, verifier) + else: + raise SerializationError("Unrecognized message format version: {}".format(version)) + + def deserialize_non_framed_values(stream, header, verifier=None): """Deserializes the IV and body length from a non-framed stream. diff --git a/src/aws_encryption_sdk/internal/formatting/encryption_context.py b/src/aws_encryption_sdk/internal/formatting/encryption_context.py index 4d3a5e773..5f8b39327 100644 --- a/src/aws_encryption_sdk/internal/formatting/encryption_context.py +++ b/src/aws_encryption_sdk/internal/formatting/encryption_context.py @@ -43,7 +43,7 @@ def assemble_content_aad(message_id, aad_content_string, seq_num, length): """ if not isinstance(aad_content_string, aws_encryption_sdk.identifiers.ContentAADString): raise SerializationError("Unknown aad_content_string") - fmt = ">16s{}sIQ".format(len(aad_content_string.value)) + fmt = ">{}s{}sIQ".format(len(message_id), len(aad_content_string.value)) return struct.pack(fmt, message_id, aad_content_string.value, seq_num, length) diff --git a/src/aws_encryption_sdk/internal/formatting/serialize.py b/src/aws_encryption_sdk/internal/formatting/serialize.py index e7c86a0cb..b4d866099 100644 --- a/src/aws_encryption_sdk/internal/formatting/serialize.py +++ b/src/aws_encryption_sdk/internal/formatting/serialize.py @@ -17,7 +17,7 @@ import aws_encryption_sdk.internal.defaults import aws_encryption_sdk.internal.formatting.encryption_context from aws_encryption_sdk.exceptions import SerializationError -from aws_encryption_sdk.identifiers import ContentAADString, EncryptionType, SequenceIdentifier +from aws_encryption_sdk.identifiers import ContentAADString, EncryptionType, SequenceIdentifier, SerializationVersion from aws_encryption_sdk.internal.crypto.encryption import encrypt from aws_encryption_sdk.internal.crypto.iv import frame_iv, header_auth_iv from aws_encryption_sdk.internal.str_ops import to_bytes @@ -60,8 +60,8 @@ def serialize_encrypted_data_key(encrypted_data_key): ) -def serialize_header(header, signer=None): - """Serializes a header object. +def _serialize_header_v1(header, signer=None): + """Serializes a header object for messages with SerializationVersion.V1. :param header: Header to serialize :type header: aws_encryption_sdk.structures.MessageHeader @@ -118,8 +118,79 @@ def serialize_header(header, signer=None): return output -def serialize_header_auth(algorithm, header, data_encryption_key, signer=None): - """Creates serialized header authentication data. +def _serialize_header_v2(header, signer=None): + """Serializes a header object for messages with SerializationVersion.V2. + + :param header: Header to serialize + :type header: aws_encryption_sdk.structures.MessageHeader + :param signer: Cryptographic signer object (optional) + :type signer: aws_encryption_sdk.internal.crypto.Signer + :returns: Serialized header + :rtype: bytes + """ + ec_serialized = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( + header.encryption_context + ) + header_start_format = ( + ">" # big endian + "B" # version + "H" # algorithm ID + "32s" # message ID + "H" # encryption context length + "{}s" # serialized encryption context + ).format(len(ec_serialized)) + header_bytes = bytearray() + header_bytes.extend( + struct.pack( + header_start_format, + header.version.value, + header.algorithm.algorithm_id, + header.message_id, + len(ec_serialized), + ec_serialized, + ) + ) + + serialized_data_keys = bytearray() + for data_key in header.encrypted_data_keys: + serialized_data_keys.extend(serialize_encrypted_data_key(data_key)) + + header_bytes.extend(struct.pack(">H", len(header.encrypted_data_keys))) + header_bytes.extend(serialized_data_keys) + + header_bytes.extend(struct.pack(">B", header.content_type.value)) + header_bytes.extend(struct.pack(">I", header.frame_length)) + + if header.algorithm.is_committing(): + algorithm_suite_data_length = header.algorithm.algorithm_suite_data_length() + header_bytes.extend(struct.pack(">{}s".format(algorithm_suite_data_length), header.commitment_key)) + + output = bytes(header_bytes) + if signer is not None: + signer.update(output) + return output + + +def serialize_header(header, signer=None): + """Serializes a header object. + + :param header: Header to serialize + :type header: aws_encryption_sdk.structures.MessageHeader + :param signer: Cryptographic signer object (optional) + :type signer: aws_encryption_sdk.internal.crypto.Signer + :returns: Serialized header + :rtype: bytes + """ + if header.version == SerializationVersion.V1: + return _serialize_header_v1(header, signer) + elif header.version == SerializationVersion.V2: + return _serialize_header_v2(header, signer) + else: + raise SerializationError("Unrecognized message format version: {}".format(header.version)) + + +def _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer=None): + """Creates serialized header authentication data for messages in serialization version V1. :param algorithm: Algorithm to use for encryption :type algorithm: aws_encryption_sdk.identifiers.Algorithm @@ -147,6 +218,56 @@ def serialize_header_auth(algorithm, header, data_encryption_key, signer=None): return output +def _serialize_header_auth_v2(algorithm, header, data_encryption_key, signer=None): + """Creates serialized header authentication data for messages in serialization version V2. + + :param algorithm: Algorithm to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes header: Serialized message header + :param bytes data_encryption_key: Data key with which to encrypt message + :param signer: Cryptographic signer object (optional) + :type signer: aws_encryption_sdk.Signer + :returns: Serialized header authentication data + :rtype: bytes + """ + header_auth = encrypt( + algorithm=algorithm, + key=data_encryption_key, + plaintext=b"", + associated_data=header, + iv=header_auth_iv(algorithm), + ) + output = struct.pack( + ">{tag_len}s".format(tag_len=algorithm.tag_len), + header_auth.tag, + ) + if signer is not None: + signer.update(output) + return output + + +def serialize_header_auth(version, algorithm, header, data_encryption_key, signer=None): + """Creates serialized header authentication data. + + :param version: The serialization version of the message + :type version: int + :param algorithm: Algorithm to use for encryption + :type algorithm: aws_encryption_sdk.identifiers.Algorithm + :param bytes header: Serialized message header + :param bytes data_encryption_key: Data key with which to encrypt message + :param signer: Cryptographic signer object (optional) + :type signer: aws_encryption_sdk.Signer + :returns: Serialized header authentication data + :rtype: bytes + """ + if version == SerializationVersion.V1: + return _serialize_header_auth_v1(algorithm, header, data_encryption_key, signer) + elif version == SerializationVersion.V2: + return _serialize_header_auth_v2(algorithm, header, data_encryption_key, signer) + else: + raise SerializationError("Unrecognized message format version: {}".format(version)) + + def serialize_non_framed_open(algorithm, iv, plaintext_length, signer=None): """Serializes the opening block for a non-framed message body. diff --git a/src/aws_encryption_sdk/internal/utils/commitment.py b/src/aws_encryption_sdk/internal/utils/commitment.py new file mode 100644 index 000000000..d8f5e3605 --- /dev/null +++ b/src/aws_encryption_sdk/internal/utils/commitment.py @@ -0,0 +1,29 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Helper functions for validating commitment policies and algorithms for the AWS Encryption SDK.""" +from aws_encryption_sdk.exceptions import ActionNotAllowedError +from aws_encryption_sdk.identifiers import CommitmentPolicy + +TROUBLESHOOTING_URL = "https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/troubleshooting-migration.html" + + +def validate_commitment_policy_on_encrypt(commitment_policy, algorithm): + """Validates that the provided algorithm does not violate the commitment policy for an encrypt request.""" + if commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT and ( + algorithm is not None and algorithm.is_committing() + ): + error_message = ( + "Configuration conflict. Cannot encrypt due to {} requiring only non-committed messages. " + "Algorithm ID was {}. See: " + TROUBLESHOOTING_URL + ) + raise ActionNotAllowedError(error_message.format(commitment_policy, algorithm.algorithm_id)) diff --git a/src/aws_encryption_sdk/key_providers/base.py b/src/aws_encryption_sdk/key_providers/base.py index 3112cba6d..e67219a3e 100644 --- a/src/aws_encryption_sdk/key_providers/base.py +++ b/src/aws_encryption_sdk/key_providers/base.py @@ -49,7 +49,7 @@ class MasterKeyProvider(object): #: Determines whether a MasterKeyProvider attempts to add a MasterKey on decrypt_data_key call. vend_masterkey_on_decrypt = True - @abc.abstractproperty + @abc.abstractmethod def provider_id(self): """String defining provider ID. @@ -57,7 +57,7 @@ def provider_id(self): Must be implemented by specific MasterKeyProvider implementations. """ - @abc.abstractproperty + @abc.abstractmethod def _config_class(self): """Configuration class to use when setting up this class. @@ -71,7 +71,7 @@ def __new__(cls, **kwargs): """ instance = super(MasterKeyProvider, cls).__new__(cls) config = kwargs.pop("config", None) - if not isinstance(config, instance._config_class): # pylint: disable=protected-access + if not isinstance(config, instance._config_class): # pylint: disable=protected-access,W1116 config = instance._config_class(**kwargs) # pylint: disable=protected-access instance.config = config #: Index matching key IDs to existing MasterKey objects. @@ -324,7 +324,7 @@ def __new__(cls, **kwargs): # Only allow override if provider_id is NOT set to non-None for the class if instance.provider_id is None: instance.provider_id = instance.config.provider_id - elif instance.provider_id != instance.config.provider_id: + elif instance.provider_id != instance.config.provider_id: # pylint: disable=comparison-with-callable raise ConfigMismatchError( "Config provider_id does not match MasterKey provider_id: {config} != {instance}".format( config=instance.config.provider_id, instance=instance.provider_id @@ -474,8 +474,8 @@ def _encrypt_data_key(self, data_key, algorithm, encryption_context): def decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context): """Decrypts an encrypted data key and returns the plaintext. - :param data_key: Encrypted data key - :type data_key: aws_encryption_sdk.structures.EncryptedDataKey + :param encrypted_data_key: Encrypted data key + :type encrypted_data_key: aws_encryption_sdk.structures.EncryptedDataKey :param algorithm: Algorithm object which directs how this Master Key will encrypt the data key :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param dict encryption_context: Encryption context to use in decryption diff --git a/src/aws_encryption_sdk/key_providers/kms.py b/src/aws_encryption_sdk/key_providers/kms.py index c0a2dc46e..ff5ef350d 100644 --- a/src/aws_encryption_sdk/key_providers/kms.py +++ b/src/aws_encryption_sdk/key_providers/kms.py @@ -11,18 +11,29 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Master Key Providers for use with AWS KMS""" +import abc import functools import logging +import warnings import attr import boto3 import botocore.client import botocore.config import botocore.session +import six from botocore.exceptions import ClientError -from aws_encryption_sdk.exceptions import DecryptKeyError, EncryptKeyError, GenerateKeyError, UnknownRegionError +from aws_encryption_sdk.exceptions import ( + ConfigMismatchError, + DecryptKeyError, + EncryptKeyError, + GenerateKeyError, + MasterKeyProviderError, + UnknownRegionError, +) from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX +from aws_encryption_sdk.internal.arn import arn_from_str from aws_encryption_sdk.internal.str_ops import to_str from aws_encryption_sdk.key_providers.base import MasterKey, MasterKeyConfig, MasterKeyProvider, MasterKeyProviderConfig from aws_encryption_sdk.structures import DataKey, EncryptedDataKey, MasterKeyInfo @@ -52,6 +63,20 @@ def _region_from_key_id(key_id, default_region=None): return region_name +@attr.s(hash=True) +class DiscoveryFilter(object): + """DiscoveryFilter to control accounts and partitions that can be used by a KMS Master Key Provider. + + :param list account_ids: List of AWS Account Ids that are allowed to be used for decryption + :param str partition: The AWS partition to which account_ids belong + """ + + account_ids = attr.ib( + default=attr.Factory(tuple), hash=True, validator=attr.validators.instance_of(tuple), converter=tuple + ) + partition = attr.ib(default=None, hash=True, validator=attr.validators.optional(attr.validators.instance_of(str))) + + @attr.s(hash=True) class KMSMasterKeyProviderConfig(MasterKeyProviderConfig): """Configuration object for KMSMasterKeyProvider objects. @@ -60,6 +85,8 @@ class KMSMasterKeyProviderConfig(MasterKeyProviderConfig): :type botocore_session: botocore.session.Session :param list key_ids: List of KMS CMK IDs with which to pre-populate provider (optional) :param list region_names: List of regions for which to pre-populate clients (optional) + :param DiscoveryFilter discovery_filter: Filter indicating AWS accounts and partitions whose keys will be trusted + for decryption """ botocore_session = attr.ib( @@ -73,34 +100,18 @@ class KMSMasterKeyProviderConfig(MasterKeyProviderConfig): region_names = attr.ib( hash=True, default=attr.Factory(tuple), validator=attr.validators.instance_of(tuple), converter=tuple ) + discovery_filter = attr.ib( + hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(DiscoveryFilter)) + ) -class KMSMasterKeyProvider(MasterKeyProvider): +@six.add_metaclass(abc.ABCMeta) +class BaseKMSMasterKeyProvider(MasterKeyProvider): """Master Key Provider for KMS. - >>> import aws_encryption_sdk - >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ - ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', - ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' - ... ]) - >>> kms_key_provider.add_master_key('arn:aws:kms:ap-northeast-1:4444444444444:alias/another-key') - .. note:: - If no botocore_session is provided, the default botocore session will be used. + Cannot be instantiated directly. Callers should use one of the implementing classes. - .. note:: - If multiple AWS Identities are needed, one of two options are available: - - * Additional KMSMasterKeyProvider instances may be added to the primary MasterKeyProvider. - - * KMSMasterKey instances may be manually created and added to this KMSMasterKeyProvider. - - :param config: Configuration object (optional) - :type config: aws_encryption_sdk.key_providers.kms.KMSMasterKeyProviderConfig - :param botocore_session: botocore session object (optional) - :type botocore_session: botocore.session.Session - :param list key_ids: List of KMS CMK IDs with which to pre-populate provider (optional) - :param list region_names: List of regions for which to pre-populate clients (optional) """ provider_id = _PROVIDER_ID @@ -112,10 +123,20 @@ def __init__(self, **kwargs): # pylint: disable=unused-argument self._regional_clients = {} self._process_config() + @abc.abstractmethod + def validate_config(self): + """Validates the provided configuration. + + .. note:: + Must be implemented by specific KMSMasterKeyProvider implementations. + """ + def _process_config(self): """Traverses the config and adds master keys and regional clients as needed.""" self._user_agent_adding_config = botocore.config.Config(user_agent_extra=USER_AGENT_SUFFIX) + self.validate_config() + if self.config.region_names: self.add_regional_clients_from_list(self.config.region_names) self.default_region = self.config.region_names[0] @@ -190,11 +211,165 @@ def _new_master_key(self, key_id): :returns: KMS Master Key based on key_id :rtype: aws_encryption_sdk.key_providers.kms.KMSMasterKey :raises InvalidKeyIdError: if key_id is not a valid KMS CMK ID to which this key provider has access + :raises MasterKeyProviderError: if this MasterKeyProvider is in discovery mode and key_id is not allowed """ _key_id = to_str(key_id) # KMS client requires str, not bytes + + if self.config.discovery_filter: + arn = arn_from_str(_key_id) + + if ( + arn.partition != self.config.discovery_filter.partition + or arn.account_id not in self.config.discovery_filter.account_ids + ): + raise MasterKeyProviderError("Key {} not allowed by this Master Key Provider".format(key_id)) + return KMSMasterKey(config=KMSMasterKeyConfig(key_id=key_id, client=self._client(_key_id))) +class KMSMasterKeyProvider(BaseKMSMasterKeyProvider): + """Master Key Provider for KMS. + + >>> import aws_encryption_sdk + >>> kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[ + ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ... ]) + >>> kms_key_provider.add_master_key('arn:aws:kms:ap-northeast-1:4444444444444:alias/another-key') + + .. note:: + If no botocore_session is provided, the default botocore session will be used. + + .. note:: + If multiple AWS Identities are needed, one of two options are available: + + * Additional KMSMasterKeyProvider instances may be added to the primary MasterKeyProvider. + + * KMSMasterKey instances may be manually created and added to this KMSMasterKeyProvider. + + :param config: Configuration object (optional) + :type config: aws_encryption_sdk.key_providers.kms.KMSMasterKeyProviderConfig + :param botocore_session: botocore session object (optional) + :type botocore_session: botocore.session.Session + :param list key_ids: List of KMS CMK IDs with which to pre-populate provider (optional) + :param list region_names: List of regions for which to pre-populate clients (optional) + """ + + def __init__(self, **kwargs): + """Sets configuration required by this provider type.""" + warnings.warn( + "KMSMasterKeyProvider is deprecated. Use a StrictAwsKmsMasterKeyProvider or " + "DiscoveryAwsKmsMasterKeyProvider instead.", + DeprecationWarning, + ) + super(KMSMasterKeyProvider, self).__init__(**kwargs) + + self.vend_masterkey_on_decrypt = True + + def validate_config(self): + """Validates the provided configuration.""" + if self.config.discovery_filter: + raise ConfigMismatchError("To explicitly enable discovery mode use a DiscoveryAwsKmsMasterKeyProvider") + + +class StrictAwsKmsMasterKeyProvider(BaseKMSMasterKeyProvider): + """Strict Master Key Provider for KMS. It is configured with an explicit list of AWS KMS master keys that + should be used for encryption in decryption. On encryption, the plaintext will be encrypted with all configured + master keys. On decryption, the ciphertext will be decrypted with the first master key that can decrypt. If the + ciphertext is encrypted with a master key that was not explicitly configured, decryption will fail. + + >>> import aws_encryption_sdk + >>> kms_key_provider = aws_encryption_sdk.StrictAwsKmsMasterKeyProvider(key_ids=[ + ... 'arn:aws:kms:us-east-1:2222222222222:key/22222222-2222-2222-2222-222222222222', + ... 'arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333' + ... ]) + >>> kms_key_provider.add_master_key('arn:aws:kms:ap-northeast-1:4444444444444:alias/another-key') + + .. note:: + If no botocore_session is provided, the default botocore session will be used. + + .. note:: + If multiple AWS Identities are needed, one of two options are available: + + * Additional KMSMasterKeyProvider instances may be added to the primary MasterKeyProvider. + + * KMSMasterKey instances may be manually created and added to this KMSMasterKeyProvider. + + :param config: Configuration object (optional) + :type config: aws_encryption_sdk.key_providers.kms.KMSMasterKeyProviderConfig + :param botocore_session: botocore session object (optional) + :type botocore_session: botocore.session.Session + :param list key_ids: List of KMS CMK IDs with which to pre-populate provider (optional) + :param list region_names: List of regions for which to pre-populate clients (optional) + """ + + def __init__(self, **kwargs): + """Sets configuration required by this provider type.""" + super(StrictAwsKmsMasterKeyProvider, self).__init__(**kwargs) + + self.vend_masterkey_on_decrypt = False + + def validate_config(self): + """Validates the provided configuration.""" + if not self.config.key_ids: + raise ConfigMismatchError("To enable strict mode you must provide key ids") + + for key_id in self.config.key_ids: + if not key_id: + raise ConfigMismatchError("Key ids must be valid AWS KMS ARNs") + + if self.config.discovery_filter: + raise ConfigMismatchError("To enable discovery mode, use a DiscoveryAwsKmsMasterKeyProvider") + + +class DiscoveryAwsKmsMasterKeyProvider(BaseKMSMasterKeyProvider): + """Discovery Master Key Provider for KMS. This can only be used for decryption. It is configured with an optional + Discovery Filter containing AWS account ids and partitions that should be trusted for decryption. If a ciphertext + was encrypted with an AWS KMS master key that matches an account and partition listed by this provider, decryption + will succeed. Otherwise, decryption will fail. If no Discovery Filter is configured, the provider will attempt + to decrypt any ciphertext created by an AWS KMS Master Key Provider. + + >>> import aws_encryption_sdk + >>> kms_key_provider = aws_encryption_sdk.DiscoveryAwsKmsMasterKeyProvider(discovery_filter=DiscoveryFilter( + ... account_ids=['2222222222222', '3333333333333'] + ... ) + + .. note:: + If no botocore_session is provided, the default botocore session will be used. + + :param config: Configuration object (optional) + :type config: aws_encryption_sdk.key_providers.kms.KMSMasterKeyProviderConfig + :param botocore_session: botocore session object (optional) + :type botocore_session: botocore.session.Session + :param list key_ids: List of KMS CMK IDs with which to pre-populate provider (optional) + :param list region_names: List of regions for which to pre-populate clients (optional) + """ + + def __init__(self, **kwargs): + """Sets configuration required by this provider type.""" + super(DiscoveryAwsKmsMasterKeyProvider, self).__init__(**kwargs) + + self.vend_masterkey_on_decrypt = True + + def validate_config(self): + """Validates the provided configuration.""" + if self.config.key_ids: + raise ConfigMismatchError( + "To explicitly identify which keys should be used, use a " "StrictAwsKmsMasterKeyProvider." + ) + + if self.config.discovery_filter: + if not self.config.discovery_filter.account_ids or not self.config.discovery_filter.partition: + raise ConfigMismatchError( + "When specifying a discovery filter you must include both account ids and " "partition" + ) + for account in self.config.discovery_filter.account_ids: + if not account: + raise ConfigMismatchError( + "When specifying a discovery filter, account ids must be non-empty " "strings" + ) + + @attr.s(hash=True) class KMSMasterKeyConfig(MasterKeyConfig): """Configuration object for MasterKey objects. @@ -312,7 +487,14 @@ def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context=No :rtype: aws_encryption_sdk.structures.DataKey :raises DecryptKeyError: if Master Key is unable to decrypt data key """ - kms_params = {"CiphertextBlob": encrypted_data_key.encrypted_data_key} + edk_key_id = to_str(encrypted_data_key.key_provider.key_info) + if edk_key_id != self._key_id: + raise DecryptKeyError( + "Cannot decrypt EDK wrapped by key_id={}, because it does not match this " + "provider's key_id={}".format(edk_key_id, self._key_id) + ) + + kms_params = {"CiphertextBlob": encrypted_data_key.encrypted_data_key, "KeyId": edk_key_id} if encryption_context: kms_params["EncryptionContext"] = encryption_context if self.config.grant_tokens: @@ -320,6 +502,14 @@ def _decrypt_data_key(self, encrypted_data_key, algorithm, encryption_context=No # Catch any boto3 errors and normalize to expected DecryptKeyError try: response = self.config.client.decrypt(**kms_params) + + returned_key_id = response["KeyId"] + if returned_key_id != edk_key_id: + error_message = "AWS KMS returned unexpected key_id {returned} (expected {key_id})".format( + returned=returned_key_id, key_id=edk_key_id + ) + _LOGGER.exception(error_message) + raise DecryptKeyError(error_message) plaintext = response["Plaintext"] except (ClientError, KeyError): error_message = "Master Key {key_id} unable to decrypt data key".format(key_id=self._key_id) diff --git a/src/aws_encryption_sdk/materials_managers/__init__.py b/src/aws_encryption_sdk/materials_managers/__init__.py index bc5230c51..9db1dafae 100644 --- a/src/aws_encryption_sdk/materials_managers/__init__.py +++ b/src/aws_encryption_sdk/materials_managers/__init__.py @@ -17,7 +17,7 @@ import attr import six -from ..identifiers import Algorithm +from ..identifiers import Algorithm, CommitmentPolicy from ..internal.utils.streams import ROStream from ..structures import DataKey @@ -42,6 +42,10 @@ class EncryptionMaterialsRequest(object): encryption_context = attr.ib(validator=attr.validators.instance_of(dict)) frame_length = attr.ib(validator=attr.validators.instance_of(six.integer_types)) + commitment_policy = attr.ib( + default=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + validator=attr.validators.optional(attr.validators.instance_of(CommitmentPolicy)), + ) plaintext_rostream = attr.ib( default=None, validator=attr.validators.optional(attr.validators.instance_of(ROStream)) ) @@ -90,6 +94,10 @@ class DecryptionMaterialsRequest(object): algorithm = attr.ib(validator=attr.validators.instance_of(Algorithm)) encrypted_data_keys = attr.ib(validator=attr.validators.instance_of(set)) encryption_context = attr.ib(validator=attr.validators.instance_of(dict)) + commitment_policy = attr.ib( + default=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + validator=attr.validators.optional(attr.validators.instance_of(CommitmentPolicy)), + ) @attr.s(hash=False) diff --git a/src/aws_encryption_sdk/materials_managers/caching.py b/src/aws_encryption_sdk/materials_managers/caching.py index 992a39a7a..826c3be79 100644 --- a/src/aws_encryption_sdk/materials_managers/caching.py +++ b/src/aws_encryption_sdk/materials_managers/caching.py @@ -194,6 +194,7 @@ def get_encryption_materials(self, request): encryption_context=request.encryption_context, frame_length=request.frame_length, algorithm=request.algorithm, + commitment_policy=request.commitment_policy, ) cache_key = build_encryption_materials_cache_key(partition=self.partition_name, request=inner_request) diff --git a/src/aws_encryption_sdk/materials_managers/default.py b/src/aws_encryption_sdk/materials_managers/default.py index 6d10465a9..dc4400d90 100644 --- a/src/aws_encryption_sdk/materials_managers/default.py +++ b/src/aws_encryption_sdk/materials_managers/default.py @@ -21,6 +21,7 @@ from ..internal.defaults import ALGORITHM, ENCODED_SIGNER_KEY from ..internal.str_ops import to_str from ..internal.utils import prepare_data_keys +from ..internal.utils.commitment import validate_commitment_policy_on_encrypt from ..key_providers.base import MasterKeyProvider from . import DecryptionMaterials, EncryptionMaterials from .base import CryptoMaterialsManager @@ -68,8 +69,13 @@ def get_encryption_materials(self, request): :raises MasterKeyProviderError: if no master keys are available from the underlying master key provider :raises MasterKeyProviderError: if the primary master key provided by the underlying master key provider is not included in the full set of master keys provided by that provider + :raises ActionNotAllowedError: if the commitment policy in the request is violated by the algorithm being + used """ algorithm = request.algorithm if request.algorithm is not None else self.algorithm + + validate_commitment_policy_on_encrypt(request.commitment_policy, algorithm) + encryption_context = request.encryption_context.copy() signing_key = self._generate_signing_key_and_update_encryption_context(algorithm, encryption_context) diff --git a/src/aws_encryption_sdk/streaming_client.py b/src/aws_encryption_sdk/streaming_client.py index 504f68977..f33831508 100644 --- a/src/aws_encryption_sdk/streaming_client.py +++ b/src/aws_encryption_sdk/streaming_client.py @@ -14,6 +14,7 @@ from __future__ import division import abc +import hmac import io import logging import math @@ -26,12 +27,13 @@ ActionNotAllowedError, AWSEncryptionSDKClientError, CustomMaximumValueExceeded, + MasterKeyProviderError, NotSupportedError, SerializationError, ) -from aws_encryption_sdk.identifiers import Algorithm, ContentType +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy, ContentType, SerializationVersion from aws_encryption_sdk.internal.crypto.authentication import Signer, Verifier -from aws_encryption_sdk.internal.crypto.data_keys import derive_data_encryption_key +from aws_encryption_sdk.internal.crypto.data_keys import calculate_commitment_key, derive_data_encryption_key from aws_encryption_sdk.internal.crypto.encryption import Decryptor, Encryptor, decrypt from aws_encryption_sdk.internal.crypto.iv import non_framed_body_iv from aws_encryption_sdk.internal.defaults import FRAME_LENGTH, LINE_LENGTH, MAX_NON_FRAMED_SIZE, TYPE, VERSION @@ -53,6 +55,7 @@ serialize_non_framed_close, serialize_non_framed_open, ) +from aws_encryption_sdk.internal.utils.commitment import validate_commitment_policy_on_encrypt from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager @@ -92,6 +95,11 @@ class _ClientConfig(object): source_length = attr.ib( hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(six.integer_types)) ) + commitment_policy = attr.ib( + hash=True, + default=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + validator=attr.validators.optional(attr.validators.instance_of(CommitmentPolicy)), + ) line_length = attr.ib( hash=True, default=LINE_LENGTH, validator=attr.validators.instance_of(six.integer_types) ) # DEPRECATED: Value is no longer configurable here. Parameter left here to avoid breaking consumers. @@ -423,6 +431,8 @@ def _prep_message(self): """ message_id = aws_encryption_sdk.internal.utils.message_id() + validate_commitment_policy_on_encrypt(self.config.commitment_policy, self.config.algorithm) + try: plaintext_length = self.stream_length except NotSupportedError: @@ -433,6 +443,7 @@ def _prep_message(self): frame_length=self.config.frame_length, plaintext_rostream=aws_encryption_sdk.internal.utils.streams.ROStream(self.source_stream), plaintext_length=plaintext_length, + commitment_policy=self.config.commitment_policy, ) self._encryption_materials = self.config.materials_manager.get_encryption_materials( request=encryption_materials_request @@ -464,27 +475,53 @@ def _prep_message(self): message_id=message_id, ) - self._header = MessageHeader( - version=VERSION, - type=TYPE, + self._header = self.generate_header(message_id) + + self._write_header() + if self.content_type == ContentType.NO_FRAMING: + self._prep_non_framed() + self._message_prepped = True + + def generate_header(self, message_id): + """Generates the header object. + + :param message_id: The randomly generated id for the message + :type message_id: bytes + """ + version = VERSION + if self._encryption_materials.algorithm.message_format_version == 0x02: + version = SerializationVersion.V2 + + kwargs = dict( + version=version, algorithm=self._encryption_materials.algorithm, message_id=message_id, encryption_context=self._encryption_materials.encryption_context, encrypted_data_keys=self._encryption_materials.encrypted_data_keys, content_type=self.content_type, - content_aad_length=0, - header_iv_length=self._encryption_materials.algorithm.iv_len, frame_length=self.config.frame_length, ) - self._write_header() - if self.content_type == ContentType.NO_FRAMING: - self._prep_non_framed() - self._message_prepped = True + + if self._encryption_materials.algorithm.is_committing(): + commitment_key = calculate_commitment_key( + source_key=self._encryption_materials.data_encryption_key.data_key, + algorithm=self._encryption_materials.algorithm, + message_id=message_id, + ) + kwargs["commitment_key"] = commitment_key + + if version == SerializationVersion.V1: + kwargs["type"] = TYPE + kwargs["content_aad_length"] = 0 + kwargs["header_iv_length"] = self._encryption_materials.algorithm.iv_len + + return MessageHeader(**kwargs) def _write_header(self): """Builds the message header and writes it to the output stream.""" self.output_buffer += serialize_header(header=self._header, signer=self.signer) self.output_buffer += serialize_header_auth( + version=self._header.version, algorithm=self._encryption_materials.algorithm, header=self.output_buffer, data_encryption_key=self._derived_data_key, @@ -761,6 +798,7 @@ def _read_header(self): encrypted_data_keys=header.encrypted_data_keys, algorithm=header.algorithm, encryption_context=header.encryption_context, + commitment_policy=self.config.commitment_policy, ) decryption_materials = self.config.materials_manager.decrypt_materials(request=decrypt_materials_request) if decryption_materials.verification_key is None: @@ -773,12 +811,27 @@ def _read_header(self): self.verifier.update(raw_header) header_auth = deserialize_header_auth( - stream=self.source_stream, algorithm=header.algorithm, verifier=self.verifier + version=header.version, stream=self.source_stream, algorithm=header.algorithm, verifier=self.verifier ) self._derived_data_key = derive_data_encryption_key( source_key=decryption_materials.data_key.data_key, algorithm=header.algorithm, message_id=header.message_id ) + + if header.algorithm.is_committing(): + expected_commitment_key = calculate_commitment_key( + source_key=decryption_materials.data_key.data_key, + algorithm=header.algorithm, + message_id=header.message_id, + ) + + if not hmac.compare_digest(expected_commitment_key, header.commitment_key): + raise MasterKeyProviderError( + "Key commitment validation failed. Key identity does not match the identity asserted in the " + "message. Halting processing of this message." + ) + validate_header(header=header, header_auth=header_auth, raw_header=raw_header, data_key=self._derived_data_key) + return header, header_auth def _prep_non_framed(self): diff --git a/src/aws_encryption_sdk/structures.py b/src/aws_encryption_sdk/structures.py index 97e4c1d13..52d16dd4c 100644 --- a/src/aws_encryption_sdk/structures.py +++ b/src/aws_encryption_sdk/structures.py @@ -43,16 +43,33 @@ class MessageHeader(object): version = attr.ib( hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.SerializationVersion) ) - type = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.ObjectType)) algorithm = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.Algorithm)) message_id = attr.ib(hash=True, validator=attr.validators.instance_of(bytes)) encryption_context = attr.ib(hash=True, validator=attr.validators.instance_of(dict)) encrypted_data_keys = attr.ib(hash=True, validator=attr.validators.instance_of(set)) content_type = attr.ib(hash=True, validator=attr.validators.instance_of(aws_encryption_sdk.identifiers.ContentType)) - content_aad_length = attr.ib(hash=True, validator=attr.validators.instance_of(six.integer_types)) - header_iv_length = attr.ib(hash=True, validator=attr.validators.instance_of(six.integer_types)) frame_length = attr.ib(hash=True, validator=attr.validators.instance_of(six.integer_types)) + # Only present in SerializationVersion.V1 + type = attr.ib( + hash=True, + default=None, + validator=attr.validators.optional(attr.validators.instance_of(aws_encryption_sdk.identifiers.ObjectType)), + ) + content_aad_length = attr.ib( + hash=True, + default=None, + validator=attr.validators.optional(attr.validators.optional(attr.validators.instance_of(six.integer_types))), + ) + header_iv_length = attr.ib( + hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(six.integer_types)) + ) + + # Only present in SerializationVersion.V2 with certain algorithm suites + commitment_key = attr.ib( + hash=True, default=None, validator=attr.validators.optional(attr.validators.instance_of(bytes)) + ) + @attr.s(hash=True) class MasterKeyInfo(object): diff --git a/test/functional/test_f_aws_encryption_sdk_client.py b/test/functional/test_f_aws_encryption_sdk_client.py index fb19e868a..a43e44796 100644 --- a/test/functional/test_f_aws_encryption_sdk_client.py +++ b/test/functional/test_f_aws_encryption_sdk_client.py @@ -27,14 +27,14 @@ from wrapt import ObjectProxy import aws_encryption_sdk -from aws_encryption_sdk import KMSMasterKeyProvider from aws_encryption_sdk.caches import build_decryption_materials_cache_key, build_encryption_materials_cache_key from aws_encryption_sdk.exceptions import CustomMaximumValueExceeded -from aws_encryption_sdk.identifiers import Algorithm, EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy, EncryptionKeyType, WrappingAlgorithm from aws_encryption_sdk.internal.crypto.wrapping_keys import WrappingKey from aws_encryption_sdk.internal.defaults import LINE_LENGTH from aws_encryption_sdk.internal.formatting.encryption_context import serialize_encryption_context from aws_encryption_sdk.key_providers.base import MasterKeyProviderConfig +from aws_encryption_sdk.key_providers.kms import DiscoveryAwsKmsMasterKeyProvider from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest @@ -91,7 +91,7 @@ b"\x0e\xae|%\xd8L]\xa2\xa2\x08\x1f" ), "encryption_context": {"key_a": "value_a", "key_b": "value_b", "key_c": "value_c"}, - "arn": b"arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333", + "arn": "arn:aws:kms:us-east-1:3333333333333:key/33333333-3333-3333-3333-333333333333", "provided": { "key": b"\x90\x86Z\x95\x96l'\xa7\x00yA\x9a\x1a\"\xa9\x8e", "ciphertext": ( @@ -220,7 +220,7 @@ def fake_kms_client(keysize=32): def fake_kms_key_provider(keysize=32): - mock_kms_key_provider = KMSMasterKeyProvider() + mock_kms_key_provider = DiscoveryAwsKmsMasterKeyProvider() mock_kms_key_provider._regional_clients["us-east-1"] = fake_kms_client(keysize) mock_kms_key_provider.add_master_key(VALUES["arn"]) return mock_kms_key_provider @@ -246,8 +246,8 @@ def build_fake_raw_key_provider(wrapping_algorithm, encryption_key_type): def test_no_infinite_encryption_cycle_on_empty_source(): """This catches a race condition where when calling encrypt with - an empty byte string, encrypt would enter an infinite loop. - If this test does not hang, the race condition is not present. + an empty byte string, encrypt would enter an infinite loop. + If this test does not hang, the race condition is not present. """ aws_encryption_sdk.encrypt(source=b"", key_provider=fake_kms_key_provider()) @@ -297,6 +297,7 @@ def test_encrypt_decrypt_header_only(): [frame_length, algorithm_suite, encryption_context] for frame_length in VALUES["frame_lengths"] for algorithm_suite in Algorithm + if not algorithm_suite.is_committing() for encryption_context in [{}, VALUES["encryption_context"]] ], ) @@ -375,6 +376,7 @@ def test_encryption_cycle_raw_mkp_openssl_102_plus(wrapping_algorithm, encryptio [frame_length, algorithm_suite, encryption_context] for frame_length in VALUES["frame_lengths"] for algorithm_suite in Algorithm + if not algorithm_suite.is_committing() for encryption_context in [{}, VALUES["encryption_context"]] ], ) @@ -400,6 +402,7 @@ def test_encryption_cycle_oneshot_kms(frame_length, algorithm, encryption_contex [frame_length, algorithm_suite, encryption_context] for frame_length in VALUES["frame_lengths"] for algorithm_suite in Algorithm + if not algorithm_suite.is_committing() for encryption_context in [{}, VALUES["encryption_context"]] ], ) @@ -435,7 +438,7 @@ def test_decrypt_legacy_provided_message(): region = "us-west-2" key_info = "arn:aws:kms:us-west-2:249645522726:key/d1720f4e-953b-44bb-b9dd-fc8b9d0baa5f" mock_kms_client = fake_kms_client() - mock_kms_client.decrypt.return_value = {"Plaintext": VALUES["provided"]["key"]} + mock_kms_client.decrypt.return_value = {"Plaintext": VALUES["provided"]["key"], "KeyId": key_info} mock_kms_key_provider = fake_kms_key_provider() mock_kms_key_provider._regional_clients[region] = mock_kms_client mock_kms_key_provider.add_master_key(key_info) @@ -467,6 +470,7 @@ def test_encryption_cycle_with_caching(): frame_length=frame_length, algorithm=algorithm, plaintext_length=len(VALUES["plaintext_128"]), + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, ), ) ciphertext, header = aws_encryption_sdk.encrypt(**encrypt_kwargs) @@ -476,6 +480,7 @@ def test_encryption_cycle_with_caching(): algorithm=algorithm, encrypted_data_keys=header.encrypted_data_keys, encryption_context=header.encryption_context, + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, ), ) diff --git a/test/functional/test_f_commitment.py b/test/functional/test_f_commitment.py new file mode 100644 index 000000000..1f60b7965 --- /dev/null +++ b/test/functional/test_f_commitment.py @@ -0,0 +1,118 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Functional test suite for functionality related to key commitment.""" +from test.functional.test_f_aws_encryption_sdk_client import fake_kms_key_provider + +import attr +import pytest +import six + +import aws_encryption_sdk +from aws_encryption_sdk.exceptions import ActionNotAllowedError, MasterKeyProviderError +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy, EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.internal.crypto import WrappingKey +from aws_encryption_sdk.key_providers.base import MasterKeyProviderConfig +from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider + +pytestmark = [pytest.mark.functional, pytest.mark.local] + +VALUES = { + "ciphertext_v2_good_commitment": six.b( + "\x02\x04xM\xfb\xd11M\x9dU\x92[\x81r2\xc5\xe3mn>^#\x0f\x01\x890\xe2\xc2\xc1\xf2C\xf6}\xc1y\x00\x00\x00\x01" + "\x00\x0cProviderName\x00\x19KeyId\x00\x00\x00\x80\x00\x00\x00\x0c\xf8\xe6\xc77p;\xc9g\xb0\xf8?{" + "\x000\xa1;w\xfc<\xce$-\xd8-*\x1e\xcc\xb5B\xed\x84\xda\xafvx\x81\x84\xfeB\xe7\x17\xf64\xd3q\xca\xbdB\xc1pL\xa2\xe0A\xda\xe7\x1a \x95f" + ), + "ciphertext_v2_bad_commitment": six.b( + "\x02\x04xo_\xfb\xdd~D\xac\x82\xe9\x8fF\x92@\x8cz\xc0\xd9\xc7," + "G\r/\x13\xf3\x03I\xba\xbd\x84k\xee@\x00\x00\x00\x01\x00\x0cProviderName\x00\x19KeyId\x00\x00\x00\x80\x00\x00" + "\x00\x0c\xa9&e\xf0\xca,\xd4\xaf\x7f\xe9\xca\xf2\x000r\xd4\xfe.F\x85\x99Fk'\x98\x0b\x9c?\n5(" + "o!\xb07\x84)x\xc0t^\x93C\x1934\x7fVq\xefex\xb8\xcd\x87\xe7\x03#\xcb\xf8f\xdb\x02\x00\x00\x10\x00\x00\x01\x02" + "\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e" + "\x1f\xe9@\xcd\x95J\xf9b\xcfd \x8b\x92Y\x7fkZ\xff\xff\xff\xff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x01\x00\x00\x00\rK\x82\x1d\xe3\xe7U\x1e\x13\xeb\xbe\xe2G\x12#\xac\xc2\x8e\x98\x19$cHe\xf7T:\xed" + "\xfbK" + ), +} + + +@attr.s(hash=False) +class StaticRawMasterKeyProviderConfig(MasterKeyProviderConfig): + wrapping_algorithm = attr.ib() + encryption_key_type = attr.ib() + key_bytes = attr.ib(default=None) + + +class StaticRawMasterKeyProvider(RawMasterKeyProvider): + """Master Key Provider for testing which always returns keys consisting of the raw bytes which were configuring + during instantiation.""" + + provider_id = "ProviderName" + _config_class = StaticRawMasterKeyProviderConfig + + def _get_raw_key(self, key_id): + return WrappingKey( + wrapping_algorithm=self.config.wrapping_algorithm, + wrapping_key=self.config.key_bytes, + wrapping_key_type=self.config.encryption_key_type, + ) + + +def test_decrypt_v2_good_commitment(): + """Tests forwards compatibility with message serialization format v2.""" + provider = StaticRawMasterKeyProvider( + wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + encryption_key_type=EncryptionKeyType.SYMMETRIC, + key_bytes=b"\00" * 32, + ) + provider.add_master_key("KeyId") + ciphertext = VALUES["ciphertext_v2_good_commitment"] + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=provider) + assert plaintext == b"GoodCommitment" + + +def test_decrypt_v2_bad_commitment(): + """Tests that we fail as expected when receiving a message with incorrect commitment value.""" + provider = StaticRawMasterKeyProvider( + wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + encryption_key_type=EncryptionKeyType.SYMMETRIC, + key_bytes=b"\00" * 32, + ) + provider.add_master_key("KeyId") + + ciphertext = VALUES["ciphertext_v2_bad_commitment"] + + with pytest.raises(MasterKeyProviderError) as excinfo: + aws_encryption_sdk.decrypt(source=ciphertext, key_provider=provider) + excinfo.match("Key commitment validation failed") + + +def test_encrypt_with_committing_algorithm_policy_forbids_encrypt(): + """Tests that a request with CommitmentPolicy FORBID_ENCRYPT_ALLOW_DECRYPT cannot encrypt using an + algorithm that provides commitment.""" + algorithm = aws_encryption_sdk.Algorithm.AES_256_GCM_HKDF_SHA512_COMMIT_KEY + provider = fake_kms_key_provider(algorithm.kdf_input_len) + plaintext = b"Yellow Submarine" + + with pytest.raises(ActionNotAllowedError) as excinfo: + aws_encryption_sdk.encrypt( + source=plaintext, + key_provider=provider, + algorithm=Algorithm.AES_256_GCM_HKDF_SHA512_COMMIT_KEY, + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + ) + + excinfo.match("Configuration conflict. Cannot encrypt due to .* requiring only non-committed messages") diff --git a/test/integration/integration_test_utils.py b/test/integration/integration_test_utils.py index b65d93570..fea3cc278 100644 --- a/test/integration/integration_test_utils.py +++ b/test/integration/integration_test_utils.py @@ -15,27 +15,37 @@ import botocore.session -from aws_encryption_sdk.key_providers.kms import KMSMasterKeyProvider +from aws_encryption_sdk.key_providers.kms import StrictAwsKmsMasterKeyProvider AWS_KMS_KEY_ID = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID" +AWS_KMS_KEY_ID_2 = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID_2" _KMS_MKP = None _KMS_MKP_BOTO = None -def get_cmk_arn(): - """Retrieves the target CMK ARN from environment variable.""" - arn = os.environ.get(AWS_KMS_KEY_ID, None) +def _get_single_cmk_arn(name): + # type: (str) -> str + """Retrieve a single target AWS KMS CMK ARN from the specified environment variable name.""" + arn = os.environ.get(name, None) if arn is None: raise ValueError( - 'Environment variable "{}" must be set to a valid KMS CMK ARN for integration tests to run'.format( - AWS_KMS_KEY_ID - ) + 'Environment variable "{}" must be set to a valid KMS CMK ARN for integration tests to run'.format(name) ) if arn.startswith("arn:") and ":alias/" not in arn: return arn raise ValueError("KMS CMK ARN provided for integration tests much be a key not an alias") +def get_cmk_arn(): + """Retrieves the target AWS KMS CMK ARN from environment variable.""" + return _get_single_cmk_arn(AWS_KMS_KEY_ID) + + +def get_second_cmk_arn(): + """Retrieves the target AWS KMS CMK ARN from environment variable.""" + return _get_single_cmk_arn(AWS_KMS_KEY_ID_2) + + def setup_kms_master_key_provider(cache=True): """Reads the test_values config file and builds the requested KMS Master Key Provider.""" global _KMS_MKP # pylint: disable=global-statement @@ -43,8 +53,7 @@ def setup_kms_master_key_provider(cache=True): return _KMS_MKP cmk_arn = get_cmk_arn() - kms_master_key_provider = KMSMasterKeyProvider() - kms_master_key_provider.add_master_key(cmk_arn) + kms_master_key_provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn]) if cache: _KMS_MKP = kms_master_key_provider @@ -59,8 +68,9 @@ def setup_kms_master_key_provider_with_botocore_session(cache=True): return _KMS_MKP_BOTO cmk_arn = get_cmk_arn() - kms_master_key_provider = KMSMasterKeyProvider(botocore_session=botocore.session.Session()) - kms_master_key_provider.add_master_key(cmk_arn) + kms_master_key_provider = StrictAwsKmsMasterKeyProvider( + key_ids=[cmk_arn], botocore_session=botocore.session.Session() + ) if cache: _KMS_MKP_BOTO = kms_master_key_provider diff --git a/test/integration/test_i_aws_encrytion_sdk_client.py b/test/integration/test_i_aws_encrytion_sdk_client.py index 26df431dc..028e7b61d 100644 --- a/test/integration/test_i_aws_encrytion_sdk_client.py +++ b/test/integration/test_i_aws_encrytion_sdk_client.py @@ -18,8 +18,16 @@ from botocore.exceptions import BotoCoreError import aws_encryption_sdk +from aws_encryption_sdk.exceptions import DecryptKeyError, EncryptKeyError, MasterKeyProviderError from aws_encryption_sdk.identifiers import USER_AGENT_SUFFIX, Algorithm -from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider +from aws_encryption_sdk.internal.arn import arn_from_str +from aws_encryption_sdk.key_providers.kms import ( + DiscoveryAwsKmsMasterKeyProvider, + DiscoveryFilter, + KMSMasterKey, + KMSMasterKeyProvider, + StrictAwsKmsMasterKeyProvider, +) from .integration_test_utils import ( get_cmk_arn, @@ -63,13 +71,13 @@ def test_encrypt_verify_user_agent_kms_master_key(caplog): def test_remove_bad_client(): - test = KMSMasterKeyProvider() + test = setup_kms_master_key_provider(False) test.add_regional_client("us-fakey-12") with pytest.raises(BotoCoreError): test._regional_clients["us-fakey-12"].list_keys() - assert not test._regional_clients + assert "us-fakey-12" not in test._regional_clients def test_regional_client_does_not_modify_botocore_session(caplog): @@ -88,7 +96,7 @@ def apply_fixtures(self): def test_encryption_cycle_default_algorithm_framed_stream(self): """Test that the enrypt/decrypt cycle completes successfully - for a framed message using the default algorithm. + for a framed message using the default algorithm. """ with aws_encryption_sdk.stream( source=io.BytesIO(VALUES["plaintext_128"]), @@ -108,7 +116,7 @@ def test_encryption_cycle_default_algorithm_framed_stream(self): def test_encryption_cycle_default_algorithm_framed_stream_many_lines(self): """Test that the enrypt/decrypt cycle completes successfully - for a framed message with many frames using the default algorithm. + for a framed message with many frames using the default algorithm. """ ciphertext = b"" with aws_encryption_sdk.stream( @@ -133,7 +141,7 @@ def test_encryption_cycle_default_algorithm_framed_stream_many_lines(self): def test_encryption_cycle_default_algorithm_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully - for a non-framed message using the default algorithm. + for a non-framed message using the default algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -146,7 +154,7 @@ def test_encryption_cycle_default_algorithm_non_framed(self): def test_encryption_cycle_default_algorithm_non_framed_no_encryption_context(self): """Test that the enrypt/decrypt cycle completes successfully - for a non-framed message using the default algorithm. + for a non-framed message using the default algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], key_provider=self.kms_master_key_provider, frame_length=0 @@ -156,7 +164,7 @@ def test_encryption_cycle_default_algorithm_non_framed_no_encryption_context(sel def test_encryption_cycle_default_algorithm_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully - for a single frame message using the default algorithm. + for a single frame message using the default algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -169,8 +177,8 @@ def test_encryption_cycle_default_algorithm_single_frame(self): def test_encryption_cycle_default_algorithm_multiple_frames(self): """Test that the enrypt/decrypt cycle completes successfully - for a framed message with multiple frames using the - default algorithm. + for a framed message with multiple frames using the + default algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"] * 100, @@ -183,8 +191,8 @@ def test_encryption_cycle_default_algorithm_multiple_frames(self): def test_encryption_cycle_aes_128_gcm_iv12_tag16_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully - for a single frame message using the aes_128_gcm_iv12_tag16 - algorithm. + for a single frame message using the aes_128_gcm_iv12_tag16 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -198,8 +206,8 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_single_frame(self): def test_encryption_cycle_aes_128_gcm_iv12_tag16_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully - for a non-framed message using the aes_128_gcm_iv12_tag16 - algorithm. + for a non-framed message using the aes_128_gcm_iv12_tag16 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -213,8 +221,8 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_non_framed(self): def test_encryption_cycle_aes_192_gcm_iv12_tag16_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully - for a single frame message using the aes_192_gcm_iv12_tag16 - algorithm. + for a single frame message using the aes_192_gcm_iv12_tag16 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -228,8 +236,8 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_single_frame(self): def test_encryption_cycle_aes_192_gcm_iv12_tag16_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully - for a non-framed message using the aes_192_gcm_iv12_tag16 - algorithm. + for a non-framed message using the aes_192_gcm_iv12_tag16 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -243,8 +251,8 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_non_framed(self): def test_encryption_cycle_aes_256_gcm_iv12_tag16_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully - for a single frame message using the aes_256_gcm_iv12_tag16 - algorithm. + for a single frame message using the aes_256_gcm_iv12_tag16 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -258,8 +266,8 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_single_frame(self): def test_encryption_cycle_aes_256_gcm_iv12_tag16_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully - for a non-framed message using the aes_256_gcm_iv12_tag16 - algorithm. + for a non-framed message using the aes_256_gcm_iv12_tag16 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -273,8 +281,8 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_non_framed(self): def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully for a - single frame message using the aes_128_gcm_iv12_tag16_hkdf_sha256 - algorithm. + single frame message using the aes_128_gcm_iv12_tag16_hkdf_sha256 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -288,8 +296,8 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_single_frame(self): def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully for a - non-framed message using the aes_128_gcm_iv12_tag16_hkdf_sha256 - algorithm. + non-framed message using the aes_128_gcm_iv12_tag16_hkdf_sha256 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -303,8 +311,8 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_non_framed(self): def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha256_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully for a - single frame message using the aes_192_gcm_iv12_tag16_hkdf_sha256 - algorithm. + single frame message using the aes_192_gcm_iv12_tag16_hkdf_sha256 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -318,8 +326,8 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha256_single_frame(self): def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha256_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully for a - non-framed message using the aes_192_gcm_iv12_tag16_hkdf_sha256 - algorithm. + non-framed message using the aes_192_gcm_iv12_tag16_hkdf_sha256 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -333,8 +341,8 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha256_non_framed(self): def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha256_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully for a - single frame message using the aes_256_gcm_iv12_tag16_hkdf_sha256 - algorithm. + single frame message using the aes_256_gcm_iv12_tag16_hkdf_sha256 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -348,8 +356,8 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha256_single_frame(self): def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha256_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully for a - non-framed message using the aes_256_gcm_iv12_tag16_hkdf_sha256 - algorithm. + non-framed message using the aes_256_gcm_iv12_tag16_hkdf_sha256 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -363,8 +371,8 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha256_non_framed(self): def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully for a single - frame message using the aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256 - algorithm. + frame message using the aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -378,8 +386,8 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256_single_f def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully for a single - block message using the aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256 - algorithm. + block message using the aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -393,8 +401,8 @@ def test_encryption_cycle_aes_128_gcm_iv12_tag16_hkdf_sha256_ecdsa_p256_non_fram def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully for a single - frame message using the aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 - algorithm. + frame message using the aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -408,8 +416,8 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_single_f def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully for a single - block message using the aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 - algorithm. + block message using the aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -423,8 +431,8 @@ def test_encryption_cycle_aes_192_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_non_fram def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_single_frame(self): """Test that the enrypt/decrypt cycle completes successfully for a single - frame message using the aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 - algorithm. + frame message using the aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -438,8 +446,8 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_single_f def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_non_framed(self): """Test that the enrypt/decrypt cycle completes successfully for a single - block message using the aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 - algorithm. + block message using the aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384 + algorithm. """ ciphertext, _ = aws_encryption_sdk.encrypt( source=VALUES["plaintext_128"], @@ -450,3 +458,239 @@ def test_encryption_cycle_aes_256_gcm_iv12_tag16_hkdf_sha384_ecdsa_p384_non_fram ) plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=self.kms_master_key_provider) assert plaintext == VALUES["plaintext_128"] + + def test_decrypt_success_strict_matching_key_id(self): + """Test that a Strict KMS Master Key Provider can successfully + decrypt an EDK when it has been configured with the correct key id + """ + cmk_arn = get_cmk_arn() + provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn]) + + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=provider) + assert plaintext == VALUES["plaintext_128"] + + def test_decrypt_failure_strict_mismatched_key_id(self): + """Test that a Strict KMS Master Key Provider fails to decrypt an + EDK when it has not been configured with the correct key id + """ + cmk_arn = get_cmk_arn() + encrypt_provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn]) + + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=encrypt_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + + # Check that we can decrypt the ciphertext using the original provider + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=encrypt_provider) + assert plaintext == VALUES["plaintext_128"] + + # Check that we cannot decrypt the ciphertext using a non-discovery provider without the correct key_id + second_cmk_arn = cmk_arn + "-doesnotexist" + decrypt_provider = StrictAwsKmsMasterKeyProvider(key_ids=[second_cmk_arn]) + + with pytest.raises(DecryptKeyError) as excinfo: + aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypt_provider) + excinfo.match("Unable to decrypt any data key") + + def test_decrypt_success_discovery_no_filter(self): + """Test that a Discovery KMS Master Key Provider in unfiltered discovery mode can + decrypt a valid EDK. + """ + cmk_arn = get_cmk_arn() + encrypt_provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn]) + + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=encrypt_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + + # Check that we can decrypt the ciphertext using the original provider + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=encrypt_provider) + assert plaintext == VALUES["plaintext_128"] + + # Check that we can decrypt the ciphertext using a discovery provider with no filter + decrypt_provider = DiscoveryAwsKmsMasterKeyProvider() + + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypt_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_decrypt_success_discovery_filter(self): + """Test that a Discovery KMS Master Key Provider in filtered discovery mode can + decrypt a ciphertext when it is configured with the correct account id and partition. + """ + cmk_arn = get_cmk_arn() + encrypt_provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn]) + + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=encrypt_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + + # Check that we can decrypt the ciphertext using the original provider + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=encrypt_provider) + assert plaintext == VALUES["plaintext_128"] + + # Check that we can decrypt the ciphertext using a discovery provider that allows this account and partition + arn = arn_from_str(cmk_arn) + discovery_filter = DiscoveryFilter(partition=arn.partition, account_ids=[arn.account_id]) + decrypt_provider = DiscoveryAwsKmsMasterKeyProvider(discovery_filter=discovery_filter) + + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypt_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_decrypt_failure_discovery_disallowed_account(self): + """Test that a KMS Master Key Provider in filtered discovery mode fails to + decrypt an EDK if the EDK was wrapped by a KMS Master Key in an + AWS account that is not allowed by the filter. + """ + cmk_arn = get_cmk_arn() + encrypt_provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn]) + + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=encrypt_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + + # Check that we can decrypt the ciphertext using the original provider + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=encrypt_provider) + assert plaintext == VALUES["plaintext_128"] + + # Check that we cannot decrypt the ciphertext using a discovery provider that does not match this key's account + arn = arn_from_str(cmk_arn) + discovery_filter = DiscoveryFilter(partition=arn.partition, account_ids=["99"]) + decrypt_provider = DiscoveryAwsKmsMasterKeyProvider(discovery_filter=discovery_filter) + + with pytest.raises(MasterKeyProviderError) as excinfo: + aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypt_provider) + excinfo.match("not allowed by this Master Key Provider") + + def test_decrypt_failure_discovery_disallowed_partition(self): + """Test that a KMS Master Key Provider in filtered discovery mode fails to + decrypt an EDK if the EDK was wrapped by a KMS Master Key in an + AWS partition that is not allowed by the filter. + """ + cmk_arn = get_cmk_arn() + encrypt_provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn]) + + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=encrypt_provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + + # Check that we can decrypt the ciphertext using the original provider + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=encrypt_provider) + assert plaintext == VALUES["plaintext_128"] + + # Check that we cannot decrypt the ciphertext using a discovery provider that does not match this key's + # partition + arn = arn_from_str(cmk_arn) + discovery_filter = DiscoveryFilter(partition="aws-cn", account_ids=[arn.account_id]) + decrypt_provider = DiscoveryAwsKmsMasterKeyProvider(discovery_filter=discovery_filter) + + with pytest.raises(MasterKeyProviderError) as excinfo: + aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypt_provider) + excinfo.match("not allowed by this Master Key Provider") + + def test_encrypt_failure_unknown_cmk(self): + """Test that a Master Key Provider returns the correct error when one of the + keys with which it was configured is unable to encrypt + """ + cmk_arn = get_cmk_arn() + second_cmk_arn = cmk_arn + "-doesnotexist" + provider = StrictAwsKmsMasterKeyProvider(key_ids=[cmk_arn, second_cmk_arn]) + + with pytest.raises(EncryptKeyError) as excinfo: + aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + excinfo.match("Master Key {key_id} unable to encrypt".format(key_id=second_cmk_arn)) + + def test_encrypt_failure_discovery_provider(self): + """Test that a Discovery Master Key Provider cannot encrypt""" + provider = DiscoveryAwsKmsMasterKeyProvider() + + with pytest.raises(MasterKeyProviderError) as excinfo: + aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + excinfo.match("No Master Keys available from Master Key Provider") + + def test_decrypt_success_kms_provider_explicit(self): + """Test that an old-style KMS Master Key Provider can decrypt a ciphertext when + it was explicitly configured with the CMK for that ciphertext. + """ + cmk_arn = get_cmk_arn() + provider = KMSMasterKeyProvider(key_ids=[cmk_arn]) + + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=provider) + assert plaintext == VALUES["plaintext_128"] + + def test_decrypt_success_kms_provider_discovery(self): + """Test that an old-style KMS Master Key Provider can decrypt a ciphertext even + if it was not explicitly configured with the CMK for that ciphertext. + """ + cmk_arn = get_cmk_arn() + provider = KMSMasterKeyProvider(key_ids=[cmk_arn]) + + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + + second_cmk_arn = cmk_arn + "-doesnotexist" + decrypt_provider = KMSMasterKeyProvider(key_ids=[second_cmk_arn]) + + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypt_provider) + assert plaintext == VALUES["plaintext_128"] + + def test_decrypt_success_kms_provider_no_key_ids(self): + """Test that an old-style KMS Master Key Provider configured without any + key ids can successfully decrypt a ciphertext. + """ + cmk_arn = get_cmk_arn() + provider = KMSMasterKeyProvider(key_ids=[cmk_arn]) + + ciphertext, _ = aws_encryption_sdk.encrypt( + source=VALUES["plaintext_128"], + key_provider=provider, + encryption_context=VALUES["encryption_context"], + frame_length=1024, + ) + + decrypt_provider = KMSMasterKeyProvider() + + plaintext, _ = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=decrypt_provider) + assert plaintext == VALUES["plaintext_128"] diff --git a/test/integration/test_kat_commitment.py b/test/integration/test_kat_commitment.py new file mode 100644 index 000000000..ae5a2495c --- /dev/null +++ b/test/integration/test_kat_commitment.py @@ -0,0 +1,97 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Known answer test suite for functionality related to key commitment.""" +import base64 +import json +import os +import sys +from test.functional.test_f_commitment import StaticRawMasterKeyProvider + +import pytest + +import aws_encryption_sdk +from aws_encryption_sdk.exceptions import MasterKeyProviderError +from aws_encryption_sdk.identifiers import EncryptionKeyType, WrappingAlgorithm +from aws_encryption_sdk.key_providers.kms import DiscoveryAwsKmsMasterKeyProvider + +pytestmark = [pytest.mark.integ] + + +FILE_NAME = "commitment-test-vectors.json" + + +# Environment-specific test file locator. May not always exist. +def _file_root(): + return "." + + +try: + from .aws_test_file_finder import file_root +except ImportError: + file_root = _file_root + + +try: + root_dir = os.path.abspath(file_root()) +except Exception: # pylint: disable=broad-except + root_dir = os.getcwd() +if not os.path.isdir(root_dir): + root_dir = os.getcwd() +base_dir = os.path.join(root_dir, "test", "resources") +file_path = os.path.join(base_dir, FILE_NAME) + +test_str = open(file_path, "r").read() +test_json = json.loads(test_str) +kat_tests = test_json["tests"] + + +@pytest.mark.parametrize("info", kat_tests, ids=[info["comment"] for info in kat_tests]) +def test_kat(info): + """Tests known answer tests""" + provider = None + if info["keyring-type"] == "static": + key_bytes = b"\00" * 32 + provider = StaticRawMasterKeyProvider( + wrapping_algorithm=WrappingAlgorithm.AES_256_GCM_IV12_TAG16_NO_PADDING, + encryption_key_type=EncryptionKeyType.SYMMETRIC, + key_bytes=key_bytes, + ) + provider.add_master_key("KeyId") + else: + provider = DiscoveryAwsKmsMasterKeyProvider() + + ciphertext = base64.b64decode(info["ciphertext"]) + + if info["exception"]: + with pytest.raises(MasterKeyProviderError) as excinfo: + aws_encryption_sdk.decrypt(source=ciphertext, key_provider=provider) + expected_error = "Key commitment validation failed" + excinfo.match(expected_error) + else: + plaintext, header = aws_encryption_sdk.decrypt(source=ciphertext, key_provider=provider) + + # Only supporting single frame messages for now + if sys.version_info[0] == 3: + expected_plaintext = bytes("".join(info["plaintext-frames"]), "utf-8") + else: + expected_plaintext = bytes(str("".join(info["plaintext-frames"]).encode("utf-8"))) + assert expected_plaintext == plaintext + + expected_commitment = base64.b64decode(info["commitment"]) + assert expected_commitment == header.commitment_key + + expected_message_id = base64.b64decode(info["message-id"]) + assert expected_message_id == header.message_id + + expected_encryption_context = info["encryption-context"] + assert expected_encryption_context == header.encryption_context diff --git a/test/resources/commitment-test-vectors.json b/test/resources/commitment-test-vectors.json new file mode 100644 index 000000000..5090599a4 --- /dev/null +++ b/test/resources/commitment-test-vectors.json @@ -0,0 +1,1026 @@ +{ + "title": "AWS Encryption SDK - Message Format V2 Test Vectors", + "date": "2020-09-18", + "status": "2.0 Release", + "tests": [ + { + "ciphertext": "AgR4TfvRMU2dVZJbgXIyxeNtbj5eIw8BiTDiwsHyQ\/Z9wXkAAAABAAxQcm92aWRlck5hbWUAGUtleUlkAAAAgAAAAAz45sc3cDvJZ7D4P3sAMKE7d\/w8ziQt2C0qHsy1Qu2E2q92eIGE\/kLnF\/Y003HKvTxx7xv2Zv83YuOdwHML5QIAABAAF88I9zPbUQSfOlzLXv+uIY2+m\/E6j2PMsbgeHVH\/L0wLqQlY+5CL0z3xnNOMIZae\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAAOSZBKHHRpTwXOFTQVGapXXj5CwXBMouBB2ucaIJVm", + "commitment": "F88I9zPbUQSfOlzLXv+uIY2+m\/E6j2PMsbgeHVH\/L0w=", + "content-encryption-key": "V67301yMJtk0jxOc3QJeBac6uKxO3XylWtkKTYmUU+M=", + "decrypted-dek": "+p6+whPVw9kOrYLZFMRBJ2n6Vli6T\/7TkjDouS+25s0=", + "exception": null, + "frames": [ + "\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAAOSZBKHHRpTwXOFTQVGapXXj5CwXBMouBB2ucaIJVm" + ], + "header": "AgR4TfvRMU2dVZJbgXIyxeNtbj5eIw8BiTDiwsHyQ\/Z9wXkAAAABAAxQcm92aWRlck5hbWUAGUtleUlkAAAAgAAAAAz45sc3cDvJZ7D4P3sAMKE7d\/w8ziQt2C0qHsy1Qu2E2q92eIGE\/kLnF\/Y003HKvTxx7xv2Zv83YuOdwHML5QIAABAAF88I9zPbUQSfOlzLXv+uIY2+m\/E6j2PMsbgeHVH\/L0wLqQlY+5CL0z3xnNOMIZae", + "encryption-context": {}, + "keyring-type": "static", + "message-id": "TfvRMU2dVZJbgXIyxeNtbj5eIw8BiTDiwsHyQ\/Z9wXk=", + "plaintext-frames": [ + "GoodCommitment" + ], + "status": true, + "comment": "1. Non-KMS key provider" + }, + { + "ciphertext": "AgR4b1\/73X5ErILpj0aSQIx6wNnHLEcNLxPzA0m6vYRr7kAAAAABAAxQcm92aWRlck5hbWUAGUtleUlkAAAAgAAAAAypJmXwyizUr3\/pyvIAMHLU\/i5GhZlGayeYC5w\/CjUobyGwN4QpeMB0XpNDGTM0f1Zx72V4uM2H5wMjy\/hm2wIAABAAAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh\/pQM2VSvliz2Qgi5JZf2ta\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAANS4Id4+dVHhPrvuJHEiOswo6YGSRjSGX3VDrt+0s=", + "commitment": "G7WvkcK+MF0AWhp8XhNcd8k5defmfi1dMqSgsN9v8e4=", + "content-encryption-key": "Q\/TITiE1CtPUr736a90u\/WjxXmKd\/M8bfb7Mo4TAXwA=", + "decrypted-dek": "8Bu+AFAu9ZT8BwYK+QAKXKQ2iaySSiQwlPUrKMf6fdo=", + "exception": "EXCEPTION: Invalid commitment", + "frames": [ + "\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAANS4Id4+dVHhPrvuJHEiOswo6YGSRjSGX3VDrt+0s=" + ], + "encryption-context": {}, + "header": "AgR4b1\/73X5ErILpj0aSQIx6wNnHLEcNLxPzA0m6vYRr7kAAAAABAAxQcm92aWRlck5hbWUAGUtleUlkAAAAgAAAAAypJmXwyizUr3\/pyvIAMHLU\/i5GhZlGayeYC5w\/CjUobyGwN4QpeMB0XpNDGTM0f1Zx72V4uM2H5wMjy\/hm2wIAABAAAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh\/pQM2VSvliz2Qgi5JZf2ta", + "keyring-type": "static", + "message-id": "b1\/73X5ErILpj0aSQIx6wNnHLEcNLxPzA0m6vYRr7kA=", + "status": false, + "comment": "2. Non-KMS key provider (Expected Failure)" + }, + { + "ciphertext": "AgV4vjf7DnZHP0MgQ4\/QHZH1Z\/1Lt24oyMR0DigenSpro9wAjgAEAAUwVGhpcwACaXMAAzFhbgAKZW5jcnlwdGlvbgAIMmNvbnRleHQAB2V4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQXRnM3JwOEVBNFFhNnBmaTk3MUlTNTk3NHpOMnlZWE5vSmtwRHFPc0dIYkVaVDRqME5OMlFkRStmbTFVY01WdThnPT0AAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMOTLXUpQGBjgD+EYIAgEQgDsqRrwjQTGW0pA78dc+2Y\/IqUrG7eAO4hZ07BNJEnd1d3+gUqW6Yunk8qyN9ryxdY8s4PshzJ7lyXIDuwIAABAABc0DWynVSZ1Fh1cLh1Aq\/mNPeyzD3yqpiKEBBUosdod2yzOfJTZ0H1mhwgJPJZSr\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAAJ+m45xgKSc5k+9oOlZEBdaNvusGVs1XyesABnMGUCMCoWR62YhnklwXEuj63nCz8qK8O4UOuR71bP3RiWfZHYQtkrrzV7ukj2Nseghpyt4gIxAKquEtCPigr+heUFSAMRDJ7YEbLgisSgUqkHQhfqOwG2YFNKySG\/CR0SNlfisJNovQ==", + "commitment": "Bc0DWynVSZ1Fh1cLh1Aq\/mNPeyzD3yqpiKEBBUosdoc=", + "content-encryption-key": "YqelE6F+cSyDvu7BTR8ZnPQzZ+7NumfwuwdOzaRb44g=", + "decrypted-dek": "FX5R4LJUJ1XkzcV5GGRS9MSdtc+2kzyvEsVFiETwdi4=", + "exception": null, + "footer": "AGcwZQIwKhZHrZiGeSXBcS6PrecLPyorw7hQ65HvVs\/dGJZ9kdhC2SuvNXu6SPY2x6CGnK3iAjEAqq4S0I+KCv6F5QVIAxEMntgRsuCKxKBSqQdCF+o7AbZgU0rJIb8JHRI2V+Kwk2i9", + "frames": [ + "\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAAJ+m45xgKSc5k+9oOlZEBdaNvusGVs1XyesA==" + ], + "header": "AgV4vjf7DnZHP0MgQ4\/QHZH1Z\/1Lt24oyMR0DigenSpro9wAjgAEAAUwVGhpcwACaXMAAzFhbgAKZW5jcnlwdGlvbgAIMmNvbnRleHQAB2V4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQXRnM3JwOEVBNFFhNnBmaTk3MUlTNTk3NHpOMnlZWE5vSmtwRHFPc0dIYkVaVDRqME5OMlFkRStmbTFVY01WdThnPT0AAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMOTLXUpQGBjgD+EYIAgEQgDsqRrwjQTGW0pA78dc+2Y\/IqUrG7eAO4hZ07BNJEnd1d3+gUqW6Yunk8qyN9ryxdY8s4PshzJ7lyXIDuwIAABAABc0DWynVSZ1Fh1cLh1Aq\/mNPeyzD3yqpiKEBBUosdod2yzOfJTZ0H1mhwgJPJZSr", + "encryption-context": { + "0This": "is", + "1an": "encryption", + "2context": "example", + "aws-crypto-public-key": "Atg3rp8EA4Qa6pfi971IS5974zN2yYXNoJkpDqOsGHbEZT4j0NN2QdE+fm1UcMVu8g==" + }, + "keyring-type": "aws-kms", + "message-id": "vjf7DnZHP0MgQ4\/QHZH1Z\/1Lt24oyMR0DigenSpro9w=", + "plaintext-frames": [ + "Plaintext" + ], + "status": true, + "comment": "3. KMS key provider (with ECDSA)" + }, + { + "ciphertext": "AgR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAw9EJqts2PkPA43eeMCARCAO9JPXvk6hofpX8P50mlDfAEwIiJc9sTS82KeLPBiZRnvmWcf2YSceNCoKTOB819M1auXncAYO8JJ\/VzPAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAw4v1P6lkHuuyIOZ7ECARCAO0iAkJa\/Ivo37+t5rryGAGMiIHuamdq21HBOULwcGmMzCT69PWNgm1l59xq+8AOinEEzohfm2jBueXA2AgAAEABDPYN\/Wct+m8YzTRVK\/4MRCcY3LZj4tayFiL\/376umUUTUenheMypVEflUomVblvr\/\/\/\/\/AAAAAQAAAAAAAAAAAAAAAQAAAAlCp+rAMHiuKyGr0KRmjDC6C7TXAvAwtFjR", + "commitment": "Qz2Df1nLfpvGM00VSv+DEQnGNy2Y+LWshYi\/9++rplE=", + "content-encryption-key": "qSmd3ox7r+cIeGmJhuguY3i5S\/LMKVUYJGgWR7rhE6M=", + "decrypted-dek": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "encryption-context": {}, + "exception": null, + "frames": [ + "\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAAJQqfqwDB4rishq9CkZowwugu01wLwMLRY0Q==" + ], + "header": "AgR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAw9EJqts2PkPA43eeMCARCAO9JPXvk6hofpX8P50mlDfAEwIiJc9sTS82KeLPBiZRnvmWcf2YSceNCoKTOB819M1auXncAYO8JJ\/VzPAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAw4v1P6lkHuuyIOZ7ECARCAO0iAkJa\/Ivo37+t5rryGAGMiIHuamdq21HBOULwcGmMzCT69PWNgm1l59xq+8AOinEEzohfm2jBueXA2AgAAEABDPYN\/Wct+m8YzTRVK\/4MRCcY3LZj4tayFiL\/376umUUTUenheMypVEflUomVblvo=", + "message-id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "4. Key = zero, Message ID = zero" + }, + { + "ciphertext": "AgV4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDOl5m0bj8TSUWO4GBwIBEIA7V0a+DvNMcbD7jfMcMuk0Rz8vB3oEp9wlIATpXzJmjqWefsFPJy5izbrFcR5CydFN2KS3h7E\/9AjlQiUAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDIbOPUE8vBwp3Z6CLQIBEIA7mvK9rkzLwhrM+A8KXqqfj6pktEbnUrfggiAYnpss2KuZhM\/vh\/ha1SE9mSXwd4SFGVYOG5Q9\/WevH1ICAAAQAEM9g39Zy36bxjNNFUr\/gxEJxjctmPi1rIWIv\/fvq6ZRrGZZkIZ1T4L6ZU5vqj\/DrP\/\/\/\/8AAAABAAAAAAAAAAAAAAABAAAACYppWXO1LeMi\/qxGk3haWIs2N4VSEWHPa7cAZzBlAjAnb0SKcZVySyKIYvYvJA0yDUuftkXNoi01Umw+9MpwGh\/y3cR3+TKU4DnNuljEkfACMQCNMCiS30oMIWNlhrWBQ852fhfhLvg8jLGIYLwFhEE9NrnyDYfj2H8Ej7+qK4C9OTY=", + "commitment": "Qz2Df1nLfpvGM00VSv+DEQnGNy2Y+LWshYi\/9++rplE=", + "content-encryption-key": "FMygob85VLR2Y0EWK6hq5K4OQI2fYoVt0qQp9VWrRAE=", + "decrypted-dek": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "encryption-context": { + "aws-crypto-public-key": "AhGCxFc6Os7i62uzi0GJy4xNbfcC9QTsQhdioOhLHJIAuqbZiOJhhB1\/AoyTL+1Ocg==" + }, + "exception": null, + "footer": "AGcwZQIwJ29EinGVcksiiGL2LyQNMg1Ln7ZFzaItNVJsPvTKcBof8t3Ed\/kylOA5zbpYxJHwAjEAjTAokt9KDCFjZYa1gUPOdn4X4S74PIyxiGC8BYRBPTa58g2H49h\/BI+\/qiuAvTk2", + "frames": [ + "\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAAJimlZc7Ut4yL+rEaTeFpYizY3hVIRYc9rtw==" + ], + "header": "AgV4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDOl5m0bj8TSUWO4GBwIBEIA7V0a+DvNMcbD7jfMcMuk0Rz8vB3oEp9wlIATpXzJmjqWefsFPJy5izbrFcR5CydFN2KS3h7E\/9AjlQiUAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDIbOPUE8vBwp3Z6CLQIBEIA7mvK9rkzLwhrM+A8KXqqfj6pktEbnUrfggiAYnpss2KuZhM\/vh\/ha1SE9mSXwd4SFGVYOG5Q9\/WevH1ICAAAQAEM9g39Zy36bxjNNFUr\/gxEJxjctmPi1rIWIv\/fvq6ZRrGZZkIZ1T4L6ZU5vqj\/DrA==", + "message-id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "5. Key = zero, Message ID = zero (signed)" + }, + { + "ciphertext": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyLV34wpxvMYsbEiU8CARCAO0bxzvbstOlsWM526OaxxrXGZcngJ\/76lY0BzOXIX9AXwtTsJo665uBaTIr4\/vRykIKYzaZHSAuXKsdgAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAzI7Ml\/HzSTtW5N\/8wCARCAO8cdJ+NTV5FmL2ct3yQSJDgoyBdZPBdm4jU9l4jcDt5lbYFd1zDxgPeNk31VXLPNsX0mTx0OaEPIK6KlAgAAEAACl+KPtzMY6hHYeXFawsClEaCgrwZP3NxMctmWVgd4gnqay0u\/SsaSuLWWsLJs7bH\/\/\/\/\/AAAAAf\/\/\/\/\/\/\/\/\/\/AAAAAQAAAAlL5waUrU\/1SiTVGftdt6I+oiP381iEHj9x", + "commitment": "Apfij7czGOoR2HlxWsLApRGgoK8GT9zcTHLZllYHeII=", + "content-encryption-key": "4zt1+EPrf\/1X9lyHGwI9TaX4KF6nMIZLK6BTRzsHkUc=", + "decrypted-dek": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "encryption-context": {}, + "exception": null, + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJS+cGlK1P9Uok1Rn7XbeiPqIj9\/NYhB4\/cQ==" + ], + "header": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyLV34wpxvMYsbEiU8CARCAO0bxzvbstOlsWM526OaxxrXGZcngJ\/76lY0BzOXIX9AXwtTsJo665uBaTIr4\/vRykIKYzaZHSAuXKsdgAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAzI7Ml\/HzSTtW5N\/8wCARCAO8cdJ+NTV5FmL2ct3yQSJDgoyBdZPBdm4jU9l4jcDt5lbYFd1zDxgPeNk31VXLPNsX0mTx0OaEPIK6KlAgAAEAACl+KPtzMY6hHYeXFawsClEaCgrwZP3NxMctmWVgd4gnqay0u\/SsaSuLWWsLJs7bE=", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "6. Key = zero, Message ID = example" + }, + { + "ciphertext": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDLIcLILCEW0b\/akcFQIBEIA7sN7bHvnMwOLqzk8ZQgRTZSyIRSbXV8XucXF6jh\/cB6q7KQHak72WGEowX06j+q1CmqIHQsHgLJJ7Y7cAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDEJci+3Rbh2YQr2wVgIBEIA78+\/l+kW07ZozOJ\/aA2eZ3KlNAy6rT6DC\/18vT+rT8kXgJAtvcLfYGL8QvVcZnxeLX4ebtzdzIWmUZhACAAAQAAKX4o+3MxjqEdh5cVrCwKURoKCvBk\/c3Exy2ZZWB3iCOgF1daFLUF+WmSaKQstsl\/\/\/\/\/8AAAAB\/\/\/\/\/\/\/\/\/\/8AAAABAAAACQY49UBR9fGrbSLGqwWF\/gAL17cwTR18A5MAZjBkAjAKrkLQ1xAPssfM1rfJibkZQb0260Mm2vRCetEgl3RDJx\/sBSxnRBZo53aRQHML6rwCMHmqQaG\/tBzeWp9N0xengvRNL7eHJFSLxbCCgOOHlUllPWa03oYrvRCUPQ9RfREeDg==", + "commitment": "Apfij7czGOoR2HlxWsLApRGgoK8GT9zcTHLZllYHeII=", + "content-encryption-key": "JsOW8DkFqoSmowhVoHjl5YhgMFWqtt8qluHB5vMtH7Y=", + "decrypted-dek": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "encryption-context": { + "aws-crypto-public-key": "AhGCxFc6Os7i62uzi0GJy4xNbfcC9QTsQhdioOhLHJIAuqbZiOJhhB1\/AoyTL+1Ocg==" + }, + "exception": null, + "footer": "AGYwZAIwCq5C0NcQD7LHzNa3yYm5GUG9NutDJtr0QnrRIJd0Qycf7AUsZ0QWaOd2kUBzC+q8AjB5qkGhv7Qc3lqfTdMXp4L0TS+3hyRUi8WwgoDjh5VJZT1mtN6GK70QlD0PUX0RHg4=", + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJBjj1QFH18attIsarBYX+AAvXtzBNHXwDkw==" + ], + "header": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDLIcLILCEW0b\/akcFQIBEIA7sN7bHvnMwOLqzk8ZQgRTZSyIRSbXV8XucXF6jh\/cB6q7KQHak72WGEowX06j+q1CmqIHQsHgLJJ7Y7cAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDEJci+3Rbh2YQr2wVgIBEIA78+\/l+kW07ZozOJ\/aA2eZ3KlNAy6rT6DC\/18vT+rT8kXgJAtvcLfYGL8QvVcZnxeLX4ebtzdzIWmUZhACAAAQAAKX4o+3MxjqEdh5cVrCwKURoKCvBk\/c3Exy2ZZWB3iCOgF1daFLUF+WmSaKQstslw==", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "7. Key = zero, Message ID = example (signed)" + }, + { + "ciphertext": "AgR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAzyYxT13KXwmUdiy88CARCAOzGOUQoGACVGrO4G0peHG71kP2zcDJpbdgZwUJBED49U3gpnQpBTWp2hp1N7Qti\/fxNTccVKGZzutdZoAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwP+Chc1R00x7BpDcsCARCAO3vvz3yc9wbc2BBLvX0Mdc4Z5gVDOCLOXuNiSNmCFqHAZqVgwQZPJb8xg+LQ0Li+luAffrro75j4bV3ZAgAAEABCgKFhvD9vTCe32kD42QLPj7aksASoP1T02N4az5lpkszyG+f3sYswBonWP9RwXEv\/\/\/\/\/AAAAAf\/\/\/\/\/\/\/\/\/\/AAAAAQAAAAl9Q+pOIP6ElqvCiPy7rOA36dQnyyOGg463", + "commitment": "QoChYbw\/b0wnt9pA+NkCz4+2pLAEqD9U9NjeGs+ZaZI=", + "content-encryption-key": "GQvu4IjcA\/2Yfpk1GYkuT\/7ZBOlzHYuwVvvrEfVOfXw=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": {}, + "exception": null, + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJfUPqTiD+hJarwoj8u6zgN+nUJ8sjhoOOtw==" + ], + "header": "AgR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAzyYxT13KXwmUdiy88CARCAOzGOUQoGACVGrO4G0peHG71kP2zcDJpbdgZwUJBED49U3gpnQpBTWp2hp1N7Qti\/fxNTccVKGZzutdZoAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwP+Chc1R00x7BpDcsCARCAO3vvz3yc9wbc2BBLvX0Mdc4Z5gVDOCLOXuNiSNmCFqHAZqVgwQZPJb8xg+LQ0Li+luAffrro75j4bV3ZAgAAEABCgKFhvD9vTCe32kD42QLPj7aksASoP1T02N4az5lpkszyG+f3sYswBonWP9RwXEs=", + "message-id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "8. Key = example, Message ID = zero" + }, + { + "ciphertext": "AgV4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHeEI3Z1nYS1NsWO3gIBEIA7kheJ0Nc6B3mnlQSehdOnpAQfk1DWf4Yio61pzLJJxdjAL\/mxnkczLPTUbbbQKPwyAozKoE324+Tbu0wAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDGY7wZesL6TorCErTwIBEIA7\/+0ch6ZtFmPqI8UkrwueqwRBJsFGNWcFqgL9jnyVGkw9Nb422X9wzAvmAZxffxbdmTNEzTaQPiOTpOMCAAAQAEKAoWG8P29MJ7faQPjZAs+PtqSwBKg\/VPTY3hrPmWmSO8OkA7vPXdTYugnXxz8umP\/\/\/\/8AAAAB\/\/\/\/\/\/\/\/\/\/8AAAABAAAACcv5HJwalZMTSUDIh9Z5MNr+qA7gnMqHxM0AZzBlAjEAin8CuSVzytkAqI+TiqPyaslB8bb1OFd2RY1xUuIeFCmYZSo+53ok5nyTquzxEGRLAjALrF\/ggOtvZ8qUNJCWaYOz9UGYll3YmU8de0x6NEwCj5XednEd8Jesw9mOZ5+qbSg=", + "commitment": "QoChYbw\/b0wnt9pA+NkCz4+2pLAEqD9U9NjeGs+ZaZI=", + "content-encryption-key": "61\/Wu0\/yvuQ2KHTjUpHpSIPSouZb\/AtU8jl2HtEmjIs=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": { + "aws-crypto-public-key": "AhGCxFc6Os7i62uzi0GJy4xNbfcC9QTsQhdioOhLHJIAuqbZiOJhhB1\/AoyTL+1Ocg==" + }, + "exception": null, + "footer": "AGcwZQIxAIp\/Arklc8rZAKiPk4qj8mrJQfG29ThXdkWNcVLiHhQpmGUqPud6JOZ8k6rs8RBkSwIwC6xf4IDrb2fKlDSQlmmDs\/VBmJZd2JlPHXtMejRMAo+V3nZxHfCXrMPZjmefqm0o", + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJy\/kcnBqVkxNJQMiH1nkw2v6oDuCcyofEzQ==" + ], + "header": "AgV4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHeEI3Z1nYS1NsWO3gIBEIA7kheJ0Nc6B3mnlQSehdOnpAQfk1DWf4Yio61pzLJJxdjAL\/mxnkczLPTUbbbQKPwyAozKoE324+Tbu0wAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDGY7wZesL6TorCErTwIBEIA7\/+0ch6ZtFmPqI8UkrwueqwRBJsFGNWcFqgL9jnyVGkw9Nb422X9wzAvmAZxffxbdmTNEzTaQPiOTpOMCAAAQAEKAoWG8P29MJ7faQPjZAs+PtqSwBKg\/VPTY3hrPmWmSO8OkA7vPXdTYugnXxz8umA==", + "message-id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "9. Key = example, Message ID = zero (signed)" + }, + { + "ciphertext": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAx3EP1N\/LumYE8aNewCARCAO8m7yeBMjLVEHoeMmbylI3QdPRoqp+mJDgcN5ykeh5OpAr7flh9VlZcik9OOPViXcGSKodlDLibhi1W1AAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAztBB+UBueMi1l2QyQCARCAOw8NELkDmYdYArDjxBiHF3nlbbMjhPN\/6tsCTrryk78nIe1kUj6dhOW4jv9UAK9v8II+kLeOwq1JsCr0AgAAEADxsVyYp96\/hpK+FPm+py4GHisVMco6nM7oDHr08PByitCSr8UpuX4JwQvWDz3Em\/b\/\/\/\/\/AAAAAf\/\/\/\/\/\/\/\/\/\/AAAAAQAAAAnIeIJlIPwbFrcG232KWGshMJ9+1gKublnM", + "commitment": "8bFcmKfev4aSvhT5vqcuBh4rFTHKOpzO6Ax69PDwcoo=", + "content-encryption-key": "o+avOr85YWbGFlh4G5kA5I8wBW4qre0d5\/+BsW\/uOis=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": {}, + "exception": null, + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJyHiCZSD8Gxa3Btt9ilhrITCfftYCrm5ZzA==" + ], + "header": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAx3EP1N\/LumYE8aNewCARCAO8m7yeBMjLVEHoeMmbylI3QdPRoqp+mJDgcN5ykeh5OpAr7flh9VlZcik9OOPViXcGSKodlDLibhi1W1AAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAztBB+UBueMi1l2QyQCARCAOw8NELkDmYdYArDjxBiHF3nlbbMjhPN\/6tsCTrryk78nIe1kUj6dhOW4jv9UAK9v8II+kLeOwq1JsCr0AgAAEADxsVyYp96\/hpK+FPm+py4GHisVMco6nM7oDHr08PByitCSr8UpuX4JwQvWDz3Em\/Y=", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "10. Key = example, Message ID = example" + }, + { + "ciphertext": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDFJv9+79usIu0JHDLwIBEIA7SELzODxUMVbIbIzq4Bxlq5VgO5IByEOFWGi+Q+NxyubE2cwXwVLptW6y\/jiLn6CGrDaBzxuthwHgxmEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDFGVND+QpXSW67k+5gIBEIA7Lm792H0cZeQGH0D1MXjYnkOdjSMRSCSjU9nmMwEuOdr16kYAXBul9dY4KpWyRNTfrWJxfoEZh4uldlcCAAAQAPGxXJin3r+Gkr4U+b6nLgYeKxUxyjqczugMevTw8HKKxiu8Qpy4U65J+9ZSXS4lv\/\/\/\/\/8AAAAB\/\/\/\/\/\/\/\/\/\/8AAAABAAAACYT3EZfkxPxdFqk\/tnQn8jJN2OYvIcbqw7cAaDBmAjEAhszsRN2RAPaEgspAJwZYi0LcrM+8glcTL3HwNlzUHEkd75YGVKb\/UNAElxXU6IKCAjEAmiw4LPFwAJ6ex2VwIIo++injUUHa1BfiF2HMpqnB5jruGCk3KxS64h0NvdPco6nW", + "commitment": "8bFcmKfev4aSvhT5vqcuBh4rFTHKOpzO6Ax69PDwcoo=", + "content-encryption-key": "o2yaJSa81QOYkfWaMhtLntiLLyB3Zfn6b+VifPwBEJ8=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": { + "aws-crypto-public-key": "AhGCxFc6Os7i62uzi0GJy4xNbfcC9QTsQhdioOhLHJIAuqbZiOJhhB1\/AoyTL+1Ocg==" + }, + "exception": null, + "footer": "AGgwZgIxAIbM7ETdkQD2hILKQCcGWItC3KzPvIJXEy9x8DZc1BxJHe+WBlSm\/1DQBJcV1OiCggIxAJosOCzxcACensdlcCCKPvop41FB2tQX4hdhzKapweY67hgpNysUuuIdDb3T3KOp1g==", + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJhPcRl+TE\/F0WqT+2dCfyMk3Y5i8hxurDtw==" + ], + "header": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDFJv9+79usIu0JHDLwIBEIA7SELzODxUMVbIbIzq4Bxlq5VgO5IByEOFWGi+Q+NxyubE2cwXwVLptW6y\/jiLn6CGrDaBzxuthwHgxmEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDFGVND+QpXSW67k+5gIBEIA7Lm792H0cZeQGH0D1MXjYnkOdjSMRSCSjU9nmMwEuOdr16kYAXBul9dY4KpWyRNTfrWJxfoEZh4uldlcCAAAQAPGxXJin3r+Gkr4U+b6nLgYeKxUxyjqczugMevTw8HKKxiu8Qpy4U65J+9ZSXS4lvw==", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "11. Key = example, Message ID = example (signed)" + }, + { + "ciphertext": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAzWRW49EX50QiQO8gsCARCAO5sgMFpr76NxknbZ8CCeup3xNPeF2Mm7Fm0l17+Le0DdI8MBujB9lyGmQWMWIXq5URWbHKLN7sqiM2yiAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyXAus4K5pnm0NpcJ8CARCAO0HKCnxolKBLsbqRPh\/WaXxQi1VkJoz\/oOVfL4+IFQymTsgKMGgHtFG77hngnoSJQyFPo6b\/sMuN4KVKAgAAEAC0UBWiNYSJJvXRl\/IXIBh0uo\/DOGcPO1rP+V\/sOGmM+bZERA+G8H4wcefWYWZ8dv7\/\/\/\/\/AAAAAf\/\/\/\/\/\/\/\/\/\/AAAAAQAAAAkgsJoIIYNmoGTtuNrrNcRdC3nxmJaY+Bhu", + "commitment": "tFAVojWEiSb10ZfyFyAYdLqPwzhnDztaz\/lf7DhpjPk=", + "content-encryption-key": "iFGOJpIlmjhVGVThuhE5JZtme0m470naJ3PwCG6oIs0=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": {}, + "exception": "EXCEPTION: Invalid commitment", + "frames": [], + "header": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAAAACAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAgB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAzWRW49EX50QiQO8gsCARCAO5sgMFpr76NxknbZ8CCeup3xNPeF2Mm7Fm0l17+Le0DdI8MBujB9lyGmQWMWIXq5URWbHKLN7sqiM2yiAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS81OTBmZDc4MS1kZGRlLTQwMzYtYWJlYy0zZTFhYjVhNWQyYWQApwEBAgB4IDgBgT3DGKHrXsN2bi23PO+MOMGydcgwgWav8w1SQk0AAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyXAus4K5pnm0NpcJ8CARCAO0HKCnxolKBLsbqRPh\/WaXxQi1VkJoz\/oOVfL4+IFQymTsgKMGgHtFG77hngnoSJQyFPo6b\/sMuN4KVKAgAAEAC0UBWiNYSJJvXRl\/IXIBh0uo\/DOGcPO1rP+V\/sOGmM+bZERA+G8H4wcefWYWZ8dv4=", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": false, + "keyring-type": "aws-kms", + "comment": "12. Two different plaintext data keys, same ciphertext" + }, + { + "ciphertext": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDBJwQx7rLsF9SMURIgIBEIA76C0ub3htb4Bo0ZgIAoYSRzahiRunNMjvEfZ4oAUq0v6q7BQeeZXFuH0DycxuIwJuaftxZDUR6GEPfA8AB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDFCRxguNQerLwoT9TQIBEIA7a9HTYxjgD8GssZNegRz3dwDmNp4NGohmVxI3wwwL1ZxJzSIkwsuwKobQbbNWH149c0fhZyHJX5dk3OoCAAAQALRQFaI1hIkm9dGX8hcgGHS6j8M4Zw87Ws\/5X+w4aYz5UtBXqCzIpb8Cd4\/WZwbHh\/\/\/\/\/8AAAAB\/\/\/\/\/\/\/\/\/\/8AAAABAAAACYedXbtB+YnSiC8XC2WPDytoXd+hEH9zWv8AaDBmAjEAuhsI42YXIDtHJV9QNXWxh1QefwdH8yjcz1ewdCJKHrLFpmvCy5vErQduqGRXSotVAjEAvQNjxDDpDGRjictnjev+3slPy927Jr0SXs7xa\/AslIsZHJNI\/WQrPc7KVq6DzKKT", + "commitment": "tFAVojWEiSb10ZfyFyAYdLqPwzhnDztaz\/lf7DhpjPk=", + "content-encryption-key": "Mz6CgFku++S+d3kVoozSOiJXpLqaz5m8ClDYGchHsZY=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": { + "aws-crypto-public-key": "AhGCxFc6Os7i62uzi0GJy4xNbfcC9QTsQhdioOhLHJIAuqbZiOJhhB1\/AoyTL+1Ocg==" + }, + "exception": "EXCEPTION: Invalid commitment", + "footer": "AGgwZgIxALobCONmFyA7RyVfUDV1sYdUHn8HR\/Mo3M9XsHQiSh6yxaZrwsubxK0HbqhkV0qLVQIxAL0DY8Qw6QxkY4nLZ43r\/t7JT8vduya9El7O8WvwLJSLGRyTSP1kKz3Oylaug8yikw==", + "frames": [], + "header": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFoR0N4RmM2T3M3aTYydXppMEdKeTR4TmJmY0M5UVRzUWhkaW9PaExISklBdXFiWmlPSmhoQjEvQW95VEwrMU9jZz09AAIAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQECAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDBJwQx7rLsF9SMURIgIBEIA76C0ub3htb4Bo0ZgIAoYSRzahiRunNMjvEfZ4oAUq0v6q7BQeeZXFuH0DycxuIwJuaftxZDUR6GEPfA8AB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5LzU5MGZkNzgxLWRkZGUtNDAzNi1hYmVjLTNlMWFiNWE1ZDJhZACnAQECAHggOAGBPcMYoetew3ZuLbc874w4wbJ1yDCBZq\/zDVJCTQAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDFCRxguNQerLwoT9TQIBEIA7a9HTYxjgD8GssZNegRz3dwDmNp4NGohmVxI3wwwL1ZxJzSIkwsuwKobQbbNWH149c0fhZyHJX5dk3OoCAAAQALRQFaI1hIkm9dGX8hcgGHS6j8M4Zw87Ws\/5X+w4aYz5UtBXqCzIpb8Cd4\/WZwbHhw==", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": false, + "keyring-type": "aws-kms", + "comment": "13. Two different plaintext data keys, same ciphertext (signed)" + }, + { + "ciphertext": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMVS2kQTl1wrYLE2eLAgEQgDulTL6UW+E6FTj+tivbEgzVQCko4XyfLCHO9p6+XhhzZ4ASQdB+InX3zlUO0nzvo6ncpznnFwucVziULgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM3CmTGX0yeaiG9NrQAgEQgDsnrSa\/wp3e\/eyjabdqfNOdRCgPRfrJg+bSSzs6Y8WogxrrXuCdv\/Gxd\/tpoGgrfckTXXAvDyzh2snYXAIAABAAApfij7czGOoR2HlxWsLApRGgoK8GT9zcTHLZllYHeIL5z\/RijnIgTxn9phSilA70\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJS+cGlK1P9Uok1Rn7XbeiPqIj9\/NYhB4\/cQ==", + "commitment": "Apfij7czGOoR2HlxWsLApRGgoK8GT9zcTHLZllYHeII=", + "content-encryption-key": "4zt1+EPrf\/1X9lyHGwI9TaX4KF6nMIZLK6BTRzsHkUc=", + "decrypted-dek": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example" + }, + "exception": null, + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJS+cGlK1P9Uok1Rn7XbeiPqIj9\/NYhB4\/cQ==" + ], + "header": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMVS2kQTl1wrYLE2eLAgEQgDulTL6UW+E6FTj+tivbEgzVQCko4XyfLCHO9p6+XhhzZ4ASQdB+InX3zlUO0nzvo6ncpznnFwucVziULgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM3CmTGX0yeaiG9NrQAgEQgDsnrSa\/wp3e\/eyjabdqfNOdRCgPRfrJg+bSSzs6Y8WogxrrXuCdv\/Gxd\/tpoGgrfckTXXAvDyzh2snYXAIAABAAApfij7czGOoR2HlxWsLApRGgoK8GT9zcTHLZllYHeIL5z\/RijnIgTxn9phSilA70", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "14. Key = zero, Message ID = example (with AAD)" + }, + { + "ciphertext": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAlwADAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWhHQ3hGYzZPczdpNjJ1emkwR0p5NHhOYmZjQzlRVHNRaGRpb09oTEhKSUF1cWJaaU9KaGhCMS9Bb3lUTCsxT2NnPT0AAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMlgzxMfOVccgo\/NfWAgEQgDuBa8xMNPel0q7fr4r9y9cKoeaaxqo5vLVr\/KNnDbzr13J3Edl70FJhu9iuS3E9Ed81jwt8FeIntzPfuQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMiz3Umk1\/gWN+lSq5AgEQgDvGK8\/b7k6VRkOHOwisVDZilScjgbNNHNWnPJjo7NKm2\/8t\/\/\/KTjL\/QJ\/zD5cLsEInvsyltBX9jEd83gIAABAAApfij7czGOoR2HlxWsLApRGgoK8GT9zcTHLZllYHeIIg5X2rC+bMh\/YSXh8AcrNA\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJBjj1QFH18attIsarBYX+AAvXtzBNHXwDkwBnMGUCMQC3jREI99riv0SYM2G3dYMvA26KOHM\/f7lhd6VQdM0MX+fHo\/LfTEanr2AW9UlustkCMCpX\/x8S84qJeTQbnTS0OCEvSjRCWluK4xqnSTc2PvZiOTALHUVBTkvRxBRnaUPa\/g==", + "commitment": "Apfij7czGOoR2HlxWsLApRGgoK8GT9zcTHLZllYHeII=", + "content-encryption-key": "JsOW8DkFqoSmowhVoHjl5YhgMFWqtt8qluHB5vMtH7Y=", + "decrypted-dek": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example", + "aws-crypto-public-key": "AhGCxFc6Os7i62uzi0GJy4xNbfcC9QTsQhdioOhLHJIAuqbZiOJhhB1\/AoyTL+1Ocg==" + }, + "exception": null, + "footer": "AGcwZQIxALeNEQj32uK\/RJgzYbd1gy8Dboo4cz9\/uWF3pVB0zQxf58ej8t9MRqevYBb1SW6y2QIwKlf\/HxLziol5NBudNLQ4IS9KNEJaW4rjGqdJNzY+9mI5MAsdRUFOS9HEFGdpQ9r+", + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJBjj1QFH18attIsarBYX+AAvXtzBNHXwDkw==" + ], + "header": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAlwADAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWhHQ3hGYzZPczdpNjJ1emkwR0p5NHhOYmZjQzlRVHNRaGRpb09oTEhKSUF1cWJaaU9KaGhCMS9Bb3lUTCsxT2NnPT0AAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMlgzxMfOVccgo\/NfWAgEQgDuBa8xMNPel0q7fr4r9y9cKoeaaxqo5vLVr\/KNnDbzr13J3Edl70FJhu9iuS3E9Ed81jwt8FeIntzPfuQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMiz3Umk1\/gWN+lSq5AgEQgDvGK8\/b7k6VRkOHOwisVDZilScjgbNNHNWnPJjo7NKm2\/8t\/\/\/KTjL\/QJ\/zD5cLsEInvsyltBX9jEd83gIAABAAApfij7czGOoR2HlxWsLApRGgoK8GT9zcTHLZllYHeIIg5X2rC+bMh\/YSXh8AcrNA", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "15. Key = zero, Message ID = example (signed, with AAD)" + }, + { + "ciphertext": "AgR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMFEBnKyt3QstLVqt+AgEQgDvjFgXze5zC18mw1EL22Sk1L9s2x\/d\/yyKUFVcqcxsIN0YBh9nOUkMji\/KbaroJticmBBH5iVuC58W7CAAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMpstDQzF757dbNzujAgEQgDtMFvMf2MmJumFtDnpVae1UIZqEhrFGIgtRDd\/BVPeA3KZA+HzImTd0bNiOnL6flxyITvnjMkXAstQa3wIAABAAQoChYbw\/b0wnt9pA+NkCz4+2pLAEqD9U9NjeGs+ZaZJzgj4W\/ZtkD2K6nrgp64FH\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJfUPqTiD+hJarwoj8u6zgN+nUJ8sjhoOOtw==", + "commitment": "QoChYbw\/b0wnt9pA+NkCz4+2pLAEqD9U9NjeGs+ZaZI=", + "content-encryption-key": "GQvu4IjcA\/2Yfpk1GYkuT\/7ZBOlzHYuwVvvrEfVOfXw=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example" + }, + "exception": null, + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJfUPqTiD+hJarwoj8u6zgN+nUJ8sjhoOOtw==" + ], + "header": "AgR4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMFEBnKyt3QstLVqt+AgEQgDvjFgXze5zC18mw1EL22Sk1L9s2x\/d\/yyKUFVcqcxsIN0YBh9nOUkMji\/KbaroJticmBBH5iVuC58W7CAAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMpstDQzF757dbNzujAgEQgDtMFvMf2MmJumFtDnpVae1UIZqEhrFGIgtRDd\/BVPeA3KZA+HzImTd0bNiOnL6flxyITvnjMkXAstQa3wIAABAAQoChYbw\/b0wnt9pA+NkCz4+2pLAEqD9U9NjeGs+ZaZJzgj4W\/ZtkD2K6nrgp64FH", + "message-id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "16. Key = example, Message ID = zero (with AAD)" + }, + { + "ciphertext": "AgV4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlwADAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWhHQ3hGYzZPczdpNjJ1emkwR0p5NHhOYmZjQzlRVHNRaGRpb09oTEhKSUF1cWJaaU9KaGhCMS9Bb3lUTCsxT2NnPT0AAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM5vYU7k2tK4Y4ChgDAgEQgDu7X3F084Gf5T+8\/cP+Qge\/+xj8lZN95hogWxYwC\/HA649wqOHc2dvQeP0rc7OJIUj8QwmCcITyAWvRXgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMbxIh5bCSDVpF64zaAgEQgDsKuqZd6LSW4WtYmeQcydeqQbxnYXzhDlSla6QNcknXuOaACDsonsrh6+0tk7Z1OOA0Jxbrcx8oojE0WgIAABAAQoChYbw\/b0wnt9pA+NkCz4+2pLAEqD9U9NjeGs+ZaZKuxbySIS3cRk6BGotnokRl\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJy\/kcnBqVkxNJQMiH1nkw2v6oDuCcyofEzQBnMGUCMQCmRHBH53c9klyofyrrze8i\/Al0AW4K2\/3lJF1lc7yV43y2FI1jOByqzsEvu4NjYTgCMDUiSCmLWNOZUdLhGzA7+6q3al2b0eDfV\/zpsIKZrQPZccRftNTbxR\/m1Wo7udndPg==", + "commitment": "QoChYbw\/b0wnt9pA+NkCz4+2pLAEqD9U9NjeGs+ZaZI=", + "content-encryption-key": "61\/Wu0\/yvuQ2KHTjUpHpSIPSouZb\/AtU8jl2HtEmjIs=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example", + "aws-crypto-public-key": "AhGCxFc6Os7i62uzi0GJy4xNbfcC9QTsQhdioOhLHJIAuqbZiOJhhB1\/AoyTL+1Ocg==" + }, + "exception": null, + "footer": "AGcwZQIxAKZEcEfndz2SXKh\/KuvN7yL8CXQBbgrb\/eUkXWVzvJXjfLYUjWM4HKrOwS+7g2NhOAIwNSJIKYtY05lR0uEbMDv7qrdqXZvR4N9X\/OmwgpmtA9lxxF+01NvFH+bVaju52d0+", + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJy\/kcnBqVkxNJQMiH1nkw2v6oDuCcyofEzQ==" + ], + "header": "AgV4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlwADAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWhHQ3hGYzZPczdpNjJ1emkwR0p5NHhOYmZjQzlRVHNRaGRpb09oTEhKSUF1cWJaaU9KaGhCMS9Bb3lUTCsxT2NnPT0AAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM5vYU7k2tK4Y4ChgDAgEQgDu7X3F084Gf5T+8\/cP+Qge\/+xj8lZN95hogWxYwC\/HA649wqOHc2dvQeP0rc7OJIUj8QwmCcITyAWvRXgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMbxIh5bCSDVpF64zaAgEQgDsKuqZd6LSW4WtYmeQcydeqQbxnYXzhDlSla6QNcknXuOaACDsonsrh6+0tk7Z1OOA0Jxbrcx8oojE0WgIAABAAQoChYbw\/b0wnt9pA+NkCz4+2pLAEqD9U9NjeGs+ZaZKuxbySIS3cRk6BGotnokRl", + "message-id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "17. Key = example, Message ID = zero (signed, with AAD)" + }, + { + "ciphertext": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMA0otLRQxvR8Ud+pKAgEQgDvVR2YZbiRGnzk9VHphC2z0gf\/3fnC856VJsjDHyXfeveuOAOg8lHBR2yqcbV6kFafqsLGuhoNM7kVkhAAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM+7tKI00Bt\/e3ZvEiAgEQgDtVAzyv+65kZInUtQjH5uEkHKcMGXPDWMGjaGo5u8AEVGkwM+Sph6+lykd21OT67IqUt6g25v8O0+PBSwIAABAA8bFcmKfev4aSvhT5vqcuBh4rFTHKOpzO6Ax69PDwcopiz5Sh5k0vkhhnD960r\/31\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJyHiCZSD8Gxa3Btt9ilhrITCfftYCrm5ZzA==", + "commitment": "8bFcmKfev4aSvhT5vqcuBh4rFTHKOpzO6Ax69PDwcoo=", + "content-encryption-key": "o+avOr85YWbGFlh4G5kA5I8wBW4qre0d5\/+BsW\/uOis=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example" + }, + "exception": null, + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJyHiCZSD8Gxa3Btt9ilhrITCfftYCrm5ZzA==" + ], + "header": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMA0otLRQxvR8Ud+pKAgEQgDvVR2YZbiRGnzk9VHphC2z0gf\/3fnC856VJsjDHyXfeveuOAOg8lHBR2yqcbV6kFafqsLGuhoNM7kVkhAAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM+7tKI00Bt\/e3ZvEiAgEQgDtVAzyv+65kZInUtQjH5uEkHKcMGXPDWMGjaGo5u8AEVGkwM+Sph6+lykd21OT67IqUt6g25v8O0+PBSwIAABAA8bFcmKfev4aSvhT5vqcuBh4rFTHKOpzO6Ax69PDwcopiz5Sh5k0vkhhnD960r\/31", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "18. Key = example, Message ID = example (with AAD)" + }, + { + "ciphertext": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAlwADAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWhHQ3hGYzZPczdpNjJ1emkwR0p5NHhOYmZjQzlRVHNRaGRpb09oTEhKSUF1cWJaaU9KaGhCMS9Bb3lUTCsxT2NnPT0AAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMxOjP1UAeC+vE5J1fAgEQgDvgHwPc3KpNStTjhawDEa7Z5UDCnKwSH5KaTYT0Qbnu2o3RVgjLQxsa5FjdBUzi3lusy2g4HRMeGgk5QQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMKTHgS3LLlQH3xP7EAgEQgDu+iRlWxVymazFlhKAAaNkQhpZzxyljqYBgctCjsmVwSfic4+VH5gOLsLyNUC0JwqNHTH5+hcphGVgXTQIAABAA8bFcmKfev4aSvhT5vqcuBh4rFTHKOpzO6Ax69PDwcormGII0al4n1z8nUbSVXezJ\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJhPcRl+TE\/F0WqT+2dCfyMk3Y5i8hxurDtwBoMGYCMQCES2bdqjxadCcKb\/NgzQ+KxCXix0VBh0mJwKyyUXvwjUFoGJkecdswSXhPiYO7EocCMQDWPwhemHv5ObNVjv9iEmTF5wghBIi3aYeY4N3QQRcPtkuCdcaqKRR3u8VzZsFR9eg=", + "commitment": "8bFcmKfev4aSvhT5vqcuBh4rFTHKOpzO6Ax69PDwcoo=", + "content-encryption-key": "o2yaJSa81QOYkfWaMhtLntiLLyB3Zfn6b+VifPwBEJ8=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example", + "aws-crypto-public-key": "AhGCxFc6Os7i62uzi0GJy4xNbfcC9QTsQhdioOhLHJIAuqbZiOJhhB1\/AoyTL+1Ocg==" + }, + "exception": null, + "footer": "AGgwZgIxAIRLZt2qPFp0Jwpv82DND4rEJeLHRUGHSYnArLJRe\/CNQWgYmR5x2zBJeE+Jg7sShwIxANY\/CF6Ye\/k5s1WO\/2ISZMXnCCEEiLdph5jg3dBBFw+2S4J1xqopFHe7xXNmwVH16A==", + "frames": [ + "\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJhPcRl+TE\/F0WqT+2dCfyMk3Y5i8hxurDtw==" + ], + "header": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAlwADAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWhHQ3hGYzZPczdpNjJ1emkwR0p5NHhOYmZjQzlRVHNRaGRpb09oTEhKSUF1cWJaaU9KaGhCMS9Bb3lUTCsxT2NnPT0AAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMxOjP1UAeC+vE5J1fAgEQgDvgHwPc3KpNStTjhawDEa7Z5UDCnKwSH5KaTYT0Qbnu2o3RVgjLQxsa5FjdBUzi3lusy2g4HRMeGgk5QQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMKTHgS3LLlQH3xP7EAgEQgDu+iRlWxVymazFlhKAAaNkQhpZzxyljqYBgctCjsmVwSfic4+VH5gOLsLyNUC0JwqNHTH5+hcphGVgXTQIAABAA8bFcmKfev4aSvhT5vqcuBh4rFTHKOpzO6Ax69PDwcormGII0al4n1z8nUbSVXezJ", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "19. Key = example, Message ID = example (signed, with AAD)" + }, + { + "ciphertext": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMa0HbVm3pJUfxLRYYAgEQgDuR\/OmD0OFsgzBNOppbGC20b+e4iMYVRb2\/MocrN8fFc+\/lC6ERZzLFh90CO4QEcKKfelssXufLxx7qLAAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM4PTMwlCPPqF2SFfOAgEQgDtHXTkMqX6j3VPqV9RxZjlPEGGB3twqK2eX8g2kAKYIObPvJNZvsDHR0ge8k0U9eQ7WDBwCwyaNsDpCiwIAABAAtFAVojWEiSb10ZfyFyAYdLqPwzhnDztaz\/lf7DhpjPlwqyAp6svYC2BmtqRuFAlr\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJILCaCCGDZqBk7bja6zXEXQt58ZiWmPgYbg==", + "commitment": "tFAVojWEiSb10ZfyFyAYdLqPwzhnDztaz\/lf7DhpjPk=", + "content-encryption-key": "iFGOJpIlmjhVGVThuhE5JZtme0m470naJ3PwCG6oIs0=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example" + }, + "exception": "EXCEPTION: Invalid commitment", + "frames": [], + "header": "AgR4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMa0HbVm3pJUfxLRYYAgEQgDuR\/OmD0OFsgzBNOppbGC20b+e4iMYVRb2\/MocrN8fFc+\/lC6ERZzLFh90CO4QEcKKfelssXufLxx7qLAAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM4PTMwlCPPqF2SFfOAgEQgDtHXTkMqX6j3VPqV9RxZjlPEGGB3twqK2eX8g2kAKYIObPvJNZvsDHR0ge8k0U9eQ7WDBwCwyaNsDpCiwIAABAAtFAVojWEiSb10ZfyFyAYdLqPwzhnDztaz\/lf7DhpjPlwqyAp6svYC2BmtqRuFAlr", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": false, + "keyring-type": "aws-kms", + "comment": "20. Two different plaintext data keys, same ciphertext (with AAD)" + }, + { + "ciphertext": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAlwADAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWhHQ3hGYzZPczdpNjJ1emkwR0p5NHhOYmZjQzlRVHNRaGRpb09oTEhKSUF1cWJaaU9KaGhCMS9Bb3lUTCsxT2NnPT0AAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMrp6QFLdmNOISqjdzAgEQgDuCXiJsMNKTfNWmYDoMnJcI+oRQBeIl0d1pZBu5pBxGgS6chIfLVbcmweuUZDk0TCJLah7PVv3JfTSpLQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM1QFgfyGcwGCV+dGjAgEQgDvI+3I0\/U4wng4yWrV4RYtozOmW+lgipeTBRm3+6icDcD0A\/8gzF6t4LjzgNm812nbcazbYazNAvd0xuwIAABAAtFAVojWEiSb10ZfyFyAYdLqPwzhnDztaz\/lf7DhpjPlwgv4XNIzljFNfv4FZni21\/\/\/\/\/wAAAAH\/\/\/\/\/\/\/\/\/\/wAAAAEAAAAJh51du0H5idKILxcLZY8PK2hd36EQf3Na\/wBnMGUCMQCRoSvXwlzNpXaMoH3xaSwRKxekj1t8GpfiULRl\/KEjC6gRIWYcxV2zmMy1DCqwC7sCMHVZkw\/zs6sbyWcMPz1Rsl6kM2lSm8BWls9ZIqw7yF3I4fob1sdjxu0iIRwYrtSlSg==", + "commitment": "tFAVojWEiSb10ZfyFyAYdLqPwzhnDztaz\/lf7DhpjPk=", + "content-encryption-key": "Mz6CgFku++S+d3kVoozSOiJXpLqaz5m8ClDYGchHsZY=", + "decrypted-dek": "Sfdon2EodFWiGY6ITvIDJZXhzKZPj2IQCi+1x\/tw2ho=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example", + "aws-crypto-public-key": "AhGCxFc6Os7i62uzi0GJy4xNbfcC9QTsQhdioOhLHJIAuqbZiOJhhB1\/AoyTL+1Ocg==" + }, + "exception": "EXCEPTION: Invalid commitment", + "footer": "AGcwZQIxAJGhK9fCXM2ldoygffFpLBErF6SPW3wal+JQtGX8oSMLqBEhZhzFXbOYzLUMKrALuwIwdVmTD\/OzqxvJZww\/PVGyXqQzaVKbwFaWz1kirDvIXcjh+hvWx2PG7SIhHBiu1KVK", + "frames": [], + "header": "AgV4PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVsAlwADAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWhHQ3hGYzZPczdpNjJ1emkwR0p5NHhOYmZjQzlRVHNRaGRpb09oTEhKSUF1cWJaaU9KaGhCMS9Bb3lUTCsxT2NnPT0AAgAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQIAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMrp6QFLdmNOISqjdzAgEQgDuCXiJsMNKTfNWmYDoMnJcI+oRQBeIl0d1pZBu5pBxGgS6chIfLVbcmweuUZDk0TCJLah7PVv3JfTSpLQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvNTkwZmQ3ODEtZGRkZS00MDM2LWFiZWMtM2UxYWI1YTVkMmFkAKcBAQIAeCA4AYE9wxih617Ddm4ttzzvjDjBsnXIMIFmr\/MNUkJNAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM1QFgfyGcwGCV+dGjAgEQgDvI+3I0\/U4wng4yWrV4RYtozOmW+lgipeTBRm3+6icDcD0A\/8gzF6t4LjzgNm812nbcazbYazNAvd0xuwIAABAAtFAVojWEiSb10ZfyFyAYdLqPwzhnDztaz\/lf7DhpjPlwgv4XNIzljFNfv4FZni21", + "message-id": "PKWpyXOBmk4yDyakq0VRlXuStoPoaQ0n0tO1i\/9LdVs=", + "plaintext-frames": [ + "testing12" + ], + "status": false, + "keyring-type": "aws-kms", + "comment": "21. Two different plaintext data keys, same ciphertext (signed, with AAD)" + }, + { + "ciphertext": "AgR4ZzjLWV5kAQKVlXj57IcJa1iEqTYVzYLoqG8PRUdtGnEAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAzm\/W4IEwyw1XGMDx8CARCAOzWxBU81iv0uAE17I6C3HxgVlMZm2br9GAktcyJ5IgZKA6N8MLzLAmbDoMb1HxJEKkb8F49QTArCUhX7AgAAEADreoUk9jDTH\/FR\/nDtTKFjBD6r2ipZhT5LsJtgx2rbJGBYOeO5FCZSGlsZTIsoNDL\/\/\/\/\/AAAAAQAAAAAAAAAAAAAAAQAAAAm5gmDN\/oEFl97JI39GIyXlS3CudSGOWm8p", + "commitment": "63qFJPYw0x\/xUf5w7UyhYwQ+q9oqWYU+S7CbYMdq2yQ=", + "decrypted-dek": "4aY8MK4AdgTXDgz7DXIUfOZ81MR7\/v8Vh1qS4hha4U4=", + "encryption-context": {}, + "exception": null, + "header": "AgR4ZzjLWV5kAQKVlXj57IcJa1iEqTYVzYLoqG8PRUdtGnEAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAzm\/W4IEwyw1XGMDx8CARCAOzWxBU81iv0uAE17I6C3HxgVlMZm2br9GAktcyJ5IgZKA6N8MLzLAmbDoMb1HxJEKkb8F49QTArCUhX7AgAAEADreoUk9jDTH\/FR\/nDtTKFjBD6r2ipZhT5LsJtgx2rbJGBYOeO5FCZSGlsZTIsoNDI=", + "message-id": "ZzjLWV5kAQKVlXj57IcJa1iEqTYVzYLoqG8PRUdtGnE=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "22. Simple JS encrypt" + }, + { + "ciphertext": "AgR4BiIWGA4lhe6nW3EBq9ri5hyuIcvnhaWt6s6yP70JnwQAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM4qQYbJbU6HvDqgEBAgEQgDsSqi\/xWCa5VP0Ax1s+G6AIZ3GkNE2kR2WzLYXpA9HCZ4pny25nD5vguvGtbdKGR4TlpTPkxvUTa+LHNQIAABAAbzal1s5Ht+XmPGs+lzwMTU3VsVKI1h73jcUgz4G30oAmkkGJNdjmZFflISSBirPH\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAAJ5LRWdzEYzFurmIpJRc9PzENBizW3+v7\/Qg==", + "commitment": "bzal1s5Ht+XmPGs+lzwMTU3VsVKI1h73jcUgz4G30oA=", + "decrypted-dek": "\/tCswbBkCYGs0DgkPDm775OnlQXO6N8zWLMTuHvPrqg=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example" + }, + "exception": null, + "header": "AgR4BiIWGA4lhe6nW3EBq9ri5hyuIcvnhaWt6s6yP70JnwQAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM4qQYbJbU6HvDqgEBAgEQgDsSqi\/xWCa5VP0Ax1s+G6AIZ3GkNE2kR2WzLYXpA9HCZ4pny25nD5vguvGtbdKGR4TlpTPkxvUTa+LHNQIAABAAbzal1s5Ht+XmPGs+lzwMTU3VsVKI1h73jcUgz4G30oAmkkGJNdjmZFflISSBirPH", + "message-id": "BiIWGA4lhe6nW3EBq9ri5hyuIcvnhaWt6s6yP70JnwQ=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "23. Simple JS encrypt (with AAD)" + }, + { + "ciphertext": "AgR4iCL3obBFAgl2H4KE8R96eNgBjLqFzwofaagV\/SF1UJgAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMFUkHCQBWWWqXIwgfAgEQgDt+DtsGGoRXX\/c2o6u8WUX7EPdRCBuYBjKTEexmgld+jqfu9ogp+XVs\/lEPCnThJE7lZ26ufBNtuZpNZwIAAAABW3KhrYH9MFe3n+Js+v5arMzHmH58k\/3TjhBfn28QENxyh41IeBjszPCSS9P1WjiFAAAAAQAAAAAAAAAAAAAAATFhtmleghwYLnacJo9PvJ8zAAAAAgAAAAAAAAAAAAAAAqN1bVJRZ7u4SxGPLFCAaUCIAAAAAwAAAAAAAAAAAAAAAxHeThX1MRHvZDw6RRc0GbaMAAAABAAAAAAAAAAAAAAABLxdlMXxvjmARlkmTHYjH0MaAAAABQAAAAAAAAAAAAAABYpcAmbgJbPAei4O+8e\/7rC8AAAABgAAAAAAAAAAAAAABnylrtV\/x7G9ll5XX7+l5qPkAAAABwAAAAAAAAAAAAAAB92sSPXn8rtFFw\/H8zMcax38AAAACAAAAAAAAAAAAAAACOg8ggvUao5tOL\/CqpTEdNHpAAAACQAAAAAAAAAAAAAACSaxo\/DM3P6NKU3BPlZAubgR\/\/\/\/\/wAAAAoAAAAAAAAAAAAAAAoAAAAAhh5ydHx1RXNFYRN5zY0jhg==", + "commitment": "W3KhrYH9MFe3n+Js+v5arMzHmH58k\/3TjhBfn28QENw=", + "decrypted-dek": "0fdi6NBvUeXX+pmFX6SUir0Q7\/q5b2cQJJOtJpbWC5Q=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example" + }, + "exception": null, + "header": "AgR4iCL3obBFAgl2H4KE8R96eNgBjLqFzwofaagV\/SF1UJgAOgACAAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMFUkHCQBWWWqXIwgfAgEQgDt+DtsGGoRXX\/c2o6u8WUX7EPdRCBuYBjKTEexmgld+jqfu9ogp+XVs\/lEPCnThJE7lZ26ufBNtuZpNZwIAAAABW3KhrYH9MFe3n+Js+v5arMzHmH58k\/3TjhBfn28QENxyh41IeBjszPCSS9P1WjiF", + "message-id": "iCL3obBFAgl2H4KE8R96eNgBjLqFzwofaagV\/SF1UJg=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "24. JS encrypt (with AAD) 1byte frame" + }, + { + "ciphertext": "AgV4nod6k6U3+hHpc2+9TE2fJvNJYmXxy5HKeGP2976E24wAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFydDRid3dTWVFUdnJpRUJQcWpDVzhWMTdMYkNidHNtb0F4MHpXdTFxa2Nnd0lVUWVOV2RnckJnVWhQTEkxUE9ZQT09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDJpAGhSRmDYszjWV6wIBEIA7uVaaElPbdkhU606jGGVzlu7jtdib9n7IEBMxhTveoM3I7Jx4WeEp5V6PJJVRlYgSaYnWZgd+pyNo7uUCAAAQAEhoaOayTlJx1SbHzfKvoTojWNzxHF2S8tryntZVRRBV13bvyHPyFxkLMXVkCze8V\/\/\/\/\/8AAAABAAAAAAAAAAAAAAABAAAACTCts6F2Ov1\/AGioyQndGwQnZw3UZlO38WoAZjBkAi9JY1EB\/YcIGvwbkjfvlzraylN1beJzTZuIKzMWvQAMUPaLe80Szxw\/F8p9KPlhKgIxAP\/+fRUljLTg5Inw7LeAyW8oFBQNuSgmdIAZavogUFU+e1A+kQtvWdHUiLA0DgElQg==", + "commitment": "SGho5rJOUnHVJsfN8q+hOiNY3PEcXZLy2vKe1lVFEFU=", + "decrypted-dek": "rnlg7ipPE0mbNg7SiAf039ey9BjswFQyHOPiLp5u1V4=", + "encryption-context": { + "aws-crypto-public-key": "Art4bwwSYQTvriEBPqjCW8V17LbCbtsmoAx0zWu1qkcgwIUQeNWdgrBgUhPLI1POYA==" + }, + "exception": null, + "header": "AgV4nod6k6U3+hHpc2+9TE2fJvNJYmXxy5HKeGP2976E24wAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFydDRid3dTWVFUdnJpRUJQcWpDVzhWMTdMYkNidHNtb0F4MHpXdTFxa2Nnd0lVUWVOV2RnckJnVWhQTEkxUE9ZQT09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDJpAGhSRmDYszjWV6wIBEIA7uVaaElPbdkhU606jGGVzlu7jtdib9n7IEBMxhTveoM3I7Jx4WeEp5V6PJJVRlYgSaYnWZgd+pyNo7uUCAAAQAEhoaOayTlJx1SbHzfKvoTojWNzxHF2S8tryntZVRRBV13bvyHPyFxkLMXVkCze8Vw==", + "message-id": "nod6k6U3+hHpc2+9TE2fJvNJYmXxy5HKeGP2976E24w=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "25. Simple JS encrypt (signed)" + }, + { + "ciphertext": "AgV4SRXxd2NDKOwAK7EYmcVdoN70\/a4cd0UQxPLBy8QlCBcAlwADABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF2VE4rUkR2UzJISG1URk1KZCtGNFNMMGl0MXdvVTZOL0tSU2FQVng2WWREdkQrZVlvSmZiT0JNcnh3MnNqM3ZkQT09AAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMLtjZcR8parum7ZR3AgEQgDv09dkEH\/ruNg2NegEnyE70usveM7797\/b0IuEMu+0SDqES0zViAvGONZJsV67nHYbNPrs8lsPb9AwCCAIAABAAkbBHwH7I84SFz+s\/eagR9kB1Oo1VEJLCWf+8p9CEdvS7eYYjthsdoDEufe8ssndV\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAAJSV1n7Qqi45oXfU2nLU8ZNKAsGlCjF62apQBoMGYCMQC7CzoB18g7LBHxmbaC\/TscsTkeSIoztKh9LzWrdny6AX5KXedpKkj957mlLFJ7xvkCMQC\/BpMw8W6hnEa8yQyJNRtKvg8i9UhbV2tJ7kGzpE0nUNcsUn\/1BwR1AHYv0MtLIy4=", + "commitment": "kbBHwH7I84SFz+s\/eagR9kB1Oo1VEJLCWf+8p9CEdvQ=", + "decrypted-dek": "RtABha+g0NdkwR43yxTAIFHmVm3dN2UuYywpNUarrF0=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example", + "aws-crypto-public-key": "AvTN+RDvS2HHmTFMJd+F4SL0it1woU6N\/KRSaPVx6YdDvD+eYoJfbOBMrxw2sj3vdA==" + }, + "exception": null, + "header": "AgV4SRXxd2NDKOwAK7EYmcVdoN70\/a4cd0UQxPLBy8QlCBcAlwADABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF2VE4rUkR2UzJISG1URk1KZCtGNFNMMGl0MXdvVTZOL0tSU2FQVng2WWREdkQrZVlvSmZiT0JNcnh3MnNqM3ZkQT09AAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMLtjZcR8parum7ZR3AgEQgDv09dkEH\/ruNg2NegEnyE70usveM7797\/b0IuEMu+0SDqES0zViAvGONZJsV67nHYbNPrs8lsPb9AwCCAIAABAAkbBHwH7I84SFz+s\/eagR9kB1Oo1VEJLCWf+8p9CEdvS7eYYjthsdoDEufe8ssndV", + "message-id": "SRXxd2NDKOwAK7EYmcVdoN70\/a4cd0UQxPLBy8QlCBc=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "26. Simple JS encrypt (signed with AAD)" + }, + { + "ciphertext": "AgV41wCZ4BqY2Cx0CxGm8\/koQpqTRMu8nP1ntIHLGLxB9X4AlwADABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFuaEdPcGM4TVM1NFdWTjJteTB3NHhaOVk1M1h5RTRIeFpBSUJPTlRrYW9UT3NOU0xJVEJVRXVVVmNnVjJOK0ZsZz09AAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMpMUYWP285UGw\/AmCAgEQgDtmuzcaMPiY+zoGW\/HupbJJOTicJEzFGYqz8VD\/uYbapQixhJMYJAfVDU6N7f\/tsMBckKE1JAAm4\/NpfAIAAAABGDzmHkvsXZw\/S51Z2IwF5tXFhiM47NTo+UiRotqRuOAKAW9WKd5x2ONAuWrzDDcUAAAAAQAAAAAAAAAAAAAAAYtOnuOM43FbAh8YQX42NmIyAAAAAgAAAAAAAAAAAAAAAm2fsrBCSB0FgjPZYIzgMTUdAAAAAwAAAAAAAAAAAAAAA1bydLuJbuThvFatZ5Lzm4AzAAAABAAAAAAAAAAAAAAABDokA1\/+j4d+3wCNTYy6iIVjAAAABQAAAAAAAAAAAAAABYc0mcYJjilk8+MwijGHFtE6AAAABgAAAAAAAAAAAAAABqvmulDqkXcIBKNGBM\/awn4+AAAABwAAAAAAAAAAAAAAB8TbxRI0xBDBBUkGOc1l4e53AAAACAAAAAAAAAAAAAAACNPZlcnytPsFK2iqj\/JV9LU7AAAACQAAAAAAAAAAAAAACeLMC3KMhVfzTAGG4q9Yclp3\/\/\/\/\/wAAAAoAAAAAAAAAAAAAAAoAAAAAIyJHRhgr+RXwWwmgULn3dwBmMGQCMBAuknmnfN782mJM\/SB\/fXc1x1uwyUsdvjlfSFDP0LX4YfwcXvehapxu\/06ciltuHwIwJkbmdd+qrKTupj6UE6JR\/ia19qPx3BF8XccN5AyDeKZ5nk\/+4q\/KfW5+D8inMuV0", + "commitment": "GDzmHkvsXZw\/S51Z2IwF5tXFhiM47NTo+UiRotqRuOA=", + "decrypted-dek": "YW6c551XES\/jyr5MtVN1vkCG2+wxgxePpDGyk2K871M=", + "encryption-context": { + "test-key": "test value", + "test-key-2": "another test example", + "aws-crypto-public-key": "AnhGOpc8MS54WVN2my0w4xZ9Y53XyE4HxZAIBONTkaoTOsNSLITBUEuUVcgV2N+Flg==" + }, + "exception": null, + "header": " AgV41wCZ4BqY2Cx0CxGm8\/koQpqTRMu8nP1ntIHLGLxB9X4AlwADABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFuaEdPcGM4TVM1NFdWTjJteTB3NHhaOVk1M1h5RTRIeFpBSUJPTlRrYW9UT3NOU0xJVEJVRXVVVmNnVjJOK0ZsZz09AAh0ZXN0LWtleQAKdGVzdCB2YWx1ZQAKdGVzdC1rZXktMgAUYW5vdGhlciB0ZXN0IGV4YW1wbGUAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMpMUYWP285UGw\/AmCAgEQgDtmuzcaMPiY+zoGW\/HupbJJOTicJEzFGYqz8VD\/uYbapQixhJMYJAfVDU6N7f\/tsMBckKE1JAAm4\/NpfAIAAAABGDzmHkvsXZw\/S51Z2IwF5tXFhiM47NTo+UiRotqRuOAKAW9WKd5x2ONAuWrzDDcU", + "message-id": "1wCZ4BqY2Cx0CxGm8\/koQpqTRMu8nP1ntIHLGLxB9X4=", + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "27. JS encrypt (signed with AAD) 1byte frame" + }, + { + "ciphertext": "AgR4fhEwYp4uvtSjKKVBRNo8M4cCzGNZCYJlKZ6dNV2TBPEAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyjF8TPWFNeTAVPNiUCARCAOwHa2IwF6OfmFdnZ1thO3nryExnRRrpV6SuDQMcVE4kN7GJSLMjUNKOHWy+tc6w4GJ8YZQPaXh3FZpi5AQAAAACBQTGyBZRrmYgb72ew18KF56PZxUWaw0tTi22YAY\/r6oGdS+5h09VRBcpMLGuaDu4AAAAAAAAAAAAAAAEAAAAAAAAAQJIOz+8G3\/o\/pAvlKSNdtiPli7T\/33N3BIOT8XsWqnzSF5JEYpaNdFpKLC6t9zgK6HHBr73ehnL1yHPU3FvWBqeD8QDElTDQ3prt4rUiN8kN", + "commitment": "gUExsgWUa5mIG+9nsNfCheej2cVFmsNLU4ttmAGP6+o=", + "message-id": "fhEwYp4uvtSjKKVBRNo8M4cCzGNZCYJlKZ6dNV2TBPE=", + "header": "AgR4fhEwYp4uvtSjKKVBRNo8M4cCzGNZCYJlKZ6dNV2TBPEAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyjF8TPWFNeTAVPNiUCARCAOwHa2IwF6OfmFdnZ1thO3nryExnRRrpV6SuDQMcVE4kN7GJSLMjUNKOHWy+tc6w4GJ8YZQPaXh3FZpi5AQAAAACBQTGyBZRrmYgb72ew18KF56PZxUWaw0tTi22YAY\/r6oGdS+5h09VRBcpMLGuaDu4=", + "decrypted-dek": "5SD48CW8Md8mKuygllS+zJlE5X8mhIqk+5nq3e2lnhU=", + "encryption-context": {}, + "exception": null, + "plaintext-frames": [ + "28. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY; unframed" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "28. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY; unframed" + }, + { + "ciphertext": "AgR4r4RshhYvCOY\/ajQPeb54T49paP2DVvo2PfIXU+3hbTUAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyEE40xvDMtH0\/MW9YCARCAO40maS\/hEokvZST+ZLwNSRl4kFobypfiG9PrkivJeUZfuKYL3biHGamGxbcO0GhiuVNIUpLTnOjqD8g\/AgAAAAH8Bm55usOgGEMbDyTfJicgxdHefqLxo+HuXSKndykWA8gq5RRBe5igAl5sT7DzowYAAAABAAAAAAAAAAAAAAABMA4WqB8EkMeJx4cyQ70WV5oAAAACAAAAAAAAAAAAAAACQmmAo07TB+dSz16QkT1OS8EAAAADAAAAAAAAAAAAAAADQzZ\/MMfXCFpUnLygsyjBnDQAAAAEAAAAAAAAAAAAAAAEwQ+A9y7\/neEzHvOPT0JZJgEAAAAFAAAAAAAAAAAAAAAFBcu7alEz\/i5L0uvZxpzzq5UAAAAGAAAAAAAAAAAAAAAGrwGWbfW\/8RnfzLkAXra0W1kAAAAHAAAAAAAAAAAAAAAHBcDtQxYl1mOlndLEQfEYjFwAAAAIAAAAAAAAAAAAAAAIBPx3Qwbkm09\/lz2nvmjn2OkAAAAJAAAAAAAAAAAAAAAJamWvUMnS\/CAp9L4KL1wAhsIAAAAKAAAAAAAAAAAAAAAKyqKVWZigTf58AxE7A+xLmREAAAALAAAAAAAAAAAAAAALwLY1R7bouUXkAd1YoI7HBmwAAAAMAAAAAAAAAAAAAAAMY\/RCtOfKSkeBh4xJf16ZCQQAAAANAAAAAAAAAAAAAAAN8WoUr0cqxQ89aDrSddvk+W8AAAAOAAAAAAAAAAAAAAAOHYYJKq1QV3\/6V1WcuD5Y\/R0AAAAPAAAAAAAAAAAAAAAP\/m5+y5\/NXjZZnkVjPrvSA9gAAAAQAAAAAAAAAAAAAAAQbXsEM\/qCXhBWrPa4BgHTS8kAAAARAAAAAAAAAAAAAAARKOgYlYSnm+u4sn2XcmGnQ5wAAAASAAAAAAAAAAAAAAASMwkW7YxzIfyAvhO5\/ykV0JoAAAATAAAAAAAAAAAAAAATkhOKrtMO+BxeuDWm4sXsmE8AAAAUAAAAAAAAAAAAAAAU3MPOB0jN6CcB9oS\/S5KPwzIAAAAVAAAAAAAAAAAAAAAVA2QP8QvKYUK4CdtUS8ix3BQAAAAWAAAAAAAAAAAAAAAWr8Dzxk0iCDRKW0vGQNEcqVoAAAAXAAAAAAAAAAAAAAAXtmBrCnoCjI5LFHBdGl1YgKkAAAAYAAAAAAAAAAAAAAAYQ37CSNBlDUOMjwSuEW9IMZsAAAAZAAAAAAAAAAAAAAAZFblf6wXcba1K9UoMelgXB2AAAAAaAAAAAAAAAAAAAAAaRkhuC6Z2IndOzVoAKMhq618AAAAbAAAAAAAAAAAAAAAbk2xy91C8AZ95olGSQDsqqBoAAAAcAAAAAAAAAAAAAAAcNcinOzBhfLJ0wRUE6fV7auoAAAAdAAAAAAAAAAAAAAAd3XEGUEX4svWSP9KtBC3v3D4AAAAeAAAAAAAAAAAAAAAeFGrkwpRhYK7XUdZ8MJlES5MAAAAfAAAAAAAAAAAAAAAfLkjlFYZoP0BkCGjFfm91qaoAAAAgAAAAAAAAAAAAAAAgHi2qpRdVZCIEqQZqudSvfsYAAAAhAAAAAAAAAAAAAAAh7XpuyzK3jrNlqk1z58t82vsAAAAiAAAAAAAAAAAAAAAiOI74jps\/JqPZccX467sGtcYAAAAjAAAAAAAAAAAAAAAj4z8T6WnUpD43U\/OnrLi+094AAAAkAAAAAAAAAAAAAAAkL6FH5aOiNFtzWKxNj66+4wQAAAAlAAAAAAAAAAAAAAAl57Gyxw44NMj9BXgAvEtEGn0AAAAmAAAAAAAAAAAAAAAmmMVB+3iOBqPqKUJBpNK8tb4AAAAnAAAAAAAAAAAAAAAnLmMwJmdVQfVMhwqKWGih81EAAAAoAAAAAAAAAAAAAAAomVJSJRFYS04t9iCCai0emk8AAAApAAAAAAAAAAAAAAApwCCRA9z39R7NWADqsdLZoNkAAAAqAAAAAAAAAAAAAAAq5XaUizsv2PF8UJvIr4cJfaIAAAArAAAAAAAAAAAAAAArA\/QwjpPKNOUgrrYvYbvdnzgAAAAsAAAAAAAAAAAAAAAsy1PiK5CMXUzRJx3CBoV9algAAAAtAAAAAAAAAAAAAAAt8wt6cjL7Vf9PeRw6qdt9LL8AAAAuAAAAAAAAAAAAAAAuLs+cGjx3J09ftazhUeMFqikAAAAvAAAAAAAAAAAAAAAvFDrgc6VKRKkraKIlLus8VdkAAAAwAAAAAAAAAAAAAAAwP7\/xaQ9CH6Q6btXVtkdm9gkAAAAxAAAAAAAAAAAAAAAxupv2FG\/Zgs0936k0rgcsIDwAAAAyAAAAAAAAAAAAAAAyavQLYrHkEExgPxUbt\/vfbuwAAAAzAAAAAAAAAAAAAAAzMtwxLnqssaOrbBN47iC07zIAAAA0AAAAAAAAAAAAAAA06+TZXkgFRr7OXZaAJs2nV1EAAAA1AAAAAAAAAAAAAAA1V1j4TPCEXEaRgCZ7jYe0aqEAAAA2AAAAAAAAAAAAAAA25V+G5V1LucwE1DEjAHyvNLUAAAA3AAAAAAAAAAAAAAA3\/IZO0adn3+LFZ4XaQCtwddwAAAA4AAAAAAAAAAAAAAA4IkEVGVxSbVb9Mr\/UifVLFaAAAAA5AAAAAAAAAAAAAAA5vz4t0fCOxv18jd17F8Dx0j0AAAA6AAAAAAAAAAAAAAA69Uuvr8AKEuDi2i2K5CkPVj4AAAA7AAAAAAAAAAAAAAA7awFnbvdm1YILhKYSDP6UC3UAAAA8AAAAAAAAAAAAAAA8n5mdN5YgiOrPTdsRvkxioZEAAAA9AAAAAAAAAAAAAAA9U1WdxSc4rC\/Xj4kZkSmPTWIAAAA+AAAAAAAAAAAAAAA++lfacqdiulf\/XLg7xv\/ZgaEAAAA\/AAAAAAAAAAAAAAA\/L1PLKnbcfGIEbrBBCZQGLtcAAABAAAAAAAAAAAAAAABAp5JtK66yhtJhiQlro2zH+R8AAABBAAAAAAAAAAAAAABBCpkQSoXjGtEzdGBDDhbss\/EAAABCAAAAAAAAAAAAAABCYQiHrK9MfUlep5LJMqCPYhoAAABDAAAAAAAAAAAAAABD\/vhTvqXEZWJRIX7rLZcnoRwAAABEAAAAAAAAAAAAAABEba8jHDEjkmBYDCPcBwI6+cf\/\/\/\/\/AAAARQAAAAAAAAAAAAAARQAAAAC1uN37SrCiVmcllzaYPnEj", + "commitment": "\/AZuebrDoBhDGw8k3yYnIMXR3n6i8aPh7l0ip3cpFgM=", + "message-id": "r4RshhYvCOY\/ajQPeb54T49paP2DVvo2PfIXU+3hbTU=", + "header": "AgR4r4RshhYvCOY\/ajQPeb54T49paP2DVvo2PfIXU+3hbTUAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyEE40xvDMtH0\/MW9YCARCAO40maS\/hEokvZST+ZLwNSRl4kFobypfiG9PrkivJeUZfuKYL3biHGamGxbcO0GhiuVNIUpLTnOjqD8g\/AgAAAAH8Bm55usOgGEMbDyTfJicgxdHefqLxo+HuXSKndykWA8gq5RRBe5igAl5sT7DzowY=", + "decrypted-dek": "wElcw7jHSJ5f\/aQpQyxuodFP3OgmxBnO1LNZPn+QU2U=", + "encryption-context": {}, + "exception": null, + "plaintext-frames": [ + "2", + "9", + ".", + " ", + "[", + "C", + " ", + "E", + "S", + "D", + "K", + "]", + " ", + "a", + "l", + "g", + "=", + "A", + "L", + "G", + "_", + "A", + "E", + "S", + "2", + "5", + "6", + "_", + "G", + "C", + "M", + "_", + "H", + "K", + "D", + "F", + "_", + "S", + "H", + "A", + "5", + "1", + "2", + "_", + "C", + "O", + "M", + "M", + "I", + "T", + "_", + "K", + "E", + "Y", + ";", + " ", + "f", + "r", + "a", + "m", + "e", + "_", + "s", + "i", + "z", + "e", + "=", + "1" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "29. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY; frame_size=1" + }, + { + "ciphertext": "AgR4K3m8tPW6+GZbv2h6l\/Zog57SL5sT9o50Bf9FbObk18gAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwHMATBwGsO+itTfXkCARCAO2quKOuXQ1vgTLsWWcF5Xxta5xGkziCmXh3MjZz7fg9Rh9+uM7giP9yGqpLPhws29+QCSDc+U+MjbjvPAgAAABBKcTTOB2n0NhpGIjqX6asWzqnVxGL9ElHDsIADOwl25HBnjqaKG2LS5Yb+aVyMJW4AAAABAAAAAAAAAAAAAAABabtPdgY42AWzHXmW92NiQzrnvQV9OrG3UN89DTZZ\/tkAAAACAAAAAAAAAAAAAAACXd1zoo32cOde25\/kpfepcMB8JCqFeYQRFlyc0YrKJf0AAAADAAAAAAAAAAAAAAADKLae04FNWjljylpTTZPVdmAMQ2WoHKzMPC1qnsWZHvAAAAAEAAAAAAAAAAAAAAAEBt6stQeuur0FKyYK9bR1K6+sJn\/q8wB8gajT1pxb+Xb\/\/\/\/\/AAAABQAAAAAAAAAAAAAABQAAAAXwamnjgyqZoZJjJMf8KLi4WFC5AzM=", + "commitment": "SnE0zgdp9DYaRiI6l+mrFs6p1cRi\/RJRw7CAAzsJduQ=", + "message-id": "K3m8tPW6+GZbv2h6l\/Zog57SL5sT9o50Bf9FbObk18g=", + "header": "AgR4K3m8tPW6+GZbv2h6l\/Zog57SL5sT9o50Bf9FbObk18gAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwHMATBwGsO+itTfXkCARCAO2quKOuXQ1vgTLsWWcF5Xxta5xGkziCmXh3MjZz7fg9Rh9+uM7giP9yGqpLPhws29+QCSDc+U+MjbjvPAgAAABBKcTTOB2n0NhpGIjqX6asWzqnVxGL9ElHDsIADOwl25HBnjqaKG2LS5Yb+aVyMJW4=", + "decrypted-dek": "E4ocDFKJ9JGz2dXyOBNqh9atsl6ceVqoftZzhiTAVK8=", + "encryption-context": {}, + "exception": null, + "plaintext-frames": [ + "30. [C ESDK] alg", + "=ALG_AES256_GCM_", + "HKDF_SHA512_COMM", + "IT_KEY; frame_si", + "ze=16" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "30. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY; frame_size=16" + }, + { + "ciphertext": "AgR4gAA7pUhGfkfy82zd66QV7oEpgAY\/d0PQv+69MJwcZaQAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAz2OsdsmlWWA\/LqJscCARCAO\/Zfy5nh0mNvvn4WKkjt6cTqBl\/tAmmJKHyxt9orNTGGoTRAyUy4EA5A794sqU0AVXiSP3y4GIcg3GT9AgAABACcuZNYrK2pO\/AtSUAL495ZtuouZUHtgi66pbyqaJkJglijsti80CwWoVmBdkCk+2f\/\/\/\/\/AAAAAQAAAAAAAAAAAAAAAQAAAEdqhQognTmY0tGmRPwKMMi5WhdUab5fEA4aqCR5QaKp9J4JnTdrqepBxkZ1KWjlmiUnqCGyTIPhKhTMTYFztz3cphx85xe1raLWByDQbPSaD\/6FOEixwLY=", + "commitment": "nLmTWKytqTvwLUlAC+PeWbbqLmVB7YIuuqW8qmiZCYI=", + "message-id": "gAA7pUhGfkfy82zd66QV7oEpgAY\/d0PQv+69MJwcZaQ=", + "header": "AgR4gAA7pUhGfkfy82zd66QV7oEpgAY\/d0PQv+69MJwcZaQAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAz2OsdsmlWWA\/LqJscCARCAO\/Zfy5nh0mNvvn4WKkjt6cTqBl\/tAmmJKHyxt9orNTGGoTRAyUy4EA5A794sqU0AVXiSP3y4GIcg3GT9AgAABACcuZNYrK2pO\/AtSUAL495ZtuouZUHtgi66pbyqaJkJglijsti80CwWoVmBdkCk+2c=", + "decrypted-dek": "CcR2nrT\/kcO4xJDogk5djrW6fSbjErxmXf9anzG5kSU=", + "encryption-context": {}, + "exception": null, + "plaintext-frames": [ + "31. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY; frame_size=1024" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "31. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY; frame_size=1024" + }, + { + "ciphertext": "AgV4L2S0PjlT+XA5n5U65oi\/3NDRjFAuzOzfc8ceMbAh5DYAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFzanM1TklJblNoM2RrVmtyQmlxSkhXK2dNVzV5eWx5elNUbVprVUZMOTBiS09kMmdhUUM2N1k4WmpGTktNVmY0Zz09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDEoy6kj\/pFN3NU5iXQIBEIA7X7fjiNBdtgeYDNAoe\/DcVQVw5rgnJS9VqnSp0KlF0Km8a\/ZzXCgYBb00WwTns4vcxq0rZwVL4DkULToBAAAAAJB7d2YxM9LBEXbzT3LVQHP3yotU2Y2n6pLQmYeo8bMe7YU6Q4zMaICWf7u2RP6NQAAAAAAAAAAAAAAAAQAAAAAAAABLCoBohy10UfWSBVipyqTKUw6ur\/9jPvsWSww4CaQ7mtjQ+CY9xwO10H6zRIxnq3bR43iYHM8d0clSV4k6dNet1Y2HJt1bFXnuuY3rdAxH\/Iee\/\/SvqvVjcOwNsABnMGUCMQCY+AHrGu\/0hAy6dWbX+zVTu97+MUfSl8NcMmTNCKhtwcxKHnTQZlFEc7Qzu7A6iDMCMBReZ6xwJp1h8fzYyZ9m50XDwG\/jzx\/v+lRbGVulEjVdfdWvyYx+GAkNJEEQaBVZwQ==", + "commitment": "kHt3ZjEz0sERdvNPctVAc\/fKi1TZjafqktCZh6jxsx4=", + "message-id": "L2S0PjlT+XA5n5U65oi\/3NDRjFAuzOzfc8ceMbAh5DY=", + "header": "AgV4L2S0PjlT+XA5n5U65oi\/3NDRjFAuzOzfc8ceMbAh5DYAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFzanM1TklJblNoM2RrVmtyQmlxSkhXK2dNVzV5eWx5elNUbVprVUZMOTBiS09kMmdhUUM2N1k4WmpGTktNVmY0Zz09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDEoy6kj\/pFN3NU5iXQIBEIA7X7fjiNBdtgeYDNAoe\/DcVQVw5rgnJS9VqnSp0KlF0Km8a\/ZzXCgYBb00WwTns4vcxq0rZwVL4DkULToBAAAAAJB7d2YxM9LBEXbzT3LVQHP3yotU2Y2n6pLQmYeo8bMe7YU6Q4zMaICWf7u2RP6NQA==", + "decrypted-dek": "p7jVbJV7IWyZ+VKrPIMcPRU0kdAqQxOtCLDYwuRps84=", + "encryption-context": { + "aws-crypto-public-key": "Asjs5NIInSh3dkVkrBiqJHW+gMW5yylyzSTmZkUFL90bKOd2gaQC67Y8ZjFNKMVf4g==" + }, + "exception": null, + "plaintext-frames": [ + "32. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; unframed" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "32. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; unframed" + }, + { + "ciphertext": "AgV4G59FPxp4eCwrZrzjP0zIia9+Xmv+DdaTCNUj9UdyT5UAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREEvS3ljdHpsZXJlY3lKVkprbmhaWWxJVTFvMUwyRVFhTW5kR2crWXVaM20xUGhKc2FhMzNTb3VkZXVkVnBTdWZVQT09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDFIoc1AQR8OC2l7aXwIBEIA7pAVMPn0bKicv2GF06mdRGqr6IZctNrW869kDcgeqXsC\/IhAiyA3brWRtHO8lDcCx6TOui4cUBl5LbRkCAAAAAVgQnJX1I3I\/gwqHN9WBR+wktovleZA+FcMfZK53PD4HJNlQi+hiddz7BzZ2x8dz1QAAAAEAAAAAAAAAAAAAAAH6hiLwm61FKyZIVJ5Dgr1acgAAAAIAAAAAAAAAAAAAAAIpjg7By8dC2ZHjZ\/S9cSOvzwAAAAMAAAAAAAAAAAAAAAObNSUiZWX8lwFtNHjqrvX4sgAAAAQAAAAAAAAAAAAAAAR6gBWPSh2VN9MuwZnPQvaQggAAAAUAAAAAAAAAAAAAAAVV\/wH26SAUDlq7BAKUnORfWQAAAAYAAAAAAAAAAAAAAAZDS7iasEfC8+aNvMKUvxmfQgAAAAcAAAAAAAAAAAAAAAcw8DfzpbUpdLE+kMhvjIa5ggAAAAgAAAAAAAAAAAAAAAjkp+B7\/MjQZYfgm2sBVmHMdAAAAAkAAAAAAAAAAAAAAAmFsLrFImT9XKNX0tVRBQqLQQAAAAoAAAAAAAAAAAAAAAq6EEV0z6hlVJp3AL30LehixwAAAAsAAAAAAAAAAAAAAAsaxNNz\/sYHvhgCR0PQQkH04wAAAAwAAAAAAAAAAAAAAAxjJldqNlEu6ITLCakDw0gY3gAAAA0AAAAAAAAAAAAAAA172WI0cwhEQsbiQWgoIFa\/fQAAAA4AAAAAAAAAAAAAAA6+3tSZUsawS\/rrPYmvcdtFkQAAAA8AAAAAAAAAAAAAAA8Z81209RGv8Z0QimqPThyerwAAABAAAAAAAAAAAAAAABCByy4MtjCN3Wy9Nn9hsI9vbAAAABEAAAAAAAAAAAAAABFbFO\/gycPiUcTkHzh4klXH5gAAABIAAAAAAAAAAAAAABLCAvSTNXaxkORs1mrELxX1CgAAABMAAAAAAAAAAAAAABP46C4rpY0w6O3UnQw120osSAAAABQAAAAAAAAAAAAAABRoXlUncJeXgYFMsh9wcXGGpwAAABUAAAAAAAAAAAAAABVPH6Nx42ntHZl0WPSDaVBpNAAAABYAAAAAAAAAAAAAABborIPmvL4hkpO8csnyVszbDQAAABcAAAAAAAAAAAAAABfoAarvLJzMo9LzvplYJ0z7UgAAABgAAAAAAAAAAAAAABh6xAbHnaHgvRianxFxcoi91wAAABkAAAAAAAAAAAAAABmUGdoPiR+LaqleN4ruQ5n8UgAAABoAAAAAAAAAAAAAABrbke10pO7tdGjMk9Spwp7EJwAAABsAAAAAAAAAAAAAABuJ0hqaSzY1TBNDSLeqdqRq3wAAABwAAAAAAAAAAAAAABwyTl6mHYrnSAfqOfgWn2s5nwAAAB0AAAAAAAAAAAAAAB0eWf7GVKxO4LEkI9XtFFk2NQAAAB4AAAAAAAAAAAAAAB6nAa1DdDySbukA7at+XH6aLwAAAB8AAAAAAAAAAAAAAB\/qAJOKUQalSgU75GGbM1yAzgAAACAAAAAAAAAAAAAAACCCBw4vb8imgRzdnUMlXhHe6QAAACEAAAAAAAAAAAAAACEkbHaqI9rRCi9MarTGSodhEQAAACIAAAAAAAAAAAAAACIvmgnKEYHpqc+dJ\/0sCrM\/mQAAACMAAAAAAAAAAAAAACN8QFGdt8QEg+URLecB1EbO4QAAACQAAAAAAAAAAAAAACQjIcpT9lnC5UA3pjns4TX+CAAAACUAAAAAAAAAAAAAACXBE7fy5ZCePzy3STbn2dUIsgAAACYAAAAAAAAAAAAAACZHRnjw\/lca05LLkLydCeSxNwAAACcAAAAAAAAAAAAAACeuAnxpbbZPLdU0IJ3whFoowQAAACgAAAAAAAAAAAAAACgzFy0vZNVUgH9isdIhO4GCrwAAACkAAAAAAAAAAAAAACnTg3PoHTMN4673nGAkCGzRmwAAACoAAAAAAAAAAAAAACo3WM6OCtVhXhHjgVK92UXX9QAAACsAAAAAAAAAAAAAACuEMGasWfG7QC6WtNju\/PoTCgAAACwAAAAAAAAAAAAAACzq1QxIxYOR43+vDzYfnTSAMAAAAC0AAAAAAAAAAAAAAC3wrVUo0rXbuJVjsV1vt0hGUAAAAC4AAAAAAAAAAAAAAC7MQHqvQChia9xJbr81eakxlgAAAC8AAAAAAAAAAAAAAC8X0rpxvH52BMZliw38\/O\/\/DQAAADAAAAAAAAAAAAAAADCE31B8VdUDWhcQD8Kmq+3efQAAADEAAAAAAAAAAAAAADHq2\/OHej6R9ISLLOpVGzCtCQAAADIAAAAAAAAAAAAAADLkIimNGhmvQsAaD3DVPIJGBAAAADMAAAAAAAAAAAAAADMPZv0nxfJWZguIWSRXsfyglQAAADQAAAAAAAAAAAAAADSAb9IJ7bLeZEImaGCTiVrn6QAAADUAAAAAAAAAAAAAADVFt5mGXVMjaWv6WYuEAZjdqQAAADYAAAAAAAAAAAAAADYI+SbSqhvd39VgFMer790y0wAAADcAAAAAAAAAAAAAADcXdXGZ2k+t49satVhz9LOcWgAAADgAAAAAAAAAAAAAADgPW3aT6KKizLm2A73ogc9oZwAAADkAAAAAAAAAAAAAADkG2dTERDi0cbqvMD501HGdqwAAADoAAAAAAAAAAAAAADoVf9abCyD7\/0Mkm2ySzCwuHwAAADsAAAAAAAAAAAAAADsnLxuSiKLU8RY2Aq4rYKHK3QAAADwAAAAAAAAAAAAAADzGGZwcbZ2e61lGPhOOhpyffgAAAD0AAAAAAAAAAAAAAD0lruywXjsHw\/iYThi+a5zCHQAAAD4AAAAAAAAAAAAAAD5JnSIpbIhsgJhfWBbcx\/1EoAAAAD8AAAAAAAAAAAAAAD\/9RL67BjPpPBr+Djfu0ushVwAAAEAAAAAAAAAAAAAAAEANjJvUtnvY4sObpmQVMRsiAgAAAEEAAAAAAAAAAAAAAEFZo1rTPAjWN3oBgdr9k+yC2gAAAEIAAAAAAAAAAAAAAEIa9b4AZlHCy1rFI+iA1I3jgAAAAEMAAAAAAAAAAAAAAEPwE8D9vvJIjwXdAmOfb2Bb2AAAAEQAAAAAAAAAAAAAAETOLOQQ0DoTcu+P38lYEHhw0wAAAEUAAAAAAAAAAAAAAEXRh443rHCOlQVzE6QkY+JLSwAAAEYAAAAAAAAAAAAAAEax2zeECli3YAMJeWV8vkTjUgAAAEcAAAAAAAAAAAAAAEesScoaij1eduqyFJI8XZ3r4AAAAEgAAAAAAAAAAAAAAEi1g3Ph6isEe84fJD23O66uowAAAEkAAAAAAAAAAAAAAEkAtev7EfCHIbP1hUPSa+APmgAAAEoAAAAAAAAAAAAAAEpTmxls3i5+3RLS3Kzm3gRR2wAAAEsAAAAAAAAAAAAAAEs1RBRPyLdVra0D7j9upDsjcwAAAEwAAAAAAAAAAAAAAEyktVQw\/UXx5rxR8CDOe86nUgAAAE0AAAAAAAAAAAAAAE09Lc+gByhxZDXZ6Bu4\/Es4LwAAAE4AAAAAAAAAAAAAAE6dJ2VUGaNSQmTQ2mGUFagNewAAAE8AAAAAAAAAAAAAAE\/\/JhhaZgGShFlVu+xyl5yuLv\/\/\/\/8AAABQAAAAAAAAAAAAAABQAAAAAC2N8l4GZZsE0yShavVhe0QAZzBlAjAEPZdxRS4gWPjqXXHLWN4nEGHkLHVDnSe1EkadA7267tE0w04BezAnOLkPS9ipwBYCMQCJG7fYh6YP81Nzaw4445SHEo0TNOhk\/tkUjvsUq3bNB9dGuHZaQQ6BqbMQvG4VxEE=", + "commitment": "WBCclfUjcj+DCoc31YFH7CS2i+V5kD4Vwx9krnc8Pgc=", + "message-id": "G59FPxp4eCwrZrzjP0zIia9+Xmv+DdaTCNUj9UdyT5U=", + "header": "AgV4G59FPxp4eCwrZrzjP0zIia9+Xmv+DdaTCNUj9UdyT5UAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREEvS3ljdHpsZXJlY3lKVkprbmhaWWxJVTFvMUwyRVFhTW5kR2crWXVaM20xUGhKc2FhMzNTb3VkZXVkVnBTdWZVQT09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDFIoc1AQR8OC2l7aXwIBEIA7pAVMPn0bKicv2GF06mdRGqr6IZctNrW869kDcgeqXsC\/IhAiyA3brWRtHO8lDcCx6TOui4cUBl5LbRkCAAAAAVgQnJX1I3I\/gwqHN9WBR+wktovleZA+FcMfZK53PD4HJNlQi+hiddz7BzZ2x8dz1Q==", + "decrypted-dek": "ApPHw6yUnnWea70HYU9vvgxIiWuk7gMb7lMXR21W8Kk=", + "encryption-context": { + "aws-crypto-public-key": "A\/KyctzlerecyJVJknhZYlIU1o1L2EQaMndGg+YuZ3m1PhJsaa33SoudeudVpSufUA==" + }, + "exception": null, + "plaintext-frames": [ + "3", + "3", + ".", + " ", + "[", + "C", + " ", + "E", + "S", + "D", + "K", + "]", + " ", + "a", + "l", + "g", + "=", + "A", + "L", + "G", + "_", + "A", + "E", + "S", + "2", + "5", + "6", + "_", + "G", + "C", + "M", + "_", + "H", + "K", + "D", + "F", + "_", + "S", + "H", + "A", + "5", + "1", + "2", + "_", + "C", + "O", + "M", + "M", + "I", + "T", + "_", + "K", + "E", + "Y", + "_", + "E", + "C", + "D", + "S", + "A", + "_", + "P", + "3", + "8", + "4", + ";", + " ", + "f", + "r", + "a", + "m", + "e", + "_", + "s", + "i", + "z", + "e", + "=", + "1" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "33. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; frame_size=1" + }, + { + "ciphertext": "AgV4Cri6kFSyB84mx+wt4qgx2ExzbBbVhfDaCweFtJmyyawAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF5N09CbzhYUW9lc0treU1WZHJiZi9TQ3F5MDNtdjU2Y3hkS1ZOa2JoRUU2VWpGYng0VzUzZGkrRytsRTVSMjdOdz09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDM5cn+Y\/7Y9bQ29WGwIBEIA7TG4kG9yQfLtTTbvL\/NkE9PWXfFT12F9\/0zlKLgcHn3ba1Jv8LE7kd5bGR4WZBj4\/yZ6QOVOlZKdhU8ECAAAAEOFIohyrSsm7hQfZ7DbWFJhEn6uZoCO7Q0VBqGMiH\/\/ROcwUJFQI0V1re2sMD2gKXwAAAAEAAAAAAAAAAAAAAAHDBPMQUYEwG2fKOjLSy5TzIsJuX6yGSneyX7N8twkLgAAAAAIAAAAAAAAAAAAAAALHIwp6ifqKY4ST4ht1YPK3tMgVKbMA2MGnxO\/KASWUZQAAAAMAAAAAAAAAAAAAAAPe7atR4Zuw7rws2Lq3LNDx9HQLgtbxXg2Uop5GGCrMYAAAAAQAAAAAAAAAAAAAAATvARGcM3tkX8Rfw+Z2OG+66JDtoVCeTwwrIKZ0SBYd1gAAAAUAAAAAAAAAAAAAAAXz16pY+9hY6+4aSLMNcMp1uNrLUsSOlZzOqpK+Fugsnv\/\/\/\/8AAAAGAAAAAAAAAAAAAAAGAAAAAEpDJdbidG3or3xz3g+j9a0AZzBlAjEA9LQRM\/AjRx+cYDuMo0NnK9ZmAaWboUmeRJqQjdaOTLtdaZfn2hDBV6gBsQIaQ2jbAjBP+cYTlMcb2dguFKM0DJRr9bQGcoAp45aIm1IgSSt8\/4zvBHAuE38TYu5FFgpnbd0=", + "commitment": "4UiiHKtKybuFB9nsNtYUmESfq5mgI7tDRUGoYyIf\/9E=", + "message-id": "Cri6kFSyB84mx+wt4qgx2ExzbBbVhfDaCweFtJmyyaw=", + "header": "AgV4Cri6kFSyB84mx+wt4qgx2ExzbBbVhfDaCweFtJmyyawAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF5N09CbzhYUW9lc0treU1WZHJiZi9TQ3F5MDNtdjU2Y3hkS1ZOa2JoRUU2VWpGYng0VzUzZGkrRytsRTVSMjdOdz09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDM5cn+Y\/7Y9bQ29WGwIBEIA7TG4kG9yQfLtTTbvL\/NkE9PWXfFT12F9\/0zlKLgcHn3ba1Jv8LE7kd5bGR4WZBj4\/yZ6QOVOlZKdhU8ECAAAAEOFIohyrSsm7hQfZ7DbWFJhEn6uZoCO7Q0VBqGMiH\/\/ROcwUJFQI0V1re2sMD2gKXw==", + "decrypted-dek": "VJP9B8uCnNxrdEtV8kPaU5pjiK7xL\/CgTkLycy1kU+U=", + "encryption-context": { + "aws-crypto-public-key": "Ay7OBo8XQoesKkyMVdrbf\/SCqy03mv56cxdKVNkbhEE6UjFbx4W53di+G+lE5R27Nw==" + }, + "exception": null, + "plaintext-frames": [ + "34. [C ESDK] alg", + "=ALG_AES256_GCM_", + "HKDF_SHA512_COMM", + "IT_KEY_ECDSA_P38", + "4; frame_size=16" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "34. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; frame_size=16" + }, + { + "ciphertext": "AgV4vg62vd1Hv5VE+VGjBa7oc3FPvQTDk7ZguY9GiKGulTkAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFrR0hBdmFSNVB2bWxRbnQzWXpZQWRKWFlrTUdBMmZLNjJzU1VwSC9jbzd0elM1ZTdXekFuZzNhTlVQU3dLSk15dz09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDMpcqA4MbfdI8aAUOgIBEIA7R9FnrsMXuhPlFikOTW3aKzvEOVWNQPtpeOCLfU+aZhGKhSmkCYomwyogxt3FX3nrjfC3T9qCgCR7Z+UCAAAEAIhjUi358MWbu7FHlNnUMi9LI+65mze4uaZKVo24z2vFwi6+JWRjsiDczse9YIuKCP\/\/\/\/8AAAABAAAAAAAAAAAAAAABAAAAUtmGBbD+Xwh+szMmNcT+da9ik8xoO5QTFdDp2SrPulgAb1GaXcbrSCH\/3wGjFqbcP0X8x2t9mwuZk7LL+bRme5aYdgy8KsTH4QoYQEVaiVGdV54L2jkURaCut8W4puj0S\/qAAGcwZQIwb9jj+9a4kwMeg2TIaBnDlFsRFNz4pru5AkTjm785jJRzxqmNo+8\/AxyHjdLpPL3uAjEAnyBFU1XaaHI7+iRblm\/9FdbdgvxcvN4aqhjvujvZTbZncE9IMALJviNMHxV9wa0g", + "commitment": "iGNSLfnwxZu7sUeU2dQyL0sj7rmbN7i5pkpWjbjPa8U=", + "message-id": "vg62vd1Hv5VE+VGjBa7oc3FPvQTDk7ZguY9GiKGulTk=", + "header": "AgV4vg62vd1Hv5VE+VGjBa7oc3FPvQTDk7ZguY9GiKGulTkAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFrR0hBdmFSNVB2bWxRbnQzWXpZQWRKWFlrTUdBMmZLNjJzU1VwSC9jbzd0elM1ZTdXekFuZzNhTlVQU3dLSk15dz09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDMpcqA4MbfdI8aAUOgIBEIA7R9FnrsMXuhPlFikOTW3aKzvEOVWNQPtpeOCLfU+aZhGKhSmkCYomwyogxt3FX3nrjfC3T9qCgCR7Z+UCAAAEAIhjUi358MWbu7FHlNnUMi9LI+65mze4uaZKVo24z2vFwi6+JWRjsiDczse9YIuKCA==", + "decrypted-dek": "B83FiL96eWtjx7o\/RNhfeJfHS5n0cGIaPNZjS52TV1w=", + "encryption-context": { + "aws-crypto-public-key": "AkGHAvaR5PvmlQnt3YzYAdJXYkMGA2fK62sSUpH\/co7tzS5e7WzAng3aNUPSwKJMyw==" + }, + "exception": null, + "plaintext-frames": [ + "35. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; frame_size=1024" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "35. [C ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; frame_size=1024" + }, + { + "ciphertext": "AgR4yNblPxh8dEafsUL5bpyJNqiqn8p+y\/7AXcHUbywUVpwAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxa70VQY6\/+Av\/OLD8CARCAO\/v5YAqSmoWIuYyYXF1mrgXuRMSCIan5ihnGlBfWLeayEd35vc+XCXUPkFhkq518ngB09ul3bHPgvphgAgAACAAZ8+mwxDWarFDhqjHmpnIy2q2R80WwF9lAWy4ZCmei64JGBkBhGG4U4vF+Qh4TOMb\/\/\/\/\/AAAAAQAAAAAAAAAAAAAAAQAAAAmoJ2RCTnS8yT+k0RTr16lJzJ\/ISzKB1paB", + "commitment": "GfPpsMQ1mqxQ4aox5qZyMtqtkfNFsBfZQFsuGQpnous=", + "message-id": "yNblPxh8dEafsUL5bpyJNqiqn8p+y\/7AXcHUbywUVpw=", + "header": "AgR4yNblPxh8dEafsUL5bpyJNqiqn8p+y\/7AXcHUbywUVpwAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxa70VQY6\/+Av\/OLD8CARCAO\/v5YAqSmoWIuYyYXF1mrgXuRMSCIan5ihnGlBfWLeayEd35vc+XCXUPkFhkq518ngB09ul3bHPgvphgAgAACAAZ8+mwxDWarFDhqjHmpnIy2q2R80WwF9lAWy4ZCmei64JGBkBhGG4U4vF+Qh4TOMY=", + "decrypted-dek": "vfPpEnCooy3XFzxt\/4vPIZ2i7ec07PiYmasAU0PT7JY=", + "encryption-context": {}, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "36. [Python ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY; frame_size=2048" + }, + { + "ciphertext": "AgV47ifhy2pEjH4uPOG+7E9js5isde\/lfQ99SPOJ6k44gtMAfwADABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFqZGJtM2lxSXFPQWQyT3QzQjZuWGpTYVN2aWZrTmtDMzNpUEpJMjZYNEo0KzRBRGJ4VnFRTndOcjljOFgzcUZOZz09AAVrZXlfYQAHdmFsdWVfYQAFa2V5X2IAB3ZhbHVlX2IAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM8OlFmmVp8XwQLrSrAgEQgDsFOqtWv5PZxNNeWRRGiaTgSHUr69mvDLohdL88CEkTaiAExVWhByMUSpvbbJW9TUmNJWkCd3nhGl90SQIAAAgA+9+06T874sHO44BxZyxznHnDFdH9j1qoZ\/XIjE1IZeBnGjkqNF7n262O13osDfHh\/\/\/\/\/wAAAAEAAAAAAAAAAAAAAAEAAAAJ7Vzzk9nJuGI6j4rF7n3eLTTMWmU0rH25jABnMGUCMQDiZmYTwm7emvFwByAYHYXJQUYfc5FfSpjUp7nNQz9gCboKc3O8z0E\/+832YqPQLj8CMA5LI5IofS9NBy5j4GpNpu7CWqUZHDWAEXDjGydjpCBL5Rw9IrrjuIJ81S48w\/cY1Q==", + "commitment": "+9+06T874sHO44BxZyxznHnDFdH9j1qoZ\/XIjE1IZeA=", + "message-id": "7ifhy2pEjH4uPOG+7E9js5isde\/lfQ99SPOJ6k44gtM=", + "header": "AgV47ifhy2pEjH4uPOG+7E9js5isde\/lfQ99SPOJ6k44gtMAfwADABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFqZGJtM2lxSXFPQWQyT3QzQjZuWGpTYVN2aWZrTmtDMzNpUEpJMjZYNEo0KzRBRGJ4VnFRTndOcjljOFgzcUZOZz09AAVrZXlfYQAHdmFsdWVfYQAFa2V5X2IAB3ZhbHVlX2IAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM8OlFmmVp8XwQLrSrAgEQgDsFOqtWv5PZxNNeWRRGiaTgSHUr69mvDLohdL88CEkTaiAExVWhByMUSpvbbJW9TUmNJWkCd3nhGl90SQIAAAgA+9+06T874sHO44BxZyxznHnDFdH9j1qoZ\/XIjE1IZeBnGjkqNF7n262O13osDfHh", + "decrypted-dek": "27Mr50n9EYgz\/iYs6a1xpgQJaw0u4bPtxI2gUE08Dkg=", + "encryption-context": { + "key_a": "value_a", + "key_b": "value_b", + "aws-crypto-public-key": "Ajdbm3iqIqOAd2Ot3B6nXjSaSvifkNkC33iPJI26X4J4+4ADbxVqQNwNr9c8X3qFNg==" + }, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "37. [Python ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; frame_size=2048" + }, + { + "ciphertext": "AgV4wjhFuoHhF6np5UvxBujGNCGe8CcoCsePw7aXzeLgH8wAfwADABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREE4dXY0S0lYWlIwTWhwUm5kQ3ZZK0p1elRRcStqVWxacnBWMHM5RXFNV0ZjaFRBd0FIVXdIK2tOSzA5S3BBQnkwdz09AAVrZXlfYQAHdmFsdWVfYQAFa2V5X2IAB3ZhbHVlX2IAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMvsueY9q2K9SdGKfuAgEQgDvLQ5OljOCV1\/YH\/tYzl8WF+LGPRRj07YoY41t9XjjzZkhjH6nhZfY+QO8CLSM8ut1UDcjsiHMc8wAPPQEAAAAArqoSW\/LCsWqA2PzItFUJsyDF\/wAPCbMzc6YuAdKnWXvUPTIdnPA3H7jAmRegjNLnAAAAAAAAAAAAAAABAAAAAAAAAAkdnEdnIYuM46iNVFb59YSGI28tcMvNOTHdAGcwZQIxAItH5NgTud0q8MNvhktGLyG2UDTJHxiDJ5yZs5Tuc9Vlxl\/t2cGFdJqp1vopF4RxhAIwToqh7JWjG24wQ3t9mXSLpoNwJL2WotrznE\/XIcjW4SZLtjETsQGq+yF92XR6s8Ur", + "commitment": "rqoSW\/LCsWqA2PzItFUJsyDF\/wAPCbMzc6YuAdKnWXs=", + "message-id": "wjhFuoHhF6np5UvxBujGNCGe8CcoCsePw7aXzeLgH8w=", + "header": "AgV4wjhFuoHhF6np5UvxBujGNCGe8CcoCsePw7aXzeLgH8wAfwADABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREE4dXY0S0lYWlIwTWhwUm5kQ3ZZK0p1elRRcStqVWxacnBWMHM5RXFNV0ZjaFRBd0FIVXdIK2tOSzA5S3BBQnkwdz09AAVrZXlfYQAHdmFsdWVfYQAFa2V5X2IAB3ZhbHVlX2IAAQAHYXdzLWttcwBLYXJuOmF3czprbXM6dXMtd2VzdC0yOjY1ODk1NjYwMDgzMzprZXkvYjM1MzdlZjEtZDhkYy00NzgwLTlmNWEtNTU3NzZjYmIyZjdmAKcBAQEAeEDzjCdeMQl0FsEHKVFQVxlkraPvHCHpTIugvbydD7QUAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMvsueY9q2K9SdGKfuAgEQgDvLQ5OljOCV1\/YH\/tYzl8WF+LGPRRj07YoY41t9XjjzZkhjH6nhZfY+QO8CLSM8ut1UDcjsiHMc8wAPPQEAAAAArqoSW\/LCsWqA2PzItFUJsyDF\/wAPCbMzc6YuAdKnWXvUPTIdnPA3H7jAmRegjNLn", + "decrypted-dek": "qApow0AClB0e1zK5u4NLs33LpEbugfQgH5JTYXn2MvY=", + "encryption-context": { + "key_a": "value_a", + "key_b": "value_b", + "aws-crypto-public-key": "A8uv4KIXZR0MhpRndCvY+JuzTQq+jUlZrpV0s9EqMWFchTAwAHUwH+kNK09KpABy0w==" + }, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "38. [Python ESDK] alg=ALG_AES256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; unframed" + }, + { + "ciphertext": "AgR4SKFdV3T67xu9VbyFL2tA8QdxcqAxzgD7Eh13p8M5cMcAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxln6D0fSs0n3QylqECARCAO49ROJ2rH9g3yzoyD2O3CqMCM+vYYYf9LdgxgWSpic1pBiox441RgiXNTen/xE8KH5KFApq7UxF6cVmUAgAACAAVpe6A8Ntdm2I23PgG9j0YsKxz+5AzUVADJtE5fhaQktSunzx3GUVkkZNV9X66ONn/////AAAAAQAAAAAAAAAAAAAAAQAAAAnLTOSn7ir+xUizlogMTuv032ic0uHdwvS+", + "commitment": "FaXugPDbXZtiNtz4BvY9GLCsc/uQM1FQAybROX4WkJI=", + "message-id": "SKFdV3T67xu9VbyFL2tA8QdxcqAxzgD7Eh13p8M5cMc=", + "header": "AgR4SKFdV3T67xu9VbyFL2tA8QdxcqAxzgD7Eh13p8M5cMcAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxln6D0fSs0n3QylqECARCAO49ROJ2rH9g3yzoyD2O3CqMCM+vYYYf9LdgxgWSpic1pBiox441RgiXNTen/xE8KH5KFApq7UxF6cVmUAgAACAAVpe6A8Ntdm2I23PgG9j0YsKxz+5AzUVADJtE5fhaQktSunzx3GUVkkZNV9X66ONk=", + "decrypted-dek": "bg8qt1PWj3LyfQbmltjoZyFAm/Pgs0Ft8YvOwhzEO78=", + "encryption-context": {}, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "39. [Java ESDK] alg=ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY; frameSize=2048" + }, + { + "ciphertext": "AgR489rpKG1gc7jEIpNVoPmEidpm/S6/mMNmpo5DUPtjfvoAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwj+RmdJoCGvYKswYkCARCAO4v0/pYT9NN+9UN1a+Wwt4ME2TQUXCGDLktXwOPTrpzzKbsdey847gJLMscis/PnhJFjIInSl7YXTs4sAQAAAAD4mNRyy2Ilyp28lKHdEvDtfZG56XuJjZUp1tPYfAy0EmkB6jixZ5UADkWCIjCQLYkAAAAAAAAAAAAAAAEAAAAAAAAACaZigN09HSXKB3hv/nlrI7juPtl66ZV0Pz8=", + "commitment": "+JjUcstiJcqdvJSh3RLw7X2Ruel7iY2VKdbT2HwMtBI=", + "message-id": "89rpKG1gc7jEIpNVoPmEidpm/S6/mMNmpo5DUPtjfvo=", + "header": "AgR489rpKG1gc7jEIpNVoPmEidpm/S6/mMNmpo5DUPtjfvoAAAABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwj+RmdJoCGvYKswYkCARCAO4v0/pYT9NN+9UN1a+Wwt4ME2TQUXCGDLktXwOPTrpzzKbsdey847gJLMscis/PnhJFjIInSl7YXTs4sAQAAAAD4mNRyy2Ilyp28lKHdEvDtfZG56XuJjZUp1tPYfAy0EmkB6jixZ5UADkWCIjCQLYk=", + "decrypted-dek": "jUopPfY/IoBzCxwLOuIAMcPIgV8CqHbET1rRxbVLYZc=", + "encryption-context": {}, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "40. [Java ESDK] alg=ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY; unframed" + }, + { + "ciphertext": "AgR4vafj1mv/vUetSfKzMMMdBBhex4G16PjlJ8K+9OByS6IAHgACAARLZXlBAAZWYWx1ZUEABEtleUIABlZhbHVlQgABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyI8tQDiO0m7qOfNg0CARCAO+VgyLtxT+Eb02elXBaCQ3cz55+SvPcqHHUXXuCX8JaWXgq0InC8zDfr6KfTc1itDb86rBsOLEoyAPT9AgAACAASKDlK/F3PcVnpZ/IV03tWNmt60wMWMIxI40arC9KXaLV0vuEnPh6RTqoptrrWITT/////AAAAAQAAAAAAAAAAAAAAAQAAAAkCrP0akjzj2aPe8qr9NwhMueXE1c27YMph", + "commitment": "Eig5Svxdz3FZ6WfyFdN7VjZretMDFjCMSONGqwvSl2g=", + "message-id": "vafj1mv/vUetSfKzMMMdBBhex4G16PjlJ8K+9OByS6I=", + "header": "AgR4vafj1mv/vUetSfKzMMMdBBhex4G16PjlJ8K+9OByS6IAHgACAARLZXlBAAZWYWx1ZUEABEtleUIABlZhbHVlQgABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyI8tQDiO0m7qOfNg0CARCAO+VgyLtxT+Eb02elXBaCQ3cz55+SvPcqHHUXXuCX8JaWXgq0InC8zDfr6KfTc1itDb86rBsOLEoyAPT9AgAACAASKDlK/F3PcVnpZ/IV03tWNmt60wMWMIxI40arC9KXaLV0vuEnPh6RTqoptrrWITQ=", + "decrypted-dek": "MZv4plYYs6q9TOExQ5P+HeH3NOKIRXjiLVv2Vj8LSxQ=", + "encryption-context": { + "KeyB": "ValueB", + "KeyA": "ValueA" + }, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "41. [Java ESDK] alg=ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY; frameSize=2048" + }, + { + "ciphertext": "AgR4bhUk0J5Af3Nq/UkQD6Dqx3ru9xUluYP6CW+318aF9hUAHgACAARLZXlBAAZWYWx1ZUEABEtleUIABlZhbHVlQgABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAw6iQ92YNQm0DM9RKACARCAO/86abwJHCx3jzUuMGFStBVCKwXla3beFh0tsco5I9yuNKahzl60HDzG08n3szNFsCFgSg39Mg47ABiXAQAAAADoJEHTCZXi5+RjdcDY/SDrBc4gcsZIJ7SjSj1D3hsUf6rJ1u+UKD8iaMm0Ae6gVX0AAAAAAAAAAAAAAAEAAAAAAAAACQaK024+LpXF6tV/ekHQSmrC4ZABZTZqUJY=", + "commitment": "6CRB0wmV4ufkY3XA2P0g6wXOIHLGSCe0o0o9Q94bFH8=", + "message-id": "bhUk0J5Af3Nq/UkQD6Dqx3ru9xUluYP6CW+318aF9hU=", + "header": "AgR4bhUk0J5Af3Nq/UkQD6Dqx3ru9xUluYP6CW+318aF9hUAHgACAARLZXlBAAZWYWx1ZUEABEtleUIABlZhbHVlQgABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAw6iQ92YNQm0DM9RKACARCAO/86abwJHCx3jzUuMGFStBVCKwXla3beFh0tsco5I9yuNKahzl60HDzG08n3szNFsCFgSg39Mg47ABiXAQAAAADoJEHTCZXi5+RjdcDY/SDrBc4gcsZIJ7SjSj1D3hsUf6rJ1u+UKD8iaMm0Ae6gVX0=", + "decrypted-dek": "67b7K61ls7BZ76vRXY1Ydl13KvFEtF44Lb8V1A+qaWk=", + "encryption-context": { + "KeyB": "ValueB", + "KeyA": "ValueA" + }, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "42. [Java ESDK] alg=ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY; unframed" + }, + { + "ciphertext": "AgV4kRpnmOGf7OG3OqrPlL6SqR4ZO7nvoOOfkB/NOmJ9VJUAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFtN1Z3eG12OGhoc0kxL2VEUThMTzVna2dyTGw5Y3MvZE5QWWRsL1laMkxjamxCdldxRzVwb2FWaytLM0psejFBUT09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDOj+C2KslbEZcT2PSgIBEIA7K6QwakePtQ0xW+Ip/0cNi7DkMqwTGORmv5asamzXbUKz28JNCInaqW14aIuuwwsm0z3+809CE+ko72MCAAAIAG4FL0GhcP4Vc8AUVzjbvzYhLikm6htqY5pQtOEbLI8vwrg/XVgcMv7hpEMsgmnr1v////8AAAABAAAAAAAAAAAAAAABAAAACWk9CHIOP9+CMH6UJi1wsxwKA2UiLbPKa0oAZzBlAjBN+Qychto+YKgiEJUtrFuWjEs7M+V8UCim4+pN9FtZmiEgchHyQnMSCyvV95kHtFsCMQDs0M1Bjg3s2bfLKsiH+hkJePl+Hasn8bNwPShA54u1eXCnm1mv8+yjNLy19DDXSl4=", + "commitment": "bgUvQaFw/hVzwBRXONu/NiEuKSbqG2pjmlC04Rssjy8=", + "message-id": "kRpnmOGf7OG3OqrPlL6SqR4ZO7nvoOOfkB/NOmJ9VJU=", + "header": "AgV4kRpnmOGf7OG3OqrPlL6SqR4ZO7nvoOOfkB/NOmJ9VJUAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREFtN1Z3eG12OGhoc0kxL2VEUThMTzVna2dyTGw5Y3MvZE5QWWRsL1laMkxjamxCdldxRzVwb2FWaytLM0psejFBUT09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDOj+C2KslbEZcT2PSgIBEIA7K6QwakePtQ0xW+Ip/0cNi7DkMqwTGORmv5asamzXbUKz28JNCInaqW14aIuuwwsm0z3+809CE+ko72MCAAAIAG4FL0GhcP4Vc8AUVzjbvzYhLikm6htqY5pQtOEbLI8vwrg/XVgcMv7hpEMsgmnr1g==", + "decrypted-dek": "N1FQDxFaNrglCmz/yTTVfZVh36k3KvgeKpZuHrvdCbg=", + "encryption-context": { + "aws-crypto-public-key": "Am7Vwxmv8hhsI1/eDQ8LO5gkgrLl9cs/dNPYdl/YZ2LcjlBvWqG5poaVk+K3Jlz1AQ==" + }, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "43. [Java ESDK] alg=ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; frameSize=2048" + }, + { + "ciphertext": "AgV48AP2Janq1bcZ1DrZJtaJNM24UZD9UX7R2/yP+4OzLuMAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF4UWphQ1NXQzkzSWJZV3ZLbFhNUWlybFEyZVBBeUlRcUJ6am1zK1NtZVo4d0txazJyS1lPVHJMOFFqWVVzMVR3Zz09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDDD5mSvuYzwT9/eJ9QIBEIA7vnyXsGXnLURceBmqLlK8soxg/cIstDKW1XCI0OfGY4yhYjjr0l9XwAu0clOYrJdpwJNjYAz8uIpnXs0BAAAAANDc3n2TAsoULoixfm5jipiQ2qABoB2KBteVpJ1xvjgRidbCTV7O5ggCOjqfRt8YcgAAAAAAAAAAAAAAAQAAAAAAAAAJ7o9NINW0Q1jdL6PfMKn+f6izn2bU5QJOcQBnMGUCMBF6eRMvcIRQ+7kb/dxKaFem2GFa0vnpoBclaik6SWH1/J+qSyIwskpIG8yrWvkjDwIxAOSeqMPZX2uZbbExbyWZUE1/rC1f4JqA0wFTkWPTEIkfu9YCo2ZhdZrVm1xeqjmr0g==", + "commitment": "0NzefZMCyhQuiLF+bmOKmJDaoAGgHYoG15WknXG+OBE=", + "message-id": "8AP2Janq1bcZ1DrZJtaJNM24UZD9UX7R2/yP+4OzLuM=", + "header": "AgV48AP2Janq1bcZ1DrZJtaJNM24UZD9UX7R2/yP+4OzLuMAXwABABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF4UWphQ1NXQzkzSWJZV3ZLbFhNUWlybFEyZVBBeUlRcUJ6am1zK1NtZVo4d0txazJyS1lPVHJMOFFqWVVzMVR3Zz09AAEAB2F3cy1rbXMAS2Fybjphd3M6a21zOnVzLXdlc3QtMjo2NTg5NTY2MDA4MzM6a2V5L2IzNTM3ZWYxLWQ4ZGMtNDc4MC05ZjVhLTU1Nzc2Y2JiMmY3ZgCnAQEBAHhA84wnXjEJdBbBBylRUFcZZK2j7xwh6UyLoL28nQ+0FAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDDD5mSvuYzwT9/eJ9QIBEIA7vnyXsGXnLURceBmqLlK8soxg/cIstDKW1XCI0OfGY4yhYjjr0l9XwAu0clOYrJdpwJNjYAz8uIpnXs0BAAAAANDc3n2TAsoULoixfm5jipiQ2qABoB2KBteVpJ1xvjgRidbCTV7O5ggCOjqfRt8Ycg==", + "decrypted-dek": "xhOuDy6HPNHtVzACWDor5m2KyT69vFGsv3wRP0OMJG0=", + "encryption-context": { + "aws-crypto-public-key": "AxQjaCSWC93IbYWvKlXMQirlQ2ePAyIQqBzjms+SmeZ8wKqk2rKYOTrL8QjYUs1Twg==" + }, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "44. [Java ESDK] alg=ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; unframed" + }, + { + "ciphertext": "AgV4wPGtUdkHghbL8J2/2JO48DSSazj5Z2HLrY1NsW0XKnUAewADAARLZXlBAAZWYWx1ZUEABEtleUIABlZhbHVlQgAVYXdzLWNyeXB0by1wdWJsaWMta2V5AERBMnJLdFI0Q082NWtiWEttaVlQY3VXZEk0aXJ2OUJZWGV5RXJzdFp6VzU1eDZNOGFBZTJSR1hnZjF5Q1Y2cmZiZGc9PQABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxM5zuF0GHdLiOWtw0CARCAOyep7YpLEJtixgbgjRDDP15ffP7C9Sma9PC1T55rcDTBKiArCbu4zH849ZPSzc+zeeHcSDc1LZOtCFUGAgAACADpb8LgUoW6cWWE7VLLY1Yp/w3r9hw02ZPVxxXc4WrPwp6oUhEnIzWOoXWnOF+KX5//////AAAAAQAAAAAAAAAAAAAAAQAAAAnFSX0svHUHX5oYM5mZeQH+lI/B9cYxRX2EAGcwZQIwTWCrBLIkRLWjWpKmdbpKSexC4w7UJlR0SMud5kym5+Hs9+VFho3PblnEQ5qXtS5RAjEApDNPjapvgGdC5t+ti8L6NFjFhrsc1fROpUiNno2A86+QjLUQe0BuRoPmHlPWl0ez", + "commitment": "6W/C4FKFunFlhO1Sy2NWKf8N6/YcNNmT1ccV3OFqz8I=", + "message-id": "wPGtUdkHghbL8J2/2JO48DSSazj5Z2HLrY1NsW0XKnU=", + "header": "AgV4wPGtUdkHghbL8J2/2JO48DSSazj5Z2HLrY1NsW0XKnUAewADAARLZXlBAAZWYWx1ZUEABEtleUIABlZhbHVlQgAVYXdzLWNyeXB0by1wdWJsaWMta2V5AERBMnJLdFI0Q082NWtiWEttaVlQY3VXZEk0aXJ2OUJZWGV5RXJzdFp6VzU1eDZNOGFBZTJSR1hnZjF5Q1Y2cmZiZGc9PQABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxM5zuF0GHdLiOWtw0CARCAOyep7YpLEJtixgbgjRDDP15ffP7C9Sma9PC1T55rcDTBKiArCbu4zH849ZPSzc+zeeHcSDc1LZOtCFUGAgAACADpb8LgUoW6cWWE7VLLY1Yp/w3r9hw02ZPVxxXc4WrPwp6oUhEnIzWOoXWnOF+KX58=", + "decrypted-dek": "vxGKyd1THdpAdVHmYdoLwsY7Vw9C6pGQLhdVwarE0Lw=", + "encryption-context": { + "KeyB": "ValueB", + "KeyA": "ValueA", + "aws-crypto-public-key": "A2rKtR4CO65kbXKmiYPcuWdI4irv9BYXeyErstZzW55x6M8aAe2RGXgf1yCV6rfbdg==" + }, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "45. [Java ESDK] alg=ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; frameSize=2048" + }, + { + "ciphertext": "AgV4NIrbgvJHnrCNOqOP2FRfO9GITBLJd8xbEvhJu3WNCUAAewADAARLZXlBAAZWYWx1ZUEABEtleUIABlZhbHVlQgAVYXdzLWNyeXB0by1wdWJsaWMta2V5AERBd1NZQ21RODYybmVKRUlBRU9YczRqVGIzVlBLL1pwbWtOSkR6eGFRaVVjR0VTUGxZekd1K3cxQkt2cjhIOWlYemc9PQABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwcMP008tCOAHWwKMQCARCAO6JddAzG8rjYhkiifNg+u9CV3YrfgX3/WJLQVfruPM8rAY6b0h1CA59q9287F3wAz4TdBDvOJx25OjL0AQAAAAB/DPmV33Jz8DtfV/0gBSp21AE5JTdylNEwTsUcnHyofXYjPvkvq6aJCt3gWOLtnSkAAAAAAAAAAAAAAAEAAAAAAAAACaX0vpCBWsCd0SKJWqWLPmqKgG2g961qBIoAZzBlAjEAo3oAliRCOvhExMFaAtgdHJhguUdlEKE8HYGEbCWILUcmY3pdQEZWF9av1eMGOT5uAjBeKCuCrzI496vqYC3eChefw4J7bGrWTcUZVwCbijJV/VehvV8WoH1kds8esTZl24Y=", + "commitment": "fwz5ld9yc/A7X1f9IAUqdtQBOSU3cpTRME7FHJx8qH0=", + "message-id": "NIrbgvJHnrCNOqOP2FRfO9GITBLJd8xbEvhJu3WNCUA=", + "header": "AgV4NIrbgvJHnrCNOqOP2FRfO9GITBLJd8xbEvhJu3WNCUAAewADAARLZXlBAAZWYWx1ZUEABEtleUIABlZhbHVlQgAVYXdzLWNyeXB0by1wdWJsaWMta2V5AERBd1NZQ21RODYybmVKRUlBRU9YczRqVGIzVlBLL1pwbWtOSkR6eGFRaVVjR0VTUGxZekd1K3cxQkt2cjhIOWlYemc9PQABAAdhd3Mta21zAEthcm46YXdzOmttczp1cy13ZXN0LTI6NjU4OTU2NjAwODMzOmtleS9iMzUzN2VmMS1kOGRjLTQ3ODAtOWY1YS01NTc3NmNiYjJmN2YApwEBAQB4QPOMJ14xCXQWwQcpUVBXGWSto+8cIelMi6C9vJ0PtBQAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwcMP008tCOAHWwKMQCARCAO6JddAzG8rjYhkiifNg+u9CV3YrfgX3/WJLQVfruPM8rAY6b0h1CA59q9287F3wAz4TdBDvOJx25OjL0AQAAAAB/DPmV33Jz8DtfV/0gBSp21AE5JTdylNEwTsUcnHyofXYjPvkvq6aJCt3gWOLtnSk=", + "decrypted-dek": "k/f3Ora/LfrKWi/gAkrVUJy7BTVeotHdFR4fTPk2Lc8=", + "encryption-context": { + "KeyB": "ValueB", + "KeyA": "ValueA", + "aws-crypto-public-key": "AwSYCmQ862neJEIAEOXs4jTb3VPK/ZpmkNJDzxaQiUcGESPlYzGu+w1BKvr8H9iXzg==" + }, + "exception": null, + "plaintext-frames": [ + "testing12" + ], + "status": true, + "keyring-type": "aws-kms", + "comment": "46. [Java ESDK] alg=ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384; unframed" + } + ] +} \ No newline at end of file diff --git a/test/unit/test_algorithm_suite.py b/test/unit/test_algorithm_suite.py new file mode 100644 index 000000000..0176a9f8f --- /dev/null +++ b/test/unit/test_algorithm_suite.py @@ -0,0 +1,49 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for aws_encryption_sdk.identifiers.AlgorithmSuite.""" +import pytest + +from aws_encryption_sdk.identifiers import AlgorithmSuite + + +@pytest.mark.parametrize( + "suite", + ( + AlgorithmSuite.AES_256_GCM_HKDF_SHA512_COMMIT_KEY, + AlgorithmSuite.AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384, + ), +) +def test_committing_suites_properties(suite): + assert suite.is_committing() + assert suite.message_format_version == 0x02 + assert suite.message_id_length() == 32 + + +@pytest.mark.parametrize( + "suite", + ( + AlgorithmSuite.AES_128_GCM_IV12_TAG16, + AlgorithmSuite.AES_192_GCM_IV12_TAG16, + AlgorithmSuite.AES_256_GCM_IV12_TAG16, + AlgorithmSuite.AES_128_GCM_IV12_TAG16_HKDF_SHA256, + AlgorithmSuite.AES_192_GCM_IV12_TAG16_HKDF_SHA256, + AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA256, + AlgorithmSuite.AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256, + AlgorithmSuite.AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + AlgorithmSuite.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + ), +) +def test_noncommitting_suites_properties(suite): + assert not suite.is_committing() + assert suite.message_format_version == 0x01 + assert suite.message_id_length() == 16 diff --git a/test/unit/test_arn.py b/test/unit/test_arn.py new file mode 100644 index 000000000..bdb796f2e --- /dev/null +++ b/test/unit/test_arn.py @@ -0,0 +1,78 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for aws_encryption_sdk.internal.arn functions.""" +import pytest + +from aws_encryption_sdk.exceptions import MalformedArnError +from aws_encryption_sdk.internal.arn import Arn + + +class TestArn(object): + def test_malformed_arn_missing_arn(self): + arn = "aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + + with pytest.raises(MalformedArnError) as excinfo: + Arn.from_str(arn) + excinfo.match("Resource {} could not be parsed as an ARN".format(arn)) + + def test_malformed_arn_missing_service(self): + arn = "aws:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + + with pytest.raises(MalformedArnError) as excinfo: + Arn.from_str(arn) + excinfo.match("Resource {} could not be parsed as an ARN".format(arn)) + + def test_malformed_arn_missing_region(self): + arn = "arn:aws:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + + with pytest.raises(MalformedArnError) as excinfo: + Arn.from_str(arn) + excinfo.match("Resource {} could not be parsed as an ARN".format(arn)) + + def test_malformed_arn_missing_account(self): + arn = "arn:aws:us-east-1:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + + with pytest.raises(MalformedArnError) as excinfo: + Arn.from_str(arn) + excinfo.match("Resource {} could not be parsed as an ARN".format(arn)) + + def test_malformed_arn_missing_resource_type(self): + arn = "arn:aws:us-east-1:222222222222" + + with pytest.raises(MalformedArnError) as excinfo: + Arn.from_str(arn) + excinfo.match("Resource {} could not be parsed as an ARN".format(arn)) + + def test_parse_key_arn_success(self): + arn_str = "arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + + arn = Arn.from_str(arn_str) + + assert arn.partition == "aws" + assert arn.service == "kms" + assert arn.region == "us-east-1" + assert arn.account_id == "222222222222" + assert arn.resource_type == "key" + assert arn.resource_id == "aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + + def test_parse_alias_arn_success(self): + arn_str = "arn:aws:kms:us-east-1:222222222222:alias/aws/service" + + arn = Arn.from_str(arn_str) + + assert arn.partition == "aws" + assert arn.service == "kms" + assert arn.region == "us-east-1" + assert arn.account_id == "222222222222" + assert arn.resource_type == "alias" + assert arn.resource_id == "aws/service" diff --git a/test/unit/test_aws_encryption_sdk.py b/test/unit/test_aws_encryption_sdk.py index 38dfff85a..dc3976b77 100644 --- a/test/unit/test_aws_encryption_sdk.py +++ b/test/unit/test_aws_encryption_sdk.py @@ -11,6 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Unit test suite for high-level functions in aws_encryption_sdk module""" +import warnings + import pytest from mock import MagicMock, patch, sentinel @@ -78,3 +80,24 @@ def test_stream_unknown(self): with pytest.raises(ValueError) as excinfo: aws_encryption_sdk.stream(mode="ERROR", a=sentinel.a, b=sentinel.b, c=sentinel.b) excinfo.match("Unsupported mode: *") + + def test_encrypt_deprecation_warning(self): + warnings.simplefilter("error") + + with pytest.raises(DeprecationWarning) as excinfo: + aws_encryption_sdk.encrypt(source=sentinel.a, key_provider=sentinel.b) + excinfo.match("This method is deprecated and will be removed in a future version") + + def test_decrypt_deprecation_warning(self): + warnings.simplefilter("error") + + with pytest.raises(DeprecationWarning) as excinfo: + aws_encryption_sdk.decrypt(source=sentinel.a, key_provider=sentinel.b) + excinfo.match("This method is deprecated and will be removed in a future version") + + def test_stream_deprecation_warning(self): + warnings.simplefilter("error") + + with pytest.raises(DeprecationWarning) as excinfo: + aws_encryption_sdk.stream(mode="e", source=sentinel.a, key_provider=sentinel.b) + excinfo.match("This method is deprecated and will be removed in a future version") diff --git a/test/unit/test_caches.py b/test/unit/test_caches.py index 250ad6d5b..8f7d32280 100644 --- a/test/unit/test_caches.py +++ b/test/unit/test_caches.py @@ -25,7 +25,7 @@ build_decryption_materials_cache_key, build_encryption_materials_cache_key, ) -from aws_encryption_sdk.identifiers import Algorithm +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy from aws_encryption_sdk.materials_managers import DecryptionMaterialsRequest, EncryptionMaterialsRequest from aws_encryption_sdk.structures import DataKey, MasterKeyInfo @@ -74,6 +74,7 @@ "partition_name": VALUES["basic"]["partition_name"], "algorithm": None, "encryption_context": VALUES["basic"]["encryption_context"]["empty"]["raw"], + "commitment_policy": CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, }, "id": b"rkrFAso1YyPbOJbmwVMjrPw+wwLJT7xusn8tA8zMe9e3+OqbtfDueB7bvoKLU3fsmdUvZ6eMt7mBp1ThMMB25Q==", }, @@ -82,6 +83,7 @@ "partition_name": VALUES["basic"]["partition_name"], "algorithm": Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, "encryption_context": VALUES["basic"]["encryption_context"]["empty"]["raw"], + "commitment_policy": CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, }, "id": b"3icBIkLK4V3fVwbm3zSxUdUQV6ZvZYUOLl8buN36g6gDMqAkghcGryxX7QiVABkW1JhB6GRp5z+bzbiuciBcKQ==", }, @@ -90,6 +92,7 @@ "partition_name": VALUES["basic"]["partition_name"], "algorithm": None, "encryption_context": VALUES["basic"]["encryption_context"]["full"]["raw"], + "commitment_policy": CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, }, "id": b"IHiUHYOUVUEFTc3BcZPJDlsWct2Qy1A7JdfQl9sQoV/ILIbRpoz9q7RtGd/MlibaGl5ihE66cN8ygM8A5rtYbg==", }, @@ -98,6 +101,7 @@ "partition_name": VALUES["basic"]["partition_name"], "algorithm": Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, "encryption_context": VALUES["basic"]["encryption_context"]["full"]["raw"], + "commitment_policy": CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, }, "id": b"mRNK7qhTb/kJiiyGPgAevp0gwFRcET4KeeNYwZHhoEDvSUzQiDgl8Of+YRDaVzKxAqpNBgcAuFXde9JlaRRsmw==", }, @@ -109,6 +113,7 @@ "algorithm": Algorithm.AES_128_GCM_IV12_TAG16_HKDF_SHA256, "encrypted_data_keys": set([VALUES["basic"]["encrypted_data_keys"][0]["key"]]), "encryption_context": VALUES["basic"]["encryption_context"]["empty"]["raw"], + "commitment_policy": CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, }, "id": b"n0zVzk9QIVxhz6ET+aJIKKOJNxtpGtSe1yAbu7WU5l272Iw/jmhlER4psDHJs9Mr8KYiIvLGSXzggNDCc23+9w==", }, @@ -118,6 +123,7 @@ "algorithm": Algorithm.AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, "encrypted_data_keys": {entry["key"] for entry in VALUES["basic"]["encrypted_data_keys"]}, "encryption_context": VALUES["basic"]["encryption_context"]["full"]["raw"], + "commitment_policy": CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, }, "id": b"+rtwUe38CGnczGmYu12iqGWHIyDyZ44EvYQ4S6ACmsgS8VaEpiw0RTGpDk6Z/7YYN/jVHOAcNKDyCNP8EmstFg==", }, @@ -144,6 +150,7 @@ def test_encryption_context_hash(encryption_context, result): encryption_context=scenario["components"]["encryption_context"], frame_length=0, algorithm=scenario["components"]["algorithm"], + commitment_policy=scenario["components"]["commitment_policy"], ), ), scenario["id"], @@ -181,6 +188,7 @@ def test_encrypted_data_keys_hash(encrypted_data_keys, result): algorithm=scenario["components"]["algorithm"], encrypted_data_keys=scenario["components"]["encrypted_data_keys"], encryption_context=scenario["components"]["encryption_context"], + commitment_policy=scenario["components"]["commitment_policy"], ), ), scenario["id"], diff --git a/test/unit/test_caches_crypto_cache_entry.py b/test/unit/test_caches_crypto_cache_entry.py index 191d0bab9..fd1737b3e 100644 --- a/test/unit/test_caches_crypto_cache_entry.py +++ b/test/unit/test_caches_crypto_cache_entry.py @@ -35,8 +35,6 @@ @pytest.yield_fixture def patch_time(mocker): mocker.patch.object(aws_encryption_sdk.caches.time, "time") - aws_encryption_sdk.caches.time.time.side_effect = (3.0, 10.0) - yield 7.0 @pytest.mark.parametrize( @@ -70,13 +68,16 @@ def test_crypto_cache_entry_valid_attributes(valid_kwargs_overrides): def test_crypto_cache_entry_init(patch_time): + mock_creation_time = 3.0 + aws_encryption_sdk.caches.time.time.return_value = mock_creation_time + entry = CryptoMaterialsCacheEntry( cache_key=b"ex_cache_key", value=MagicMock(__class__=EncryptionMaterials), hints=CryptoMaterialsCacheEntryHints(lifetime=10.0), ) - assert entry.creation_time == 3.0 + assert entry.creation_time == mock_creation_time assert entry.bytes_encrypted == 0 assert entry.messages_encrypted == 0 assert entry.valid @@ -104,9 +105,13 @@ def test_crypto_cache_entry_setattr(): def test_crypto_cache_entry_age(patch_time): + mock_creation_time = 5 + mock_check_time = 10 + aws_encryption_sdk.caches.time.time.return_value = mock_creation_time entry = CryptoMaterialsCacheEntry(**_VALID_KWARGS["CryptoMaterialsCacheEntry"]) + aws_encryption_sdk.caches.time.time.return_value = mock_check_time - assert entry.age == patch_time + assert entry.age == mock_check_time - mock_creation_time def test_crypto_cache_entry_is_too_old_no_lifetime_hint(patch_time): @@ -119,11 +124,15 @@ def test_crypto_cache_entry_is_too_old_no_lifetime_hint(patch_time): @pytest.mark.parametrize("age_modifier, result", ((-1.0, True), (0.0, False), (1.0, False))) def test_crypto_cache_entry_is_too_old(patch_time, age_modifier, result): - lifetime = patch_time + age_modifier + mock_creation_time = 1 + aws_encryption_sdk.caches.time.time.return_value = mock_creation_time + lifetime = mock_creation_time + age_modifier kwargs = _VALID_KWARGS["CryptoMaterialsCacheEntry"].copy() kwargs["hints"] = MagicMock(__class__=CryptoMaterialsCacheEntryHints, lifetime=lifetime) entry = CryptoMaterialsCacheEntry(**kwargs) + assert entry.creation_time == mock_creation_time + aws_encryption_sdk.caches.time.time.return_value = mock_creation_time + 1 if result: assert entry.is_too_old() else: diff --git a/test/unit/test_caches_local.py b/test/unit/test_caches_local.py index db2b21ff1..a0ec14f1a 100644 --- a/test/unit/test_caches_local.py +++ b/test/unit/test_caches_local.py @@ -305,9 +305,7 @@ def test_get_encryption_materials(patch_get_single_entry): test = cache.get_encryption_materials(cache_key=sentinel.cache_key, plaintext_length=plaintext_length) patch_get_single_entry.assert_called_once_with(sentinel.cache_key) - patch_get_single_entry.return_value._update_with_message_bytes_encrypted.assert_called_once_with( - plaintext_length - ) + patch_get_single_entry.return_value._update_with_message_bytes_encrypted.assert_called_once_with(plaintext_length) assert test is patch_get_single_entry.return_value diff --git a/test/unit/test_commitment.py b/test/unit/test_commitment.py new file mode 100644 index 000000000..ce18d337c --- /dev/null +++ b/test/unit/test_commitment.py @@ -0,0 +1,44 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit testing suite for commitment utility functions""" +import pytest +from mock import MagicMock + +from aws_encryption_sdk.exceptions import ActionNotAllowedError +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy +from aws_encryption_sdk.internal.utils.commitment import TROUBLESHOOTING_URL, validate_commitment_policy_on_encrypt + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +def test_on_encrypt_committing_algorithm_policy_forbids(): + """Checks that validate_commitment_policy_on_encrypt with a committing algorithm and a policy that does not allow + commitment fails.""" + algorithm = MagicMock(__class__=Algorithm) + algorithm.is_committing.return_value = True + + with pytest.raises(ActionNotAllowedError) as excinfo: + validate_commitment_policy_on_encrypt(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, algorithm) + excinfo.match( + "Configuration conflict. Cannot encrypt due to .* requiring only non-committed messages. Algorithm ID was .*. " + "See: " + TROUBLESHOOTING_URL + ) + + +def test_on_encrypt_uncommitting_algorithm_policy_allows(): + """Checks that validate_commitment_policy_on_encrypt with an uncommitting algorithm and a policy that does not + require commitment succeeds.""" + algorithm = MagicMock(__class__=Algorithm) + algorithm.is_committing.return_value = False + + validate_commitment_policy_on_encrypt(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, algorithm) diff --git a/test/unit/test_crypto_data_keys.py b/test/unit/test_crypto_data_keys.py index 0ceccb5b4..2e8649317 100644 --- a/test/unit/test_crypto_data_keys.py +++ b/test/unit/test_crypto_data_keys.py @@ -15,28 +15,28 @@ from mock import MagicMock, sentinel from pytest_mock import mocker # noqa pylint: disable=unused-import -import aws_encryption_sdk.internal.crypto.data_keys -from aws_encryption_sdk.internal.crypto.data_keys import derive_data_encryption_key +import aws_encryption_sdk.internal.crypto.data_keys as data_keys pytestmark = [pytest.mark.unit, pytest.mark.local] @pytest.yield_fixture def patch_default_backend(mocker): - mocker.patch.object(aws_encryption_sdk.internal.crypto.data_keys, "default_backend") - yield aws_encryption_sdk.internal.crypto.data_keys.default_backend + mocker.patch.object(data_keys, "default_backend") + yield data_keys.default_backend @pytest.yield_fixture def patch_struct(mocker): - mocker.patch.object(aws_encryption_sdk.internal.crypto.data_keys, "struct") - yield aws_encryption_sdk.internal.crypto.data_keys.struct + mocker.patch.object(data_keys, "struct") + yield data_keys.struct def test_derive_data_encryption_key_with_hkdf(patch_default_backend, patch_struct): algorithm = MagicMock() algorithm.kdf_hash_type.return_value = sentinel.kdf_hash_type - test = derive_data_encryption_key( + algorithm.is_committing.return_value = False + test = data_keys.derive_data_encryption_key( source_key=sentinel.source_key, algorithm=algorithm, message_id=sentinel.message_id ) patch_struct.pack.assert_called_with(">H16s", algorithm.algorithm_id, sentinel.message_id) @@ -51,9 +51,48 @@ def test_derive_data_encryption_key_with_hkdf(patch_default_backend, patch_struc assert test == algorithm.kdf_type.return_value.derive.return_value -def test_derive_data_encryption_key_no_hkdf(patch_default_backend): +def test_derive_data_encryption_key_with_hkdf_committing(patch_default_backend, patch_struct): + algorithm = MagicMock() + algorithm.kdf_hash_type.return_value = sentinel.kdf_hash_type + algorithm.data_key_len = sentinel.data_key_len + algorithm.is_committing.return_value = True + test = data_keys.derive_data_encryption_key( + source_key=sentinel.source_key, algorithm=algorithm, message_id=sentinel.message_id + ) + patch_struct.pack.assert_called_with(">H9s", algorithm.algorithm_id, data_keys.KEY_LABEL) + algorithm.kdf_type.assert_called_with( + algorithm=sentinel.kdf_hash_type, + length=sentinel.data_key_len, + salt=sentinel.message_id, + info=patch_struct.pack.return_value, + backend=patch_default_backend.return_value, + ) + algorithm.kdf_type.return_value.derive.assert_called_with(sentinel.source_key) + assert test == algorithm.kdf_type.return_value.derive.return_value + + +def test_derive_data_encryption_key_no_hkdf(): algorithm = MagicMock(kdf_type=None) - test = derive_data_encryption_key( + test = test = data_keys.derive_data_encryption_key( source_key=sentinel.source_key, algorithm=algorithm, message_id=sentinel.message_id ) assert test == sentinel.source_key + + +def test_calculate_commitment_key(patch_default_backend, patch_struct): + algorithm = MagicMock() + algorithm.kdf_hash_type.return_value = sentinel.kdf_hash_type + algorithm.is_committing.return_value = False + test = test = data_keys.calculate_commitment_key( + source_key=sentinel.source_key, algorithm=algorithm, message_id=sentinel.message_id + ) + patch_struct.pack.assert_called_with(">9s", data_keys.COMMIT_LABEL) + algorithm.kdf_type.assert_called_with( + algorithm=sentinel.kdf_hash_type, + length=data_keys.L_C, + salt=sentinel.message_id, + info=patch_struct.pack.return_value, + backend=patch_default_backend.return_value, + ) + algorithm.kdf_type.return_value.derive.assert_called_with(sentinel.source_key) + assert test == algorithm.kdf_type.return_value.derive.return_value diff --git a/test/unit/test_crypto_elliptic_curve.py b/test/unit/test_crypto_elliptic_curve.py index b030db5c2..d1c627dc0 100644 --- a/test/unit/test_crypto_elliptic_curve.py +++ b/test/unit/test_crypto_elliptic_curve.py @@ -92,9 +92,9 @@ def patch_prehashed(mocker): def test_ecc_curve_not_in_cryptography(): """If this test fails, then this pull or similar has gone through - and this library should be updated to use the ECC curve - parameters from cryptography. - https://github.com/pyca/cryptography/pull/2499 + and this library should be updated to use the ECC curve + parameters from cryptography. + https://github.com/pyca/cryptography/pull/2499 """ assert not hasattr(ec.SECP384R1, "a") diff --git a/test/unit/test_deserialize.py b/test/unit/test_deserialize.py index 8a96ea4ca..cef66b986 100644 --- a/test/unit/test_deserialize.py +++ b/test/unit/test_deserialize.py @@ -20,7 +20,7 @@ import aws_encryption_sdk.internal.formatting.deserialize from aws_encryption_sdk.exceptions import NotSupportedError, SerializationError, UnknownIdentityError -from aws_encryption_sdk.identifiers import AlgorithmSuite +from aws_encryption_sdk.identifiers import AlgorithmSuite, SerializationVersion from aws_encryption_sdk.internal.structures import EncryptedData from .test_values import VALUES @@ -86,7 +86,7 @@ def apply_fixtures(self): def test_validate_header_valid(self): """Validate that the validate_header function behaves - as expected for a valid header. + as expected for a valid header. """ self.mock_bytesio.read.return_value = VALUES["header"] self.mock_decrypt.return_value = sentinel.decrypted @@ -105,7 +105,7 @@ def test_validate_header_valid(self): def test_validate_header_invalid(self): """Validate that the validate_header function behaves - as expected for a valid header. + as expected for a valid header. """ self.mock_decrypt.side_effect = InvalidTag() with pytest.raises(SerializationError) as excinfo: @@ -119,7 +119,7 @@ def test_validate_header_invalid(self): def test_deserialize_header_unknown_object_type(self): """Validate that the deserialize_header function behaves - as expected for an unknown object type. + as expected for an unknown object type. """ with pytest.raises(NotSupportedError) as excinfo: stream = io.BytesIO(VALUES["serialized_header_invalid_object_type"]) @@ -128,7 +128,7 @@ def test_deserialize_header_unknown_object_type(self): def test_deserialize_header_unknown_version(self): """Validate that the deserialize_header function behaves - as expected for an unknown message version. + as expected for an unknown message version. """ with pytest.raises(NotSupportedError) as excinfo: stream = io.BytesIO(VALUES["serialized_header_invalid_version"]) @@ -138,7 +138,7 @@ def test_deserialize_header_unknown_version(self): @patch("aws_encryption_sdk.internal.formatting.deserialize.AlgorithmSuite.get_by_id") def test_deserialize_header_unsupported_data_encryption_algorithm(self, mock_algorithm_get): """Validate that the deserialize_header function behaves - as expected for an unsupported/disallowed algorithm. + as expected for an unsupported/disallowed algorithm. """ mock_unsupported_algorithm = MagicMock() mock_unsupported_algorithm.allowed = False @@ -151,7 +151,7 @@ def test_deserialize_header_unsupported_data_encryption_algorithm(self, mock_alg @patch("aws_encryption_sdk.internal.formatting.deserialize.AlgorithmSuite.get_by_id") def test_deserialize_header_unknown_data_encryption_algorithm(self, mock_algorithm_get): """Validate that the deserialize_header function behaves - as expected for an unknown algorithm. + as expected for an unknown algorithm. """ mock_algorithm_get.side_effect = KeyError() with pytest.raises(UnknownIdentityError) as excinfo: @@ -161,7 +161,7 @@ def test_deserialize_header_unknown_data_encryption_algorithm(self, mock_algorit def test_deserialize_header_unknown_content_type(self): """Validate that the deserialize_header function behaves - as expected for an unknown content type. + as expected for an unknown content type. """ with pytest.raises(UnknownIdentityError) as excinfo: stream = io.BytesIO(VALUES["serialized_header_unknown_content_type"]) @@ -170,8 +170,8 @@ def test_deserialize_header_unknown_content_type(self): def test_deserialize_header_invalid_reserved_space(self): """Validate that the deserialize_header function behaves - as expected for an invalid value in the reserved - space (formerly content AAD). + as expected for an invalid value in the reserved + space (formerly content AAD). """ with pytest.raises(SerializationError) as excinfo: stream = io.BytesIO(VALUES["serialized_header_bad_reserved_space"]) @@ -180,8 +180,8 @@ def test_deserialize_header_invalid_reserved_space(self): def test_deserialize_header_bad_iv_len(self): """Validate that the deserialize_header function behaves - as expected for bad IV length (incompatible with - specified algorithm). + as expected for bad IV length (incompatible with + specified algorithm). """ with pytest.raises(SerializationError) as excinfo: stream = io.BytesIO(VALUES["serialized_header_bad_iv_len"]) @@ -190,8 +190,8 @@ def test_deserialize_header_bad_iv_len(self): def test_deserialize_header_framed_bad_frame_length(self): """Validate that the deserialize_header function behaves - as expected for bad frame length values (greater than - the default maximum). + as expected for bad frame length values (greater than + the default maximum). """ with pytest.raises(SerializationError) as excinfo: stream = io.BytesIO(VALUES["serialized_header_bad_frame_len"]) @@ -200,36 +200,57 @@ def test_deserialize_header_framed_bad_frame_length(self): def test_deserialize_header_non_framed_bad_frame_length(self): """Validate that the deserialize_header function behaves - as expected for bad frame length values for non-framed - messages (non-zero). + as expected for bad frame length values for non-framed + messages (non-zero). """ with pytest.raises(SerializationError) as excinfo: stream = io.BytesIO(VALUES["serialized_non_framed_header_bad_frame_len"]) aws_encryption_sdk.internal.formatting.deserialize.deserialize_header(stream) excinfo.match("Non-zero frame length found for non-framed message") - def test_deserialize_header_valid(self): + def test_deserialize_header_v1_valid(self): """Validate that the deserialize_header function behaves - as expected for a valid header. + as expected for a valid header. """ stream = io.BytesIO(VALUES["serialized_header_small_frame"]) test_header, test_raw_header = aws_encryption_sdk.internal.formatting.deserialize.deserialize_header(stream) assert test_header == VALUES["deserialized_header_frame"] assert test_raw_header == VALUES["serialized_header_small_frame"] + def test_deserialize_header_v2_valid(self): + """Validate that the deserialize_header function behaves + as expected for a valid header. + """ + stream = io.BytesIO(VALUES["serialized_header_v2_committing"]) + test_header, test_raw_header = aws_encryption_sdk.internal.formatting.deserialize.deserialize_header(stream) + assert test_header == VALUES["deserialized_header_v2_committing"] + assert test_raw_header == VALUES["serialized_header_v2_committing"] + def test_deserialize_header_auth(self): """Validate that the deserialize_header_auth function - behaves as expected for a valid header auth. + behaves as expected for a valid header auth. """ stream = io.BytesIO(VALUES["serialized_header_auth"]) test = aws_encryption_sdk.internal.formatting.deserialize.deserialize_header_auth( - stream=stream, algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16 + version=SerializationVersion.V1, stream=stream, algorithm=AlgorithmSuite.AES_256_GCM_IV12_TAG16 ) assert test == VALUES["deserialized_header_auth_block"] + def test_deserialize_header_auth_v2(self): + """Validate that the deserialize_header_auth function + behaves as expected for a valid header auth. + """ + stream = io.BytesIO(VALUES["serialized_header_auth_v2"]) + test = aws_encryption_sdk.internal.formatting.deserialize.deserialize_header_auth( + version=SerializationVersion.V2, + stream=stream, + algorithm=AlgorithmSuite.AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384, + ) + assert test == VALUES["deserialized_header_auth_block_v2"] + def test_deserialize_body_frame_standard(self): """Validate that the deserialize_body_frame function - behaves as expected for a valid body frame. + behaves as expected for a valid body frame. """ stream = io.BytesIO(VALUES["serialized_frame"]) test_body, test_final = aws_encryption_sdk.internal.formatting.deserialize.deserialize_frame( @@ -240,7 +261,7 @@ def test_deserialize_body_frame_standard(self): def test_deserialize_body_frame_final(self): """Validate that the deserialize_body_frame function - behaves as expected for a valid final body frame. + behaves as expected for a valid final body frame. """ stream = io.BytesIO(VALUES["serialized_final_frame"]) test_body, test_final = aws_encryption_sdk.internal.formatting.deserialize.deserialize_frame( @@ -251,7 +272,7 @@ def test_deserialize_body_frame_final(self): def test_deserialize_body_frame_final_invalid_final_frame_length(self): """Validate that the deserialize_body_frame function - behaves as expected for a valid final body frame. + behaves as expected for a valid final body frame. """ stream = io.BytesIO(VALUES["serialized_final_frame_bad_length"]) with pytest.raises(SerializationError) as excinfo: @@ -262,7 +283,7 @@ def test_deserialize_body_frame_final_invalid_final_frame_length(self): def test_deserialize_footer_no_verifier(self): """Vaidate that the deserialize_footer function behaves - as expected when called with no verifier. + as expected when called with no verifier. """ stream = io.BytesIO(VALUES["serialized_footer"]) test = aws_encryption_sdk.internal.formatting.deserialize.deserialize_footer(stream) @@ -270,7 +291,7 @@ def test_deserialize_footer_no_verifier(self): def test_deserialize_footer(self): """Vaidate that the deserialize_footer function behaves - as expected when called with a verifier. + as expected when called with a verifier. """ stream = io.BytesIO(VALUES["serialized_footer"]) test = aws_encryption_sdk.internal.formatting.deserialize.deserialize_footer(stream, self.mock_verifier) @@ -279,8 +300,8 @@ def test_deserialize_footer(self): def test_deserialize_footer_verifier_no_footer(self): """Vaidate that the deserialize_footer function behaves - as expected when called with a verifier but a message - with no footer. + as expected when called with a verifier but a message + with no footer. """ stream = io.BytesIO(b"") with pytest.raises(SerializationError) as excinfo: @@ -305,8 +326,8 @@ def test_unpack_values(self, mock_struct): @patch("aws_encryption_sdk.internal.formatting.deserialize.struct") def test_unpack_values_no_verifier(self, mock_struct): """Validate that the unpack_values function - behaves as expected when no verifier is - provided. + behaves as expected when no verifier is + provided. """ self.mock_bytesio.read.return_value = sentinel.message_bytes mock_struct.calcsize.return_value = sentinel.size diff --git a/test/unit/test_encryption_client.py b/test/unit/test_encryption_client.py new file mode 100644 index 000000000..561d91475 --- /dev/null +++ b/test/unit/test_encryption_client.py @@ -0,0 +1,93 @@ +# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +"""Unit test suite for aws_encryption_sdk.EncryptionSDKClient""" +import pytest +from mock import MagicMock + +import aws_encryption_sdk +from aws_encryption_sdk import CommitmentPolicy +from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager + +pytestmark = [pytest.mark.unit, pytest.mark.local] + + +def test_init_success(): + test = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + assert test.config.commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + + +def test_init_fails_without_commitment_policy(): + with pytest.raises(TypeError): + aws_encryption_sdk.EncryptionSDKClient() + + +def test_client_encrypt(mocker): + mocker.patch.object(aws_encryption_sdk, "StreamEncryptor") + cmm = MagicMock(__class__=CryptoMaterialsManager) + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + + kwargs = dict() + kwargs["source"] = b"plaintext" + kwargs["materials_manager"] = cmm + client.encrypt(**kwargs) + expected_kwargs = kwargs.copy() + expected_kwargs["commitment_policy"] = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + aws_encryption_sdk.StreamEncryptor.assert_called_once_with(**expected_kwargs) + + +def test_client_decrypt(mocker): + mocker.patch.object(aws_encryption_sdk, "StreamDecryptor") + cmm = MagicMock(__class__=CryptoMaterialsManager) + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + + kwargs = dict() + kwargs["source"] = b"ciphertext" + kwargs["materials_manager"] = cmm + client.decrypt(**kwargs) + expected_kwargs = kwargs.copy() + expected_kwargs["commitment_policy"] = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + aws_encryption_sdk.StreamDecryptor.assert_called_once_with(**expected_kwargs) + + +@pytest.mark.parametrize("mode_string", ("e", "encrypt", "ENCRYPT")) +def test_client_stream_encrypt(mocker, mode_string): + mocker.patch.object(aws_encryption_sdk, "StreamEncryptor") + cmm = MagicMock(__class__=CryptoMaterialsManager) + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + + kwargs = dict() + kwargs["mode"] = mode_string + kwargs["source"] = b"plaintext" + kwargs["materials_manager"] = cmm + client.stream(**kwargs) + expected_kwargs = kwargs.copy() + expected_kwargs.pop("mode") + expected_kwargs["commitment_policy"] = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + aws_encryption_sdk.StreamEncryptor.assert_called_once_with(**expected_kwargs) + + +@pytest.mark.parametrize("mode_string", ("d", "decrypt", "DECRYPT")) +def test_client_stream_decrypt(mocker, mode_string): + mocker.patch.object(aws_encryption_sdk, "StreamDecryptor") + cmm = MagicMock(__class__=CryptoMaterialsManager) + client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + + kwargs = dict() + kwargs["mode"] = mode_string + kwargs["source"] = b"ciphertext" + kwargs["materials_manager"] = cmm + client.stream(**kwargs) + expected_kwargs = kwargs.copy() + expected_kwargs.pop("mode") + expected_kwargs["commitment_policy"] = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + aws_encryption_sdk.StreamDecryptor.assert_called_once_with(**expected_kwargs) diff --git a/test/unit/test_encryption_context.py b/test/unit/test_encryption_context.py index 187365783..094430707 100644 --- a/test/unit/test_encryption_context.py +++ b/test/unit/test_encryption_context.py @@ -26,7 +26,7 @@ class TestEncryptionContext(object): def test_assemble_content_aad(self): """Validate that the assemble_content_aad function - behaves as expected. + behaves as expected. """ test = aws_encryption_sdk.internal.formatting.encryption_context.assemble_content_aad( message_id=VALUES["message_id"], @@ -45,17 +45,17 @@ def test_assemble_content_aad_unknown_type(self): def test_serialize_encryption_context_no_encryption_context(self): """Validate that the serialize_encryption_context - function behaves as expected when presented - with an empty encryption context. + function behaves as expected when presented + with an empty encryption context. """ test = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context({}) assert test == bytes() def test_serialize_encryption_context_too_many_elements(self): """Validate that the serialize_encryption_context - function behaves as expected when presented - with an encryption context with too many - elements. + function behaves as expected when presented + with an encryption context with too many + elements. """ with pytest.raises(SerializationError) as excinfo: aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( @@ -65,8 +65,8 @@ def test_serialize_encryption_context_too_many_elements(self): def test_serialize_encryption_context_too_large(self): """Validate that the serialize_encryption_context - function behaves as expected when presented - with an encryption context which is too large. + function behaves as expected when presented + with an encryption context which is too large. """ with pytest.raises(SerializationError) as excinfo: aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( @@ -76,9 +76,9 @@ def test_serialize_encryption_context_too_large(self): def test_serialize_encryption_context_unencodable(self): """Validate that the serialize_encryption_context - function behaves as expected when presented - with an encryption context which contains - unencodable elements. + function behaves as expected when presented + with an encryption context which contains + unencodable elements. """ for encryption_context in [{"a": b"\xc4"}, {b"\xc4": "a"}, {b"\xc4": b"\xc4"}]: with pytest.raises(SerializationError) as excinfo: @@ -89,8 +89,8 @@ def test_serialize_encryption_context_unencodable(self): def test_serialize_encryption_context_valid(self): """Validate that the serialize_encryption_context - function behaves as expected for a valid - encryption context. + function behaves as expected for a valid + encryption context. """ test = aws_encryption_sdk.internal.formatting.encryption_context.serialize_encryption_context( VALUES["updated_encryption_context"] @@ -99,7 +99,7 @@ def test_serialize_encryption_context_valid(self): def test_read_short_too_short(self): """Validate that the read_short function behaves - as expected when it encounters a struct error. + as expected when it encounters a struct error. """ with pytest.raises(SerializationError) as excinfo: aws_encryption_sdk.internal.formatting.encryption_context.read_short(b"d", 0) @@ -107,7 +107,7 @@ def test_read_short_too_short(self): def test_read_short_valid(self): """Validate that the read_short function behaves - as expected with a valid call. + as expected with a valid call. """ test_value, test_offset = aws_encryption_sdk.internal.formatting.encryption_context.read_short(b"\x00\x05df", 0) assert test_value == 5 @@ -115,8 +115,8 @@ def test_read_short_valid(self): def test_read_string_encoding_error(self): """Validate that the read_string function behaves - as expected when it encounters an encoding - error. + as expected when it encounters an encoding + error. """ with pytest.raises(SerializationError) as excinfo: aws_encryption_sdk.internal.formatting.encryption_context.read_string(b"\xc4", 0, 1) @@ -124,7 +124,7 @@ def test_read_string_encoding_error(self): def test_read_string_valid(self): """Validate that the read_string function behaves - as expected with a valid call. + as expected with a valid call. """ test_value, test_offset = aws_encryption_sdk.internal.formatting.encryption_context.read_string(b"asdf", 0, 2) assert test_value == "as" @@ -132,9 +132,9 @@ def test_read_string_valid(self): def test_deserialize_encryption_context_too_large(self): """Validate that the deserialize_encryption_context - function behaves as expected when it encounters - a serialized encryption context which is too - large. + function behaves as expected when it encounters + a serialized encryption context which is too + large. """ data = "" for i in range(aws_encryption_sdk.internal.defaults.MAX_BYTE_ARRAY_SIZE + 1): @@ -147,9 +147,9 @@ def test_deserialize_encryption_context_too_large(self): def test_deserialize_encryption_context_duplicate_key(self): """Validate that the deserialize_encryption_context - function behaves as expected when it encounters - a serialized encryption context which contains - duplicate keys. + function behaves as expected when it encounters + a serialized encryption context which contains + duplicate keys. """ with pytest.raises(SerializationError) as excinfo: aws_encryption_sdk.internal.formatting.encryption_context.deserialize_encryption_context( @@ -159,10 +159,10 @@ def test_deserialize_encryption_context_duplicate_key(self): def test_deserialize_encryption_context_extra_data(self): """Validate that the deserialize_encryption_context - function behaves as expected when it encounters - a serialized encryption context which contains - extra data after processing the encoded number - of pairs (formatting error). + function behaves as expected when it encounters + a serialized encryption context which contains + extra data after processing the encoded number + of pairs (formatting error). """ data = VALUES["serialized_encryption_context"] + b"jhofguijhsuskldfh" with pytest.raises(SerializationError) as excinfo: @@ -173,8 +173,8 @@ def test_deserialize_encryption_context_extra_data(self): def test_deserialize_encryption_context_valid(self): """Validate that the deserialize_encryption_context - function behaves as expected for a valid - encryption context. + function behaves as expected for a valid + encryption context. """ test = aws_encryption_sdk.internal.formatting.encryption_context.deserialize_encryption_context( serialized_encryption_context=VALUES["serialized_encryption_context"] @@ -183,8 +183,8 @@ def test_deserialize_encryption_context_valid(self): def test_deserialize_encryption_context_empty(self): """Validate that the deserialize_encryption_context - function behaves as expected for an empty - encryption context. + function behaves as expected for an empty + encryption context. """ test = aws_encryption_sdk.internal.formatting.encryption_context.deserialize_encryption_context( serialized_encryption_context=b"" diff --git a/test/unit/test_material_managers.py b/test/unit/test_material_managers.py index fcd4977f5..22a7a753e 100644 --- a/test/unit/test_material_managers.py +++ b/test/unit/test_material_managers.py @@ -15,7 +15,7 @@ from mock import MagicMock from pytest_mock import mocker # noqa pylint: disable=unused-import -from aws_encryption_sdk.identifiers import Algorithm +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy from aws_encryption_sdk.internal.utils.streams import ROStream from aws_encryption_sdk.materials_managers import ( DecryptionMaterials, @@ -42,11 +42,18 @@ encrypted_data_keys=set([]), encryption_context={}, signing_key=b"", + commitment_policy=MagicMock(__class__=CommitmentPolicy), ), "DecryptionMaterialsRequest": dict( - algorithm=MagicMock(__class__=Algorithm), encrypted_data_keys=set([]), encryption_context={} + algorithm=MagicMock(__class__=Algorithm), + encrypted_data_keys=set([]), + encryption_context={}, + ), + "DecryptionMaterials": dict( + data_key=MagicMock(__class__=DataKey), + verification_key=b"ex_verification_key", + commitment_policy=MagicMock(__class__=CommitmentPolicy), ), - "DecryptionMaterials": dict(data_key=MagicMock(__class__=DataKey), verification_key=b"ex_verification_key"), } @@ -77,7 +84,11 @@ def test_attributes_fails(attr_class, invalid_kwargs): def test_encryption_materials_request_attributes_defaults(): - test = EncryptionMaterialsRequest(encryption_context={}, frame_length=5) + test = EncryptionMaterialsRequest( + encryption_context={}, + frame_length=5, + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + ) assert test.plaintext_rostream is None assert test.algorithm is None assert test.plaintext_length is None diff --git a/test/unit/test_material_managers_caching.py b/test/unit/test_material_managers_caching.py index 833d6aa53..1d38d54c2 100644 --- a/test/unit/test_material_managers_caching.py +++ b/test/unit/test_material_managers_caching.py @@ -11,6 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Unit test suite for CachingCryptoMaterialsManager""" + import pytest from mock import MagicMock, sentinel from pytest_mock import mocker # noqa pylint: disable=unused-import @@ -43,6 +44,7 @@ def fake_encryption_request(): frame_length=sentinel.frame_length, algorithm=sentinel.algorithm, plaintext_length=sentinel.plaintext_length, + commitment_policy=sentinel.commitment_policy, ) @@ -277,7 +279,10 @@ def test_get_encryption_materials_cache_hit_expired_entry( test = ccmm.get_encryption_materials(mock_request) patch_encryption_materials_request.assert_called_once_with( - encryption_context=sentinel.encryption_context, frame_length=sentinel.frame_length, algorithm=sentinel.algorithm + encryption_context=sentinel.encryption_context, + frame_length=sentinel.frame_length, + algorithm=sentinel.algorithm, + commitment_policy=sentinel.commitment_policy, ) patch_build_encryption_materials_cache_key.assert_called_once_with( partition=ccmm.partition_name, request=patch_encryption_materials_request.return_value diff --git a/test/unit/test_material_managers_default.py b/test/unit/test_material_managers_default.py index 9d6bd949f..674b38175 100644 --- a/test/unit/test_material_managers_default.py +++ b/test/unit/test_material_managers_default.py @@ -11,13 +11,14 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Test suite for aws_encryption_sdk.materials_managers.default""" + import pytest from mock import MagicMock, sentinel from pytest_mock import mocker # noqa pylint: disable=unused-import import aws_encryption_sdk.materials_managers.default -from aws_encryption_sdk.exceptions import MasterKeyProviderError, SerializationError -from aws_encryption_sdk.identifiers import Algorithm +from aws_encryption_sdk.exceptions import ActionNotAllowedError, MasterKeyProviderError, SerializationError +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy from aws_encryption_sdk.internal.defaults import ALGORITHM, ENCODED_SIGNER_KEY from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.materials_managers import EncryptionMaterials @@ -48,13 +49,18 @@ def patch_for_dcmm_decrypt(mocker): yield mock_verification_key -def build_cmm(): +def build_mkp(): mock_mkp = MagicMock(__class__=MasterKeyProvider) mock_mkp.decrypt_data_key_from_list.return_value = MagicMock(__class__=DataKey) mock_mkp.master_keys_for_encryption.return_value = ( sentinel.primary_mk, set([sentinel.primary_mk, sentinel.mk_a, sentinel.mk_b]), ) + return mock_mkp + + +def build_cmm(): + mock_mkp = build_mkp() return DefaultCryptoMaterialsManager(master_key_provider=mock_mkp) @@ -126,7 +132,7 @@ def test_get_encryption_materials(patch_for_dcmm_encrypt): encryption_context=encryption_context, ) assert isinstance(test, EncryptionMaterials) - assert test.algorithm is cmm.algorithm + assert test.algorithm is cmm.algorithm is ALGORITHM assert test.data_encryption_key is patch_for_dcmm_encrypt[0][0] assert test.encrypted_data_keys is patch_for_dcmm_encrypt[0][1] assert test.encryption_context == encryption_context @@ -134,7 +140,9 @@ def test_get_encryption_materials(patch_for_dcmm_encrypt): def test_get_encryption_materials_override_algorithm(patch_for_dcmm_encrypt): - mock_request = MagicMock(algorithm=MagicMock(__class__=Algorithm), encryption_context={}) + mock_request = MagicMock( + algorithm=MagicMock(__class__=Algorithm, is_committing=MagicMock(return_value=False)), encryption_context={} + ) cmm = build_cmm() test = cmm.get_encryption_materials(request=mock_request) @@ -142,8 +150,26 @@ def test_get_encryption_materials_override_algorithm(patch_for_dcmm_encrypt): assert test.algorithm is mock_request.algorithm +def test_get_encryption_materials_committing_algorithm_policy_forbids(): + """Tests that a Default Crypto Materials Manager configured with policy FORBID_ENCRYPT_ALLOW_DECRYPT cannot + encrypt using an algorithm that provides commitment.""" + mock_alg = MagicMock(__class__=Algorithm) + mock_alg.is_committing.return_value = True + mock_request = MagicMock(algorithm=mock_alg, encryption_context={}) + mock_request.commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT + + cmm = DefaultCryptoMaterialsManager(master_key_provider=build_mkp()) + + with pytest.raises(ActionNotAllowedError) as excinfo: + cmm.get_encryption_materials(request=mock_request) + + excinfo.match("Configuration conflict. Cannot encrypt due to .* requiring only non-committed messages") + + def test_get_encryption_materials_no_mks(patch_for_dcmm_encrypt): - mock_request = MagicMock(algorithm=MagicMock(__class__=Algorithm), encryption_context={}) + mock_request = MagicMock( + algorithm=MagicMock(__class__=Algorithm, is_committing=MagicMock(return_value=False)), encryption_context={} + ) cmm = build_cmm() cmm.master_key_provider.master_keys_for_encryption.return_value = (None, set([])) @@ -154,7 +180,9 @@ def test_get_encryption_materials_no_mks(patch_for_dcmm_encrypt): def test_get_encryption_materials_primary_mk_not_in_mks(patch_for_dcmm_encrypt): - mock_request = MagicMock(algorithm=MagicMock(__class__=Algorithm), encryption_context={}) + mock_request = MagicMock( + algorithm=MagicMock(__class__=Algorithm, is_committing=MagicMock(return_value=False)), encryption_context={} + ) cmm = build_cmm() cmm.master_key_provider.master_keys_for_encryption.return_value = ( sentinel.primary_mk, @@ -218,8 +246,11 @@ def test_load_verification_key_from_encryption_context_key_is_needed_and_is_foun assert test is mock_verifier.key_bytes.return_value -def test_decrypt_materials(mocker, patch_for_dcmm_decrypt): - mock_request = MagicMock() +@pytest.mark.parametrize("is_committing", (True, False)) +def test_decrypt_materials(mocker, patch_for_dcmm_decrypt, is_committing): + mock_alg = MagicMock(__class__=Algorithm) + mock_alg.is_committing.return_value = is_committing + mock_request = MagicMock(algorithm=mock_alg) cmm = build_cmm() test = cmm.decrypt_materials(request=mock_request) diff --git a/test/unit/test_providers_base_master_key.py b/test/unit/test_providers_base_master_key.py index 26a90ced8..de8280ba0 100644 --- a/test/unit/test_providers_base_master_key.py +++ b/test/unit/test_providers_base_master_key.py @@ -311,3 +311,26 @@ def test_decrypt_data_key(self): algorithm=ALGORITHM, encryption_context=VALUES["encryption_context"], ) + + def test_decrypt_data_key_not_owned(self): + mock_master_key = MockMasterKey( + key_id=VALUES["key_info"], + mock_generated_data_key=sentinel.generated_data_key, + mock_encrypted_data_key=sentinel.encrypted_data_key, + mock_decrypted_data_key=sentinel.decrypted_data_key, + ) + mock_master_key._decrypt_data_key = MagicMock(return_value=sentinel.raw_decrypted_data_key) + + encrypted_data_key = MagicMock() + encrypted_data_key.encrypted_data_key = sentinel.encrypted_data_key + encrypted_data_key.key_provider.key_info = b"wrong key info" + + with pytest.raises(IncorrectMasterKeyError) as excinfo: + mock_master_key.decrypt_data_key( + encrypted_data_key=encrypted_data_key, + algorithm=ALGORITHM, + encryption_context=VALUES["encryption_context"], + ) + excinfo.match("does not match Master Key provider") + + mock_master_key._decrypt_data_key.assert_not_called() diff --git a/test/unit/test_providers_base_master_key_provider.py b/test/unit/test_providers_base_master_key_provider.py index 44385ea17..e27e724be 100644 --- a/test/unit/test_providers_base_master_key_provider.py +++ b/test/unit/test_providers_base_master_key_provider.py @@ -21,7 +21,7 @@ InvalidKeyIdError, MasterKeyProviderError, ) -from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKeyProviderConfig +from aws_encryption_sdk.key_providers.base import MasterKey, MasterKeyProvider, MasterKeyProviderConfig from .test_values import VALUES @@ -270,6 +270,32 @@ def test_decrypt_data_key_successful(self): ) assert test is sentinel.data_key + def test_decrypt_data_key_successful_no_key_ids(self): + """Test that a Master Key Provider configured with vend_masterkey_on_decrypt = True + without any key ids can successfully decrypt an EDK. + """ + mock_master_key = MagicMock() + mock_master_key.decrypt_data_key.return_value = sentinel.data_key + + mock_encrypted_data_key = MagicMock() + mock_encrypted_data_key.key_provider.provider_id = sentinel.provider_id + mock_encrypted_data_key.key_provider.key_info = sentinel.key_info + + mock_master_key_provider = MockMasterKeyProvider( + provider_id=sentinel.provider_id, mock_new_master_key=mock_master_key + ) + mock_master_key_provider.vend_masterkey_on_decrypt = True + mock_master_key_provider._members = [] + test = mock_master_key_provider.decrypt_data_key( + encrypted_data_key=mock_encrypted_data_key, + algorithm=sentinel.algorithm, + encryption_context=sentinel.encryption_context, + ) + mock_master_key.decrypt_data_key.assert_called_once_with( + mock_encrypted_data_key, sentinel.algorithm, sentinel.encryption_context + ) + assert test is sentinel.data_key + def test_decrypt_data_key_successful_second_try_provider_id(self): mock_first_member = MagicMock() mock_first_member.provider_id = sentinel.another_provider_id @@ -293,7 +319,68 @@ def test_decrypt_data_key_successful_second_try_provider_id(self): assert not mock_first_member.master_key_for_decrypt.called assert test is sentinel.data_key + def test_decrypt_data_key_successful_multiple_members(self): + """Test that a Master Key Provider with multiple members which are able + to decrypt a given EDK will successfully use the first key to decrypt + and will not try the others. + """ + mock_member1 = MagicMock() + mock_member1.provider_id = sentinel.provider_id + mock_member1.key_id = sentinel.key_info1 + + mock_member2 = MagicMock() + mock_member2.provider_id = sentinel.provider_id + mock_member2.key_id = sentinel.key_info2 + + mock_master_key = MagicMock() + mock_master_key.decrypt_data_key.return_value = sentinel.data_key + + mock_member1.master_key_for_decrypt.return_value = mock_master_key + + mock_encrypted_data_key = MagicMock() + mock_encrypted_data_key.key_provider.provider_id = sentinel.provider_id + mock_encrypted_data_key.key_provider.key_info = sentinel.key_info + + mock_master_key_provider = MockMasterKeyProvider( + provider_id=sentinel.provider_id_2, mock_new_master_key=sentinel.new_master_key + ) + mock_master_key_provider._members = [mock_member1, mock_member2] + test = mock_master_key_provider.decrypt_data_key( + encrypted_data_key=mock_encrypted_data_key, + algorithm=sentinel.algorithm, + encryption_context=sentinel.encryption_context, + ) + assert mock_member1.master_key_for_decrypt.called + assert not mock_member2.master_key_for_decrypt.called + assert test is sentinel.data_key + + def test_decrypt_data_key_successful_one_matching_member_no_vend(self): + """Test that a Master Key Provider configured to not vend keys + can successfully decrypt an EDK when it was configured with a + key that is able to decrypt the EDK. + """ + mock_member = MagicMock() + mock_member.__class__ = MasterKey + mock_member.provider_id = sentinel.provider_id + mock_encrypted_data_key = MagicMock() + mock_encrypted_data_key.key_provider.provider_id = sentinel.provider_id + mock_encrypted_data_key.key_provider.key_info = sentinel.key_info + mock_master_key_provider = MockMasterKeyProviderNoVendOnDecrypt(provider_id=sentinel.provider_id) + mock_master_key_provider._members = [mock_member] + mock_master_key_provider.master_key_for_decrypt = MagicMock() + mock_master_key_provider.decrypt_data_key( + encrypted_data_key=mock_encrypted_data_key, + algorithm=sentinel.algorithm, + encryption_context=sentinel.encryption_context, + ) + mock_member.decrypt_data_key.assert_called_once_with( + mock_encrypted_data_key, sentinel.algorithm, sentinel.encryption_context + ) + def test_decrypt_data_key_unsuccessful_no_matching_members(self): + """Test that a Master Key Provider returns the correct error when none + of its members are able to successfully decrypt an EDK + """ mock_member = MagicMock() mock_member.provider_id = sentinel.another_provider_id mock_encrypted_data_key = MagicMock() @@ -334,6 +421,10 @@ def test_decrypt_data_key_unsuccessful_matching_provider_invalid_key_id(self): mock_master_key.assert_called_once_with(sentinel.key_info) def test_decrypt_data_key_unsuccessful_no_matching_members_no_vend(self): + """Test that a Master Key Provider cannot decrypt an EDK when it is + configured to not vend keys and no keys explicitly configured + match the EDK. + """ mock_member = MagicMock() mock_member.provider_id = sentinel.another_provider_id mock_encrypted_data_key = MagicMock() @@ -391,7 +482,7 @@ def test_decrypt_data_key_unsuccessful_incorrect_master_key(self): ) excinfo.match("Unable to decrypt data key") - def test_decrypt_data_key_unsuccessful_master_key_decryt_error(self): + def test_decrypt_data_key_unsuccessful_master_key_decrypt_error(self): mock_member = MagicMock() mock_member.provider_id = sentinel.provider_id mock_master_key = MagicMock() @@ -415,6 +506,9 @@ def test_decrypt_data_key_unsuccessful_master_key_decryt_error(self): excinfo.match("Unable to decrypt data key") def test_decrypt_data_key_unsuccessful_no_members(self): + """Test that a Master Key Provider configured with no master keys fails + to decrypt an EDK. + """ mock_master_key_provider = MockMasterKeyProvider( provider_id=sentinel.provider_id, mock_new_master_key=sentinel.new_master_key ) @@ -428,6 +522,10 @@ def test_decrypt_data_key_unsuccessful_no_members(self): excinfo.match("Unable to decrypt data key") def test_decrypt_data_key_from_list_first_try(self): + """Test that a Master Key Provider configured with a single master key is able + to decrypt from a list of EDKs where the first EDK is decryptable by the + master key. + """ mock_decrypt_data_key = MagicMock() mock_decrypt_data_key.return_value = sentinel.data_key mock_master_key_provider = MockMasterKeyProvider( @@ -445,6 +543,10 @@ def test_decrypt_data_key_from_list_first_try(self): assert test is sentinel.data_key def test_decrypt_data_key_from_list_second_try(self): + """Test that a Master Key Provider configured with a single master key is able + to decrypt from a list of EDKs where the first EDK is not decryptable by the + master key but the second is. + """ mock_master_key_provider = MockMasterKeyProvider( provider_id=sentinel.provider_id, mock_new_master_key=sentinel.new_master_key ) @@ -465,6 +567,9 @@ def test_decrypt_data_key_from_list_second_try(self): assert test is sentinel.data_key def test_decrypt_data_key_from_list_unsuccessful(self): + """Test that a Master Key Provider configured with a single master key fails + to decrypt from a list of EDKs when each EDK throws an exception. + """ mock_master_key_provider = MockMasterKeyProvider( provider_id=sentinel.provider_id, mock_new_master_key=sentinel.new_master_key ) diff --git a/test/unit/test_providers_kms_master_key.py b/test/unit/test_providers_kms_master_key.py index c0ab9a968..1795e6c84 100644 --- a/test/unit/test_providers_kms_master_key.py +++ b/test/unit/test_providers_kms_master_key.py @@ -35,10 +35,10 @@ def apply_fixture(self): self.mock_client.generate_data_key.return_value = { "Plaintext": VALUES["data_key"], "CiphertextBlob": VALUES["encrypted_data_key"], - "KeyId": VALUES["arn"], + "KeyId": VALUES["arn_str"], } self.mock_client.encrypt.return_value = {"CiphertextBlob": VALUES["encrypted_data_key"], "KeyId": VALUES["arn"]} - self.mock_client.decrypt.return_value = {"Plaintext": VALUES["data_key"], "KeyId": VALUES["arn"]} + self.mock_client.decrypt.return_value = {"Plaintext": VALUES["data_key"], "KeyId": VALUES["arn_str"]} self.mock_algorithm = MagicMock() self.mock_algorithm.__class__ = Algorithm self.mock_algorithm.data_key_len = sentinel.data_key_len @@ -47,6 +47,7 @@ def apply_fixture(self): self.mock_data_key.data_key = VALUES["data_key"] self.mock_encrypted_data_key = MagicMock() self.mock_encrypted_data_key.encrypted_data_key = VALUES["encrypted_data_key"] + self.mock_encrypted_data_key.key_provider.key_info = VALUES["arn_str"] self.mock_data_key_len_check_patcher = patch("aws_encryption_sdk.internal.utils.source_data_key_length_check") self.mock_data_key_len_check = self.mock_data_key_len_check_patcher.start() @@ -162,7 +163,9 @@ def test_decrypt_data_key(self): decrypted_key = test._decrypt_data_key( encrypted_data_key=self.mock_encrypted_data_key, algorithm=sentinel.algorithm ) - self.mock_client.decrypt.assert_called_once_with(CiphertextBlob=VALUES["encrypted_data_key"]) + self.mock_client.decrypt.assert_called_once_with( + CiphertextBlob=VALUES["encrypted_data_key"], KeyId=VALUES["arn_str"] + ) assert decrypted_key == DataKey( key_provider=test.key_provider, data_key=VALUES["data_key"], encrypted_data_key=VALUES["encrypted_data_key"] ) @@ -175,26 +178,57 @@ def test_decrypt_data_key_with_encryption_context(self): encryption_context=VALUES["encryption_context"], ) self.mock_client.decrypt.assert_called_once_with( - CiphertextBlob=VALUES["encrypted_data_key"], EncryptionContext=VALUES["encryption_context"] + CiphertextBlob=VALUES["encrypted_data_key"], + EncryptionContext=VALUES["encryption_context"], + KeyId=VALUES["arn_str"], ) def test_decrypt_data_key_with_grant_tokens(self): test = KMSMasterKey(config=self.mock_kms_mkc_2) test._decrypt_data_key(encrypted_data_key=self.mock_encrypted_data_key, algorithm=sentinel.algorithm) self.mock_client.decrypt.assert_called_once_with( - CiphertextBlob=VALUES["encrypted_data_key"], GrantTokens=self.mock_grant_tokens + CiphertextBlob=VALUES["encrypted_data_key"], GrantTokens=self.mock_grant_tokens, KeyId=VALUES["arn_str"] ) def test_decrypt_data_key_unsuccessful_clienterror(self): self.mock_client.decrypt.side_effect = ClientError({"Error": {}}, "This is an error!") - test = KMSMasterKey(config=self.mock_kms_mkc_3) + test = KMSMasterKey(config=self.mock_kms_mkc_1) with pytest.raises(DecryptKeyError) as excinfo: test._decrypt_data_key(encrypted_data_key=self.mock_encrypted_data_key, algorithm=sentinel.algorithm) excinfo.match("Master Key .* unable to decrypt data key") def test_decrypt_data_key_unsuccessful_keyerror(self): self.mock_client.decrypt.side_effect = KeyError + test = KMSMasterKey(config=self.mock_kms_mkc_1) + with pytest.raises(DecryptKeyError) as excinfo: + test._decrypt_data_key(encrypted_data_key=self.mock_encrypted_data_key, algorithm=sentinel.algorithm) + excinfo.match("Master Key .* unable to decrypt data key") + + def test_decrypt_data_key_unsuccessful_key_id_does_not_match_edk(self): test = KMSMasterKey(config=self.mock_kms_mkc_3) + with pytest.raises(DecryptKeyError) as excinfo: + test._decrypt_data_key(encrypted_data_key=self.mock_encrypted_data_key, algorithm=sentinel.algorithm) + excinfo.match("does not match this provider's key_id") + + self.mock_client.assert_not_called() + + def test_decrypt_data_key_unsuccessful_response_missing_key_id(self): + self.mock_client.decrypt.return_value = {"Plaintext": VALUES["data_key"]} + + test = KMSMasterKey(config=self.mock_kms_mkc_1) with pytest.raises(DecryptKeyError) as excinfo: test._decrypt_data_key(encrypted_data_key=self.mock_encrypted_data_key, algorithm=sentinel.algorithm) excinfo.match("Master Key .* unable to decrypt data key") + + self.mock_client.decrypt.assert_called_once_with( + CiphertextBlob=VALUES["encrypted_data_key"], KeyId=VALUES["arn_str"] + ) + + def test_decrypt_data_key_unsuccessful_mismatched_key_id(self): + mismatched_key_id = VALUES["arn_str"] + "-test" + self.mock_client.decrypt.return_value = {"Plaintext": VALUES["data_key"], "KeyId": mismatched_key_id} + + test = KMSMasterKey(config=self.mock_kms_mkc_1) + with pytest.raises(DecryptKeyError) as excinfo: + test._decrypt_data_key(encrypted_data_key=self.mock_encrypted_data_key, algorithm=sentinel.algorithm) + excinfo.match("AWS KMS returned unexpected key_id") diff --git a/test/unit/test_providers_kms_master_key_provider.py b/test/unit/test_providers_kms_master_key_provider.py index b99a8bb94..934f8ac3f 100644 --- a/test/unit/test_providers_kms_master_key_provider.py +++ b/test/unit/test_providers_kms_master_key_provider.py @@ -16,9 +16,22 @@ import pytest from mock import ANY, MagicMock, call, patch, sentinel -from aws_encryption_sdk.exceptions import UnknownRegionError +from aws_encryption_sdk.exceptions import ( + ConfigMismatchError, + MalformedArnError, + MasterKeyProviderError, + UnknownRegionError, +) +from aws_encryption_sdk.internal.str_ops import to_str from aws_encryption_sdk.key_providers.base import MasterKeyProvider -from aws_encryption_sdk.key_providers.kms import KMSMasterKey, KMSMasterKeyProvider +from aws_encryption_sdk.key_providers.kms import ( + BaseKMSMasterKeyProvider, + DiscoveryAwsKmsMasterKeyProvider, + DiscoveryFilter, + KMSMasterKey, + KMSMasterKeyProvider, + StrictAwsKmsMasterKeyProvider, +) pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -39,11 +52,11 @@ def patch_default_region(request, monkeypatch): def test_init_with_regionless_key_ids_and_region_names(): key_ids = ("alias/key_1",) region_names = ("test-region-1",) - provider = KMSMasterKeyProvider(region_names=region_names, key_ids=key_ids) + provider = StrictAwsKmsMasterKeyProvider(region_names=region_names, key_ids=key_ids) assert provider.master_key("alias/key_1").config.client.meta.region_name == region_names[0] -class TestKMSMasterKeyProvider(object): +class KMSMasterKeyProviderTestBase(object): @pytest.fixture(autouse=True) def apply_fixtures(self): self.botocore_no_region_session = botocore.session.Session(session_vars={"region": (None, None, None, None)}) @@ -61,30 +74,29 @@ def apply_fixtures(self): self.mock_botocore_session_patcher.stop() self.mock_boto3_session_patcher.stop() - def test_parent(self): - assert issubclass(KMSMasterKeyProvider, MasterKeyProvider) - @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider._process_config") - def test_init_bare(self, mock_process_config): - KMSMasterKeyProvider() - mock_process_config.assert_called_once_with() +class UnitTestBaseKMSMasterKeyProvider(BaseKMSMasterKeyProvider): + """Test class to enable direct testing of the shared BaseKMSMasterKeyProvider. Does nothing except + implement a no-op version of the abstract validate_config method.""" - @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider.add_master_keys_from_list") - def test_init_with_key_ids(self, mock_add_keys): - mock_ids = (sentinel.id_1, sentinel.id_2) - KMSMasterKeyProvider(key_ids=mock_ids) - mock_add_keys.assert_called_once_with(mock_ids) + def validate_config(self): + pass - @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider.add_regional_clients_from_list") + +class TestBaseKMSMasterKeyProvider(KMSMasterKeyProviderTestBase): + def test_parent(self): + assert issubclass(BaseKMSMasterKeyProvider, MasterKeyProvider) + + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider.add_regional_clients_from_list") def test_init_with_region_names(self, mock_add_clients): region_names = (sentinel.region_name_1, sentinel.region_name_2) - test = KMSMasterKeyProvider(region_names=region_names) + test = UnitTestBaseKMSMasterKeyProvider(region_names=region_names) mock_add_clients.assert_called_once_with(region_names) assert test.default_region is sentinel.region_name_1 - @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider.add_regional_client") + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider.add_regional_client") def test_init_with_default_region_found(self, mock_add_regional_client): - test = KMSMasterKeyProvider(botocore_session=self.botocore_no_region_session) + test = UnitTestBaseKMSMasterKeyProvider(botocore_session=self.botocore_no_region_session) assert test.default_region is None with patch.object( test.config.botocore_session, "get_config_variable", return_value=sentinel.default_region @@ -94,9 +106,9 @@ def test_init_with_default_region_found(self, mock_add_regional_client): assert test.default_region is sentinel.default_region mock_add_regional_client.assert_called_with(sentinel.default_region) - @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider.add_regional_client") + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider.add_regional_client") def test_init_with_default_region_not_found(self, mock_add_regional_client): - test = KMSMasterKeyProvider(botocore_session=self.botocore_no_region_session) + test = UnitTestBaseKMSMasterKeyProvider(botocore_session=self.botocore_no_region_session) assert test.default_region is None with patch.object(test.config.botocore_session, "get_config_variable", return_value=None) as mock_get_config: test._process_config() @@ -105,7 +117,7 @@ def test_init_with_default_region_not_found(self, mock_add_regional_client): assert not mock_add_regional_client.called def test_add_regional_client_new(self): - test = KMSMasterKeyProvider() + test = UnitTestBaseKMSMasterKeyProvider() test._regional_clients = {} test.add_regional_client("ex_region_name") self.mock_boto3_session.assert_called_with(botocore_session=ANY) @@ -117,28 +129,28 @@ def test_add_regional_client_new(self): assert test._regional_clients["ex_region_name"] is self.mock_boto3_client_instance def test_add_regional_client_exists(self): - test = KMSMasterKeyProvider(botocore_session=self.botocore_no_region_session) + test = UnitTestBaseKMSMasterKeyProvider(botocore_session=self.botocore_no_region_session) test._regional_clients["ex_region_name"] = sentinel.existing_client test.add_regional_client("ex_region_name") assert not self.mock_boto3_session.called - @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider.add_regional_client") + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider.add_regional_client") def test_add_regional_clients_from_list(self, mock_add_client): - test = KMSMasterKeyProvider() + test = UnitTestBaseKMSMasterKeyProvider() test.add_regional_clients_from_list([sentinel.region_a, sentinel.region_b, sentinel.region_c]) mock_add_client.assert_has_calls((call(sentinel.region_a), call(sentinel.region_b), call(sentinel.region_c))) - @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider.add_regional_client") + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider.add_regional_client") def test_client_valid_region_name(self, mock_add_client): - test = KMSMasterKeyProvider() + test = UnitTestBaseKMSMasterKeyProvider() test._regional_clients["us-east-1"] = self.mock_boto3_client_instance client = test._client("arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb") mock_add_client.assert_called_with("us-east-1") assert client is self.mock_boto3_client_instance - @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider.add_regional_client") + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider.add_regional_client") def test_client_no_region_name_with_default(self, mock_add_client): - test = KMSMasterKeyProvider() + test = UnitTestBaseKMSMasterKeyProvider() test.default_region = sentinel.default_region test._regional_clients[sentinel.default_region] = sentinel.default_client client = test._client("") @@ -146,17 +158,201 @@ def test_client_no_region_name_with_default(self, mock_add_client): mock_add_client.assert_called_with(sentinel.default_region) def test_client_no_region_name_without_default(self): - test = KMSMasterKeyProvider(botocore_session=self.botocore_no_region_session) + test = UnitTestBaseKMSMasterKeyProvider(botocore_session=self.botocore_no_region_session) with pytest.raises(UnknownRegionError) as excinfo: test._client("") excinfo.match("No default region found and no region determinable from key id: *") - @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider._client") + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider._client") def test_new_master_key(self, mock_client): """v1.2.4 : master key equality is left to the Python object identity now""" mock_client.return_value = self.mock_boto3_client_instance key_info = "example key info asdf" - test = KMSMasterKeyProvider() + test = UnitTestBaseKMSMasterKeyProvider() key = test._new_master_key(key_info) check_key = KMSMasterKey(key_id=key_info, client=self.mock_boto3_client_instance) assert key != check_key + + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider._client") + def test_new_master_key_with_discovery_filter_invalid_arn(self, mock_client): + mock_client.return_value = self.mock_boto3_client_instance + key_info = "example key info asdf" + test = UnitTestBaseKMSMasterKeyProvider() + test.config.discovery_filter = DiscoveryFilter(partition="aws", account_ids=["123"]) + + with pytest.raises(MalformedArnError) as excinfo: + test._new_master_key(key_info) + excinfo.match("Resource {} could not be parsed as an ARN".format(key_info)) + mock_client.assert_not_called() + + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider._client") + def test_new_master_key_with_discovery_filter_account_not_allowed(self, mock_client): + mock_client.return_value = self.mock_boto3_client_instance + key_info = "arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + test = UnitTestBaseKMSMasterKeyProvider() + test.config.discovery_filter = DiscoveryFilter(partition="aws", account_ids=["123"]) + + with pytest.raises(MasterKeyProviderError) as excinfo: + test._new_master_key(key_info) + excinfo.match("Key {} not allowed by this Master Key Provider".format(key_info)) + mock_client.assert_not_called() + + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider._client") + def test_new_master_key_with_discovery_filter_partition_not_allowed(self, mock_client): + mock_client.return_value = self.mock_boto3_client_instance + key_info = "arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + test = UnitTestBaseKMSMasterKeyProvider() + test.config.discovery_filter = DiscoveryFilter(partition="aws-cn", account_ids=["123"]) + + with pytest.raises(MasterKeyProviderError) as excinfo: + test._new_master_key(key_info) + excinfo.match("Key {} not allowed by this Master Key Provider".format(key_info)) + mock_client.assert_not_called() + + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider._client") + def test_new_master_key_with_discovery_filter_success(self, mock_client): + mock_client.return_value = self.mock_boto3_client_instance + key_info = b"arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + test = UnitTestBaseKMSMasterKeyProvider() + test.config.discovery_filter = DiscoveryFilter(partition="aws", account_ids=["222222222222"]) + + key = test._new_master_key(key_info) + assert key.key_id == key_info + mock_client.assert_called_with(to_str(key_info)) + + @patch("aws_encryption_sdk.key_providers.kms.BaseKMSMasterKeyProvider._client") + def test_new_master_key_no_vend(self, mock_client): + mock_client.return_value = self.mock_boto3_client_instance + key_info = b"arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb" + test = UnitTestBaseKMSMasterKeyProvider(key_ids=[key_info]) + + key = test._new_master_key(key_info) + assert key.key_id == key_info + + +class TestKMSMasterKeyProvider(KMSMasterKeyProviderTestBase): + def test_parent(self): + assert issubclass(KMSMasterKeyProvider, BaseKMSMasterKeyProvider) + + @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider._process_config") + def test_init_bare(self, mock_process_config): + test = KMSMasterKeyProvider() + assert test.vend_masterkey_on_decrypt + mock_process_config.assert_called_once_with() + + @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider.add_master_keys_from_list") + def test_init_with_key_ids(self, mock_add_keys): + mock_ids = (sentinel.id_1, sentinel.id_2) + test = KMSMasterKeyProvider(key_ids=mock_ids) + assert test.vend_masterkey_on_decrypt + mock_add_keys.assert_called_once_with(mock_ids) + + def test_init_with_discovery_filter_fails(self): + with pytest.raises(ConfigMismatchError) as excinfo: + KMSMasterKeyProvider(discovery_filter=DiscoveryFilter(partition="aws")) + excinfo.match("To explicitly enable discovery mode use a DiscoveryAwsKmsMasterKeyProvider") + + @patch("aws_encryption_sdk.key_providers.kms.KMSMasterKeyProvider.add_master_keys_from_list") + def test_init_default_vends_master_keys(self, mock_add_keys): + test = KMSMasterKeyProvider() + assert test.vend_masterkey_on_decrypt + mock_add_keys.assert_not_called() + + +class TestDiscoveryKMSMasterKeyProvider(KMSMasterKeyProviderTestBase): + def test_parent(self): + assert issubclass(DiscoveryAwsKmsMasterKeyProvider, BaseKMSMasterKeyProvider) + + def test_init_bare(self): + test = DiscoveryAwsKmsMasterKeyProvider() + assert test.vend_masterkey_on_decrypt + + def test_init_failure_discovery_filter_missing_account_ids(self): + with pytest.raises(ConfigMismatchError) as excinfo: + DiscoveryAwsKmsMasterKeyProvider(discovery_filter=DiscoveryFilter(partition="aws")) + excinfo.match("you must include both account ids and partition") + + def test_init_failure_discovery_filter_empty_account_ids(self): + with pytest.raises(ConfigMismatchError) as excinfo: + DiscoveryAwsKmsMasterKeyProvider(discovery_filter=DiscoveryFilter(account_ids=[], partition="aws")) + excinfo.match("you must include both account ids and partition") + + def test_init_failure_discovery_filter_empty_account_id_string(self): + with pytest.raises(ConfigMismatchError) as excinfo: + DiscoveryAwsKmsMasterKeyProvider( + discovery_filter=DiscoveryFilter(account_ids=["123456789012", ""], partition="aws") + ) + excinfo.match("account ids must be non-empty strings") + + def test_init_failure_discovery_filter_missing_partition(self): + with pytest.raises(ConfigMismatchError) as excinfo: + DiscoveryAwsKmsMasterKeyProvider(discovery_filter=DiscoveryFilter(account_ids=["123"])) + excinfo.match("you must include both account ids and partition") + + def test_init_failure_discovery_filter_empty_partition(self): + with pytest.raises(ConfigMismatchError) as excinfo: + DiscoveryAwsKmsMasterKeyProvider(discovery_filter=DiscoveryFilter(account_ids=["123"], partition="")) + excinfo.match("you must include both account ids and partition") + + def test_init_failure_with_key_ids(self): + with pytest.raises(ConfigMismatchError) as excinfo: + DiscoveryAwsKmsMasterKeyProvider( + discovery_filter=DiscoveryFilter(account_ids=["123"], partition="aws"), key_ids=["1234"] + ) + excinfo.match("To explicitly identify which keys should be used, use a StrictAwsKmsMasterKeyProvider.") + + def test_init_success(self): + discovery_filter = DiscoveryFilter(account_ids=["1234"], partition="aws") + test = DiscoveryAwsKmsMasterKeyProvider(discovery_filter=discovery_filter) + + assert test.vend_masterkey_on_decrypt + assert test.config.discovery_filter == discovery_filter + + +class TestStrictKMSMasterKeyProvider(KMSMasterKeyProviderTestBase): + def test_parent(self): + assert issubclass(StrictAwsKmsMasterKeyProvider, BaseKMSMasterKeyProvider) + + def test_init_bare_fails(self): + with pytest.raises(ConfigMismatchError) as excinfo: + StrictAwsKmsMasterKeyProvider() + excinfo.match("To enable strict mode you must provide key ids") + + def test_init_empty_key_ids_fails(self): + with pytest.raises(ConfigMismatchError) as excinfo: + StrictAwsKmsMasterKeyProvider(key_ids=[]) + excinfo.match("To enable strict mode you must provide key ids") + + def test_init_null_key_id_fails(self): + key_ids = ("arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb", None) + with pytest.raises(ConfigMismatchError) as excinfo: + StrictAwsKmsMasterKeyProvider(key_ids=key_ids) + excinfo.match("Key ids must be valid AWS KMS ARNs") + + def test_init_empty_string_key_id_fails(self): + key_ids = ("arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb", "") + with pytest.raises(ConfigMismatchError) as excinfo: + StrictAwsKmsMasterKeyProvider(key_ids=key_ids) + excinfo.match("Key ids must be valid AWS KMS ARNs") + + @patch("aws_encryption_sdk.key_providers.kms.StrictAwsKmsMasterKeyProvider.add_master_keys_from_list") + def test_init_with_discovery_fails(self, mock_add_keys): + key_ids = ( + "arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb", + "arn:aws:kms:us-east-1:333333333333:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb", + ) + discovery_filter = DiscoveryFilter(account_ids=["1234"], partition="aws") + with pytest.raises(ConfigMismatchError) as excinfo: + StrictAwsKmsMasterKeyProvider(key_ids=key_ids, discovery_filter=discovery_filter) + excinfo.match("To enable discovery mode, use a DiscoveryAwsKmsMasterKeyProvider") + mock_add_keys.assert_not_called() + + @patch("aws_encryption_sdk.key_providers.kms.StrictAwsKmsMasterKeyProvider.add_master_keys_from_list") + def test_init_with_key_ids(self, mock_add_keys): + key_ids = ( + "arn:aws:kms:us-east-1:222222222222:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb", + "arn:aws:kms:us-east-1:333333333333:key/aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb", + ) + test = StrictAwsKmsMasterKeyProvider(key_ids=key_ids) + assert not test.vend_masterkey_on_decrypt + mock_add_keys.assert_called_once_with(key_ids) diff --git a/test/unit/test_serialize.py b/test/unit/test_serialize.py index 511048d80..56da114b4 100644 --- a/test/unit/test_serialize.py +++ b/test/unit/test_serialize.py @@ -16,7 +16,7 @@ import aws_encryption_sdk.internal.formatting.serialize from aws_encryption_sdk.exceptions import SerializationError -from aws_encryption_sdk.identifiers import ContentAADString +from aws_encryption_sdk.identifiers import ContentAADString, SerializationVersion from aws_encryption_sdk.internal.defaults import MAX_FRAME_COUNT from aws_encryption_sdk.internal.structures import EncryptedData from aws_encryption_sdk.structures import EncryptedDataKey, MasterKeyInfo @@ -89,9 +89,9 @@ def apply_fixtures(self): self.mock_encrypt_patcher.stop() self.mock_valid_frame_length_patcher.stop() - def test_serialize_header(self): + def test_serialize_header_v1(self): """Validate that the _serialize_header function - behaves as expected. + behaves as expected. """ test = aws_encryption_sdk.internal.formatting.serialize.serialize_header( header=VALUES["deserialized_header_small_frame"], signer=self.mock_signer @@ -102,21 +102,43 @@ def test_serialize_header(self): self.mock_signer.update.assert_called_once_with(VALUES["serialized_header_small_frame"]) assert test == VALUES["serialized_header_small_frame"] - def test_serialize_header_no_signer(self): + def test_serialize_header_v1_no_signer(self): """Validate that the _serialize_header function - behaves as expected when called with no signer. + behaves as expected when called with no signer. """ aws_encryption_sdk.internal.formatting.serialize.serialize_header( header=VALUES["deserialized_header_small_frame"] ) + def test_serialize_header_v2(self): + """Validate that the _serialize_header_v2 function + behaves as expected. + """ + test = aws_encryption_sdk.internal.formatting.serialize.serialize_header( + header=VALUES["deserialized_header_v2_committing"], signer=self.mock_signer + ) + self.mock_serialize_acc.serialize_encryption_context.assert_called_once_with( + VALUES["updated_encryption_context"] + ) + self.mock_signer.update.assert_called_once_with(VALUES["serialized_header_v2_committing"]) + assert test == VALUES["serialized_header_v2_committing"] + + def test_serialize_header_v2_no_signer(self): + """Validate that the _serialize_header function + behaves as expected when called with no signer. + """ + aws_encryption_sdk.internal.formatting.serialize.serialize_header( + header=VALUES["deserialized_header_v2_committing"] + ) + @patch("aws_encryption_sdk.internal.formatting.serialize.header_auth_iv") - def test_serialize_header_auth(self, mock_header_auth_iv): + def test_serialize_header_auth_v1(self, mock_header_auth_iv): """Validate that the _create_header_auth function - behaves as expected. + behaves as expected for SerializationVersion.V1. """ self.mock_encrypt.return_value = VALUES["header_auth_base"] test = aws_encryption_sdk.internal.formatting.serialize.serialize_header_auth( + version=SerializationVersion.V1, algorithm=self.mock_algorithm, header=VALUES["serialized_header"], data_encryption_key=sentinel.encryption_key, @@ -132,20 +154,58 @@ def test_serialize_header_auth(self, mock_header_auth_iv): self.mock_signer.update.assert_called_once_with(VALUES["serialized_header_auth"]) assert test == VALUES["serialized_header_auth"] - def test_serialize_header_auth_no_signer(self): + def test_serialize_header_auth_v1_no_signer(self): """Validate that the _create_header_auth function - behaves as expected when called with no signer. + behaves as expected when called with no signer + for SerializationVersion.V1. """ self.mock_encrypt.return_value = VALUES["header_auth_base"] aws_encryption_sdk.internal.formatting.serialize.serialize_header_auth( + version=SerializationVersion.V1, algorithm=self.mock_algorithm, header=VALUES["serialized_header"], data_encryption_key=VALUES["data_key_obj"], ) + @patch("aws_encryption_sdk.internal.formatting.serialize.header_auth_iv") + def test_serialize_header_auth_v2(self, mock_header_auth_iv): + """Validate that the _create_header_auth function + behaves as expected for SerializationVersion.V2. + """ + self.mock_encrypt.return_value = VALUES["header_auth_base"] + test = aws_encryption_sdk.internal.formatting.serialize.serialize_header_auth( + version=SerializationVersion.V2, + algorithm=self.mock_algorithm, + header=VALUES["serialized_header_v2_committing"], + data_encryption_key=sentinel.encryption_key, + signer=self.mock_signer, + ) + self.mock_encrypt.assert_called_once_with( + algorithm=self.mock_algorithm, + key=sentinel.encryption_key, + plaintext=b"", + associated_data=VALUES["serialized_header_v2_committing"], + iv=mock_header_auth_iv.return_value, + ) + self.mock_signer.update.assert_called_once_with(VALUES["serialized_header_auth_v2"]) + assert test == VALUES["serialized_header_auth_v2"] + + def test_serialize_header_auth_v2_no_signer(self): + """Validate that the _create_header_auth function + behaves as expected when called with no signer + for SerializationVersion.V1. + """ + self.mock_encrypt.return_value = VALUES["header_auth_base"] + aws_encryption_sdk.internal.formatting.serialize.serialize_header_auth( + version=SerializationVersion.V2, + algorithm=self.mock_algorithm, + header=VALUES["serialized_header_v2_committing"], + data_encryption_key=VALUES["data_key_obj"], + ) + def test_serialize_non_framed_open(self): """Validate that the serialize_non_framed_open - function behaves as expected. + function behaves as expected. """ test = aws_encryption_sdk.internal.formatting.serialize.serialize_non_framed_open( algorithm=self.mock_algorithm, @@ -158,8 +218,8 @@ def test_serialize_non_framed_open(self): def test_serialize_non_framed_open_no_signer(self): """Validate that the serialize_non_framed_open - function behaves as expected when called with - no signer. + function behaves as expected when called with + no signer. """ aws_encryption_sdk.internal.formatting.serialize.serialize_non_framed_open( algorithm=self.mock_algorithm, iv=VALUES["final_frame_base"].iv, plaintext_length=len(VALUES["data_128"]) @@ -167,7 +227,7 @@ def test_serialize_non_framed_open_no_signer(self): def test_serialize_non_framed_close(self): """Validate that the serialize_non_framed_close - function behaves as expected. + function behaves as expected. """ test = aws_encryption_sdk.internal.formatting.serialize.serialize_non_framed_close( tag=VALUES["final_frame_base"].tag, signer=self.mock_signer @@ -177,15 +237,15 @@ def test_serialize_non_framed_close(self): def test_serialize_non_framed_close_no_signer(self): """Validate that the serialize_non_framed_close - function behaves as expected when called with - no signer. + function behaves as expected when called with + no signer. """ aws_encryption_sdk.internal.formatting.serialize.serialize_non_framed_close(tag=VALUES["final_frame_base"].tag) @patch("aws_encryption_sdk.internal.formatting.serialize.frame_iv") def test_encrypt_and_serialize_frame(self, mock_frame_iv): """Validate that the _encrypt_and_serialize_frame - function behaves as expected for a normal frame. + function behaves as expected for a normal frame. """ self.mock_serialize_acc.assemble_content_aad.return_value = VALUES["frame_aac"] self.mock_encrypt.return_value = VALUES["frame_base"] @@ -220,8 +280,8 @@ def test_encrypt_and_serialize_frame(self, mock_frame_iv): def test_encrypt_and_serialize_frame_no_signer(self): """Validate that the _encrypt_and_serialize_frame - function behaves as expected for a normal frame - when called with no signer. + function behaves as expected for a normal frame + when called with no signer. """ self.mock_serialize_acc.assemble_content_aad.return_value = VALUES["frame_aac"] self.mock_encrypt.return_value = VALUES["frame_base"] @@ -238,7 +298,7 @@ def test_encrypt_and_serialize_frame_no_signer(self): @patch("aws_encryption_sdk.internal.formatting.serialize.frame_iv") def test_encrypt_and_serialize_frame_final(self, mock_frame_iv): """Validate that the _encrypt_and_serialize_frame - function behaves as expected for a final frame. + function behaves as expected for a final frame. """ self.mock_serialize_acc.assemble_content_aad.return_value = VALUES["final_frame_aac"] self.mock_encrypt.return_value = VALUES["final_frame_base"] @@ -272,8 +332,8 @@ def test_encrypt_and_serialize_frame_final(self, mock_frame_iv): def test_encrypt_and_serialize_frame_final_no_signer(self): """Validate that the _encrypt_and_serialize_frame - function behaves as expected for a final frame - when called with no signer. + function behaves as expected for a final frame + when called with no signer. """ self.mock_serialize_acc.assemble_content_aad.return_value = VALUES["final_frame_aac"] self.mock_encrypt.return_value = VALUES["final_frame_base"] @@ -289,7 +349,7 @@ def test_encrypt_and_serialize_frame_final_no_signer(self): def test_serialize_footer_with_signer(self): """Validate that the serialize_footer function behaves as expected - when called with a signer. + when called with a signer. """ test = aws_encryption_sdk.internal.formatting.serialize.serialize_footer(self.mock_signer) self.mock_signer.finalize.assert_called_with() @@ -297,7 +357,7 @@ def test_serialize_footer_with_signer(self): def test_serialize_footer_no_signer(self): """Validate that the serialize_footer function behaves as expected - when called without a signer. + when called without a signer. """ test = aws_encryption_sdk.internal.formatting.serialize.serialize_footer(None) assert test == b"" diff --git a/test/unit/test_streaming_client_configs.py b/test/unit/test_streaming_client_configs.py index 98e5cb13c..8828f9c37 100644 --- a/test/unit/test_streaming_client_configs.py +++ b/test/unit/test_streaming_client_configs.py @@ -16,6 +16,7 @@ import pytest import six +from aws_encryption_sdk import CommitmentPolicy from aws_encryption_sdk.internal.defaults import ALGORITHM, FRAME_LENGTH, LINE_LENGTH from aws_encryption_sdk.key_providers.base import MasterKeyProvider, MasterKeyProviderConfig from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager @@ -92,6 +93,7 @@ def test_client_config_defaults(): test = _ClientConfig(**BASE_KWARGS) assert test.source_length is None assert test.line_length == LINE_LENGTH + assert test.commitment_policy == CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT def test_encryptor_config_defaults(): diff --git a/test/unit/test_streaming_client_encryption_stream.py b/test/unit/test_streaming_client_encryption_stream.py index e3a06347a..9aa1361b4 100644 --- a/test/unit/test_streaming_client_encryption_stream.py +++ b/test/unit/test_streaming_client_encryption_stream.py @@ -19,6 +19,7 @@ from mock import MagicMock, PropertyMock, call, patch, sentinel import aws_encryption_sdk.exceptions +from aws_encryption_sdk import CommitmentPolicy from aws_encryption_sdk.internal.defaults import LINE_LENGTH from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.streaming_client import _ClientConfig, _EncryptionStream @@ -101,12 +102,14 @@ def _prep_message(self): def test_new_with_params(self): mock_int_sentinel = MagicMock(__class__=int) + mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) mock_stream = MockEncryptionStream( source=self.mock_source_stream, key_provider=self.mock_key_provider, mock_read_bytes=sentinel.read_bytes, line_length=io.DEFAULT_BUFFER_SIZE, source_length=mock_int_sentinel, + commitment_policy=mock_commitment_policy, ) assert mock_stream.config.source == self.mock_source_stream @@ -115,6 +118,7 @@ def test_new_with_params(self): assert mock_stream.config.mock_read_bytes is sentinel.read_bytes assert mock_stream.config.line_length == io.DEFAULT_BUFFER_SIZE assert mock_stream.config.source_length is mock_int_sentinel + assert mock_stream.config.commitment_policy is mock_commitment_policy assert mock_stream.bytes_read == 0 assert mock_stream.output_buffer == b"" diff --git a/test/unit/test_streaming_client_stream_decryptor.py b/test/unit/test_streaming_client_stream_decryptor.py index 6a3ccb56d..c3c89f37b 100644 --- a/test/unit/test_streaming_client_stream_decryptor.py +++ b/test/unit/test_streaming_client_stream_decryptor.py @@ -16,8 +16,13 @@ import pytest from mock import MagicMock, call, patch, sentinel -from aws_encryption_sdk.exceptions import CustomMaximumValueExceeded, NotSupportedError, SerializationError -from aws_encryption_sdk.identifiers import Algorithm, ContentType +from aws_encryption_sdk.exceptions import ( + CustomMaximumValueExceeded, + MasterKeyProviderError, + NotSupportedError, + SerializationError, +) +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy, ContentType, SerializationVersion from aws_encryption_sdk.key_providers.base import MasterKeyProvider from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.streaming_client import StreamDecryptor @@ -36,7 +41,10 @@ def apply_fixtures(self): data_key=VALUES["data_key_obj"], verification_key=sentinel.verification_key ) self.mock_header = MagicMock() - self.mock_header.algorithm = MagicMock(__class__=Algorithm, iv_len=12) + self.mock_header.version = SerializationVersion.V1 + self.mock_header.algorithm = MagicMock( + __class__=Algorithm, iv_len=12, is_committing=MagicMock(return_value=False) + ) self.mock_header.encrypted_data_keys = sentinel.encrypted_data_keys self.mock_header.encryption_context = sentinel.encryption_context @@ -91,6 +99,11 @@ def apply_fixtures(self): # Set up decrypt patch self.mock_decrypt_patcher = patch("aws_encryption_sdk.streaming_client.decrypt") self.mock_decrypt = self.mock_decrypt_patcher.start() + # Set up hmac.compare_digest patch + self.mock_compare_digest_patcher = patch("hmac.compare_digest") + self.mock_compare_digest = self.mock_compare_digest_patcher.start() + self.mock_compare_digest.return_value = True + yield # Run tearDown self.mock_deserialize_header_patcher.stop() @@ -109,6 +122,7 @@ def test_init(self): ct_stream = io.BytesIO(VALUES["data_128"]) test_decryptor = StreamDecryptor(key_provider=self.mock_key_provider, source=ct_stream) assert test_decryptor.last_sequence_number == 0 + assert test_decryptor.config.commitment_policy is CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT @patch("aws_encryption_sdk.streaming_client.StreamDecryptor._prep_non_framed") @patch("aws_encryption_sdk.streaming_client.StreamDecryptor._read_header") @@ -139,7 +153,12 @@ def test_read_header(self, mock_derive_datakey, mock_decrypt_materials_request, mock_verifier_instance = MagicMock() mock_verifier.from_key_bytes.return_value = mock_verifier_instance ct_stream = io.BytesIO(VALUES["data_128"]) - test_decryptor = StreamDecryptor(materials_manager=self.mock_materials_manager, source=ct_stream) + mock_commitment_policy = MagicMock(__class__=CommitmentPolicy) + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=ct_stream, + commitment_policy=mock_commitment_policy, + ) test_decryptor.source_stream = ct_stream test_decryptor._stream_length = len(VALUES["data_128"]) @@ -153,13 +172,17 @@ def test_read_header(self, mock_derive_datakey, mock_decrypt_materials_request, encrypted_data_keys=sentinel.encrypted_data_keys, algorithm=self.mock_header.algorithm, encryption_context=sentinel.encryption_context, + commitment_policy=mock_commitment_policy, ) self.mock_materials_manager.decrypt_materials.assert_called_once_with( request=mock_decrypt_materials_request.return_value ) mock_verifier_instance.update.assert_called_once_with(self.mock_raw_header) self.mock_deserialize_header_auth.assert_called_once_with( - stream=ct_stream, algorithm=self.mock_header.algorithm, verifier=mock_verifier_instance + version=self.mock_header.version, + stream=ct_stream, + algorithm=self.mock_header.algorithm, + verifier=mock_verifier_instance, ) mock_derive_datakey.assert_called_once_with( source_key=VALUES["data_key_obj"].data_key, @@ -205,6 +228,81 @@ def test_read_header_no_verifier(self, mock_derive_datakey, mock_decrypt_materia test_decryptor._read_header() assert test_decryptor.verifier is None + @patch("aws_encryption_sdk.streaming_client.Verifier") + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") + def test_read_header_committing_algorithm_policy_allows_check_passes( + self, mock_derive_datakey, mock_decrypt_materials_request, mock_verifier + ): + """Verifies that when the commitment check passes for a committing algorithm on decrypt, we successfully + read the header.""" + self.mock_header.algorithm = MagicMock( + __class__=Algorithm, iv_len=12, is_committing=MagicMock(return_value=True) + ) + + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=self.mock_input_stream, + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + ) + test_decryptor.key_provider = self.mock_key_provider + test_decryptor.source_stream = self.mock_input_stream + test_decryptor._stream_length = len(VALUES["data_128"]) + test_decryptor._read_header() + self.mock_deserialize_header.assert_called_once_with(self.mock_input_stream) + + @patch("aws_encryption_sdk.streaming_client.Verifier") + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") + def test_read_header_committing_algorithm_policy_allows_check_fails( + self, mock_derive_datakey, mock_decrypt_materials_request, mock_verifier + ): + """Verifies that when the commitment check fails for a committing algorithm on decrypt, we emit the correct + exception.""" + self.mock_compare_digest.return_value = False + self.mock_header.algorithm = MagicMock( + __class__=Algorithm, iv_len=12, is_committing=MagicMock(return_value=True) + ) + + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=self.mock_input_stream, + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + ) + test_decryptor.key_provider = self.mock_key_provider + test_decryptor.source_stream = self.mock_input_stream + test_decryptor._stream_length = len(VALUES["data_128"]) + with pytest.raises(MasterKeyProviderError) as excinfo: + test_decryptor._read_header() + excinfo.match("Key commitment validation failed") + + @patch("aws_encryption_sdk.streaming_client.Verifier") + @patch("aws_encryption_sdk.streaming_client.DecryptionMaterialsRequest") + @patch("aws_encryption_sdk.streaming_client.derive_data_encryption_key") + def test_read_header_uncommitting_algorithm_policy_allows( + self, mock_derive_datakey, mock_decrypt_materials_request, mock_verifier + ): + """Verifies that when we can successfully read the header on a message encrypted with an algorithm that + does not provide commitment.""" + self.mock_header.algorithm = MagicMock( + __class__=Algorithm, iv_len=12, is_committing=MagicMock(return_value=False) + ) + + self.mock_materials_manager.decrypt_materials.return_value = MagicMock( + data_key=VALUES["data_key_obj"], verification_key=None + ) + test_decryptor = StreamDecryptor( + materials_manager=self.mock_materials_manager, + source=self.mock_input_stream, + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + ) + test_decryptor.key_provider = self.mock_key_provider + test_decryptor.source_stream = self.mock_input_stream + test_decryptor._stream_length = len(VALUES["data_128"]) + test_decryptor._read_header() + self.mock_deserialize_header.assert_called_once_with(self.mock_input_stream) + self.mock_compare_digest.assert_not_called() + def test_prep_non_framed_content_length_too_large(self): self.mock_header.content_type = ContentType.NO_FRAMING self.mock_header.frame_length = 1024 diff --git a/test/unit/test_streaming_client_stream_encryptor.py b/test/unit/test_streaming_client_stream_encryptor.py index 501214e9f..6c431f843 100644 --- a/test/unit/test_streaming_client_stream_encryptor.py +++ b/test/unit/test_streaming_client_stream_encryptor.py @@ -24,7 +24,7 @@ NotSupportedError, SerializationError, ) -from aws_encryption_sdk.identifiers import Algorithm, ContentType +from aws_encryption_sdk.identifiers import Algorithm, CommitmentPolicy, ContentType, SerializationVersion from aws_encryption_sdk.key_providers.base import MasterKey, MasterKeyProvider from aws_encryption_sdk.materials_managers.base import CryptoMaterialsManager from aws_encryption_sdk.streaming_client import StreamEncryptor @@ -43,7 +43,9 @@ def apply_fixtures(self): self.mock_key_provider.__class__ = MasterKeyProvider self.mock_materials_manager = MagicMock(__class__=CryptoMaterialsManager) self.mock_encryption_materials = MagicMock( - algorithm=MagicMock(__class__=Algorithm, iv_len=MagicMock(__class__=int)), + algorithm=MagicMock( + __class__=Algorithm, iv_len=MagicMock(__class__=int), is_committing=MagicMock(return_value=False) + ), encryption_context=MagicMock(__class__=dict), encrypted_data_keys=MagicMock(__class__=set), ) @@ -81,7 +83,7 @@ def apply_fixtures(self): "aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.utils.content_type" ) self.mock_content_type = self.mock_content_type_patcher.start() - self.mock_content_type.return_value = sentinel.content_type + self.mock_content_type.return_value = MagicMock(__class__=ContentType) # Set up validate_from_length patch self.mock_validate_frame_length_patcher = patch( "aws_encryption_sdk.streaming_client.aws_encryption_sdk.internal.utils.validate_frame_length" @@ -170,7 +172,8 @@ def test_init(self): ) assert test_encryptor.sequence_number == 1 self.mock_content_type.assert_called_once_with(self.mock_frame_length) - assert test_encryptor.content_type is sentinel.content_type + assert test_encryptor.content_type is self.mock_content_type.return_value + assert test_encryptor.config.commitment_policy is CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT def test_init_non_framed_message_too_large(self): with pytest.raises(SerializationError) as excinfo: @@ -259,6 +262,7 @@ def test_prep_message_framed_message( plaintext_rostream=sentinel.plaintext_rostream, frame_length=test_encryptor.config.frame_length, plaintext_length=5, + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, ) self.mock_materials_manager.get_encryption_materials.assert_called_once_with( request=mock_encryption_materials_request.return_value @@ -313,6 +317,36 @@ def test_prep_message_no_signer(self): test_encryptor._prep_message() assert not self.mock_signer.called + def test_prep_message_algorithm_violates_policy(self): + algorithm = MagicMock(__class__=Algorithm) + algorithm.is_committing.return_value = True + test_encryptor = StreamEncryptor( + source=VALUES["data_128"], + materials_manager=self.mock_materials_manager, + frame_length=self.mock_frame_length, + algorithm=algorithm, + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + ) + + with pytest.raises(ActionNotAllowedError) as excinfo: + test_encryptor._prep_message() + excinfo.match("Configuration conflict. Cannot encrypt due to .* requiring only non-committed messages") + + def test_prep_message_algorithm_allowed_by_policy(self): + algorithm = MagicMock(__class__=Algorithm, iv_len=12) + algorithm.is_committing.return_value = False + self.mock_encryption_materials.algorithm = algorithm + + test_encryptor = StreamEncryptor( + source=VALUES["data_128"], + materials_manager=self.mock_materials_manager, + frame_length=self.mock_frame_length, + algorithm=algorithm, + commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, + ) + + test_encryptor._prep_message() + def test_write_header(self): self.mock_serialize_header.return_value = b"12345" self.mock_serialize_header_auth.return_value = b"67890" @@ -326,6 +360,7 @@ def test_write_header(self): test_encryptor.signer = sentinel.signer test_encryptor.content_type = sentinel.content_type test_encryptor._header = sentinel.header + sentinel.header.version = SerializationVersion.V1 test_encryptor.output_buffer = b"" test_encryptor._encryption_materials = self.mock_encryption_materials test_encryptor._derived_data_key = sentinel.derived_data_key @@ -334,6 +369,7 @@ def test_write_header(self): self.mock_serialize_header.assert_called_once_with(header=test_encryptor._header, signer=sentinel.signer) self.mock_serialize_header_auth.assert_called_once_with( + version=sentinel.header.version, algorithm=self.mock_encryption_materials.algorithm, header=b"12345", data_encryption_key=sentinel.derived_data_key, diff --git a/test/unit/test_structures.py b/test/unit/test_structures.py index 1a9caa01d..416777505 100644 --- a/test/unit/test_structures.py +++ b/test/unit/test_structures.py @@ -20,7 +20,6 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] - VALID_KWARGS = { MessageHeader: [ dict( @@ -65,7 +64,9 @@ def test_attributes_valid_kwargs(cls, kwargs): cls(**kwargs) -@pytest.mark.parametrize("cls, kwargs", all_invalid_kwargs(VALID_KWARGS)) +@pytest.mark.parametrize( + "cls, kwargs", all_invalid_kwargs(VALID_KWARGS, optional_fields=["type", "content_aad_length", "header_iv_length"]) +) def test_attributes_invalid_kwargs(cls, kwargs): with pytest.raises(TypeError): cls(**kwargs) diff --git a/test/unit/test_utils.py b/test/unit/test_utils.py index b1374a09d..0f2960e07 100644 --- a/test/unit/test_utils.py +++ b/test/unit/test_utils.py @@ -144,8 +144,8 @@ def apply_fixtures(self): def test_validate_frame_length_negative_frame_length(self): """Validate that the validate_frame_length function - behaves as expected when supplied with a - negative frame length. + behaves as expected when supplied with a + negative frame length. """ with pytest.raises(SerializationError) as excinfo: aws_encryption_sdk.internal.utils.validate_frame_length(frame_length=-1, algorithm=self.mock_algorithm) @@ -153,8 +153,8 @@ def test_validate_frame_length_negative_frame_length(self): def test_validate_frame_length_invalid_frame_length(self): """Validate that the validate_frame_length function - behaves as expected when supplied with an - invalid frame length. + behaves as expected when supplied with an + invalid frame length. """ with pytest.raises(SerializationError) as excinfo: aws_encryption_sdk.internal.utils.validate_frame_length(frame_length=1, algorithm=self.mock_algorithm) @@ -162,8 +162,8 @@ def test_validate_frame_length_invalid_frame_length(self): def test_validate_frame_length_too_large(self): """Validate that the validate_frame_length function - behaves as expected when supplied with a - frame length which is too large. + behaves as expected when supplied with a + frame length which is too large. """ with pytest.raises(SerializationError) as excinfo: aws_encryption_sdk.internal.utils.validate_frame_length( @@ -179,7 +179,7 @@ def test_message_id(self): def test_get_aad_content_string_no_framing(self): """Validate that the get_aad_content_string function behaves - as expected when called with NO_FRAMING. + as expected when called with NO_FRAMING. """ test = aws_encryption_sdk.internal.utils.get_aad_content_string( aws_encryption_sdk.identifiers.ContentType.NO_FRAMING, False @@ -188,7 +188,7 @@ def test_get_aad_content_string_no_framing(self): def test_get_aad_content_string_framing(self): """Validate that the get_aad_content_string function behaves - as expected when called with FRAMED_DATA. + as expected when called with FRAMED_DATA. """ test = aws_encryption_sdk.internal.utils.get_aad_content_string( aws_encryption_sdk.identifiers.ContentType.FRAMED_DATA, False @@ -197,7 +197,7 @@ def test_get_aad_content_string_framing(self): def test_get_aad_content_string_framing_final_frame(self): """Validate that the get_aad_content_string function behaves as - expected when called with FRAMED_DATA and final frame. + expected when called with FRAMED_DATA and final frame. """ test = aws_encryption_sdk.internal.utils.get_aad_content_string( aws_encryption_sdk.identifiers.ContentType.FRAMED_DATA, True @@ -206,7 +206,7 @@ def test_get_aad_content_string_framing_final_frame(self): def test_get_aad_content_string_framing_bad_type(self): """Validate that the get_aad_content_string function behaves as - expected when called with an unknown content type. + expected when called with an unknown content type. """ with pytest.raises(UnknownIdentityError) as excinfo: aws_encryption_sdk.internal.utils.get_aad_content_string(-1, False) diff --git a/test/unit/test_values.py b/test/unit/test_values.py index 26eff1341..4283e8a84 100644 --- a/test/unit/test_values.py +++ b/test/unit/test_values.py @@ -113,6 +113,7 @@ def array_byte(source): "\x08,/\x8b\x8b" ), "message_id": b"_\xfd\xb3%\xa5}yd\x80}\xe2\x90\xf9\x0e&\x8f", + "message_id_32_byte": b"_\xfd\xb3%\xa5}yd\x80}\xe2\x90\xf9\x0e&\x8f_\xfd\xb3%\xa5}yd\x80}\xe2\x90\xf9\x0e&\x8f", "serialized_header": six.b( "\x01\x80\x03x_\xfd\xb3%\xa5}yd\x80}\xe2\x90\xf9\x0e&\x8f\x00\x8f\x00" "\x04\x00\x15aws-crypto-public-key\x00DAmZvwV/dN6o9p/usAnJdRcdnE12Uba" @@ -149,12 +150,27 @@ def array_byte(source): "\xd2\xff\x10\x13\xfc\x1aX\x08,/\x8b\x8b\x02\x00\x00\x00\x00\x0c\x00" "\x00\x00 " ), + "serialized_header_v2_committing": six.b( + "\x02\x05x_\xfd\xb3%\xa5}yd\x80}\xe2\x90\xf9\x0e&\x8f_\xfd\xb3%\xa5}yd\x80}\xe2\x90\xf9\x0e&\x8f\x00\x8f\x00" + "\x04\x00\x15aws-crypto-public-key\x00DAmZvwV/dN6o9p/usAnJdRcdnE12UbaDHuEFPeyVkw5FC1ULGlSznzDdD3FP8SW1UMg" + "==\x00\x05key_a\x00\x07value_a\x00\x05key_b\x00\x07value_b\x00\x05key_c\x00\x07value_c\x00\x01\x00\x07aws" + "-kms\x00Karn:aws:kms:us-east-1:248168362296:key/ce78d3b3-f800-4785-a3b9-63e30bb4b183\x00\xcc\n " + "\x8b\xc6\xfd\x91\xc7\xd5\xdc+S\x15n\xd9P\x99n\x1d\xb2\xdd\x15\xeaW\xc3\x13k2\xf6\x02\xd0\x0f\x85\xec\x9e\x12" + "\xa7\x01\x01\x01\x01\x00x\x8b\xc6\xfd\x91\xc7\xd5\xdc+S\x15n\xd9P\x99n\x1d\xb2\xdd\x15\xeaW\xc3\x13k2\xf6" + "\x02\xd0\x0f\x85\xec\x9e\x00\x00\x00~0|\x06\t*\x86H\x86\xf7\r\x01\x07\x06\xa0o0m\x02\x01\x000h\x06\t*\x86H" + "\x86\xf7\r\x01\x07\x010\x1e\x06\t`\x86H\x01e\x03\x04\x01.0\x11\x04\x0c\xc9rP\xa1\x08t6{" + "\xf2\xfd\xf1\xb3\x02\x01\x10\x80;D\xa4\xed`qP~c\x0f\xa0d\xd5\xa2Kj\xc7\xb2\xc6\x1e\xec\xfb\x0fK\xb2*\xd5\t2" + '\x81pR\xee\xd1\x1a\xde<"\x1b\x98\x88\x8b\xf4&\xdaB\x95I\xd2\xff\x10\x13\xfc\x1aX\x08,' + "/\x8b\x8b\x02\x00\x00\x00 \x00\xfa\x8c\xdd\x08Au\xc6\x92_4\xc5\xfb\x90\xaf\x8f\xa1D\xaf\xcc\xd25\xa8\x0b\x0b" + "\x16\x92\x91W\x01\xb7\x84" + ), "header_auth_base": EncryptedData( iv=b"s\x15