From ef90351a822890591880c5975e4ac47a0d6f917b Mon Sep 17 00:00:00 2001 From: Benjamin Farley Date: Wed, 26 Aug 2020 17:00:43 -0600 Subject: [PATCH] feat: Updates to the AWS Encryption SDK. This change includes fixes for issues that were reported by Thai Duong from Google's Security team, and for issues that were identified by AWS Cryptography. See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/migration.html --- CHANGELOG.rst | 5 + README.rst | 115 +- .../integration/integration_test_utils.py | 3 +- examples/src/basic_encryption.py | 10 +- ...file_encryption_with_multiple_providers.py | 17 +- ...c_file_encryption_with_raw_key_provider.py | 9 +- examples/src/data_key_caching_basic.py | 8 +- examples/src/discovery_kms_provider.py | 63 + examples/src/multiple_kms_cmk.py | 62 + examples/src/one_kms_cmk.py | 15 +- examples/src/one_kms_cmk_streaming_data.py | 11 +- examples/src/one_kms_cmk_unsigned.py | 14 +- examples/src/set_commitment.py | 56 + examples/test/examples_test_utils.py | 2 +- .../test/test_i_discovery_kms_provider.py | 29 + examples/test/test_i_multiple_kms_cmk.py | 29 + examples/test/test_i_set_commitment.py | 29 + src/aws_encryption_sdk/__init__.py | 230 +++- src/aws_encryption_sdk/exceptions.py | 4 + src/aws_encryption_sdk/identifiers.py | 60 +- src/aws_encryption_sdk/internal/arn.py | 66 ++ .../internal/crypto/data_keys.py | 52 +- .../internal/formatting/__init__.py | 3 +- .../internal/formatting/deserialize.py | 135 ++- .../internal/formatting/encryption_context.py | 2 +- .../internal/formatting/serialize.py | 131 ++- .../internal/utils/commitment.py | 29 + src/aws_encryption_sdk/key_providers/base.py | 12 +- src/aws_encryption_sdk/key_providers/kms.py | 238 +++- .../materials_managers/__init__.py | 10 +- .../materials_managers/caching.py | 1 + .../materials_managers/default.py | 6 + src/aws_encryption_sdk/streaming_client.py | 77 +- src/aws_encryption_sdk/structures.py | 23 +- .../test_f_aws_encryption_sdk_client.py | 19 +- test/functional/test_f_commitment.py | 118 ++ test/integration/integration_test_utils.py | 32 +- .../test_i_aws_encrytion_sdk_client.py | 336 +++++- test/integration/test_kat_commitment.py | 97 ++ test/resources/commitment-test-vectors.json | 1026 +++++++++++++++++ test/unit/test_algorithm_suite.py | 49 + test/unit/test_arn.py | 78 ++ test/unit/test_aws_encryption_sdk.py | 23 + test/unit/test_caches.py | 10 +- test/unit/test_caches_crypto_cache_entry.py | 19 +- test/unit/test_caches_local.py | 4 +- test/unit/test_commitment.py | 44 + test/unit/test_crypto_data_keys.py | 57 +- test/unit/test_crypto_elliptic_curve.py | 6 +- test/unit/test_deserialize.py | 79 +- test/unit/test_encryption_client.py | 93 ++ test/unit/test_encryption_context.py | 64 +- test/unit/test_material_managers.py | 19 +- test/unit/test_material_managers_caching.py | 7 +- test/unit/test_material_managers_default.py | 49 +- test/unit/test_providers_base_master_key.py | 23 + ...test_providers_base_master_key_provider.py | 109 +- test/unit/test_providers_kms_master_key.py | 46 +- .../test_providers_kms_master_key_provider.py | 260 ++++- test/unit/test_serialize.py | 106 +- test/unit/test_streaming_client_configs.py | 2 + ...test_streaming_client_encryption_stream.py | 4 + .../test_streaming_client_stream_decryptor.py | 108 +- .../test_streaming_client_stream_encryptor.py | 44 +- test/unit/test_structures.py | 5 +- test/unit/test_utils.py | 20 +- test/unit/test_values.py | 38 + test/unit/unit_test_utils.py | 6 +- tox.ini | 2 + 69 files changed, 4161 insertions(+), 397 deletions(-) create mode 100644 examples/src/discovery_kms_provider.py create mode 100644 examples/src/multiple_kms_cmk.py create mode 100644 examples/src/set_commitment.py create mode 100644 examples/test/test_i_discovery_kms_provider.py create mode 100644 examples/test/test_i_multiple_kms_cmk.py create mode 100644 examples/test/test_i_set_commitment.py create mode 100644 src/aws_encryption_sdk/internal/arn.py create mode 100644 src/aws_encryption_sdk/internal/utils/commitment.py create mode 100644 test/functional/test_f_commitment.py create mode 100644 test/integration/test_kat_commitment.py create mode 100644 test/resources/commitment-test-vectors.json create mode 100644 test/unit/test_algorithm_suite.py create mode 100644 test/unit/test_arn.py create mode 100644 test/unit/test_commitment.py create mode 100644 test/unit/test_encryption_client.py 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