diff --git a/README.rst b/README.rst index c0ab1b00..fc71a793 100644 --- a/README.rst +++ b/README.rst @@ -10,6 +10,10 @@ Amazon DynamoDB Encryption Client for Python :target: https://pypi.org/project/dynamodb-encryption-sdk :alt: Supported Python Versions +.. image:: https://img.shields.io/badge/code_style-black-000000.svg + :target: https://github.com/ambv/black + :alt: Code style: black + .. image:: https://readthedocs.org/projects/aws-dynamodb-encryption-python/badge/?version=latest :target: http://aws-dynamodb-encryption-python.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status diff --git a/doc/conf.py b/doc/conf.py index 70858231..576f77c5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,70 +1,76 @@ # pylint: disable=invalid-name """Sphinx configuration.""" -from datetime import datetime import io import os import re +from datetime import datetime -VERSION_RE = re.compile(r'''__version__ = ['"]([0-9.]+)['"]''') +VERSION_RE = re.compile(r"""__version__ = ['"]([0-9.]+)['"]""") HERE = os.path.abspath(os.path.dirname(__file__)) def read(*args): """Reads complete file contents.""" - return io.open(os.path.join(HERE, *args), encoding='utf-8').read() + return io.open(os.path.join(HERE, *args), encoding="utf-8").read() def get_release(): """Reads the release (full three-part version number) from this module.""" - init = read('..', 'src', 'dynamodb_encryption_sdk', 'identifiers.py') + init = read("..", "src", "dynamodb_encryption_sdk", "identifiers.py") return VERSION_RE.search(init).group(1) def get_version(): """Reads the version (MAJOR.MINOR) from this module.""" _release = get_release() - split_version = _release.split('.') + split_version = _release.split(".") if len(split_version) == 3: - return '.'.join(split_version[:2]) + return ".".join(split_version[:2]) return _release -project = u'dynamodb-encryption-sdk-python' +project = u"dynamodb-encryption-sdk-python" version = get_version() release = get_release() # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', 'sphinx.ext.todo', - 'sphinx.ext.coverage', 'sphinx.ext.autosummary', - 'sphinx.ext.napoleon', 'sphinx.ext.viewcode'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.autosummary", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", +] napoleon_include_special_with_doc = False # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] -source_suffix = '.rst' # The suffix of source filenames. -master_doc = 'index' # The master toctree document. +source_suffix = ".rst" # The suffix of source filenames. +master_doc = "index" # The master toctree document. -copyright = u'%s, Amazon' % datetime.now().year # pylint: disable=redefined-builtin +copyright = u"%s, Amazon" % datetime.now().year # pylint: disable=redefined-builtin # List of directories, relative to source directory, that shouldn't be searched # for source files. -exclude_trees = ['_build'] +exclude_trees = ["_build"] -pygments_style = 'sphinx' +pygments_style = "sphinx" autoclass_content = "both" -autodoc_default_flags = ['show-inheritance', 'members'] -autodoc_member_order = 'bysource' +autodoc_default_flags = ["show-inheritance", "members"] +autodoc_member_order = "bysource" -html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] -htmlhelp_basename = '%sdoc' % project +html_theme = "sphinx_rtd_theme" +html_static_path = ["_static"] +htmlhelp_basename = "%sdoc" % project # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'python': ('http://docs.python.org/', None)} +intersphinx_mapping = {"python": ("http://docs.python.org/", None)} # autosummary autosummary_generate = True diff --git a/examples/src/aws_kms_encrypted_client.py b/examples/src/aws_kms_encrypted_client.py index 691951c1..c3f4bd53 100644 --- a/examples/src/aws_kms_encrypted_client.py +++ b/examples/src/aws_kms_encrypted_client.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. """Example showing use of AWS KMS CMP with EncryptedClient.""" import boto3 + from dynamodb_encryption_sdk.encrypted.client import EncryptedClient from dynamodb_encryption_sdk.identifiers import CryptoAction from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider @@ -20,51 +21,41 @@ def encrypt_item(table_name, aws_cmk_id): """Demonstrate use of EncryptedClient to transparently encrypt an item.""" - index_key = { - 'partition_attribute': {'S': 'is this'}, - 'sort_attribute': {'N': '55'} - } + index_key = {"partition_attribute": {"S": "is this"}, "sort_attribute": {"N": "55"}} plaintext_item = { - 'example': {'S': 'data'}, - 'some numbers': {'N': '99'}, - 'and some binary': {'B': b'\x00\x01\x02'}, - 'leave me': {'S': 'alone'} # We want to ignore this attribute + "example": {"S": "data"}, + "some numbers": {"N": "99"}, + "and some binary": {"B": b"\x00\x01\x02"}, + "leave me": {"S": "alone"}, # We want to ignore this attribute } # Collect all of the attributes that will be encrypted (used later). encrypted_attributes = set(plaintext_item.keys()) - encrypted_attributes.remove('leave me') + encrypted_attributes.remove("leave me") # Collect all of the attributes that will not be encrypted (used later). unencrypted_attributes = set(index_key.keys()) - unencrypted_attributes.add('leave me') + unencrypted_attributes.add("leave me") # Add the index pairs to the item. plaintext_item.update(index_key) # Create a normal client. - client = boto3.client('dynamodb') + client = boto3.client("dynamodb") # Create a crypto materials provider using the specified AWS KMS key. aws_kms_cmp = AwsKmsCryptographicMaterialsProvider(key_id=aws_cmk_id) # Create attribute actions that tells the encrypted client to encrypt all attributes except one. actions = AttributeActions( - default_action=CryptoAction.ENCRYPT_AND_SIGN, - attribute_actions={ - 'leave me': CryptoAction.DO_NOTHING - } + default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={"leave me": CryptoAction.DO_NOTHING} ) # Use these objects to create an encrypted client. - encrypted_client = EncryptedClient( - client=client, - materials_provider=aws_kms_cmp, - attribute_actions=actions - ) + encrypted_client = EncryptedClient(client=client, materials_provider=aws_kms_cmp, attribute_actions=actions) # Put the item to the table, using the encrypted client to transparently encrypt it. encrypted_client.put_item(TableName=table_name, Item=plaintext_item) # Get the encrypted item using the standard client. - encrypted_item = client.get_item(TableName=table_name, Key=index_key)['Item'] + encrypted_item = client.get_item(TableName=table_name, Key=index_key)["Item"] # Get the item using the encrypted client, transparently decyrpting it. - decrypted_item = encrypted_client.get_item(TableName=table_name, Key=index_key)['Item'] + decrypted_item = encrypted_client.get_item(TableName=table_name, Key=index_key)["Item"] # Verify that all of the attributes are different in the encrypted item for name in encrypted_attributes: @@ -82,28 +73,16 @@ def encrypt_item(table_name, aws_cmk_id): def encrypt_batch_items(table_name, aws_cmk_id): """Demonstrate use of EncryptedClient to transparently encrypt multiple items in a batch request.""" index_keys = [ - { - 'partition_attribute': {'S': 'is this'}, - 'sort_attribute': {'N': '55'} - }, - { - 'partition_attribute': {'S': 'is this'}, - 'sort_attribute': {'N': '56'} - }, - { - 'partition_attribute': {'S': 'is this'}, - 'sort_attribute': {'N': '57'} - }, - { - 'partition_attribute': {'S': 'another'}, - 'sort_attribute': {'N': '55'} - } + {"partition_attribute": {"S": "is this"}, "sort_attribute": {"N": "55"}}, + {"partition_attribute": {"S": "is this"}, "sort_attribute": {"N": "56"}}, + {"partition_attribute": {"S": "is this"}, "sort_attribute": {"N": "57"}}, + {"partition_attribute": {"S": "another"}, "sort_attribute": {"N": "55"}}, ] plaintext_additional_attributes = { - 'example': {'S': 'data'}, - 'some numbers': {'N': '99'}, - 'and some binary': {'B': b'\x00\x01\x02'}, - 'leave me': {'S': 'alone'} # We want to ignore this attribute + "example": {"S": "data"}, + "some numbers": {"N": "99"}, + "and some binary": {"B": b"\x00\x01\x02"}, + "leave me": {"S": "alone"}, # We want to ignore this attribute } plaintext_items = [] for key in index_keys: @@ -113,43 +92,34 @@ def encrypt_batch_items(table_name, aws_cmk_id): # Collect all of the attributes that will be encrypted (used later). encrypted_attributes = set(plaintext_additional_attributes.keys()) - encrypted_attributes.remove('leave me') + encrypted_attributes.remove("leave me") # Collect all of the attributes that will not be encrypted (used later). unencrypted_attributes = set(index_keys[0].keys()) - unencrypted_attributes.add('leave me') + unencrypted_attributes.add("leave me") # Create a normal client. - client = boto3.client('dynamodb') + client = boto3.client("dynamodb") # Create a crypto materials provider using the specified AWS KMS key. aws_kms_cmp = AwsKmsCryptographicMaterialsProvider(key_id=aws_cmk_id) # Create attribute actions that tells the encrypted client to encrypt all attributes except one. actions = AttributeActions( - default_action=CryptoAction.ENCRYPT_AND_SIGN, - attribute_actions={ - 'leave me': CryptoAction.DO_NOTHING - } + default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={"leave me": CryptoAction.DO_NOTHING} ) # Use these objects to create an encrypted client. - encrypted_client = EncryptedClient( - client=client, - materials_provider=aws_kms_cmp, - attribute_actions=actions - ) + encrypted_client = EncryptedClient(client=client, materials_provider=aws_kms_cmp, attribute_actions=actions) # Put the items to the table, using the encrypted client to transparently encrypt them. - encrypted_client.batch_write_item(RequestItems={ - table_name: [{'PutRequest': {'Item': item}} for item in plaintext_items] - }) + encrypted_client.batch_write_item( + RequestItems={table_name: [{"PutRequest": {"Item": item}} for item in plaintext_items]} + ) # Get the encrypted item using the standard client. - encrypted_items = client.batch_get_item( - RequestItems={table_name: {'Keys': index_keys}} - )['Responses'][table_name] + encrypted_items = client.batch_get_item(RequestItems={table_name: {"Keys": index_keys}})["Responses"][table_name] # Get the item using the encrypted client, transparently decyrpting it. - decrypted_items = encrypted_client.batch_get_item( - RequestItems={table_name: {'Keys': index_keys}} - )['Responses'][table_name] + decrypted_items = encrypted_client.batch_get_item(RequestItems={table_name: {"Keys": index_keys}})["Responses"][ + table_name + ] def _select_index_from_item(item): """Find the index keys that match this item.""" @@ -178,6 +148,6 @@ def _select_item_from_index(index, all_items): assert decrypted_item[name] == encrypted_item[name] == plaintext_item[name] # Clean up the item - encrypted_client.batch_write_item(RequestItems={ - table_name: [{'DeleteRequest': {'Key': key}} for key in index_keys] - }) + encrypted_client.batch_write_item( + RequestItems={table_name: [{"DeleteRequest": {"Key": key}} for key in index_keys]} + ) diff --git a/examples/src/aws_kms_encrypted_item.py b/examples/src/aws_kms_encrypted_item.py index dd4eef4d..8527d3f2 100644 --- a/examples/src/aws_kms_encrypted_item.py +++ b/examples/src/aws_kms_encrypted_item.py @@ -13,6 +13,7 @@ """Example showing use of AWS KMS CMP with item encryption functions directly.""" import boto3 from boto3.dynamodb.types import Binary + from dynamodb_encryption_sdk.encrypted import CryptoConfig from dynamodb_encryption_sdk.encrypted.item import decrypt_python_item, encrypt_python_item from dynamodb_encryption_sdk.identifiers import CryptoAction @@ -23,27 +24,24 @@ def encrypt_item(table_name, aws_cmk_id): """Demonstrate use of EncryptedTable to transparently encrypt an item.""" - index_key = { - 'partition_attribute': 'is this', - 'sort_attribute': 55 - } + index_key = {"partition_attribute": "is this", "sort_attribute": 55} plaintext_item = { - 'example': 'data', - 'some numbers': 99, - 'and some binary': Binary(b'\x00\x01\x02'), - 'leave me': 'alone' # We want to ignore this attribute + "example": "data", + "some numbers": 99, + "and some binary": Binary(b"\x00\x01\x02"), + "leave me": "alone", # We want to ignore this attribute } # Collect all of the attributes that will be encrypted (used later). encrypted_attributes = set(plaintext_item.keys()) - encrypted_attributes.remove('leave me') + encrypted_attributes.remove("leave me") # Collect all of the attributes that will not be encrypted (used later). unencrypted_attributes = set(index_key.keys()) - unencrypted_attributes.add('leave me') + unencrypted_attributes.add("leave me") # Add the index pairs to the item. plaintext_item.update(index_key) # Create a normal table resource. - table = boto3.resource('dynamodb').Table(table_name) + table = boto3.resource("dynamodb").Table(table_name) # Use the TableInfo helper to collect information about the indexes. table_info = TableInfo(name=table_name) @@ -60,24 +58,21 @@ def encrypt_item(table_name, aws_cmk_id): # are the primary index attributes. # These attributes need to be in the form of a DynamoDB JSON structure, so first # convert the standard dictionary. - attributes=dict_to_ddb(index_key) + attributes=dict_to_ddb(index_key), ) # Create attribute actions that tells the encrypted table to encrypt all attributes, # only sign the primary index attributes, and ignore the one identified attribute to # ignore. actions = AttributeActions( - default_action=CryptoAction.ENCRYPT_AND_SIGN, - attribute_actions={'leave me': CryptoAction.DO_NOTHING} + default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={"leave me": CryptoAction.DO_NOTHING} ) actions.set_index_keys(*table_info.protected_index_keys()) # Build the crypto config to use for this item. # When using the higher-level helpers, this is handled for you. crypto_config = CryptoConfig( - materials_provider=aws_kms_cmp, - encryption_context=encryption_context, - attribute_actions=actions + materials_provider=aws_kms_cmp, encryption_context=encryption_context, attribute_actions=actions ) # Encrypt the plaintext item directly diff --git a/examples/src/aws_kms_encrypted_resource.py b/examples/src/aws_kms_encrypted_resource.py index 42f20460..1fcb3336 100644 --- a/examples/src/aws_kms_encrypted_resource.py +++ b/examples/src/aws_kms_encrypted_resource.py @@ -13,6 +13,7 @@ """Example showing use of AWS KMS CMP with EncryptedResource.""" import boto3 from boto3.dynamodb.types import Binary + from dynamodb_encryption_sdk.encrypted.resource import EncryptedResource from dynamodb_encryption_sdk.identifiers import CryptoAction from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider @@ -22,28 +23,16 @@ def encrypt_batch_items(table_name, aws_cmk_id): """Demonstrate use of EncryptedResource to transparently encrypt multiple items in a batch request.""" index_keys = [ - { - 'partition_attribute': 'is this', - 'sort_attribute': 55 - }, - { - 'partition_attribute': 'is this', - 'sort_attribute': 56 - }, - { - 'partition_attribute': 'is this', - 'sort_attribute': 57 - }, - { - 'partition_attribute': 'another', - 'sort_attribute': 55 - } + {"partition_attribute": "is this", "sort_attribute": 55}, + {"partition_attribute": "is this", "sort_attribute": 56}, + {"partition_attribute": "is this", "sort_attribute": 57}, + {"partition_attribute": "another", "sort_attribute": 55}, ] plaintext_additional_attributes = { - 'example': 'data', - 'some numbers': 99, - 'and some binary': Binary(b'\x00\x01\x02'), - 'leave me': 'alone' # We want to ignore this attribute + "example": "data", + "some numbers": 99, + "and some binary": Binary(b"\x00\x01\x02"), + "leave me": "alone", # We want to ignore this attribute } plaintext_items = [] for key in index_keys: @@ -53,43 +42,34 @@ def encrypt_batch_items(table_name, aws_cmk_id): # Collect all of the attributes that will be encrypted (used later). encrypted_attributes = set(plaintext_additional_attributes.keys()) - encrypted_attributes.remove('leave me') + encrypted_attributes.remove("leave me") # Collect all of the attributes that will not be encrypted (used later). unencrypted_attributes = set(index_keys[0].keys()) - unencrypted_attributes.add('leave me') + unencrypted_attributes.add("leave me") # Create a normal service resource. - resource = boto3.resource('dynamodb') + resource = boto3.resource("dynamodb") # Create a crypto materials provider using the specified AWS KMS key. aws_kms_cmp = AwsKmsCryptographicMaterialsProvider(key_id=aws_cmk_id) # Create attribute actions that tells the encrypted resource to encrypt all attributes except one. actions = AttributeActions( - default_action=CryptoAction.ENCRYPT_AND_SIGN, - attribute_actions={ - 'leave me': CryptoAction.DO_NOTHING - } + default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={"leave me": CryptoAction.DO_NOTHING} ) # Use these objects to create an encrypted service resource. - encrypted_resource = EncryptedResource( - resource=resource, - materials_provider=aws_kms_cmp, - attribute_actions=actions - ) + encrypted_resource = EncryptedResource(resource=resource, materials_provider=aws_kms_cmp, attribute_actions=actions) # Put the items to the table, using the encrypted service resource to transparently encrypt them. - encrypted_resource.batch_write_item(RequestItems={ - table_name: [{'PutRequest': {'Item': item}} for item in plaintext_items] - }) + encrypted_resource.batch_write_item( + RequestItems={table_name: [{"PutRequest": {"Item": item}} for item in plaintext_items]} + ) # Get the encrypted item using the standard service resource. - encrypted_items = resource.batch_get_item( - RequestItems={table_name: {'Keys': index_keys}} - )['Responses'][table_name] + encrypted_items = resource.batch_get_item(RequestItems={table_name: {"Keys": index_keys}})["Responses"][table_name] # Get the item using the encrypted service resource, transparently decyrpting it. - decrypted_items = encrypted_resource.batch_get_item( - RequestItems={table_name: {'Keys': index_keys}} - )['Responses'][table_name] + decrypted_items = encrypted_resource.batch_get_item(RequestItems={table_name: {"Keys": index_keys}})["Responses"][ + table_name + ] def _select_index_from_item(item): """Find the index keys that match this item.""" @@ -118,6 +98,6 @@ def _select_item_from_index(index, all_items): assert decrypted_item[name] == encrypted_item[name] == plaintext_item[name] # Clean up the item - encrypted_resource.batch_write_item(RequestItems={ - table_name: [{'DeleteRequest': {'Key': key}} for key in index_keys] - }) + encrypted_resource.batch_write_item( + RequestItems={table_name: [{"DeleteRequest": {"Key": key}} for key in index_keys]} + ) diff --git a/examples/src/aws_kms_encrypted_table.py b/examples/src/aws_kms_encrypted_table.py index 9607a258..f781f64c 100644 --- a/examples/src/aws_kms_encrypted_table.py +++ b/examples/src/aws_kms_encrypted_table.py @@ -13,6 +13,7 @@ """Example showing use of AWS KMS CMP with EncryptedTable.""" import boto3 from boto3.dynamodb.types import Binary + from dynamodb_encryption_sdk.encrypted.table import EncryptedTable from dynamodb_encryption_sdk.identifiers import CryptoAction from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider @@ -21,51 +22,41 @@ def encrypt_item(table_name, aws_cmk_id): """Demonstrate use of EncryptedTable to transparently encrypt an item.""" - index_key = { - 'partition_attribute': 'is this', - 'sort_attribute': 55 - } + index_key = {"partition_attribute": "is this", "sort_attribute": 55} plaintext_item = { - 'example': 'data', - 'some numbers': 99, - 'and some binary': Binary(b'\x00\x01\x02'), - 'leave me': 'alone' # We want to ignore this attribute + "example": "data", + "some numbers": 99, + "and some binary": Binary(b"\x00\x01\x02"), + "leave me": "alone", # We want to ignore this attribute } # Collect all of the attributes that will be encrypted (used later). encrypted_attributes = set(plaintext_item.keys()) - encrypted_attributes.remove('leave me') + encrypted_attributes.remove("leave me") # Collect all of the attributes that will not be encrypted (used later). unencrypted_attributes = set(index_key.keys()) - unencrypted_attributes.add('leave me') + unencrypted_attributes.add("leave me") # Add the index pairs to the item. plaintext_item.update(index_key) # Create a normal table resource. - table = boto3.resource('dynamodb').Table(table_name) + table = boto3.resource("dynamodb").Table(table_name) # Create a crypto materials provider using the specified AWS KMS key. aws_kms_cmp = AwsKmsCryptographicMaterialsProvider(key_id=aws_cmk_id) # Create attribute actions that tells the encrypted table to encrypt all attributes except one. actions = AttributeActions( - default_action=CryptoAction.ENCRYPT_AND_SIGN, - attribute_actions={ - 'leave me': CryptoAction.DO_NOTHING - } + default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={"leave me": CryptoAction.DO_NOTHING} ) # Use these objects to create an encrypted table resource. - encrypted_table = EncryptedTable( - table=table, - materials_provider=aws_kms_cmp, - attribute_actions=actions - ) + encrypted_table = EncryptedTable(table=table, materials_provider=aws_kms_cmp, attribute_actions=actions) # Put the item to the table, using the encrypted table resource to transparently encrypt it. encrypted_table.put_item(Item=plaintext_item) # Get the encrypted item using the standard table resource. - encrypted_item = table.get_item(Key=index_key)['Item'] + encrypted_item = table.get_item(Key=index_key)["Item"] # Get the item using the encrypted table resource, transparently decyrpting it. - decrypted_item = encrypted_table.get_item(Key=index_key)['Item'] + decrypted_item = encrypted_table.get_item(Key=index_key)["Item"] # Verify that all of the attributes are different in the encrypted item for name in encrypted_attributes: diff --git a/examples/src/most_recent_provider_encrypted_table.py b/examples/src/most_recent_provider_encrypted_table.py index c75bb405..81e0cc73 100644 --- a/examples/src/most_recent_provider_encrypted_table.py +++ b/examples/src/most_recent_provider_encrypted_table.py @@ -15,6 +15,7 @@ """ import boto3 from boto3.dynamodb.types import Binary + from dynamodb_encryption_sdk.encrypted.table import EncryptedTable from dynamodb_encryption_sdk.identifiers import CryptoAction from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider @@ -25,64 +26,51 @@ def encrypt_item(table_name, aws_cmk_id, meta_table_name, material_name): """Demonstrate use of EncryptedTable to transparently encrypt an item.""" - index_key = { - 'partition_attribute': 'is this', - 'sort_attribute': 55 - } + index_key = {"partition_attribute": "is this", "sort_attribute": 55} plaintext_item = { - 'example': 'data', - 'some numbers': 99, - 'and some binary': Binary(b'\x00\x01\x02'), - 'leave me': 'alone' # We want to ignore this attribute + "example": "data", + "some numbers": 99, + "and some binary": Binary(b"\x00\x01\x02"), + "leave me": "alone", # We want to ignore this attribute } # Collect all of the attributes that will be encrypted (used later). encrypted_attributes = set(plaintext_item.keys()) - encrypted_attributes.remove('leave me') + encrypted_attributes.remove("leave me") # Collect all of the attributes that will not be encrypted (used later). unencrypted_attributes = set(index_key.keys()) - unencrypted_attributes.add('leave me') + unencrypted_attributes.add("leave me") # Add the index pairs to the item. plaintext_item.update(index_key) # Create a normal table resource for the meta store. - meta_table = boto3.resource('dynamodb').Table(meta_table_name) + meta_table = boto3.resource("dynamodb").Table(meta_table_name) # Create a crypto materials provider for the meta store using the specified AWS KMS key. aws_kms_cmp = AwsKmsCryptographicMaterialsProvider(key_id=aws_cmk_id) # Create a meta store using the AWS KMS crypto materials provider. - meta_store = MetaStore( - table=meta_table, - materials_provider=aws_kms_cmp - ) + meta_store = MetaStore(table=meta_table, materials_provider=aws_kms_cmp) # Create a most recent provider using the meta store. most_recent_cmp = MostRecentProvider( provider_store=meta_store, material_name=material_name, - version_ttl=600.0 # Check for a new material version every five minutes. + version_ttl=600.0, # Check for a new material version every five minutes. ) # Create a normal table resource. - table = boto3.resource('dynamodb').Table(table_name) + table = boto3.resource("dynamodb").Table(table_name) # Create attribute actions that tells the encrypted table to encrypt all attributes except one. actions = AttributeActions( - default_action=CryptoAction.ENCRYPT_AND_SIGN, - attribute_actions={ - 'leave me': CryptoAction.DO_NOTHING - } + default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={"leave me": CryptoAction.DO_NOTHING} ) # Use these objects to create an encrypted table resource. - encrypted_table = EncryptedTable( - table=table, - materials_provider=most_recent_cmp, - attribute_actions=actions - ) + encrypted_table = EncryptedTable(table=table, materials_provider=most_recent_cmp, attribute_actions=actions) # Put the item to the table, using the encrypted table resource to transparently encrypt it. encrypted_table.put_item(Item=plaintext_item) # Get the encrypted item using the standard table resource. - encrypted_item = table.get_item(Key=index_key)['Item'] + encrypted_item = table.get_item(Key=index_key)["Item"] # Get the item using the encrypted table resource, transparently decyrpting it. - decrypted_item = encrypted_table.get_item(Key=index_key)['Item'] + decrypted_item = encrypted_table.get_item(Key=index_key)["Item"] # Verify that all of the attributes are different in the encrypted item for name in encrypted_attributes: diff --git a/examples/src/wrapped_rsa_encrypted_table.py b/examples/src/wrapped_rsa_encrypted_table.py index 85816593..d6eed86f 100644 --- a/examples/src/wrapped_rsa_encrypted_table.py +++ b/examples/src/wrapped_rsa_encrypted_table.py @@ -13,6 +13,7 @@ """Example showing use of a RSA wrapped CMP with EncryptedTable.""" import boto3 from boto3.dynamodb.types import Binary + from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey from dynamodb_encryption_sdk.encrypted.table import EncryptedTable from dynamodb_encryption_sdk.identifiers import CryptoAction, EncryptionKeyType, KeyEncodingType @@ -22,69 +23,57 @@ def encrypt_item(table_name, rsa_wrapping_private_key_bytes, rsa_signing_private_key_bytes): """Demonstrate use of EncryptedTable to transparently encrypt an item.""" - index_key = { - 'partition_attribute': 'is this', - 'sort_attribute': 55 - } + index_key = {"partition_attribute": "is this", "sort_attribute": 55} plaintext_item = { - 'example': 'data', - 'some numbers': 99, - 'and some binary': Binary(b'\x00\x01\x02'), - 'leave me': 'alone' # We want to ignore this attribute + "example": "data", + "some numbers": 99, + "and some binary": Binary(b"\x00\x01\x02"), + "leave me": "alone", # We want to ignore this attribute } # Collect all of the attributes that will be encrypted (used later). encrypted_attributes = set(plaintext_item.keys()) - encrypted_attributes.remove('leave me') + encrypted_attributes.remove("leave me") # Collect all of the attributes that will not be encrypted (used later). unencrypted_attributes = set(index_key.keys()) - unencrypted_attributes.add('leave me') + unencrypted_attributes.add("leave me") # Add the index pairs to the item. plaintext_item.update(index_key) # Create a normal table resource. - table = boto3.resource('dynamodb').Table(table_name) + table = boto3.resource("dynamodb").Table(table_name) # Create a crypto materials provider using the provided wrapping and signing keys. # We show private keys used here, but public keys could be used as well, allowing # only wrapping or signature verification. wrapping_key = JceNameLocalDelegatedKey( key=rsa_wrapping_private_key_bytes, - algorithm='RSA', + algorithm="RSA", key_type=EncryptionKeyType.PRIVATE, - key_encoding=KeyEncodingType.DER + key_encoding=KeyEncodingType.DER, ) signing_key = JceNameLocalDelegatedKey( key=rsa_signing_private_key_bytes, - algorithm='SHA512withRSA', + algorithm="SHA512withRSA", key_type=EncryptionKeyType.PRIVATE, - key_encoding=KeyEncodingType.DER + key_encoding=KeyEncodingType.DER, ) wrapped_cmp = WrappedCryptographicMaterialsProvider( - wrapping_key=wrapping_key, - unwrapping_key=wrapping_key, - signing_key=signing_key + wrapping_key=wrapping_key, unwrapping_key=wrapping_key, signing_key=signing_key ) # Create attribute actions that tells the encrypted table to encrypt all attributes except one. actions = AttributeActions( - default_action=CryptoAction.ENCRYPT_AND_SIGN, - attribute_actions={ - 'leave me': CryptoAction.DO_NOTHING - } + default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={"leave me": CryptoAction.DO_NOTHING} ) # Use these objects to create an encrypted table resource. - encrypted_table = EncryptedTable( - table=table, - materials_provider=wrapped_cmp, - attribute_actions=actions - ) + encrypted_table = EncryptedTable(table=table, materials_provider=wrapped_cmp, attribute_actions=actions) # Put the item to the table, using the encrypted table resource to transparently encrypt it. encrypted_table.put_item(Item=plaintext_item) # Get the encrypted item using the standard table resource. - encrypted_item = table.get_item(Key=index_key)['Item'] + encrypted_item = table.get_item(Key=index_key)["Item"] # Get the item using the encrypted table resource, transparently decyrpting it. - decrypted_item = encrypted_table.get_item(Key=index_key)['Item'] + decrypted_item = encrypted_table.get_item(Key=index_key)["Item"] # Verify that all of the attributes are different in the encrypted item for name in encrypted_attributes: diff --git a/examples/src/wrapped_symmetric_encrypted_table.py b/examples/src/wrapped_symmetric_encrypted_table.py index 6295a630..22a65087 100644 --- a/examples/src/wrapped_symmetric_encrypted_table.py +++ b/examples/src/wrapped_symmetric_encrypted_table.py @@ -13,6 +13,7 @@ """Example showing use of a symmetric wrapped CMP with EncryptedTable.""" import boto3 from boto3.dynamodb.types import Binary + from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey from dynamodb_encryption_sdk.encrypted.table import EncryptedTable from dynamodb_encryption_sdk.identifiers import CryptoAction, EncryptionKeyType, KeyEncodingType @@ -22,67 +23,55 @@ def encrypt_item(table_name, aes_wrapping_key_bytes, hmac_signing_key_bytes): """Demonstrate use of EncryptedTable to transparently encrypt an item.""" - index_key = { - 'partition_attribute': 'is this', - 'sort_attribute': 55 - } + index_key = {"partition_attribute": "is this", "sort_attribute": 55} plaintext_item = { - 'example': 'data', - 'some numbers': 99, - 'and some binary': Binary(b'\x00\x01\x02'), - 'leave me': 'alone' # We want to ignore this attribute + "example": "data", + "some numbers": 99, + "and some binary": Binary(b"\x00\x01\x02"), + "leave me": "alone", # We want to ignore this attribute } # Collect all of the attributes that will be encrypted (used later). encrypted_attributes = set(plaintext_item.keys()) - encrypted_attributes.remove('leave me') + encrypted_attributes.remove("leave me") # Collect all of the attributes that will not be encrypted (used later). unencrypted_attributes = set(index_key.keys()) - unencrypted_attributes.add('leave me') + unencrypted_attributes.add("leave me") # Add the index pairs to the item. plaintext_item.update(index_key) # Create a normal table resource. - table = boto3.resource('dynamodb').Table(table_name) + table = boto3.resource("dynamodb").Table(table_name) # Create a crypto materials provider using the provided wrapping and signing keys. wrapping_key = JceNameLocalDelegatedKey( key=aes_wrapping_key_bytes, - algorithm='AES', + algorithm="AES", key_type=EncryptionKeyType.SYMMETRIC, - key_encoding=KeyEncodingType.RAW + key_encoding=KeyEncodingType.RAW, ) signing_key = JceNameLocalDelegatedKey( key=hmac_signing_key_bytes, - algorithm='HmacSHA512', + algorithm="HmacSHA512", key_type=EncryptionKeyType.SYMMETRIC, - key_encoding=KeyEncodingType.RAW + key_encoding=KeyEncodingType.RAW, ) wrapped_cmp = WrappedCryptographicMaterialsProvider( - wrapping_key=wrapping_key, - unwrapping_key=wrapping_key, - signing_key=signing_key + wrapping_key=wrapping_key, unwrapping_key=wrapping_key, signing_key=signing_key ) # Create attribute actions that tells the encrypted table to encrypt all attributes except one. actions = AttributeActions( - default_action=CryptoAction.ENCRYPT_AND_SIGN, - attribute_actions={ - 'leave me': CryptoAction.DO_NOTHING - } + default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={"leave me": CryptoAction.DO_NOTHING} ) # Use these objects to create an encrypted table resource. - encrypted_table = EncryptedTable( - table=table, - materials_provider=wrapped_cmp, - attribute_actions=actions - ) + encrypted_table = EncryptedTable(table=table, materials_provider=wrapped_cmp, attribute_actions=actions) # Put the item to the table, using the encrypted table resource to transparently encrypt it. encrypted_table.put_item(Item=plaintext_item) # Get the encrypted item using the standard table resource. - encrypted_item = table.get_item(Key=index_key)['Item'] + encrypted_item = table.get_item(Key=index_key)["Item"] # Get the item using the encrypted table resource, transparently decyrpting it. - decrypted_item = encrypted_table.get_item(Key=index_key)['Item'] + decrypted_item = encrypted_table.get_item(Key=index_key)["Item"] # Verify that all of the attributes are different in the encrypted item for name in encrypted_attributes: diff --git a/examples/test/test_aws_kms_encrypted_examples.py b/examples/test/test_aws_kms_encrypted_examples.py index 3427a592..8b8b5551 100644 --- a/examples/test/test_aws_kms_encrypted_examples.py +++ b/examples/test/test_aws_kms_encrypted_examples.py @@ -13,10 +13,13 @@ """Test ``aws_kms_encrypted_*`` examples.""" import os import sys -sys.path.extend([ # noqa - os.sep.join([os.path.dirname(__file__), '..', '..', 'test', 'integration']), - os.sep.join([os.path.dirname(__file__), '..', 'src']) -]) + +sys.path.extend( + [ # noqa + os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"]), + os.sep.join([os.path.dirname(__file__), "..", "src"]), + ] +) import pytest diff --git a/examples/test/test_most_recent_provider_encrypted_examples.py b/examples/test/test_most_recent_provider_encrypted_examples.py index 5427f1ee..7db4160b 100644 --- a/examples/test/test_most_recent_provider_encrypted_examples.py +++ b/examples/test/test_most_recent_provider_encrypted_examples.py @@ -13,10 +13,13 @@ """Test most recent provider examples.""" import os import sys -sys.path.extend([ # noqa - os.sep.join([os.path.dirname(__file__), '..', '..', 'test', 'integration']), - os.sep.join([os.path.dirname(__file__), '..', 'src']) -]) + +sys.path.extend( + [ # noqa + os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"]), + os.sep.join([os.path.dirname(__file__), "..", "src"]), + ] +) import uuid import boto3 @@ -31,13 +34,13 @@ def test_most_recent_encrypted_table(ddb_table_name, cmk_arn): # define random new names for material and metastore table - meta_table_name = 'meta-table-{}'.format(uuid.uuid4()) - material_name = 'material-{}'.format(uuid.uuid4()) + meta_table_name = "meta-table-{}".format(uuid.uuid4()) + material_name = "material-{}".format(uuid.uuid4()) # create the metastore table - client = boto3.client('dynamodb') + client = boto3.client("dynamodb") MetaStore.create_table(client, meta_table_name, 10, 10) - waiter = client.get_waiter('table_exists') + waiter = client.get_waiter("table_exists") waiter.wait(TableName=meta_table_name) # run the actual test @@ -45,5 +48,5 @@ def test_most_recent_encrypted_table(ddb_table_name, cmk_arn): # clean up the meta store table client.delete_table(TableName=meta_table_name) - waiter = client.get_waiter('table_not_exists') + waiter = client.get_waiter("table_not_exists") waiter.wait(TableName=meta_table_name) diff --git a/examples/test/test_wrapped_encrypted_examples.py b/examples/test/test_wrapped_encrypted_examples.py index 9988e819..fc69f186 100644 --- a/examples/test/test_wrapped_encrypted_examples.py +++ b/examples/test/test_wrapped_encrypted_examples.py @@ -13,10 +13,13 @@ """Test ``wrapped_*_encrypted_*`` examples.""" import os import sys -sys.path.extend([ # noqa - os.sep.join([os.path.dirname(__file__), '..', '..', 'test', 'integration']), - os.sep.join([os.path.dirname(__file__), '..', 'src']) -]) + +sys.path.extend( + [ # noqa + os.sep.join([os.path.dirname(__file__), "..", "..", "test", "integration"]), + os.sep.join([os.path.dirname(__file__), "..", "src"]), + ] +) import pytest @@ -29,12 +32,12 @@ def test_wrapped_rsa_encrypted_table(ddb_table_name): - wrapping_key_bytes = JceNameLocalDelegatedKey.generate('RSA', 4096).key - signing_key_bytes = JceNameLocalDelegatedKey.generate('SHA512withRSA', 4096).key + wrapping_key_bytes = JceNameLocalDelegatedKey.generate("RSA", 4096).key + signing_key_bytes = JceNameLocalDelegatedKey.generate("SHA512withRSA", 4096).key wrapped_rsa_encrypted_table.encrypt_item(ddb_table_name, wrapping_key_bytes, signing_key_bytes) def test_wrapped_symmetric_encrypted_table(ddb_table_name): - wrapping_key_bytes = JceNameLocalDelegatedKey.generate('AES', 256).key - signing_key_bytes = JceNameLocalDelegatedKey.generate('HmacSHA512', 256).key + wrapping_key_bytes = JceNameLocalDelegatedKey.generate("AES", 256).key + signing_key_bytes = JceNameLocalDelegatedKey.generate("HmacSHA512", 256).key wrapped_symmetric_encrypted_table.encrypt_item(ddb_table_name, wrapping_key_bytes, signing_key_bytes) diff --git a/setup.cfg b/setup.cfg index 4e518fd4..0a82e619 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,9 +42,24 @@ ignore = # Ignoring D401 pending discussion of imperative mood D401, # Ignoring D202 (no blank lines after function docstring) because mypy confuses flake8 - D202 + D202, + # E203 is not PEP8 compliant https://github.com/ambv/black#slices + E203, + # W503 is not PEP8 compliant https://github.com/ambv/black#line-breaks--binary-operators + W503 # Doc8 Configuration [doc8] max-line-length = 120 + +[isort] +line_length = 120 +# https://github.com/timothycrosley/isort#multi-line-output-modes +multi_line_output = 3 +include_trailing_comma = True +force_grid_wrap = 0 +combine_as_imports = True +not_skip = __init__.py +known_first_party = dynamodb_encryption_sdk +known_third_party =attr,aws_kms_encrypted_client,aws_kms_encrypted_item,aws_kms_encrypted_resource,aws_kms_encrypted_table,boto3,botocore,cryptography,dynamodb_encryption_sdk,functional_test_utils,functional_test_vector_generators,hypothesis,hypothesis_strategies,integration_test_utils,mock,most_recent_provider_encrypted_table,moto,mypy_extensions,pytest,pytest_mock,setuptools,six,wrapped_rsa_encrypted_table,wrapped_symmetric_encrypted_table diff --git a/setup.py b/setup.py index 0a331375..a1c79400 100644 --- a/setup.py +++ b/setup.py @@ -5,62 +5,57 @@ from setuptools import find_packages, setup -VERSION_RE = re.compile(r'''__version__ = ['"]([0-9.]+)['"]''') +VERSION_RE = re.compile(r"""__version__ = ['"]([0-9.]+)['"]""") HERE = os.path.abspath(os.path.dirname(__file__)) def read(*args): """Reads complete file contents.""" - return io.open(os.path.join(HERE, *args), encoding='utf-8').read() + return io.open(os.path.join(HERE, *args), encoding="utf-8").read() def get_version(): """Reads the version from this module.""" - init = read('src', 'dynamodb_encryption_sdk', 'identifiers.py') + init = read("src", "dynamodb_encryption_sdk", "identifiers.py") return VERSION_RE.search(init).group(1) def get_requirements(): """Reads the requirements file.""" - requirements = read('requirements.txt') + requirements = read("requirements.txt") return [r for r in requirements.strip().splitlines()] setup( - name='dynamodb-encryption-sdk', + name="dynamodb-encryption-sdk", version=get_version(), - packages=find_packages('src'), - package_dir={'': 'src'}, - url='https://github.com/aws/aws-dynamodb-encryption-python', - author='Amazon Web Services', - author_email='aws-cryptools@amazon.com', - maintainer='Amazon Web Services', - description='DynamoDB Encryption Client for Python', - long_description=read('README.rst'), - keywords='dynamodb-encryption-sdk aws kms encryption dynamodb', - data_files=[ - 'README.rst', - 'CHANGELOG.rst', - 'LICENSE', - 'requirements.txt' - ], - license='Apache License 2.0', + packages=find_packages("src"), + package_dir={"": "src"}, + url="https://github.com/aws/aws-dynamodb-encryption-python", + author="Amazon Web Services", + author_email="aws-cryptools@amazon.com", + maintainer="Amazon Web Services", + description="DynamoDB Encryption Client for Python", + long_description=read("README.rst"), + keywords="dynamodb-encryption-sdk aws kms encryption dynamodb", + data_files=["README.rst", "CHANGELOG.rst", "LICENSE", "requirements.txt"], + license="Apache License 2.0", install_requires=get_requirements(), classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Security', - 'Topic :: Security :: Cryptography' - ] + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Security", + "Topic :: Security :: Cryptography", + ], ) diff --git a/src/dynamodb_encryption_sdk/__init__.py b/src/dynamodb_encryption_sdk/__init__.py index 2b66fba4..7b5dba80 100644 --- a/src/dynamodb_encryption_sdk/__init__.py +++ b/src/dynamodb_encryption_sdk/__init__.py @@ -13,20 +13,22 @@ """DynamoDB Encryption Client.""" from dynamodb_encryption_sdk.encrypted.client import EncryptedClient from dynamodb_encryption_sdk.encrypted.item import ( - decrypt_dynamodb_item, decrypt_python_item, - encrypt_dynamodb_item, encrypt_python_item + decrypt_dynamodb_item, + decrypt_python_item, + encrypt_dynamodb_item, + encrypt_python_item, ) from dynamodb_encryption_sdk.encrypted.resource import EncryptedResource from dynamodb_encryption_sdk.encrypted.table import EncryptedTable from dynamodb_encryption_sdk.identifiers import __version__ -# TableConfiguration -# MaterialDescription -# ItemConfiguration - __all__ = ( - 'decrypt_dynamodb_item', 'decrypt_python_item', - 'encrypt_dynamodb_item', 'encrypt_python_item', - 'EncryptedClient', 'EncryptedResource', 'EncryptedTable', - '__version__' + "decrypt_dynamodb_item", + "decrypt_python_item", + "encrypt_dynamodb_item", + "encrypt_python_item", + "EncryptedClient", + "EncryptedResource", + "EncryptedTable", + "__version__", ) diff --git a/src/dynamodb_encryption_sdk/delegated_keys/__init__.py b/src/dynamodb_encryption_sdk/delegated_keys/__init__.py index 35542ef8..b41caeee 100644 --- a/src/dynamodb_encryption_sdk/delegated_keys/__init__.py +++ b/src/dynamodb_encryption_sdk/delegated_keys/__init__.py @@ -12,17 +12,19 @@ # language governing permissions and limitations under the License. """Delegated keys.""" import abc + +import six + +from dynamodb_encryption_sdk.identifiers import EncryptionKeyType # noqa pylint: disable=unused-import + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Dict, Optional, Text # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass -import six - -from dynamodb_encryption_sdk.identifiers import EncryptionKeyType # noqa pylint: disable=unused-import -__all__ = ('DelegatedKey',) +__all__ = ("DelegatedKey",) def _raise_not_implemented(method_name): @@ -70,7 +72,7 @@ def generate(cls, algorithm, key_length): # type: ignore :returns: Generated delegated key :rtype: DelegatedKey """ - _raise_not_implemented('generate') + _raise_not_implemented("generate") def encrypt(self, algorithm, name, plaintext, additional_associated_data=None): # type: ignore # type: (Text, Text, bytes, Optional[Dict[Text, Text]]) -> bytes @@ -85,7 +87,7 @@ def encrypt(self, algorithm, name, plaintext, additional_associated_data=None): :returns: Encrypted ciphertext :rtype: bytes """ - _raise_not_implemented('encrypt') + _raise_not_implemented("encrypt") def decrypt(self, algorithm, name, ciphertext, additional_associated_data=None): # type: ignore # type: (Text, Text, bytes, Optional[Dict[Text, Text]]) -> bytes @@ -100,7 +102,7 @@ def decrypt(self, algorithm, name, ciphertext, additional_associated_data=None): :returns: Decrypted plaintext :rtype: bytes """ - _raise_not_implemented('decrypt') + _raise_not_implemented("decrypt") def wrap(self, algorithm, content_key, additional_associated_data=None): # type: ignore # type: (Text, bytes, Optional[Dict[Text, Text]]) -> bytes @@ -114,15 +116,10 @@ def wrap(self, algorithm, content_key, additional_associated_data=None): # type :returns: Wrapped key :rtype: bytes """ - _raise_not_implemented('wrap') + _raise_not_implemented("wrap") def unwrap( # type: ignore - self, - algorithm, - wrapped_key, - wrapped_key_algorithm, - wrapped_key_type, - additional_associated_data=None + self, algorithm, wrapped_key, wrapped_key_algorithm, wrapped_key_type, additional_associated_data=None ): # type: (Text, bytes, Text, EncryptionKeyType, Optional[Dict[Text, Text]]) -> DelegatedKey # pylint: disable=unused-argument,no-self-use @@ -137,7 +134,7 @@ def unwrap( # type: ignore :returns: Delegated key using unwrapped key :rtype: DelegatedKey """ - _raise_not_implemented('unwrap') + _raise_not_implemented("unwrap") def sign(self, algorithm, data): # type: ignore # type: (Text, bytes) -> bytes @@ -149,7 +146,7 @@ def sign(self, algorithm, data): # type: ignore :returns: Signature value :rtype: bytes """ - _raise_not_implemented('sign') + _raise_not_implemented("sign") def verify(self, algorithm, signature, data): # type: ignore # type: (Text, bytes, bytes) -> None @@ -160,7 +157,7 @@ def verify(self, algorithm, signature, data): # type: ignore :param bytes signature: Signature to verify :param bytes data: Data over which to verify signature """ - _raise_not_implemented('verify') + _raise_not_implemented("verify") def signing_algorithm(self): # type: ignore # type: () -> Text @@ -173,4 +170,4 @@ def signing_algorithm(self): # type: ignore :returns: Signing algorithm identifier :rtype: str """ - _raise_not_implemented('signing_algorithm') + _raise_not_implemented("signing_algorithm") diff --git a/src/dynamodb_encryption_sdk/delegated_keys/jce.py b/src/dynamodb_encryption_sdk/delegated_keys/jce.py index 54ad38ce..4edc6b2c 100644 --- a/src/dynamodb_encryption_sdk/delegated_keys/jce.py +++ b/src/dynamodb_encryption_sdk/delegated_keys/jce.py @@ -17,10 +17,16 @@ import os import attr +import six from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa -import six + +from dynamodb_encryption_sdk.exceptions import JceTransformationError, UnwrappingError +from dynamodb_encryption_sdk.identifiers import LOGGER_NAME, EncryptionKeyType, KeyEncodingType +from dynamodb_encryption_sdk.internal.crypto.jce_bridge import authentication, encryption, primitives + +from . import DelegatedKey try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Dict, Optional, Text # noqa pylint: disable=unused-import @@ -28,12 +34,8 @@ # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.exceptions import JceTransformationError, UnwrappingError -from dynamodb_encryption_sdk.identifiers import EncryptionKeyType, KeyEncodingType, LOGGER_NAME -from dynamodb_encryption_sdk.internal.crypto.jce_bridge import authentication, encryption, primitives -from . import DelegatedKey -__all__ = ('JceNameLocalDelegatedKey',) +__all__ = ("JceNameLocalDelegatedKey",) _LOGGER = logging.getLogger(LOGGER_NAME) @@ -54,23 +56,16 @@ def _generate_rsa_key(key_length): :returns: DER-encoded private key, private key identifier, and DER encoding identifier :rtype: tuple(bytes, :class:`EncryptionKeyType`, :class:`KeyEncodingType`) """ - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=key_length, - backend=default_backend() - ) + private_key = rsa.generate_private_key(public_exponent=65537, key_size=key_length, backend=default_backend()) key_bytes = private_key.private_bytes( encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption() + encryption_algorithm=serialization.NoEncryption(), ) return key_bytes, EncryptionKeyType.PRIVATE, KeyEncodingType.DER -_ALGORITHM_GENERATE_MAP = { - 'SYMMETRIC': _generate_symmetric_key, - 'RSA': _generate_rsa_key -} +_ALGORITHM_GENERATE_MAP = {"SYMMETRIC": _generate_symmetric_key, "RSA": _generate_rsa_key} @attr.s(init=False) @@ -119,11 +114,11 @@ class JceNameLocalDelegatedKey(DelegatedKey): _key_encoding = attr.ib(validator=attr.validators.instance_of(KeyEncodingType)) def __init__( - self, - key, # type: bytes - algorithm, # type: Text - key_type, # type: EncryptionKeyType - key_encoding, # type: KeyEncodingType + self, + key, # type: bytes + algorithm, # type: Text + key_type, # type: EncryptionKeyType + key_encoding, # type: KeyEncodingType ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -172,9 +167,7 @@ def __attrs_post_init__(self): pass else: self.__key = key_transformer.load_key( # attrs confuses pylint: disable=attribute-defined-outside-init - self.key, - self._key_type, - self._key_encoding + self.key, self._key_type, self._key_encoding ) self._enable_encryption() self._enable_wrap() @@ -189,9 +182,7 @@ def __attrs_post_init__(self): pass else: self.__key = key_transformer.load_key( # attrs confuses pylint: disable=attribute-defined-outside-init - self.key, - self._key_type, - self._key_encoding + self.key, self._key_type, self._key_encoding ) self._enable_authentication() return @@ -211,15 +202,15 @@ def generate(cls, algorithm, key_length=None): """ # Normalize to allow generating both encryption and signing keys algorithm_lookup = algorithm.upper() - if 'HMAC' in algorithm_lookup or algorithm_lookup in ('AES', 'AESWRAP'): - algorithm_lookup = 'SYMMETRIC' - elif 'RSA' in algorithm_lookup: - algorithm_lookup = 'RSA' + if "HMAC" in algorithm_lookup or algorithm_lookup in ("AES", "AESWRAP"): + algorithm_lookup = "SYMMETRIC" + elif "RSA" in algorithm_lookup: + algorithm_lookup = "RSA" try: key_generator = _ALGORITHM_GENERATE_MAP[algorithm_lookup] except KeyError: - raise ValueError('Unknown algorithm: {}'.format(algorithm)) + raise ValueError("Unknown algorithm: {}".format(algorithm)) key, key_type, key_encoding = key_generator(key_length) return cls(key=key, algorithm=algorithm, key_type=key_type, key_encoding=key_encoding) @@ -233,7 +224,7 @@ def allowed_for_raw_materials(self): :returns: decision :rtype: bool """ - return self.algorithm == 'AES' + return self.algorithm == "AES" def _encrypt(self, algorithm, name, plaintext, additional_associated_data=None): # type: (Text, Text, bytes, Optional[Dict[Text, Text]]) -> bytes @@ -281,10 +272,7 @@ def _wrap(self, algorithm, content_key, additional_associated_data=None): :rtype: bytes """ wrapper = encryption.JavaCipher.from_transformation(algorithm) - return wrapper.wrap( - wrapping_key=self.__key, - key_to_wrap=content_key - ) + return wrapper.wrap(wrapping_key=self.__key, key_to_wrap=content_key) def _unwrap(self, algorithm, wrapped_key, wrapped_key_algorithm, wrapped_key_type, additional_associated_data=None): # type: (Text, bytes, Text, EncryptionKeyType, Optional[Dict[Text, Text]]) -> DelegatedKey @@ -303,15 +291,12 @@ def _unwrap(self, algorithm, wrapped_key, wrapped_key_algorithm, wrapped_key_typ raise UnwrappingError('Unsupported wrapped key type: "{}"'.format(wrapped_key_type)) unwrapper = encryption.JavaCipher.from_transformation(algorithm) - unwrapped_key = unwrapper.unwrap( - wrapping_key=self.__key, - wrapped_key=wrapped_key - ) + unwrapped_key = unwrapper.unwrap(wrapping_key=self.__key, wrapped_key=wrapped_key) return JceNameLocalDelegatedKey( key=unwrapped_key, algorithm=wrapped_key_algorithm, key_type=wrapped_key_type, - key_encoding=KeyEncodingType.RAW + key_encoding=KeyEncodingType.RAW, ) def _sign(self, algorithm, data): diff --git a/src/dynamodb_encryption_sdk/encrypted/__init__.py b/src/dynamodb_encryption_sdk/encrypted/__init__.py index 105f3d93..e3e89ec1 100644 --- a/src/dynamodb_encryption_sdk/encrypted/__init__.py +++ b/src/dynamodb_encryption_sdk/encrypted/__init__.py @@ -15,19 +15,20 @@ import attr +from dynamodb_encryption_sdk.exceptions import InvalidArgumentError +from dynamodb_encryption_sdk.identifiers import CryptoAction +from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider +from dynamodb_encryption_sdk.materials import CryptographicMaterials # noqa pylint: disable=unused-import +from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Dict # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.exceptions import InvalidArgumentError -from dynamodb_encryption_sdk.identifiers import CryptoAction -from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider -from dynamodb_encryption_sdk.materials import CryptographicMaterials # noqa pylint: disable=unused-import -from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext -__all__ = ('CryptoConfig',) +__all__ = ("CryptoConfig",) @attr.s(init=False) @@ -48,10 +49,10 @@ class CryptoConfig(object): attribute_actions = attr.ib(validator=attr.validators.instance_of(AttributeActions)) def __init__( - self, - materials_provider, # type: CryptographicMaterialsProvider - encryption_context, # type: EncryptionContext - attribute_actions # type: AttributeActions + self, + materials_provider, # type: CryptographicMaterialsProvider + encryption_context, # type: EncryptionContext + attribute_actions, # type: AttributeActions ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -67,12 +68,15 @@ def __attrs_post_init__(self): # type: () -> None """Make sure that primary index attributes are not being encrypted.""" if self.encryption_context.partition_key_name is not None: - if self.attribute_actions.action(self.encryption_context.partition_key_name) is CryptoAction.ENCRYPT_AND_SIGN: # noqa pylint: disable=line-too-long - raise InvalidArgumentError('Cannot encrypt partition key') + if ( + self.attribute_actions.action(self.encryption_context.partition_key_name) + is CryptoAction.ENCRYPT_AND_SIGN + ): # noqa pylint: disable=line-too-long + raise InvalidArgumentError("Cannot encrypt partition key") if self.encryption_context.sort_key_name is not None: if self.attribute_actions.action(self.encryption_context.sort_key_name) is CryptoAction.ENCRYPT_AND_SIGN: - raise InvalidArgumentError('Cannot encrypt sort key') + raise InvalidArgumentError("Cannot encrypt sort key") def decryption_materials(self): # type: () -> CryptographicMaterials @@ -102,7 +106,7 @@ def copy(self): return CryptoConfig( materials_provider=self.materials_provider, encryption_context=copy.copy(self.encryption_context), - attribute_actions=self.attribute_actions + attribute_actions=self.attribute_actions, ) def with_item(self, item): diff --git a/src/dynamodb_encryption_sdk/encrypted/client.py b/src/dynamodb_encryption_sdk/encrypted/client.py index 04490a60..21720a40 100644 --- a/src/dynamodb_encryption_sdk/encrypted/client.py +++ b/src/dynamodb_encryption_sdk/encrypted/client.py @@ -16,24 +16,31 @@ import attr import botocore -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Any, Callable, Dict, Iterator, Optional # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - from dynamodb_encryption_sdk.internal.utils import ( - crypto_config_from_cache, crypto_config_from_kwargs, - decrypt_batch_get_item, decrypt_get_item, decrypt_multi_get, - encrypt_batch_write_item, encrypt_put_item, TableInfoCache, - validate_get_arguments + TableInfoCache, + crypto_config_from_cache, + crypto_config_from_kwargs, + decrypt_batch_get_item, + decrypt_get_item, + decrypt_multi_get, + encrypt_batch_write_item, + encrypt_put_item, + validate_get_arguments, ) from dynamodb_encryption_sdk.internal.validators import callable_validator from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider from dynamodb_encryption_sdk.structures import AttributeActions + from .item import decrypt_dynamodb_item, decrypt_python_item, encrypt_dynamodb_item, encrypt_python_item -__all__ = ('EncryptedClient', 'EncryptedPaginator') +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Any, Callable, Dict, Iterator, Optional # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + + +__all__ = ("EncryptedClient", "EncryptedPaginator") @attr.s(init=False) @@ -51,10 +58,10 @@ class EncryptedPaginator(object): _crypto_config_method = attr.ib(validator=callable_validator) def __init__( - self, - paginator, # type: botocore.paginate.Paginator - decrypt_method, # type: Callable - crypto_config_method # type: Callable + self, + paginator, # type: botocore.paginate.Paginator + decrypt_method, # type: Callable + crypto_config_method, # type: Callable ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -97,11 +104,8 @@ def paginate(self, **kwargs): crypto_config, ddb_kwargs = self._crypto_config_method(**kwargs) for page in self._paginator.paginate(**ddb_kwargs): - for pos, value in enumerate(page['Items']): - page['Items'][pos] = self._decrypt_method( - item=value, - crypto_config=crypto_config - ) + for pos, value in enumerate(page["Items"]): + page["Items"][pos] = self._decrypt_method(item=value, crypto_config=crypto_config) yield page @@ -152,25 +156,18 @@ class EncryptedClient(object): _client = attr.ib(validator=attr.validators.instance_of(botocore.client.BaseClient)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) _attribute_actions = attr.ib( - validator=attr.validators.instance_of(AttributeActions), - default=attr.Factory(AttributeActions) - ) - _auto_refresh_table_indexes = attr.ib( - validator=attr.validators.instance_of(bool), - default=True - ) - _expect_standard_dictionaries = attr.ib( - validator=attr.validators.instance_of(bool), - default=False + validator=attr.validators.instance_of(AttributeActions), default=attr.Factory(AttributeActions) ) + _auto_refresh_table_indexes = attr.ib(validator=attr.validators.instance_of(bool), default=True) + _expect_standard_dictionaries = attr.ib(validator=attr.validators.instance_of(bool), default=False) def __init__( - self, - client, # type: botocore.client.BaseClient - materials_provider, # type: CryptographicMaterialsProvider - attribute_actions=None, # type: Optional[AttributeActions] - auto_refresh_table_indexes=True, # type: Optional[bool] - expect_standard_dictionaries=False # type: Optional[bool] + self, + client, # type: botocore.client.BaseClient + materials_provider, # type: CryptographicMaterialsProvider + attribute_actions=None, # type: Optional[AttributeActions] + auto_refresh_table_indexes=True, # type: Optional[bool] + expect_standard_dictionaries=False, # type: Optional[bool] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -196,54 +193,31 @@ def __attrs_post_init__(self): self._encrypt_item = encrypt_dynamodb_item # attrs confuses pylint: disable=attribute-defined-outside-init self._decrypt_item = decrypt_dynamodb_item # attrs confuses pylint: disable=attribute-defined-outside-init self._table_info_cache = TableInfoCache( # attrs confuses pylint: disable=attribute-defined-outside-init - client=self._client, - auto_refresh_table_indexes=self._auto_refresh_table_indexes + client=self._client, auto_refresh_table_indexes=self._auto_refresh_table_indexes ) self._table_crypto_config = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - crypto_config_from_cache, - self._materials_provider, - self._attribute_actions, - self._table_info_cache + crypto_config_from_cache, self._materials_provider, self._attribute_actions, self._table_info_cache ) self._item_crypto_config = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - crypto_config_from_kwargs, - self._table_crypto_config + crypto_config_from_kwargs, self._table_crypto_config ) self.get_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - decrypt_get_item, - self._decrypt_item, - self._item_crypto_config, - self._client.get_item + decrypt_get_item, self._decrypt_item, self._item_crypto_config, self._client.get_item ) self.put_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - encrypt_put_item, - self._encrypt_item, - self._item_crypto_config, - self._client.put_item + encrypt_put_item, self._encrypt_item, self._item_crypto_config, self._client.put_item ) self.query = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - decrypt_multi_get, - self._decrypt_item, - self._item_crypto_config, - self._client.query + decrypt_multi_get, self._decrypt_item, self._item_crypto_config, self._client.query ) self.scan = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - decrypt_multi_get, - self._decrypt_item, - self._item_crypto_config, - self._client.scan + decrypt_multi_get, self._decrypt_item, self._item_crypto_config, self._client.scan ) self.batch_get_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - decrypt_batch_get_item, - self._decrypt_item, - self._table_crypto_config, - self._client.batch_get_item + decrypt_batch_get_item, self._decrypt_item, self._table_crypto_config, self._client.batch_get_item ) self.batch_write_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - encrypt_batch_write_item, - self._encrypt_item, - self._table_crypto_config, - self._client.batch_write_item + encrypt_batch_write_item, self._encrypt_item, self._table_crypto_config, self._client.batch_write_item ) def __getattr__(self, name): @@ -273,11 +247,9 @@ def get_paginator(self, operation_name): """ paginator = self._client.get_paginator(operation_name) - if operation_name in ('scan', 'query'): + if operation_name in ("scan", "query"): return EncryptedPaginator( - paginator=paginator, - decrypt_method=self._decrypt_item, - crypto_config_method=self._item_crypto_config + paginator=paginator, decrypt_method=self._decrypt_item, crypto_config_method=self._item_crypto_config ) return paginator diff --git a/src/dynamodb_encryption_sdk/encrypted/item.py b/src/dynamodb_encryption_sdk/encrypted/item.py index 23d8a050..8ef19631 100644 --- a/src/dynamodb_encryption_sdk/encrypted/item.py +++ b/src/dynamodb_encryption_sdk/encrypted/item.py @@ -22,15 +22,19 @@ from dynamodb_encryption_sdk.internal.crypto.authentication import sign_item, verify_item_signature from dynamodb_encryption_sdk.internal.crypto.encryption import decrypt_attribute, encrypt_attribute from dynamodb_encryption_sdk.internal.formatting.material_description import ( - deserialize as deserialize_material_description, serialize as serialize_material_description + deserialize as deserialize_material_description, + serialize as serialize_material_description, ) from dynamodb_encryption_sdk.internal.identifiers import ( - MaterialDescriptionKeys, MaterialDescriptionValues, ReservedAttributes + MaterialDescriptionKeys, + MaterialDescriptionValues, + ReservedAttributes, ) from dynamodb_encryption_sdk.transform import ddb_to_dict, dict_to_ddb + from . import CryptoConfig # noqa pylint: disable=unused-import -__all__ = ('encrypt_dynamodb_item', 'encrypt_python_item', 'decrypt_dynamodb_item', 'decrypt_python_item') +__all__ = ("encrypt_dynamodb_item", "encrypt_python_item", "decrypt_dynamodb_item", "decrypt_python_item") def encrypt_dynamodb_item(item, crypto_config): @@ -62,9 +66,9 @@ def encrypt_dynamodb_item(item, crypto_config): for reserved_name in ReservedAttributes: if reserved_name.value in item: - raise EncryptionError('Reserved attribute name "{}" is not allowed in plaintext item.'.format( - reserved_name.value - )) + raise EncryptionError( + 'Reserved attribute name "{}" is not allowed in plaintext item.'.format(reserved_name.value) + ) crypto_config.materials_provider.refresh() encryption_materials = crypto_config.encryption_materials() @@ -75,16 +79,14 @@ def encrypt_dynamodb_item(item, crypto_config): except AttributeError: if crypto_config.attribute_actions.contains_action(CryptoAction.ENCRYPT_AND_SIGN): raise EncryptionError( - 'Attribute actions ask for some attributes to be encrypted but no encryption key is available' + "Attribute actions ask for some attributes to be encrypted but no encryption key is available" ) encrypted_item = item.copy() else: # Add the attribute encryption mode to the inner material description encryption_mode = MaterialDescriptionValues.CBC_PKCS5_ATTRIBUTE_ENCRYPTION.value - inner_material_description[ - MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value - ] = encryption_mode + inner_material_description[MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value] = encryption_mode algorithm_descriptor = encryption_materials.encryption_key.algorithm + encryption_mode @@ -95,7 +97,7 @@ def encrypt_dynamodb_item(item, crypto_config): attribute_name=name, attribute=attribute, encryption_key=encryption_materials.encryption_key, - algorithm=algorithm_descriptor + algorithm=algorithm_descriptor, ) else: encrypted_item[name] = attribute.copy() @@ -181,7 +183,7 @@ def decrypt_dynamodb_item(item, crypto_config): except KeyError: # The signature is always written, so if no signature is found then the item was not # encrypted or signed. - raise DecryptionError('No signature attribute found in item') + raise DecryptionError("No signature attribute found in item") inner_crypto_config = crypto_config.copy() # Retrieve the material description from the item if found. @@ -204,7 +206,7 @@ def decrypt_dynamodb_item(item, crypto_config): except AttributeError: if inner_crypto_config.attribute_actions.contains_action(CryptoAction.ENCRYPT_AND_SIGN): raise DecryptionError( - 'Attribute actions ask for some attributes to be decrypted but no decryption key is available' + "Attribute actions ask for some attributes to be decrypted but no decryption key is available" ) return item.copy() @@ -219,10 +221,7 @@ def decrypt_dynamodb_item(item, crypto_config): for name, attribute in item.items(): if inner_crypto_config.attribute_actions.action(name) is CryptoAction.ENCRYPT_AND_SIGN: decrypted_item[name] = decrypt_attribute( - attribute_name=name, - attribute=attribute, - decryption_key=decryption_key, - algorithm=algorithm_descriptor + attribute_name=name, attribute=attribute, decryption_key=decryption_key, algorithm=algorithm_descriptor ) else: decrypted_item[name] = attribute.copy() diff --git a/src/dynamodb_encryption_sdk/encrypted/resource.py b/src/dynamodb_encryption_sdk/encrypted/resource.py index fb982c32..58b5c776 100644 --- a/src/dynamodb_encryption_sdk/encrypted/resource.py +++ b/src/dynamodb_encryption_sdk/encrypted/resource.py @@ -17,21 +17,26 @@ from boto3.resources.base import ServiceResource from boto3.resources.collection import CollectionManager -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Optional # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - from dynamodb_encryption_sdk.internal.utils import ( - crypto_config_from_cache, decrypt_batch_get_item, encrypt_batch_write_item, TableInfoCache + TableInfoCache, + crypto_config_from_cache, + decrypt_batch_get_item, + encrypt_batch_write_item, ) from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider from dynamodb_encryption_sdk.structures import AttributeActions + from .item import decrypt_python_item, encrypt_python_item from .table import EncryptedTable -__all__ = ('EncryptedResource', 'EncryptedTablesCollectionManager') +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Optional # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + + +__all__ = ("EncryptedResource", "EncryptedTablesCollectionManager") @attr.s(init=False) @@ -54,11 +59,11 @@ class EncryptedTablesCollectionManager(object): _table_info_cache = attr.ib(validator=attr.validators.instance_of(TableInfoCache)) def __init__( - self, - collection, # type: CollectionManager - materials_provider, # type: CryptographicMaterialsProvider - attribute_actions, # type: AttributeActions - table_info_cache # type: TableInfoCache + self, + collection, # type: CollectionManager + materials_provider, # type: CryptographicMaterialsProvider + attribute_actions, # type: AttributeActions + table_info_cache, # type: TableInfoCache ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -74,20 +79,16 @@ def __init__( def __attrs_post_init__(self): """Set up the translation methods.""" self.all = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - self._transform_table, - self._collection.all + self._transform_table, self._collection.all ) self.filter = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - self._transform_table, - self._collection.filter + self._transform_table, self._collection.filter ) self.limit = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - self._transform_table, - self._collection.limit + self._transform_table, self._collection.limit ) self.page_size = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - self._transform_table, - self._collection.page_size + self._transform_table, self._collection.page_size ) def __getattr__(self, name): @@ -112,7 +113,7 @@ def _transform_table(self, method, **kwargs): table=table, materials_provider=self._materials_provider, table_info=self._table_info_cache.table_info(table.name), - attribute_actions=self._attribute_actions + attribute_actions=self._attribute_actions, ) @@ -153,20 +154,16 @@ class EncryptedResource(object): _resource = attr.ib(validator=attr.validators.instance_of(ServiceResource)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) _attribute_actions = attr.ib( - validator=attr.validators.instance_of(AttributeActions), - default=attr.Factory(AttributeActions) - ) - _auto_refresh_table_indexes = attr.ib( - validator=attr.validators.instance_of(bool), - default=True + validator=attr.validators.instance_of(AttributeActions), default=attr.Factory(AttributeActions) ) + _auto_refresh_table_indexes = attr.ib(validator=attr.validators.instance_of(bool), default=True) def __init__( - self, - resource, # type: ServiceResource - materials_provider, # type: CryptographicMaterialsProvider - attribute_actions=None, # type: Optional[AttributeActions] - auto_refresh_table_indexes=True # type: Optional[bool] + self, + resource, # type: ServiceResource + materials_provider, # type: CryptographicMaterialsProvider + attribute_actions=None, # type: Optional[AttributeActions] + auto_refresh_table_indexes=True, # type: Optional[bool] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -185,32 +182,22 @@ def __init__( def __attrs_post_init__(self): """Set up the table info cache, encrypted tables collection manager, and translation methods.""" self._table_info_cache = TableInfoCache( # attrs confuses pylint: disable=attribute-defined-outside-init - client=self._resource.meta.client, - auto_refresh_table_indexes=self._auto_refresh_table_indexes + client=self._resource.meta.client, auto_refresh_table_indexes=self._auto_refresh_table_indexes ) self._crypto_config = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - crypto_config_from_cache, - self._materials_provider, - self._attribute_actions, - self._table_info_cache + crypto_config_from_cache, self._materials_provider, self._attribute_actions, self._table_info_cache ) self.tables = EncryptedTablesCollectionManager( # attrs confuses pylint: disable=attribute-defined-outside-init collection=self._resource.tables, materials_provider=self._materials_provider, attribute_actions=self._attribute_actions, - table_info_cache=self._table_info_cache + table_info_cache=self._table_info_cache, ) self.batch_get_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - decrypt_batch_get_item, - decrypt_python_item, - self._crypto_config, - self._resource.batch_get_item + decrypt_batch_get_item, decrypt_python_item, self._crypto_config, self._resource.batch_get_item ) self.batch_write_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - encrypt_batch_write_item, - encrypt_python_item, - self._crypto_config, - self._resource.batch_write_item + encrypt_batch_write_item, encrypt_python_item, self._crypto_config, self._resource.batch_write_item ) def __getattr__(self, name): @@ -241,10 +228,10 @@ def Table(self, name, **kwargs): """ table_kwargs = dict( table=self._resource.Table(name), - materials_provider=kwargs.get('materials_provider', self._materials_provider), - attribute_actions=kwargs.get('attribute_actions', self._attribute_actions), - auto_refresh_table_indexes=kwargs.get('auto_refresh_table_indexes', self._auto_refresh_table_indexes), - table_info=self._table_info_cache.table_info(name) + materials_provider=kwargs.get("materials_provider", self._materials_provider), + attribute_actions=kwargs.get("attribute_actions", self._attribute_actions), + auto_refresh_table_indexes=kwargs.get("auto_refresh_table_indexes", self._auto_refresh_table_indexes), + table_info=self._table_info_cache.table_info(name), ) return EncryptedTable(**table_kwargs) diff --git a/src/dynamodb_encryption_sdk/encrypted/table.py b/src/dynamodb_encryption_sdk/encrypted/table.py index c49e073d..51c69b74 100644 --- a/src/dynamodb_encryption_sdk/encrypted/table.py +++ b/src/dynamodb_encryption_sdk/encrypted/table.py @@ -17,22 +17,27 @@ from boto3.dynamodb.table import BatchWriter from boto3.resources.base import ServiceResource -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Optional # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - from dynamodb_encryption_sdk.internal.utils import ( - crypto_config_from_kwargs, crypto_config_from_table_info, - decrypt_get_item, decrypt_multi_get, encrypt_put_item + crypto_config_from_kwargs, + crypto_config_from_table_info, + decrypt_get_item, + decrypt_multi_get, + encrypt_put_item, ) from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider from dynamodb_encryption_sdk.structures import AttributeActions, TableInfo + from .client import EncryptedClient from .item import decrypt_python_item, encrypt_python_item -__all__ = ('EncryptedTable',) +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Optional # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + + +__all__ = ("EncryptedTable",) @attr.s(init=False) @@ -78,26 +83,19 @@ class EncryptedTable(object): _table = attr.ib(validator=attr.validators.instance_of(ServiceResource)) _materials_provider = attr.ib(validator=attr.validators.instance_of(CryptographicMaterialsProvider)) - _table_info = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(TableInfo)), - default=None - ) + _table_info = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(TableInfo)), default=None) _attribute_actions = attr.ib( - validator=attr.validators.instance_of(AttributeActions), - default=attr.Factory(AttributeActions) - ) - _auto_refresh_table_indexes = attr.ib( - validator=attr.validators.instance_of(bool), - default=True + validator=attr.validators.instance_of(AttributeActions), default=attr.Factory(AttributeActions) ) + _auto_refresh_table_indexes = attr.ib(validator=attr.validators.instance_of(bool), default=True) def __init__( - self, - table, # type: ServiceResource - materials_provider, # type: CryptographicMaterialsProvider - table_info=None, # type: Optional[TableInfo] - attribute_actions=None, # type: Optional[AttributeActions] - auto_refresh_table_indexes=True # type: Optional[bool] + self, + table, # type: ServiceResource + materials_provider, # type: CryptographicMaterialsProvider + table_info=None, # type: Optional[TableInfo] + attribute_actions=None, # type: Optional[AttributeActions] + auto_refresh_table_indexes=True, # type: Optional[bool] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -128,36 +126,19 @@ def __attrs_post_init__(self): self._crypto_config = partial( # attrs confuses pylint: disable=attribute-defined-outside-init crypto_config_from_kwargs, - partial( - crypto_config_from_table_info, - self._materials_provider, - self._attribute_actions, - self._table_info - ) + partial(crypto_config_from_table_info, self._materials_provider, self._attribute_actions, self._table_info), ) self.get_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - decrypt_get_item, - decrypt_python_item, - self._crypto_config, - self._table.get_item + decrypt_get_item, decrypt_python_item, self._crypto_config, self._table.get_item ) self.put_item = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - encrypt_put_item, - encrypt_python_item, - self._crypto_config, - self._table.put_item + encrypt_put_item, encrypt_python_item, self._crypto_config, self._table.put_item ) self.query = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - decrypt_multi_get, - decrypt_python_item, - self._crypto_config, - self._table.query + decrypt_multi_get, decrypt_python_item, self._crypto_config, self._table.query ) self.scan = partial( # attrs confuses pylint: disable=attribute-defined-outside-init - decrypt_multi_get, - decrypt_python_item, - self._crypto_config, - self._table.scan + decrypt_multi_get, decrypt_python_item, self._crypto_config, self._table.scan ) def __getattr__(self, name): @@ -188,10 +169,6 @@ def batch_writer(self, overwrite_by_pkeys=None): materials_provider=self._materials_provider, attribute_actions=self._attribute_actions, auto_refresh_table_indexes=self._auto_refresh_table_indexes, - expect_standard_dictionaries=True - ) - return BatchWriter( - table_name=self._table.name, - client=encrypted_client, - overwrite_by_pkeys=overwrite_by_pkeys + expect_standard_dictionaries=True, ) + return BatchWriter(table_name=self._table.name, client=encrypted_client, overwrite_by_pkeys=overwrite_by_pkeys) diff --git a/src/dynamodb_encryption_sdk/identifiers.py b/src/dynamodb_encryption_sdk/identifiers.py index 5b8b1954..92502590 100644 --- a/src/dynamodb_encryption_sdk/identifiers.py +++ b/src/dynamodb_encryption_sdk/identifiers.py @@ -13,11 +13,11 @@ """Unique identifiers used by the DynamoDB Encryption Client.""" from enum import Enum -__all__ = ('LOGGER_NAME', 'CryptoAction', 'EncryptionKeyType', 'KeyEncodingType') -__version__ = '1.0.5' +__all__ = ("LOGGER_NAME", "CryptoAction", "EncryptionKeyType", "KeyEncodingType") +__version__ = "1.0.5" -LOGGER_NAME = 'dynamodb_encryption_sdk' -USER_AGENT_SUFFIX = 'DynamodbEncryptionSdkPython/{}'.format(__version__) +LOGGER_NAME = "dynamodb_encryption_sdk" +USER_AGENT_SUFFIX = "DynamodbEncryptionSdkPython/{}".format(__version__) class CryptoAction(Enum): diff --git a/src/dynamodb_encryption_sdk/internal/crypto/authentication.py b/src/dynamodb_encryption_sdk/internal/crypto/authentication.py index 3a3ba3a8..c45ead11 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/authentication.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/authentication.py @@ -19,6 +19,13 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes +from dynamodb_encryption_sdk.delegated_keys import DelegatedKey # noqa pylint: disable=unused-import +from dynamodb_encryption_sdk.encrypted import CryptoConfig # noqa pylint: disable=unused-import +from dynamodb_encryption_sdk.identifiers import CryptoAction +from dynamodb_encryption_sdk.internal.formatting.serialize.attribute import serialize_attribute +from dynamodb_encryption_sdk.internal.identifiers import TEXT_ENCODING, SignatureValues, Tag +from dynamodb_encryption_sdk.structures import AttributeActions # noqa pylint: disable=unused-import + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Text # noqa pylint: disable=unused-import from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import @@ -26,14 +33,8 @@ # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.delegated_keys import DelegatedKey # noqa pylint: disable=unused-import -from dynamodb_encryption_sdk.encrypted import CryptoConfig # noqa pylint: disable=unused-import -from dynamodb_encryption_sdk.identifiers import CryptoAction -from dynamodb_encryption_sdk.internal.formatting.serialize.attribute import serialize_attribute -from dynamodb_encryption_sdk.internal.identifiers import SignatureValues, Tag, TEXT_ENCODING -from dynamodb_encryption_sdk.structures import AttributeActions # noqa pylint: disable=unused-import -__all__ = ('sign_item', 'verify_item_signature') +__all__ = ("sign_item", "verify_item_signature") def sign_item(encrypted_item, signing_key, crypto_config): @@ -51,8 +52,8 @@ def sign_item(encrypted_item, signing_key, crypto_config): data=_string_to_sign( item=encrypted_item, table_name=crypto_config.encryption_context.table_name, - attribute_actions=crypto_config.attribute_actions - ) + attribute_actions=crypto_config.attribute_actions, + ), ) return {Tag.BINARY.dynamodb_tag: signature} @@ -73,8 +74,8 @@ def verify_item_signature(signature_attribute, encrypted_item, verification_key, data=_string_to_sign( item=encrypted_item, table_name=crypto_config.encryption_context.table_name, - attribute_actions=crypto_config.attribute_actions - ) + attribute_actions=crypto_config.attribute_actions, + ), ) @@ -86,34 +87,22 @@ def _string_to_sign(item, table_name, attribute_actions): :param str table_name: Table name to use when generating the string to sign :param AttributeActions attribute_actions: Actions to take for item """ - hasher = hashes.Hash( - hashes.SHA256(), - backend=default_backend() - ) + hasher = hashes.Hash(hashes.SHA256(), backend=default_backend()) data_to_sign = bytearray() - data_to_sign.extend(_hash_data( - hasher=hasher, - data='TABLE>{} None # Workaround pending resolution of attrs/mypy interaction. @@ -123,11 +122,7 @@ def _build_hmac_signer(self, key): :param bytes key: Key to use in signer """ - return self.algorithm_type( - key, - self.hash_type(), - backend=default_backend() - ) + return self.algorithm_type(key, self.hash_type(), backend=default_backend()) def load_key(self, key, key_type, key_encoding): # (bytes, EncryptionKeyType, KeyEncodingType) -> bytes @@ -141,7 +136,7 @@ def load_key(self, key, key_type, key_encoding): :raises ValueError: if ``key_type`` is not symmetric or ``key_encoding`` is not raw """ if not (key_type is EncryptionKeyType.SYMMETRIC and key_encoding is KeyEncodingType.RAW): - raise ValueError('Key type must be symmetric and encoding must be raw.') + raise ValueError("Key type must be symmetric and encoding must be raw.") return key @@ -155,8 +150,7 @@ def validate_algorithm(self, algorithm): if not algorithm.startswith(self.java_name): raise InvalidAlgorithmError( 'Requested algorithm "{requested}" is not compatible with signature "{actual}"'.format( - requested=algorithm, - actual=self.java_name + requested=algorithm, actual=self.java_name ) ) @@ -175,7 +169,7 @@ def sign(self, key, data): signer.update(data) return signer.finalize() except Exception: - message = 'Unable to sign data' + message = "Unable to sign data" _LOGGER.exception(message) raise SigningError(message) @@ -193,7 +187,7 @@ def verify(self, key, signature, data): verifier.update(data) verifier.verify(signature) except Exception: - message = 'Unable to verify signature' + message = "Unable to verify signature" _LOGGER.exception(message) raise SignatureVerificationError(message) @@ -212,11 +206,7 @@ class JavaSignature(JavaAuthenticator): padding_type = attr.ib(validator=callable_validator) def __init__( - self, - java_name, # type: Text - algorithm_type, - hash_type, # type: Callable - padding_type # type: Callable + self, java_name, algorithm_type, hash_type, padding_type # type: Text # type: Callable # type: Callable ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -238,8 +228,7 @@ def validate_algorithm(self, algorithm): if not algorithm.endswith(self.java_name): raise InvalidAlgorithmError( 'Requested algorithm "{requested}" is not compatible with signature "{actual}"'.format( - requested=algorithm, - actual=self.java_name + requested=algorithm, actual=self.java_name ) ) @@ -269,16 +258,12 @@ def sign(self, key, data): :rtype: bytes :raises SigningError: if unable to sign ``data`` with ``key`` """ - if hasattr(key, 'public_bytes'): + if hasattr(key, "public_bytes"): raise SigningError('"sign" is not supported by public keys') try: - return key.sign( - data, - self.padding_type(), - self.hash_type() - ) + return key.sign(data, self.padding_type(), self.hash_type()) except Exception: - message = 'Unable to sign data' + message = "Unable to sign data" _LOGGER.exception(message) raise SigningError(message) @@ -293,19 +278,14 @@ def verify(self, key, signature, data): :param bytes data: Data over which to verify signature :raises SignatureVerificationError: if unable to verify ``signature`` """ - if hasattr(key, 'private_bytes'): + if hasattr(key, "private_bytes"): _key = key.public_key() else: _key = key try: - _key.verify( - signature, - data, - self.padding_type(), - self.hash_type() - ) + _key.verify(signature, data, self.padding_type(), self.hash_type()) except Exception: - message = 'Unable to verify signature' + message = "Unable to verify signature" _LOGGER.exception(message) raise SignatureVerificationError(message) @@ -315,12 +295,12 @@ def verify(self, key, signature, data): # SHA(1|224|256|384|512)with(|EC)DSA # If this changes, remember to update the JceNameLocalDelegatedKey docs. JAVA_AUTHENTICATOR = { - 'HmacSHA224': JavaMac('HmacSHA224', hmac.HMAC, hashes.SHA224), - 'HmacSHA256': JavaMac('HmacSHA256', hmac.HMAC, hashes.SHA256), - 'HmacSHA384': JavaMac('HmacSHA384', hmac.HMAC, hashes.SHA384), - 'HmacSHA512': JavaMac('HmacSHA512', hmac.HMAC, hashes.SHA512), - 'SHA224withRSA': JavaSignature('SHA224withRSA', rsa, hashes.SHA224, padding.PKCS1v15), - 'SHA256withRSA': JavaSignature('SHA256withRSA', rsa, hashes.SHA256, padding.PKCS1v15), - 'SHA384withRSA': JavaSignature('SHA384withRSA', rsa, hashes.SHA384, padding.PKCS1v15), - 'SHA512withRSA': JavaSignature('SHA512withRSA', rsa, hashes.SHA512, padding.PKCS1v15) + "HmacSHA224": JavaMac("HmacSHA224", hmac.HMAC, hashes.SHA224), + "HmacSHA256": JavaMac("HmacSHA256", hmac.HMAC, hashes.SHA256), + "HmacSHA384": JavaMac("HmacSHA384", hmac.HMAC, hashes.SHA384), + "HmacSHA512": JavaMac("HmacSHA512", hmac.HMAC, hashes.SHA512), + "SHA224withRSA": JavaSignature("SHA224withRSA", rsa, hashes.SHA224, padding.PKCS1v15), + "SHA256withRSA": JavaSignature("SHA256withRSA", rsa, hashes.SHA256, padding.PKCS1v15), + "SHA384withRSA": JavaSignature("SHA384withRSA", rsa, hashes.SHA384, padding.PKCS1v15), + "SHA512withRSA": JavaSignature("SHA512withRSA", rsa, hashes.SHA512, padding.PKCS1v15), } diff --git a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/encryption.py b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/encryption.py index 95a6c369..b09f60a1 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/encryption.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/encryption.py @@ -19,11 +19,17 @@ import attr from dynamodb_encryption_sdk.exceptions import JceTransformationError + from .primitives import ( - JAVA_ENCRYPTION_ALGORITHM, JAVA_MODE, JAVA_PADDING, JavaEncryptionAlgorithm, JavaMode, JavaPadding + JAVA_ENCRYPTION_ALGORITHM, + JAVA_MODE, + JAVA_PADDING, + JavaEncryptionAlgorithm, + JavaMode, + JavaPadding, ) -__all__ = ('JavaCipher',) +__all__ = ("JavaCipher",) @attr.s(init=False) @@ -42,10 +48,7 @@ class JavaCipher(object): padding = attr.ib(validator=attr.validators.instance_of(JavaPadding)) def __init__( - self, - cipher, # type: JavaEncryptionAlgorithm - mode, # type: JavaMode - padding # type: JavaPadding + self, cipher, mode, padding # type: JavaEncryptionAlgorithm # type: JavaMode # type: JavaPadding ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -84,14 +87,9 @@ def wrap(self, wrapping_key, key_to_wrap): :returns: Wrapped key :rtype: bytes """ - if hasattr(self.cipher, 'wrap'): + if hasattr(self.cipher, "wrap"): return self.cipher.wrap(wrapping_key, key_to_wrap) - return self.cipher.encrypt( - key=wrapping_key, - data=key_to_wrap, - mode=self.mode, - padding=self.padding - ) + return self.cipher.encrypt(key=wrapping_key, data=key_to_wrap, mode=self.mode, padding=self.padding) def unwrap(self, wrapping_key, wrapped_key): """Wrap key using loaded key. @@ -101,14 +99,9 @@ def unwrap(self, wrapping_key, wrapped_key): :returns: Unwrapped key :rtype: bytes """ - if hasattr(self.cipher, 'unwrap'): + if hasattr(self.cipher, "unwrap"): return self.cipher.unwrap(wrapping_key, wrapped_key) - return self.cipher.decrypt( - key=wrapping_key, - data=wrapped_key, - mode=self.mode, - padding=self.padding - ) + return self.cipher.decrypt(key=wrapping_key, data=wrapped_key, mode=self.mode, padding=self.padding) @property def transformation(self): @@ -119,10 +112,8 @@ def transformation(self): :returns: Formatted transformation :rtype: str """ - return '{cipher}/{mode}/{padding}'.format( - cipher=self.cipher.java_name, - mode=self.mode.java_name, - padding=self.padding.java_name + return "{cipher}/{mode}/{padding}".format( + cipher=self.cipher.java_name, mode=self.mode.java_name, padding=self.padding.java_name ) @staticmethod @@ -136,10 +127,7 @@ def _map_load_or_error(name_type, name, mappings): try: return mappings[name] except KeyError: - raise JceTransformationError('Invalid {type} name: "{name}"'.format( - type=name_type, - name=name - )) + raise JceTransformationError('Invalid {type} name: "{name}"'.format(type=name_type, name=name)) @classmethod def from_transformation(cls, cipher_transformation): @@ -151,16 +139,16 @@ def from_transformation(cls, cipher_transformation): :returns: JavaCipher instance :rtype: JavaCipher """ - if cipher_transformation == 'AESWrap': + if cipher_transformation == "AESWrap": # AESWrap does not support encrypt or decrypt, so mode and padding are never # used, but we use ECB and NoPadding as placeholders to simplify handling. - return cls.from_transformation('AESWrap/ECB/NoPadding') + return cls.from_transformation("AESWrap/ECB/NoPadding") - if cipher_transformation == 'RSA': + if cipher_transformation == "RSA": # RSA does not use mode, but as with JCE, we use ECB as a placeholder to simplify handling. - return cls.from_transformation('RSA/ECB/PKCS1Padding') + return cls.from_transformation("RSA/ECB/PKCS1Padding") - cipher_transformation_parts = cipher_transformation.split('/') + cipher_transformation_parts = cipher_transformation.split("/") if len(cipher_transformation_parts) != 3: raise JceTransformationError( 'Invalid transformation: "{}": must be three parts ALGORITHM/MODE/PADDING, "RSA", or "AESWrap"'.format( @@ -168,8 +156,8 @@ def from_transformation(cls, cipher_transformation): ) ) - cipher = cls._map_load_or_error('algorithm', cipher_transformation_parts[0], JAVA_ENCRYPTION_ALGORITHM) - mode = cls._map_load_or_error('mode', cipher_transformation_parts[1], JAVA_MODE) - padding = cls._map_load_or_error('padding', cipher_transformation_parts[2], JAVA_PADDING) + cipher = cls._map_load_or_error("algorithm", cipher_transformation_parts[0], JAVA_ENCRYPTION_ALGORITHM) + mode = cls._map_load_or_error("mode", cipher_transformation_parts[1], JAVA_MODE) + padding = cls._map_load_or_error("padding", cipher_transformation_parts[2], JAVA_PADDING) return cls(cipher, mode, padding) diff --git a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/primitives.py b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/primitives.py index c3dafccb..8d99fcf7 100644 --- a/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/primitives.py +++ b/src/dynamodb_encryption_sdk/internal/crypto/jce_bridge/primitives.py @@ -21,11 +21,21 @@ import os import attr +import six from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, keywrap, padding as symmetric_padding, serialization from cryptography.hazmat.primitives.asymmetric import padding as asymmetric_padding, rsa -from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes -import six +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + +from dynamodb_encryption_sdk.exceptions import ( + DecryptionError, + EncryptionError, + InvalidAlgorithmError, + UnwrappingError, + WrappingError, +) +from dynamodb_encryption_sdk.identifiers import LOGGER_NAME, EncryptionKeyType, KeyEncodingType +from dynamodb_encryption_sdk.internal.validators import callable_validator try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Any, Callable, Text # noqa pylint: disable=unused-import @@ -33,17 +43,19 @@ # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.exceptions import ( - DecryptionError, EncryptionError, InvalidAlgorithmError, UnwrappingError, WrappingError -) -from dynamodb_encryption_sdk.identifiers import EncryptionKeyType, KeyEncodingType, LOGGER_NAME -from dynamodb_encryption_sdk.internal.validators import callable_validator __all__ = ( - 'JavaPadding', 'SimplePadding', 'BlockSizePadding', 'OaepPadding', - 'JavaMode', - 'JavaEncryptionAlgorithm', 'JavaSymmetricEncryptionAlgorithm', 'JavaAsymmetricEncryptionAlgorithm', - 'JAVA_ENCRYPTION_ALGORITHM', 'JAVA_MODE', 'JAVA_PADDING' + "JavaPadding", + "SimplePadding", + "BlockSizePadding", + "OaepPadding", + "JavaMode", + "JavaEncryptionAlgorithm", + "JavaSymmetricEncryptionAlgorithm", + "JavaAsymmetricEncryptionAlgorithm", + "JAVA_ENCRYPTION_ALGORITHM", + "JAVA_MODE", + "JAVA_PADDING", ) _LOGGER = logging.getLogger(LOGGER_NAME) @@ -69,7 +81,7 @@ def finalize(self): :returns: Empty bytestring :rtype: bytes """ - return b'' + return b"" def padder(self): """Return NoPadder object. @@ -108,11 +120,7 @@ class SimplePadding(JavaPadding): java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) padding = attr.ib(validator=callable_validator) - def __init__( - self, - java_name, # type: Text - padding # type: Callable - ): # noqa=D107 + def __init__(self, java_name, padding): # type: Text # type: Callable # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 @@ -139,11 +147,7 @@ class BlockSizePadding(JavaPadding): java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) padding = attr.ib(validator=callable_validator) - def __init__( - self, - java_name, # type: Text - padding # type: Callable - ): # noqa=D107 + def __init__(self, java_name, padding): # type: Text # type: Callable # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 @@ -184,12 +188,12 @@ class OaepPadding(JavaPadding): mgf_digest = attr.ib(validator=callable_validator) def __init__( - self, - java_name, # type: Text - padding, # type: Callable - digest, # type: Callable - mgf, # type: Callable - mgf_digest # type: Callable + self, + java_name, # type: Text + padding, # type: Callable + digest, # type: Callable + mgf, # type: Callable + mgf_digest, # type: Callable ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -209,11 +213,7 @@ def build(self, block_size=None): :param int block_size: Not used by OaepPadding. Ignored and not required. :returns: Padding instance """ - return self.padding( - mgf=self.mgf(algorithm=self.mgf_digest()), - algorithm=self.digest(), - label=None - ) + return self.padding(mgf=self.mgf(algorithm=self.mgf_digest()), algorithm=self.digest(), label=None) @attr.s(init=False) @@ -226,11 +226,7 @@ class JavaMode(object): java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) mode = attr.ib(validator=callable_validator) - def __init__( - self, - java_name, # type: Text - mode # type: Callable - ): # noqa=D107 + def __init__(self, java_name, mode): # type: Text # type: Callable # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 @@ -259,11 +255,7 @@ class JavaEncryptionAlgorithm(object): java_name = attr.ib(validator=attr.validators.instance_of(six.string_types)) cipher = attr.ib() - def __init__( - self, - java_name, # type: Text - cipher # type: Callable - ): # noqa=D107 + def __init__(self, java_name, cipher): # type: Text # type: Callable # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 @@ -282,8 +274,7 @@ def validate_algorithm(self, algorithm): if not algorithm == self.java_name: raise InvalidAlgorithmError( 'Requested algorithm "{requested}" is not compatible with cipher "{actual}"'.format( - requested=algorithm, - actual=self.java_name + requested=algorithm, actual=self.java_name ) ) @@ -310,7 +301,7 @@ def _disable_encryption(self): def __attrs_post_init__(self): # () -> None """Disable encryption if algorithm is AESWrap.""" - if self.java_name == 'AESWrap': + if self.java_name == "AESWrap": self._disable_encryption() def load_key(self, key, key_type, key_encoding): @@ -322,16 +313,16 @@ def load_key(self, key, key_type, key_encoding): :returns: Loaded key """ if key_type is not EncryptionKeyType.SYMMETRIC: - raise ValueError('Invalid key type "{key_type}" for cipher "{cipher}"'.format( - key_type=key_type, - cipher=self.java_name - )) + raise ValueError( + 'Invalid key type "{key_type}" for cipher "{cipher}"'.format(key_type=key_type, cipher=self.java_name) + ) if key_encoding is not KeyEncodingType.RAW: - raise ValueError('Invalid key encoding "{key_encoding}" for cipher "{cipher}"'.format( - key_encoding=key_encoding, - cipher=self.java_name - )) + raise ValueError( + 'Invalid key encoding "{key_encoding}" for cipher "{cipher}"'.format( + key_encoding=key_encoding, cipher=self.java_name + ) + ) return key @@ -344,17 +335,13 @@ def wrap(self, wrapping_key, key_to_wrap): :returns: Wrapped key :rtype: bytes """ - if self.java_name not in ('AES', 'AESWrap'): + if self.java_name not in ("AES", "AESWrap"): raise NotImplementedError('"wrap" is not supported by the "{}" cipher'.format(self.java_name)) try: - return keywrap.aes_key_wrap( - wrapping_key=wrapping_key, - key_to_wrap=key_to_wrap, - backend=default_backend() - ) + return keywrap.aes_key_wrap(wrapping_key=wrapping_key, key_to_wrap=key_to_wrap, backend=default_backend()) except Exception: - error_message = 'Key wrap failed' + error_message = "Key wrap failed" _LOGGER.exception(error_message) raise WrappingError(error_message) @@ -367,17 +354,13 @@ def unwrap(self, wrapping_key, wrapped_key): :returns: Unwrapped key :rtype: bytes """ - if self.java_name not in ('AES', 'AESWrap'): + if self.java_name not in ("AES", "AESWrap"): raise NotImplementedError('"unwrap" is not supported by this cipher') try: - return keywrap.aes_key_unwrap( - wrapping_key=wrapping_key, - wrapped_key=wrapped_key, - backend=default_backend() - ) + return keywrap.aes_key_unwrap(wrapping_key=wrapping_key, wrapped_key=wrapped_key, backend=default_backend()) except Exception: - error_message = 'Key unwrap failed' + error_message = "Key unwrap failed" _LOGGER.exception(error_message) raise UnwrappingError(error_message) @@ -397,17 +380,13 @@ def encrypt(self, key, data, mode, padding): iv_len = block_size // 8 iv = os.urandom(iv_len) - encryptor = Cipher( - self.cipher(key), - mode.build(iv), - backend=default_backend() - ).encryptor() + encryptor = Cipher(self.cipher(key), mode.build(iv), backend=default_backend()).encryptor() padder = padding.build(block_size).padder() padded_data = padder.update(data) + padder.finalize() return iv + encryptor.update(padded_data) + encryptor.finalize() except Exception: - error_message = 'Encryption failed' + error_message = "Encryption failed" _LOGGER.exception(error_message) raise EncryptionError(error_message) @@ -428,17 +407,13 @@ def decrypt(self, key, data, mode, padding): iv = data[:iv_len] data = data[iv_len:] - decryptor = Cipher( - self.cipher(key), - mode.build(iv), - backend=default_backend() - ).decryptor() + decryptor = Cipher(self.cipher(key), mode.build(iv), backend=default_backend()).decryptor() decrypted_data = decryptor.update(data) + decryptor.finalize() unpadder = padding.build(block_size).unpadder() return unpadder.update(decrypted_data) + unpadder.finalize() except Exception: - error_message = 'Decryption failed' + error_message = "Decryption failed" _LOGGER.exception(error_message) raise DecryptionError(error_message) @@ -446,12 +421,12 @@ def decrypt(self, key, data, mode, padding): _RSA_KEY_LOADING = { EncryptionKeyType.PRIVATE: { KeyEncodingType.DER: serialization.load_der_private_key, - KeyEncodingType.PEM: serialization.load_pem_private_key + KeyEncodingType.PEM: serialization.load_pem_private_key, }, EncryptionKeyType.PUBLIC: { KeyEncodingType.DER: serialization.load_der_public_key, - KeyEncodingType.PEM: serialization.load_pem_public_key - } + KeyEncodingType.PEM: serialization.load_pem_public_key, + }, } @@ -470,18 +445,16 @@ def load_rsa_key(key, key_type, key_encoding): try: loader = _RSA_KEY_LOADING[key_type][key_encoding] except KeyError: - raise ValueError('Invalid key type and encoding: {} and {}'.format(key_type, key_encoding)) + raise ValueError("Invalid key type and encoding: {} and {}".format(key_type, key_encoding)) kwargs = dict(data=key, backend=default_backend()) if key_type is EncryptionKeyType.PRIVATE: - kwargs['password'] = None + kwargs["password"] = None return loader(**kwargs) -_KEY_LOADERS = { - rsa: load_rsa_key -} +_KEY_LOADERS = {rsa: load_rsa_key} class JavaAsymmetricEncryptionAlgorithm(JavaEncryptionAlgorithm): @@ -499,16 +472,16 @@ def load_key(self, key, key_type, key_encoding): :returns: Loaded key """ if key_type not in (EncryptionKeyType.PRIVATE, EncryptionKeyType.PUBLIC): - raise ValueError('Invalid key type "{key_type}" for cipher "{cipher}"'.format( - key_type=key_type, - cipher=self.java_name - )) + raise ValueError( + 'Invalid key type "{key_type}" for cipher "{cipher}"'.format(key_type=key_type, cipher=self.java_name) + ) if key_encoding not in (KeyEncodingType.DER, KeyEncodingType.PEM): - raise ValueError('Invalid key encoding "{key_encoding}" for cipher "{cipher}"'.format( - key_encoding=key_encoding, - cipher=self.java_name - )) + raise ValueError( + 'Invalid key encoding "{key_encoding}" for cipher "{cipher}"'.format( + key_encoding=key_encoding, cipher=self.java_name + ) + ) return _KEY_LOADERS[self.cipher](key, key_type, key_encoding) @@ -523,14 +496,14 @@ def encrypt(self, key, data, mode, padding): :returns: Encrypted data :rtype: bytes """ - if hasattr(key, 'private_bytes'): + if hasattr(key, "private_bytes"): _key = key.public_key() else: _key = key try: return _key.encrypt(data, padding.build()) except Exception: - error_message = 'Encryption failed' + error_message = "Encryption failed" _LOGGER.exception(error_message) raise EncryptionError(error_message) @@ -545,46 +518,46 @@ def decrypt(self, key, data, mode, padding): :returns: Decrypted data :rtype: bytes """ - if hasattr(key, 'public_bytes'): + if hasattr(key, "public_bytes"): raise NotImplementedError('"decrypt" is not supported by public keys') try: return key.decrypt(data, padding.build()) except Exception: - error_message = 'Decryption failed' + error_message = "Decryption failed" _LOGGER.exception(error_message) raise DecryptionError(error_message) # If this changes, remember to update the JceNameLocalDelegatedKey docs. JAVA_ENCRYPTION_ALGORITHM = { - 'RSA': JavaAsymmetricEncryptionAlgorithm('RSA', rsa), - 'AES': JavaSymmetricEncryptionAlgorithm('AES', algorithms.AES), - 'AESWrap': JavaSymmetricEncryptionAlgorithm('AESWrap', algorithms.AES) + "RSA": JavaAsymmetricEncryptionAlgorithm("RSA", rsa), + "AES": JavaSymmetricEncryptionAlgorithm("AES", algorithms.AES), + "AESWrap": JavaSymmetricEncryptionAlgorithm("AESWrap", algorithms.AES), } JAVA_MODE = { - 'ECB': JavaMode('ECB', modes.ECB), - 'CBC': JavaMode('CBC', modes.CBC), - 'CTR': JavaMode('CTR', modes.CTR), - 'GCM': JavaMode('GCM', modes.GCM) + "ECB": JavaMode("ECB", modes.ECB), + "CBC": JavaMode("CBC", modes.CBC), + "CTR": JavaMode("CTR", modes.CTR), + "GCM": JavaMode("GCM", modes.GCM), } JAVA_PADDING = { - 'NoPadding': SimplePadding('NoPadding', _NoPadding), - 'PKCS1Padding': SimplePadding('PKCS1Padding', asymmetric_padding.PKCS1v15), + "NoPadding": SimplePadding("NoPadding", _NoPadding), + "PKCS1Padding": SimplePadding("PKCS1Padding", asymmetric_padding.PKCS1v15), # PKCS7 padding is a generalization of PKCS5 padding. - 'PKCS5Padding': BlockSizePadding('PKCS5Padding', symmetric_padding.PKCS7), + "PKCS5Padding": BlockSizePadding("PKCS5Padding", symmetric_padding.PKCS7), # By default, Java incorrectly implements RSA OAEP for all hash functions besides SHA1. # The same hashing algorithm should be used by both OAEP and the MGF, but by default # Java always uses SHA1 for the MGF. - 'OAEPWithSHA-1AndMGF1Padding': OaepPadding( - 'OAEPWithSHA-1AndMGF1Padding', asymmetric_padding.OAEP, hashes.SHA1, asymmetric_padding.MGF1, hashes.SHA1 + "OAEPWithSHA-1AndMGF1Padding": OaepPadding( + "OAEPWithSHA-1AndMGF1Padding", asymmetric_padding.OAEP, hashes.SHA1, asymmetric_padding.MGF1, hashes.SHA1 + ), + "OAEPWithSHA-256AndMGF1Padding": OaepPadding( + "OAEPWithSHA-256AndMGF1Padding", asymmetric_padding.OAEP, hashes.SHA256, asymmetric_padding.MGF1, hashes.SHA1 ), - 'OAEPWithSHA-256AndMGF1Padding': OaepPadding( - 'OAEPWithSHA-256AndMGF1Padding', asymmetric_padding.OAEP, hashes.SHA256, asymmetric_padding.MGF1, hashes.SHA1 + "OAEPWithSHA-384AndMGF1Padding": OaepPadding( + "OAEPWithSHA-384AndMGF1Padding", asymmetric_padding.OAEP, hashes.SHA384, asymmetric_padding.MGF1, hashes.SHA1 ), - 'OAEPWithSHA-384AndMGF1Padding': OaepPadding( - 'OAEPWithSHA-384AndMGF1Padding', asymmetric_padding.OAEP, hashes.SHA384, asymmetric_padding.MGF1, hashes.SHA1 + "OAEPWithSHA-512AndMGF1Padding": OaepPadding( + "OAEPWithSHA-512AndMGF1Padding", asymmetric_padding.OAEP, hashes.SHA512, asymmetric_padding.MGF1, hashes.SHA1 ), - 'OAEPWithSHA-512AndMGF1Padding': OaepPadding( - 'OAEPWithSHA-512AndMGF1Padding', asymmetric_padding.OAEP, hashes.SHA512, asymmetric_padding.MGF1, hashes.SHA1 - ) } diff --git a/src/dynamodb_encryption_sdk/internal/formatting/deserialize/__init__.py b/src/dynamodb_encryption_sdk/internal/formatting/deserialize/__init__.py index 6e58c9bc..7fb0dd52 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/deserialize/__init__.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/deserialize/__init__.py @@ -20,7 +20,7 @@ from dynamodb_encryption_sdk.exceptions import DeserializationError -__all__ = ('unpack_value', 'decode_length', 'decode_value', 'decode_tag') +__all__ = ("unpack_value", "decode_length", "decode_value", "decode_tag") def unpack_value(format_string, stream): @@ -44,7 +44,7 @@ def decode_length(stream): :returns: Decoded length :rtype: int """ - (value,) = unpack_value('>I', stream) + (value,) = unpack_value(">I", stream) return value @@ -57,7 +57,7 @@ def decode_value(stream): :rtype: bytes """ length = decode_length(stream) - (value,) = unpack_value('>{:d}s'.format(length), stream) + (value,) = unpack_value(">{:d}s".format(length), stream) return value @@ -69,7 +69,7 @@ def decode_byte(stream): :returns: Decoded value :rtype: bytes """ - (value,) = unpack_value('>1s', stream) + (value,) = unpack_value(">1s", stream) return value @@ -81,9 +81,9 @@ def decode_tag(stream): :returns: Decoded tag :rtype: bytes """ - (reserved, tag) = unpack_value('>cc', stream) + (reserved, tag) = unpack_value(">cc", stream) - if reserved != b'\x00': - raise DeserializationError('Invalid tag: reserved byte is not null') + if reserved != b"\x00": + raise DeserializationError("Invalid tag: reserved byte is not null") return tag diff --git a/src/dynamodb_encryption_sdk/internal/formatting/deserialize/attribute.py b/src/dynamodb_encryption_sdk/internal/formatting/deserialize/attribute.py index 795be8ca..13fe0d7b 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/deserialize/attribute.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/deserialize/attribute.py @@ -17,10 +17,18 @@ namespace staying consistent. Directly reference at your own risk. """ import codecs -from decimal import Decimal import io import logging import struct +from decimal import Decimal + +from boto3.dynamodb.types import Binary + +from dynamodb_encryption_sdk.exceptions import DeserializationError +from dynamodb_encryption_sdk.identifiers import LOGGER_NAME +from dynamodb_encryption_sdk.internal.formatting.deserialize import decode_byte, decode_length, decode_tag, decode_value +from dynamodb_encryption_sdk.internal.identifiers import TEXT_ENCODING, Tag, TagValues +from dynamodb_encryption_sdk.internal.str_ops import to_str try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Callable, Dict, List, Text, Union # noqa pylint: disable=unused-import @@ -29,15 +37,8 @@ # We only actually need these imports when running the mypy checks pass -from boto3.dynamodb.types import Binary - -from dynamodb_encryption_sdk.exceptions import DeserializationError -from dynamodb_encryption_sdk.identifiers import LOGGER_NAME -from dynamodb_encryption_sdk.internal.formatting.deserialize import decode_byte, decode_length, decode_tag, decode_value -from dynamodb_encryption_sdk.internal.identifiers import Tag, TagValues, TEXT_ENCODING -from dynamodb_encryption_sdk.internal.str_ops import to_str -__all__ = ('deserialize_attribute',) +__all__ = ("deserialize_attribute",) _LOGGER = logging.getLogger(LOGGER_NAME) @@ -101,7 +102,7 @@ def _transform_number_value(value): """ raw_value = codecs.decode(value, TEXT_ENCODING) decimal_value = Decimal(to_str(raw_value)).normalize() - return '{0:f}'.format(decimal_value) + return "{0:f}".format(decimal_value) def _deserialize_number(stream): # type: (io.BytesIO) -> Dict[Text, dynamodb_types.STRING] @@ -114,10 +115,7 @@ def _deserialize_number(stream): value = decode_value(stream) return {Tag.NUMBER.dynamodb_tag: _transform_number_value(value)} - _boolean_map = { - TagValues.FALSE.value: False, - TagValues.TRUE.value: True - } + _boolean_map = {TagValues.FALSE.value: False, TagValues.TRUE.value: True} def _deserialize_boolean(stream): # type: (io.BytesIO) -> Dict[Text, dynamodb_types.BOOLEAN] @@ -149,10 +147,7 @@ def _deserialize_set(stream, member_transform): :rtype: list """ member_count = decode_length(stream) - return sorted([ - member_transform(decode_value(stream)) - for _ in range(member_count) - ]) + return sorted([member_transform(decode_value(stream)) for _ in range(member_count)]) def _deserialize_binary_set(stream): # type: (io.BytesIO) -> Dict[Text, dynamodb_types.SET[dynamodb_types.BINARY]] @@ -193,10 +188,7 @@ def _deserialize_list(stream): :rtype: dict """ member_count = decode_length(stream) - return {Tag.LIST.dynamodb_tag: [ - _deserialize(stream) - for _ in range(member_count) - ]} + return {Tag.LIST.dynamodb_tag: [_deserialize(stream) for _ in range(member_count)]} def _deserialize_map(stream): # type: (io.BytesIO) -> Dict[Text, dynamodb_types.MAP] @@ -238,7 +230,7 @@ def _deserialize_function(tag): Tag.BOOLEAN.tag: _deserialize_boolean, Tag.NULL.tag: _deserialize_null, Tag.LIST.tag: _deserialize_list, - Tag.MAP.tag: _deserialize_map + Tag.MAP.tag: _deserialize_map, } try: return deserialize_functions[tag] @@ -257,10 +249,10 @@ def _deserialize(stream): tag = decode_tag(stream) return _deserialize_function(tag)(stream) except struct.error: - raise DeserializationError('Malformed serialized data') + raise DeserializationError("Malformed serialized data") if not serialized_attribute: - raise DeserializationError('Empty serialized attribute data') + raise DeserializationError("Empty serialized attribute data") stream = io.BytesIO(serialized_attribute) return _deserialize(stream) diff --git a/src/dynamodb_encryption_sdk/internal/formatting/material_description.py b/src/dynamodb_encryption_sdk/internal/formatting/material_description.py index 004b80e6..aa0114c8 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/material_description.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/material_description.py @@ -20,6 +20,14 @@ import logging import struct +from dynamodb_encryption_sdk.exceptions import InvalidMaterialDescriptionError, InvalidMaterialDescriptionVersionError +from dynamodb_encryption_sdk.identifiers import LOGGER_NAME +from dynamodb_encryption_sdk.internal.identifiers import Tag +from dynamodb_encryption_sdk.internal.str_ops import to_bytes, to_str + +from .deserialize import decode_value, unpack_value +from .serialize import encode_value + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Dict, Text # noqa pylint: disable=unused-import from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import @@ -27,16 +35,10 @@ # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.exceptions import InvalidMaterialDescriptionError, InvalidMaterialDescriptionVersionError -from dynamodb_encryption_sdk.identifiers import LOGGER_NAME -from dynamodb_encryption_sdk.internal.identifiers import Tag -from dynamodb_encryption_sdk.internal.str_ops import to_bytes, to_str -from .deserialize import decode_value, unpack_value -from .serialize import encode_value -__all__ = ('serialize', 'deserialize') +__all__ = ("serialize", "deserialize") _LOGGER = logging.getLogger(LOGGER_NAME) -_MATERIAL_DESCRIPTION_VERSION = b'\00' * 4 +_MATERIAL_DESCRIPTION_VERSION = b"\00" * 4 def serialize(material_description): @@ -57,10 +59,7 @@ def serialize(material_description): material_description_bytes.extend(encode_value(to_bytes(value))) except (TypeError, struct.error): raise InvalidMaterialDescriptionError( - 'Invalid name or value in material description: "{name}"="{value}"'.format( - name=name, - value=value - ) + 'Invalid name or value in material description: "{name}"="{value}"'.format(name=name, value=value) ) return {Tag.BINARY.dynamodb_tag: bytes(material_description_bytes)} @@ -82,7 +81,7 @@ def deserialize(serialized_material_description): material_description_bytes = io.BytesIO(_raw_material_description) total_bytes = len(_raw_material_description) except (TypeError, KeyError): - message = 'Invalid material description' + message = "Invalid material description" _LOGGER.exception(message) raise InvalidMaterialDescriptionError(message) # We don't currently do anything with the version, but do check to make sure it is the one we know about. @@ -95,7 +94,7 @@ def deserialize(serialized_material_description): value = to_str(decode_value(material_description_bytes)) material_description[name] = value except struct.error: - message = 'Invalid material description' + message = "Invalid material description" _LOGGER.exception(message) raise InvalidMaterialDescriptionError(message) return material_description @@ -111,10 +110,10 @@ def _read_version(material_description_bytes): :raises InvalidMaterialDescriptionVersionError: if unknown version is found """ try: - (version,) = unpack_value('>4s', material_description_bytes) + (version,) = unpack_value(">4s", material_description_bytes) except struct.error: - message = 'Malformed material description version' + message = "Malformed material description version" _LOGGER.exception(message) raise InvalidMaterialDescriptionError(message) if version != _MATERIAL_DESCRIPTION_VERSION: - raise InvalidMaterialDescriptionVersionError('Invalid material description version: {}'.format(repr(version))) + raise InvalidMaterialDescriptionVersionError("Invalid material description version: {}".format(repr(version))) diff --git a/src/dynamodb_encryption_sdk/internal/formatting/serialize/__init__.py b/src/dynamodb_encryption_sdk/internal/formatting/serialize/__init__.py index 9e1723a2..1c7f7ee2 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/serialize/__init__.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/serialize/__init__.py @@ -24,7 +24,7 @@ # We only actually need these imports when running the mypy checks pass -__all__ = ('encode_length', 'encode_value') +__all__ = ("encode_length", "encode_value") def encode_length(attribute): @@ -35,7 +35,7 @@ def encode_length(attribute): :returns: Encoded value :rtype: bytes """ - return struct.pack('>I', len(attribute)) + return struct.pack(">I", len(attribute)) def encode_value(value): @@ -47,8 +47,4 @@ def encode_value(value): :returns: Length-Value encoded value :rtype: bytes """ - return struct.pack( - '>I{attr_len:d}s'.format(attr_len=len(value)), - len(value), - value - ) + return struct.pack(">I{attr_len:d}s".format(attr_len=len(value)), len(value), value) diff --git a/src/dynamodb_encryption_sdk/internal/formatting/serialize/attribute.py b/src/dynamodb_encryption_sdk/internal/formatting/serialize/attribute.py index 2b4f5cca..cf2cca83 100644 --- a/src/dynamodb_encryption_sdk/internal/formatting/serialize/attribute.py +++ b/src/dynamodb_encryption_sdk/internal/formatting/serialize/attribute.py @@ -19,6 +19,14 @@ import io import logging +from boto3.dynamodb.types import DYNAMODB_CONTEXT, Binary + +from dynamodb_encryption_sdk.exceptions import SerializationError +from dynamodb_encryption_sdk.identifiers import LOGGER_NAME +from dynamodb_encryption_sdk.internal.formatting.serialize import encode_length, encode_value +from dynamodb_encryption_sdk.internal.identifiers import Tag, TagValues +from dynamodb_encryption_sdk.internal.str_ops import to_bytes + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Callable # noqa pylint: disable=unused-import from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import,ungrouped-imports @@ -26,17 +34,10 @@ # We only actually need these imports when running the mypy checks pass -from boto3.dynamodb.types import Binary, DYNAMODB_CONTEXT -from dynamodb_encryption_sdk.exceptions import SerializationError -from dynamodb_encryption_sdk.identifiers import LOGGER_NAME -from dynamodb_encryption_sdk.internal.formatting.serialize import encode_length, encode_value -from dynamodb_encryption_sdk.internal.identifiers import Tag, TagValues -from dynamodb_encryption_sdk.internal.str_ops import to_bytes - -__all__ = ('serialize_attribute',) +__all__ = ("serialize_attribute",) _LOGGER = logging.getLogger(LOGGER_NAME) -_RESERVED = b'\x00' +_RESERVED = b"\x00" def _sorted_key_map(item, transform=to_bytes): @@ -99,7 +100,7 @@ def _transform_number_value(value): # leaves trailing zeros if they are defined in the Decimal call, but we need to # strip all trailing zeros. decimal_value = DYNAMODB_CONTEXT.create_decimal(value).normalize() - return '{0:f}'.format(decimal_value).encode('utf-8') + return "{0:f}".format(decimal_value).encode("utf-8") def _serialize_number(_attribute): # type: (str) -> bytes @@ -228,10 +229,7 @@ def _serialize_map(_attribute): serialized_attribute.write(Tag.MAP.tag) serialized_attribute.write(encode_length(_attribute)) - sorted_items = _sorted_key_map( - item=_attribute, - transform=_transform_string_value - ) + sorted_items = _sorted_key_map(item=_attribute, transform=_transform_string_value) for key, value, _original_key in sorted_items: serialized_attribute.write(_serialize_string(key)) @@ -252,7 +250,7 @@ def _serialize_function(dynamodb_tag): Tag.BOOLEAN.dynamodb_tag: _serialize_boolean, Tag.NULL.dynamodb_tag: _serialize_null, Tag.LIST.dynamodb_tag: _serialize_list, - Tag.MAP.dynamodb_tag: _serialize_map + Tag.MAP.dynamodb_tag: _serialize_map, } try: return serialize_functions[dynamodb_tag] @@ -263,8 +261,8 @@ def _serialize_function(dynamodb_tag): raise TypeError('Invalid attribute type "{}": must be dict'.format(type(attribute))) if len(attribute) != 1: - raise SerializationError('cannot serialize attribute: incorrect number of members {} != 1'.format( - len(attribute) - )) + raise SerializationError( + "cannot serialize attribute: incorrect number of members {} != 1".format(len(attribute)) + ) key, value = list(attribute.items())[0] return _serialize_function(key)(value) diff --git a/src/dynamodb_encryption_sdk/internal/identifiers.py b/src/dynamodb_encryption_sdk/internal/identifiers.py index 750edcc2..80183870 100644 --- a/src/dynamodb_encryption_sdk/internal/identifiers.py +++ b/src/dynamodb_encryption_sdk/internal/identifiers.py @@ -25,35 +25,40 @@ pass __all__ = ( - 'ReservedAttributes', 'Tag', 'TagValues', 'TEXT_ENCODING', - 'SignatureValues', 'MaterialDescriptionKeys', 'MaterialDescriptionValues' + "ReservedAttributes", + "Tag", + "TagValues", + "TEXT_ENCODING", + "SignatureValues", + "MaterialDescriptionKeys", + "MaterialDescriptionValues", ) #: Encoding to use for all text values. #: This is noted here for consistency but should not be changed. -TEXT_ENCODING = 'utf-8' +TEXT_ENCODING = "utf-8" class ReservedAttributes(Enum): """Item attributes reserved for use by DynamoDBEncryptionClient""" - MATERIAL_DESCRIPTION = '*amzn-ddb-map-desc*' - SIGNATURE = '*amzn-ddb-map-sig*' + MATERIAL_DESCRIPTION = "*amzn-ddb-map-desc*" + SIGNATURE = "*amzn-ddb-map-sig*" class Tag(Enum): """Attribute data type identifiers used for serialization and deserialization of attributes.""" - BINARY = (b'b', 'B') - BINARY_SET = (b'B', 'BS', b'b') - NUMBER = (b'n', 'N') - NUMBER_SET = (b'N', 'NS', b'n') - STRING = (b's', 'S') - STRING_SET = (b'S', 'SS', b's') - BOOLEAN = (b'?', 'BOOL') - NULL = (b'\x00', 'NULL') - LIST = (b'L', 'L') - MAP = (b'M', 'M') + BINARY = (b"b", "B") + BINARY_SET = (b"B", "BS", b"b") + NUMBER = (b"n", "N") + NUMBER_SET = (b"N", "NS", b"n") + STRING = (b"s", "S") + STRING_SET = (b"S", "SS", b"s") + BOOLEAN = (b"?", "BOOL") + NULL = (b"\x00", "NULL") + LIST = (b"L", "L") + MAP = (b"M", "M") def __init__(self, tag, dynamodb_tag, element_tag=None): # type: (bytes, Text, Optional[bytes]) -> None @@ -71,8 +76,8 @@ def __init__(self, tag, dynamodb_tag, element_tag=None): class TagValues(Enum): """Static values to use when serializing attribute values.""" - FALSE = b'\x00' - TRUE = b'\x01' + FALSE = b"\x00" + TRUE = b"\x01" class SignatureValues(Enum): @@ -85,12 +90,12 @@ class SignatureValues(Enum): """ ENCRYPTED = ( - b'ENCRYPTED', - b"9A\x15\xacN\xb0\x9a\xa4\x94)4\x88\x16\xb2\x03\x81'\xb0\xf9\xe3\xa5 7*\xe1\x00\xca\x19\xfb\x08\xfdP" + b"ENCRYPTED", + b"9A\x15\xacN\xb0\x9a\xa4\x94)4\x88\x16\xb2\x03\x81'\xb0\xf9\xe3\xa5 7*\xe1\x00\xca\x19\xfb\x08\xfdP", ) PLAINTEXT = ( - b'PLAINTEXT', - b'\xcb@\xe7\xda\xdc\x86\x16\x1b\x97\x98\xdeHQ/3-!\xc1A\xfc\xc1\xe2\x8a\x08o\xdeJ3u\xaa\xb1\xb5' + b"PLAINTEXT", + b"\xcb@\xe7\xda\xdc\x86\x16\x1b\x97\x98\xdeHQ/3-!\xc1A\xfc\xc1\xe2\x8a\x08o\xdeJ3u\xaa\xb1\xb5", ) def __init__(self, raw, sha256): @@ -107,15 +112,15 @@ def __init__(self, raw, sha256): class MaterialDescriptionKeys(Enum): """Static keys for use when building and reading material descriptions.""" - ATTRIBUTE_ENCRYPTION_MODE = 'amzn-ddb-map-sym-mode' - SIGNING_KEY_ALGORITHM = 'amzn-ddb-map-signingAlg' - WRAPPED_DATA_KEY = 'amzn-ddb-env-key' - CONTENT_ENCRYPTION_ALGORITHM = 'amzn-ddb-env-alg' - CONTENT_KEY_WRAPPING_ALGORITHM = 'amzn-ddb-wrap-alg' - ITEM_SIGNATURE_ALGORITHM = 'amzn-ddb-sig-alg' + ATTRIBUTE_ENCRYPTION_MODE = "amzn-ddb-map-sym-mode" + SIGNING_KEY_ALGORITHM = "amzn-ddb-map-signingAlg" + WRAPPED_DATA_KEY = "amzn-ddb-env-key" + CONTENT_ENCRYPTION_ALGORITHM = "amzn-ddb-env-alg" + CONTENT_KEY_WRAPPING_ALGORITHM = "amzn-ddb-wrap-alg" + ITEM_SIGNATURE_ALGORITHM = "amzn-ddb-sig-alg" class MaterialDescriptionValues(Enum): """Static default values for use when building material descriptions.""" - CBC_PKCS5_ATTRIBUTE_ENCRYPTION = '/CBC/PKCS5Padding' + CBC_PKCS5_ATTRIBUTE_ENCRYPTION = "/CBC/PKCS5Padding" diff --git a/src/dynamodb_encryption_sdk/internal/str_ops.py b/src/dynamodb_encryption_sdk/internal/str_ops.py index c72e42b4..561c6ad4 100644 --- a/src/dynamodb_encryption_sdk/internal/str_ops.py +++ b/src/dynamodb_encryption_sdk/internal/str_ops.py @@ -22,7 +22,7 @@ from dynamodb_encryption_sdk.internal.identifiers import TEXT_ENCODING -__all__ = ('to_str', 'to_bytes') +__all__ = ("to_str", "to_bytes") def to_str(data): diff --git a/src/dynamodb_encryption_sdk/internal/utils.py b/src/dynamodb_encryption_sdk/internal/utils.py index 33a92a93..6b1ee127 100644 --- a/src/dynamodb_encryption_sdk/internal/utils.py +++ b/src/dynamodb_encryption_sdk/internal/utils.py @@ -32,11 +32,16 @@ pass __all__ = ( - 'TableInfoCache', - 'crypto_config_from_kwargs', 'crypto_config_from_table_info', 'crypto_config_from_cache', - 'decrypt_get_item', 'decrypt_multi_get', 'decrypt_batch_get_item', - 'encrypt_put_item', 'encrypt_batch_write_item', - 'validate_get_arguments' + "TableInfoCache", + "crypto_config_from_kwargs", + "crypto_config_from_table_info", + "crypto_config_from_cache", + "decrypt_get_item", + "decrypt_multi_get", + "decrypt_batch_get_item", + "encrypt_put_item", + "encrypt_batch_write_item", + "validate_get_arguments", ) @@ -55,9 +60,7 @@ class TableInfoCache(object): _auto_refresh_table_indexes = attr.ib(validator=attr.validators.instance_of(bool)) def __init__( - self, - client, # type: botocore.client.BaseClient - auto_refresh_table_indexes # type: bool + self, client, auto_refresh_table_indexes # type: botocore.client.BaseClient # type: bool ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -96,12 +99,12 @@ def validate_get_arguments(kwargs): :raises InvalidArgumentError: if banned parameters are found """ - for arg in ('AttributesToGet', 'ProjectionExpression'): + for arg in ("AttributesToGet", "ProjectionExpression"): if arg in kwargs: raise InvalidArgumentError('"{}" is not supported for this operation'.format(arg)) - if kwargs.get('Select', None) in ('SPECIFIC_ATTRIBUTES', 'ALL_PROJECTED_ATTRIBUTES'): - raise InvalidArgumentError('Scan "Select" value of "{}" is not supported'.format(kwargs['Select'])) + if kwargs.get("Select", None) in ("SPECIFIC_ATTRIBUTES", "ALL_PROJECTED_ATTRIBUTES"): + raise InvalidArgumentError('Scan "Select" value of "{}" is not supported'.format(kwargs["Select"])) def crypto_config_from_kwargs(fallback, **kwargs): @@ -111,10 +114,10 @@ def crypto_config_from_kwargs(fallback, **kwargs): :rtype: dynamodb_encryption_sdk.encrypted.CryptoConfig and dict """ try: - crypto_config = kwargs.pop('crypto_config') + crypto_config = kwargs.pop("crypto_config") except KeyError: try: - fallback_kwargs = {'table_name': kwargs['TableName']} + fallback_kwargs = {"table_name": kwargs["TableName"]} except KeyError: fallback_kwargs = {} crypto_config = fallback(**fallback_kwargs) @@ -129,15 +132,14 @@ def crypto_config_from_table_info(materials_provider, attribute_actions, table_i """ ec_kwargs = table_info.encryption_context_values if table_info.primary_index is not None: - ec_kwargs.update({ - 'partition_key_name': table_info.primary_index.partition, - 'sort_key_name': table_info.primary_index.sort - }) + ec_kwargs.update( + {"partition_key_name": table_info.primary_index.partition, "sort_key_name": table_info.primary_index.sort} + ) return CryptoConfig( materials_provider=materials_provider, encryption_context=EncryptionContext(**ec_kwargs), - attribute_actions=attribute_actions + attribute_actions=attribute_actions, ) @@ -183,10 +185,10 @@ def decrypt_multi_get(decrypt_method, crypto_config_method, read_method, **kwarg validate_get_arguments(kwargs) crypto_config, ddb_kwargs = crypto_config_method(**kwargs) response = read_method(**ddb_kwargs) - for pos in range(len(response['Items'])): - response['Items'][pos] = decrypt_method( - item=response['Items'][pos], - crypto_config=crypto_config.with_item(_item_transformer(decrypt_method)(response['Items'][pos])) + for pos in range(len(response["Items"])): + response["Items"][pos] = decrypt_method( + item=response["Items"][pos], + crypto_config=crypto_config.with_item(_item_transformer(decrypt_method)(response["Items"][pos])), ) return response @@ -206,10 +208,10 @@ def decrypt_get_item(decrypt_method, crypto_config_method, read_method, **kwargs validate_get_arguments(kwargs) crypto_config, ddb_kwargs = crypto_config_method(**kwargs) response = read_method(**ddb_kwargs) - if 'Item' in response: - response['Item'] = decrypt_method( - item=response['Item'], - crypto_config=crypto_config.with_item(_item_transformer(decrypt_method)(response['Item'])) + if "Item" in response: + response["Item"] = decrypt_method( + item=response["Item"], + crypto_config=crypto_config.with_item(_item_transformer(decrypt_method)(response["Item"])), ) return response @@ -226,13 +228,13 @@ def decrypt_batch_get_item(decrypt_method, crypto_config_method, read_method, ** :return: DynamoDB response :rtype: dict """ - request_crypto_config = kwargs.pop('crypto_config', None) + request_crypto_config = kwargs.pop("crypto_config", None) - for _table_name, table_kwargs in kwargs['RequestItems'].items(): + for _table_name, table_kwargs in kwargs["RequestItems"].items(): validate_get_arguments(table_kwargs) response = read_method(**kwargs) - for table_name, items in response['Responses'].items(): + for table_name, items in response["Responses"].items(): if request_crypto_config is not None: crypto_config = request_crypto_config else: @@ -240,8 +242,7 @@ def decrypt_batch_get_item(decrypt_method, crypto_config_method, read_method, ** for pos, value in enumerate(items): items[pos] = decrypt_method( - item=value, - crypto_config=crypto_config.with_item(_item_transformer(decrypt_method)(items[pos])) + item=value, crypto_config=crypto_config.with_item(_item_transformer(decrypt_method)(items[pos])) ) return response @@ -259,9 +260,9 @@ def encrypt_put_item(encrypt_method, crypto_config_method, write_method, **kwarg :rtype: dict """ crypto_config, ddb_kwargs = crypto_config_method(**kwargs) - ddb_kwargs['Item'] = encrypt_method( - item=ddb_kwargs['Item'], - crypto_config=crypto_config.with_item(_item_transformer(encrypt_method)(ddb_kwargs['Item'])) + ddb_kwargs["Item"] = encrypt_method( + item=ddb_kwargs["Item"], + crypto_config=crypto_config.with_item(_item_transformer(encrypt_method)(ddb_kwargs["Item"])), ) return write_method(**ddb_kwargs) @@ -278,9 +279,9 @@ def encrypt_batch_write_item(encrypt_method, crypto_config_method, write_method, :return: DynamoDB response :rtype: dict """ - request_crypto_config = kwargs.pop('crypto_config', None) + request_crypto_config = kwargs.pop("crypto_config", None) - for table_name, items in kwargs['RequestItems'].items(): + for table_name, items in kwargs["RequestItems"].items(): if request_crypto_config is not None: crypto_config = request_crypto_config else: @@ -289,9 +290,9 @@ def encrypt_batch_write_item(encrypt_method, crypto_config_method, write_method, for pos, value in enumerate(items): for request_type, item in value.items(): # We don't encrypt primary indexes, so we can ignore DeleteItem requests - if request_type == 'PutRequest': - items[pos][request_type]['Item'] = encrypt_method( - item=item['Item'], - crypto_config=crypto_config.with_item(_item_transformer(encrypt_method)(item['Item'])) + if request_type == "PutRequest": + items[pos][request_type]["Item"] = encrypt_method( + item=item["Item"], + crypto_config=crypto_config.with_item(_item_transformer(encrypt_method)(item["Item"])), ) return write_method(**kwargs) diff --git a/src/dynamodb_encryption_sdk/internal/validators.py b/src/dynamodb_encryption_sdk/internal/validators.py index bc33757d..09f1b4f3 100644 --- a/src/dynamodb_encryption_sdk/internal/validators.py +++ b/src/dynamodb_encryption_sdk/internal/validators.py @@ -17,7 +17,7 @@ namespace staying consistent. Directly reference at your own risk. """ -__all__ = ('dictionary_validator', 'iterable_validator') +__all__ = ("dictionary_validator", "iterable_validator") def dictionary_validator(key_type, value_type): @@ -36,16 +36,14 @@ def _validate_dictionary(instance, attribute, value): for key, data in value.items(): if not isinstance(key, key_type): - raise TypeError('"{name}" dictionary keys must be of type "{type}"'.format( - name=attribute.name, - type=key_type - )) + raise TypeError( + '"{name}" dictionary keys must be of type "{type}"'.format(name=attribute.name, type=key_type) + ) if not isinstance(data, value_type): - raise TypeError('"{name}" dictionary values must be of type "{type}"'.format( - name=attribute.name, - type=value_type - )) + raise TypeError( + '"{name}" dictionary values must be of type "{type}"'.format(name=attribute.name, type=value_type) + ) return _validate_dictionary @@ -61,17 +59,13 @@ def _validate_tuple(instance, attribute, value): :raises TypeError: if ``value`` members are not all of ``member_type`` type """ if not isinstance(value, iterable_type): - raise TypeError('"{name}" must be a {type}'.format( - name=attribute.name, - type=iterable_type - )) + raise TypeError('"{name}" must be a {type}'.format(name=attribute.name, type=iterable_type)) for member in value: if not isinstance(member, member_type): - raise TypeError('"{name}" members must all be of type "{type}"'.format( - name=attribute.name, - type=member_type - )) + raise TypeError( + '"{name}" members must all be of type "{type}"'.format(name=attribute.name, type=member_type) + ) return _validate_tuple @@ -83,7 +77,4 @@ def callable_validator(instance, attribute, value): :raises TypeError: if ``value`` is not callable """ if not callable(value): - raise TypeError('"{name}" value "{value}" must be callable'.format( - name=attribute.name, - value=value - )) + raise TypeError('"{name}" value "{value}" must be callable'.format(name=attribute.name, value=value)) diff --git a/src/dynamodb_encryption_sdk/material_providers/__init__.py b/src/dynamodb_encryption_sdk/material_providers/__init__.py index 6be6a41c..e3ddab68 100644 --- a/src/dynamodb_encryption_sdk/material_providers/__init__.py +++ b/src/dynamodb_encryption_sdk/material_providers/__init__.py @@ -14,7 +14,7 @@ from dynamodb_encryption_sdk.materials import CryptographicMaterials # noqa pylint: disable=unused-import from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import -__all__ = ('CryptographicMaterialsProvider',) +__all__ = ("CryptographicMaterialsProvider",) class CryptographicMaterialsProvider(object): @@ -28,7 +28,7 @@ def decryption_materials(self, encryption_context): :param EncryptionContext encryption_context: Encryption context for request :raises AttributeError: if no decryption materials are available """ - raise AttributeError('No decryption materials available') + raise AttributeError("No decryption materials available") def encryption_materials(self, encryption_context): # type: (EncryptionContext) -> CryptographicMaterials @@ -38,7 +38,7 @@ def encryption_materials(self, encryption_context): :param EncryptionContext encryption_context: Encryption context for request :raises AttributeError: if no encryption materials are available """ - raise AttributeError('No encryption materials available') + raise AttributeError("No encryption materials available") def refresh(self): # type: () -> None diff --git a/src/dynamodb_encryption_sdk/material_providers/aws_kms.py b/src/dynamodb_encryption_sdk/material_providers/aws_kms.py index a1ecd486..e5e288d0 100644 --- a/src/dynamodb_encryption_sdk/material_providers/aws_kms.py +++ b/src/dynamodb_encryption_sdk/material_providers/aws_kms.py @@ -14,60 +14,62 @@ from __future__ import division import base64 -from enum import Enum import logging +from enum import Enum import attr import boto3 import botocore +import six from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF -import six - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import - from typing import Dict, Optional, Text, Tuple # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey from dynamodb_encryption_sdk.exceptions import UnknownRegionError, UnwrappingError, WrappingError -from dynamodb_encryption_sdk.identifiers import EncryptionKeyType, KeyEncodingType, LOGGER_NAME, USER_AGENT_SUFFIX -from dynamodb_encryption_sdk.internal.identifiers import MaterialDescriptionKeys, TEXT_ENCODING +from dynamodb_encryption_sdk.identifiers import LOGGER_NAME, USER_AGENT_SUFFIX, EncryptionKeyType, KeyEncodingType +from dynamodb_encryption_sdk.internal.identifiers import TEXT_ENCODING, MaterialDescriptionKeys from dynamodb_encryption_sdk.internal.str_ops import to_bytes, to_str from dynamodb_encryption_sdk.internal.validators import dictionary_validator, iterable_validator from dynamodb_encryption_sdk.materials.raw import RawDecryptionMaterials, RawEncryptionMaterials from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import + from . import CryptographicMaterialsProvider -__all__ = ('AwsKmsCryptographicMaterialsProvider',) +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from dynamodb_encryption_sdk.internal import dynamodb_types # noqa pylint: disable=unused-import + from typing import Dict, Optional, Text, Tuple # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + + +__all__ = ("AwsKmsCryptographicMaterialsProvider",) _LOGGER = logging.getLogger(LOGGER_NAME) -_COVERED_ATTR_CTX_KEY = 'aws-kms-ec-attr' -_TABLE_NAME_EC_KEY = '*aws-kms-table*' -_DEFAULT_CONTENT_ENCRYPTION_ALGORITHM = 'AES/256' +_COVERED_ATTR_CTX_KEY = "aws-kms-ec-attr" +_TABLE_NAME_EC_KEY = "*aws-kms-table*" +_DEFAULT_CONTENT_ENCRYPTION_ALGORITHM = "AES/256" _DEFAULT_CONTENT_KEY_LENGTH = 256 -_DEFAULT_SIGNING_ALGORITHM = 'HmacSHA256/256' +_DEFAULT_SIGNING_ALGORITHM = "HmacSHA256/256" _DEFAULT_SIGNING_KEY_LENGTH = 256 -_KEY_COVERAGE = '*keys*' -_KDF_ALG = 'HmacSHA256' +_KEY_COVERAGE = "*keys*" +_KDF_ALG = "HmacSHA256" class HkdfInfo(Enum): """Info strings used for HKDF calculations.""" - ENCRYPTION = b'Encryption' - SIGNING = b'Signing' + ENCRYPTION = b"Encryption" + SIGNING = b"Signing" class EncryptionContextKeys(Enum): """Special keys for use in the AWS KMS encryption context.""" - CONTENT_ENCRYPTION_ALGORITHM = '*' + MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value + '*' - SIGNATURE_ALGORITHM = '*' + MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value + '*' - TABLE_NAME = '*aws-kms-table*' + CONTENT_ENCRYPTION_ALGORITHM = "*" + MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value + "*" + SIGNATURE_ALGORITHM = "*" + MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value + "*" + TABLE_NAME = "*aws-kms-table*" @attr.s(init=False) @@ -84,12 +86,7 @@ class KeyInfo(object): algorithm = attr.ib(validator=attr.validators.instance_of(six.string_types)) length = attr.ib(validator=attr.validators.instance_of(six.integer_types)) - def __init__( - self, - description, # type: Text - algorithm, # type: Text - length # type: int - ): # noqa=D107 + def __init__(self, description, algorithm, length): # type: Text # type: Text # type: int # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 @@ -107,7 +104,7 @@ def from_description(cls, description, default_key_length=None): :param str description: Key info description :param int default_key_length: Key length to use if not found in description """ - description_parts = description.split('/', 1) + description_parts = description.split("/", 1) algorithm = description_parts[0] try: key_length = int(description_parts[1]) @@ -159,29 +156,23 @@ class AwsKmsCryptographicMaterialsProvider(CryptographicMaterialsProvider): _key_id = attr.ib(validator=attr.validators.instance_of(six.string_types)) _botocore_session = attr.ib( - validator=attr.validators.instance_of(botocore.session.Session), - default=attr.Factory(botocore.session.Session) - ) - _grant_tokens = attr.ib( - validator=iterable_validator(tuple, six.string_types), - default=attr.Factory(tuple) + validator=attr.validators.instance_of(botocore.session.Session), default=attr.Factory(botocore.session.Session) ) + _grant_tokens = attr.ib(validator=iterable_validator(tuple, six.string_types), default=attr.Factory(tuple)) _material_description = attr.ib( - validator=dictionary_validator(six.string_types, six.string_types), - default=attr.Factory(dict) + validator=dictionary_validator(six.string_types, six.string_types), default=attr.Factory(dict) ) _regional_clients = attr.ib( - validator=dictionary_validator(six.string_types, botocore.client.BaseClient), - default=attr.Factory(dict) + validator=dictionary_validator(six.string_types, botocore.client.BaseClient), default=attr.Factory(dict) ) def __init__( - self, - key_id, # type: Text - botocore_session=None, # type: Optional[botocore.session.Session] - grant_tokens=None, # type: Optional[Tuple[Text]] - material_description=None, # type: Optional[Dict[Text, Text]] - regional_clients=None # type: Optional[Dict[Text, botocore.client.BaseClient]] + self, + key_id, # type: Text + botocore_session=None, # type: Optional[botocore.session.Session] + grant_tokens=None, # type: Optional[Tuple[Text]] + material_description=None, # type: Optional[Dict[Text, Text]] + regional_clients=None, # type: Optional[Dict[Text, botocore.client.BaseClient]] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -215,13 +206,13 @@ def __attrs_post_init__(self): material_description=self._material_description, description_key=MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value, default_algorithm=_DEFAULT_CONTENT_ENCRYPTION_ALGORITHM, - default_key_length=_DEFAULT_CONTENT_KEY_LENGTH + default_key_length=_DEFAULT_CONTENT_KEY_LENGTH, ) self._signing_key_info = KeyInfo.from_material_description( # pylint: disable=attribute-defined-outside-init material_description=self._material_description, description_key=MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value, default_algorithm=_DEFAULT_SIGNING_ALGORITHM, - default_key_length=_DEFAULT_SIGNING_KEY_LENGTH + default_key_length=_DEFAULT_SIGNING_KEY_LENGTH, ) self._regional_clients = {} # type: Dict[Text, botocore.client.BaseClient] # noqa pylint: disable=attribute-defined-outside-init @@ -233,9 +224,8 @@ def _add_regional_client(self, region_name): """ if region_name not in self._regional_clients: self._regional_clients[region_name] = boto3.session.Session( - region_name=region_name, - botocore_session=self._botocore_session - ).client('kms', config=self._user_agent_adding_config) + region_name=region_name, botocore_session=self._botocore_session + ).client("kms", config=self._user_agent_adding_config) return self._regional_clients[region_name] def _client(self, key_id): @@ -246,13 +236,13 @@ def _client(self, key_id): :rtype: botocore.client.KMS """ try: - key_region = key_id.split(':', 4)[3] + key_region = key_id.split(":", 4)[3] region = key_region except IndexError: - session_region = self._botocore_session.get_config_variable('region') + session_region = self._botocore_session.get_config_variable("region") if session_region is None: raise UnknownRegionError( - 'No region determinable from key id: {} and no default region found in session'.format(key_id) + "No region determinable from key id: {} and no default region found in session".format(key_id) ) region = session_region return self._add_regional_client(region) @@ -297,9 +287,9 @@ def _attribute_to_value(self, attribute): :rtype: str """ attribute_type, attribute_value = list(attribute.items())[0] - if attribute_type == 'B': + if attribute_type == "B": return base64.b64encode(attribute_value).decode(TEXT_ENCODING) - if attribute_type in ('S', 'N'): + if attribute_type in ("S", "N"): return attribute_value raise ValueError('Attribute of type "{}" cannot be used in KMS encryption context.'.format(attribute_type)) @@ -315,7 +305,7 @@ def _kms_encryption_context(self, encryption_context, encryption_description, si """ kms_encryption_context = { EncryptionContextKeys.CONTENT_ENCRYPTION_ALGORITHM.value: encryption_description, - EncryptionContextKeys.SIGNATURE_ALGORITHM.value: signing_description + EncryptionContextKeys.SIGNATURE_ALGORITHM.value: signing_description, } if encryption_context.partition_key_name is not None: @@ -355,21 +345,17 @@ def _generate_initial_material(self, encryption_context): kms_encryption_context = self._kms_encryption_context( encryption_context=encryption_context, encryption_description=self._content_key_info.description, - signing_description=self._signing_key_info.description - ) - kms_params = dict( - KeyId=key_id, - NumberOfBytes=key_length, - EncryptionContext=kms_encryption_context + signing_description=self._signing_key_info.description, ) + kms_params = dict(KeyId=key_id, NumberOfBytes=key_length, EncryptionContext=kms_encryption_context) if self._grant_tokens: - kms_params['GrantTokens'] = self._grant_tokens + kms_params["GrantTokens"] = self._grant_tokens # Catch any boto3 errors and normalize to expected WrappingError try: response = self._client(key_id).generate_data_key(**kms_params) - return response['Plaintext'], response['CiphertextBlob'] + return response["Plaintext"], response["CiphertextBlob"] except (botocore.exceptions.ClientError, KeyError): - message = 'Failed to generate materials using AWS KMS' + message = "Failed to generate materials using AWS KMS" _LOGGER.exception(message) raise WrappingError(message) @@ -391,23 +377,20 @@ def _decrypt_initial_material(self, encryption_context): ), signing_description=encryption_context.material_description.get( MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value - ) + ), ) - encrypted_initial_material = base64.b64decode(to_bytes(encryption_context.material_description.get( - MaterialDescriptionKeys.WRAPPED_DATA_KEY.value - ))) - kms_params = dict( - CiphertextBlob=encrypted_initial_material, - EncryptionContext=kms_encryption_context + encrypted_initial_material = base64.b64decode( + to_bytes(encryption_context.material_description.get(MaterialDescriptionKeys.WRAPPED_DATA_KEY.value)) ) + kms_params = dict(CiphertextBlob=encrypted_initial_material, EncryptionContext=kms_encryption_context) if self._grant_tokens: - kms_params['GrantTokens'] = self._grant_tokens + kms_params["GrantTokens"] = self._grant_tokens # Catch any boto3 errors and normalize to expected UnwrappingError try: response = self._client(key_id).decrypt(**kms_params) - return response['Plaintext'] + return response["Plaintext"] except (botocore.exceptions.ClientError, KeyError): - message = 'Failed to unwrap AWS KMS protected materials' + message = "Failed to unwrap AWS KMS protected materials" _LOGGER.exception(message) raise UnwrappingError(message) @@ -421,13 +404,7 @@ def _hkdf(self, initial_material, key_length, info): :returns: Derived key material :rtype: bytes """ - hkdf = HKDF( - algorithm=hashes.SHA256(), - length=key_length, - salt=None, - info=info, - backend=default_backend() - ) + hkdf = HKDF(algorithm=hashes.SHA256(), length=key_length, salt=None, info=info, backend=default_backend()) return hkdf.derive(initial_material) def _derive_delegated_key(self, initial_material, key_info, hkdf_info): @@ -445,7 +422,7 @@ def _derive_delegated_key(self, initial_material, key_info, hkdf_info): key=raw_key, algorithm=key_info.algorithm, key_type=EncryptionKeyType.SYMMETRIC, - key_encoding=KeyEncodingType.RAW + key_encoding=KeyEncodingType.RAW, ) def _encryption_key(self, initial_material, key_info): @@ -484,18 +461,18 @@ def decryption_materials(self, encryption_context): material_description=encryption_context.material_description, description_key=MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value, default_algorithm=_DEFAULT_SIGNING_ALGORITHM, - default_key_length=_DEFAULT_SIGNING_KEY_LENGTH + default_key_length=_DEFAULT_SIGNING_KEY_LENGTH, ) decryption_key_info = KeyInfo.from_material_description( material_description=encryption_context.material_description, description_key=MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value, default_algorithm=_DEFAULT_CONTENT_ENCRYPTION_ALGORITHM, - default_key_length=_DEFAULT_CONTENT_KEY_LENGTH + default_key_length=_DEFAULT_CONTENT_KEY_LENGTH, ) return RawDecryptionMaterials( verification_key=self._mac_key(initial_material, signing_key_info), decryption_key=self._encryption_key(initial_material, decryption_key_info), - material_description=decryption_material_description + material_description=decryption_material_description, ) def encryption_materials(self, encryption_context): @@ -508,15 +485,17 @@ def encryption_materials(self, encryption_context): """ initial_material, encrypted_initial_material = self._generate_initial_material(encryption_context) encryption_material_description = encryption_context.material_description.copy() - encryption_material_description.update({ - _COVERED_ATTR_CTX_KEY: _KEY_COVERAGE, - MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value: 'kms', - MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value: self._content_key_info.description, - MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value: self._signing_key_info.description, - MaterialDescriptionKeys.WRAPPED_DATA_KEY.value: to_str(base64.b64encode(encrypted_initial_material)) - }) + encryption_material_description.update( + { + _COVERED_ATTR_CTX_KEY: _KEY_COVERAGE, + MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value: "kms", + MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value: self._content_key_info.description, + MaterialDescriptionKeys.ITEM_SIGNATURE_ALGORITHM.value: self._signing_key_info.description, + MaterialDescriptionKeys.WRAPPED_DATA_KEY.value: to_str(base64.b64encode(encrypted_initial_material)), + } + ) return RawEncryptionMaterials( signing_key=self._mac_key(initial_material, self._signing_key_info), encryption_key=self._encryption_key(initial_material, self._content_key_info), - material_description=encryption_material_description + material_description=encryption_material_description, ) diff --git a/src/dynamodb_encryption_sdk/material_providers/most_recent.py b/src/dynamodb_encryption_sdk/material_providers/most_recent.py index e70379dc..ed1d48a4 100644 --- a/src/dynamodb_encryption_sdk/material_providers/most_recent.py +++ b/src/dynamodb_encryption_sdk/material_providers/most_recent.py @@ -11,29 +11,31 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Cryptographic materials provider that uses a provider store to obtain cryptographic materials.""" +import logging +import time from collections import OrderedDict from enum import Enum -import logging from threading import Lock, RLock -import time import attr import six -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Any, Text # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - from dynamodb_encryption_sdk.exceptions import InvalidVersionError, NoKnownVersionError from dynamodb_encryption_sdk.identifiers import LOGGER_NAME from dynamodb_encryption_sdk.materials import CryptographicMaterials # noqa pylint: disable=unused-import from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import + from . import CryptographicMaterialsProvider from .store import ProviderStore -__all__ = ('MostRecentProvider',) +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Any, Text # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + + +__all__ = ("MostRecentProvider",) _LOGGER = logging.getLogger(LOGGER_NAME) #: Grace period during which we will return the latest local materials. This allows multiple #: threads to be using this same provider without risking lock contention or many threads @@ -53,17 +55,14 @@ def _min_capacity_validator(instance, attribute, value): # pylint: disable=unused-argument """Attrs validator to require that value is at least 1.""" if value < 1: - raise ValueError('Cache capacity must be at least 1') + raise ValueError("Cache capacity must be at least 1") @attr.s(init=False) class BasicCache(object): """Most basic LRU cache.""" - capacity = attr.ib(validator=( - attr.validators.instance_of(int), - _min_capacity_validator - )) + capacity = attr.ib(validator=(attr.validators.instance_of(int), _min_capacity_validator)) def __init__(self, capacity): # noqa=D107 # type: (int) -> None @@ -136,10 +135,7 @@ class MostRecentProvider(CryptographicMaterialsProvider): _version_ttl = attr.ib(validator=attr.validators.instance_of(float)) def __init__( - self, - provider_store, # type: ProviderStore - material_name, # type: Text - version_ttl # type: float + self, provider_store, material_name, version_ttl # type: ProviderStore # type: Text # type: float ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -174,8 +170,8 @@ def decryption_materials(self, encryption_context): try: provider = self._provider_store.provider(self._material_name, version) except InvalidVersionError: - _LOGGER.exception('Unable to get decryption materials from provider store.') - raise AttributeError('No decryption materials available') + _LOGGER.exception("Unable to get decryption materials from provider store.") + raise AttributeError("No decryption materials available") self._cache.put(version, provider) @@ -225,8 +221,8 @@ def _get_provider(self, version): try: return self._provider_store.get_or_create_provider(self._material_name, version) except InvalidVersionError: - _LOGGER.exception('Unable to get encryption materials from provider store.') - raise AttributeError('No encryption materials available') + _LOGGER.exception("Unable to get encryption materials from provider store.") + raise AttributeError("No encryption materials available") def _get_most_recent_version(self, allow_local): # type: (bool) -> CryptographicMaterialsProvider diff --git a/src/dynamodb_encryption_sdk/material_providers/static.py b/src/dynamodb_encryption_sdk/material_providers/static.py index e68ce025..966002cb 100644 --- a/src/dynamodb_encryption_sdk/material_providers/static.py +++ b/src/dynamodb_encryption_sdk/material_providers/static.py @@ -13,18 +13,20 @@ """Cryptographic materials provider for use with pre-configured encryption and decryption materials.""" import attr +from dynamodb_encryption_sdk.materials import CryptographicMaterials # noqa pylint: disable=unused-import +from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials +from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import + +from . import CryptographicMaterialsProvider + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Optional # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.materials import CryptographicMaterials # noqa pylint: disable=unused-import -from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials -from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import -from . import CryptographicMaterialsProvider -__all__ = ('StaticCryptographicMaterialsProvider',) +__all__ = ("StaticCryptographicMaterialsProvider",) @attr.s(init=False) @@ -36,18 +38,16 @@ class StaticCryptographicMaterialsProvider(CryptographicMaterialsProvider): """ _decryption_materials = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(DecryptionMaterials)), - default=None + validator=attr.validators.optional(attr.validators.instance_of(DecryptionMaterials)), default=None ) _encryption_materials = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(EncryptionMaterials)), - default=None + validator=attr.validators.optional(attr.validators.instance_of(EncryptionMaterials)), default=None ) def __init__( - self, - decryption_materials=None, # type: Optional[DecryptionMaterials] - encryption_materials=None # type: Optional[EncryptionMaterials] + self, + decryption_materials=None, # type: Optional[DecryptionMaterials] + encryption_materials=None, # type: Optional[EncryptionMaterials] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. diff --git a/src/dynamodb_encryption_sdk/material_providers/store/__init__.py b/src/dynamodb_encryption_sdk/material_providers/store/__init__.py index 77b673e4..a40b7c01 100644 --- a/src/dynamodb_encryption_sdk/material_providers/store/__init__.py +++ b/src/dynamodb_encryption_sdk/material_providers/store/__init__.py @@ -15,16 +15,19 @@ import six +from dynamodb_encryption_sdk.exceptions import NoKnownVersionError +from dynamodb_encryption_sdk.material_providers import ( # noqa pylint: disable=unused-import + CryptographicMaterialsProvider +) + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Dict, Text, Optional # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.exceptions import NoKnownVersionError -from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider # noqa pylint: disable=unused-import -__all__ = ('ProviderStore',) +__all__ = ("ProviderStore",) @six.add_metaclass(abc.ABCMeta) diff --git a/src/dynamodb_encryption_sdk/material_providers/store/meta.py b/src/dynamodb_encryption_sdk/material_providers/store/meta.py index a0fb02ce..2dbfe227 100644 --- a/src/dynamodb_encryption_sdk/material_providers/store/meta.py +++ b/src/dynamodb_encryption_sdk/material_providers/store/meta.py @@ -14,16 +14,10 @@ from enum import Enum import attr +import botocore from boto3.dynamodb.conditions import Attr, Key from boto3.dynamodb.types import Binary from boto3.resources.base import ServiceResource -import botocore - -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Dict, Optional, Text, Tuple # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey from dynamodb_encryption_sdk.encrypted.table import EncryptedTable @@ -31,34 +25,42 @@ from dynamodb_encryption_sdk.identifiers import EncryptionKeyType, KeyEncodingType from dynamodb_encryption_sdk.material_providers import CryptographicMaterialsProvider from dynamodb_encryption_sdk.material_providers.wrapped import WrappedCryptographicMaterialsProvider + from . import ProviderStore -__all__ = ('MetaStore',) +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Dict, Optional, Text, Tuple # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + + +__all__ = ("MetaStore",) class MetaStoreAttributeNames(Enum): """Names of attributes in the MetaStore table.""" - PARTITION = 'N' - SORT = 'V' - INTEGRITY_ALGORITHM = 'intAlg' - INTEGRITY_KEY = 'int' - ENCRYPTION_ALGORITHM = 'encAlg' - ENCRYPTION_KEY = 'enc' - MATERIAL_TYPE_VERSION = 't' + PARTITION = "N" + SORT = "V" + INTEGRITY_ALGORITHM = "intAlg" + INTEGRITY_KEY = "int" + ENCRYPTION_ALGORITHM = "encAlg" + ENCRYPTION_KEY = "enc" + MATERIAL_TYPE_VERSION = "t" class MetaStoreValues(Enum): """Static values for use by MetaStore.""" - INTEGRITY_ALGORITHM = 'HmacSHA256' - ENCRYPTION_ALGORITHM = 'AES' - MATERIAL_TYPE_VERSION = '0' + INTEGRITY_ALGORITHM = "HmacSHA256" + ENCRYPTION_ALGORITHM = "AES" + MATERIAL_TYPE_VERSION = "0" KEY_BITS = 256 #: Field in material description to use for the MetaStore material name and version. -_MATERIAL_DESCRIPTION_META_FIELD = 'amzn-ddb-meta-id' +_MATERIAL_DESCRIPTION_META_FIELD = "amzn-ddb-meta-id" @attr.s(init=False) @@ -88,8 +90,7 @@ def __attrs_post_init__(self): # type: () -> None """Prepare the encrypted table resource from the provided table and materials provider.""" self._encrypted_table = EncryptedTable( # attrs confuses pylint: disable=attribute-defined-outside-init - table=self._table, - materials_provider=self._materials_provider + table=self._table, materials_provider=self._materials_provider ) @classmethod @@ -107,32 +108,17 @@ def create_table(cls, client, table_name, read_units, write_units): client.create_table( TableName=table_name, AttributeDefinitions=[ - { - 'AttributeName': MetaStoreAttributeNames.PARTITION.value, - 'AttributeType': 'S' - }, - { - 'AttributeName': MetaStoreAttributeNames.SORT.value, - 'AttributeType': 'N' - } + {"AttributeName": MetaStoreAttributeNames.PARTITION.value, "AttributeType": "S"}, + {"AttributeName": MetaStoreAttributeNames.SORT.value, "AttributeType": "N"}, ], KeySchema=[ - { - 'AttributeName': MetaStoreAttributeNames.PARTITION.value, - 'KeyType': 'HASH' - }, - { - 'AttributeName': MetaStoreAttributeNames.SORT.value, - 'KeyType': 'RANGE' - } + {"AttributeName": MetaStoreAttributeNames.PARTITION.value, "KeyType": "HASH"}, + {"AttributeName": MetaStoreAttributeNames.SORT.value, "KeyType": "RANGE"}, ], - ProvisionedThroughput={ - 'ReadCapacityUnits': read_units, - 'WriteCapacityUnits': write_units - } + ProvisionedThroughput={"ReadCapacityUnits": read_units, "WriteCapacityUnits": write_units}, ) except botocore.exceptions.ClientError: - raise Exception('TODO: Could not create table') + raise Exception("TODO: Could not create table") def _load_materials(self, material_name, version): # type: (Text, int) -> Tuple[JceNameLocalDelegatedKey, JceNameLocalDelegatedKey] @@ -141,13 +127,10 @@ def _load_materials(self, material_name, version): :returns: Materials loaded into delegated keys :rtype: tuple(JceNameLocalDelegatedKey) """ - key = { - MetaStoreAttributeNames.PARTITION.value: material_name, - MetaStoreAttributeNames.SORT.value: version - } + key = {MetaStoreAttributeNames.PARTITION.value: material_name, MetaStoreAttributeNames.SORT.value: version} response = self._encrypted_table.get_item(Key=key) try: - item = response['Item'] + item = response["Item"] except KeyError: raise InvalidVersionError('Version not found: "{}#{}"'.format(material_name, version)) @@ -156,22 +139,22 @@ def _load_materials(self, material_name, version): key=item[MetaStoreAttributeNames.ENCRYPTION_KEY.value].value, algorithm=item[MetaStoreAttributeNames.ENCRYPTION_ALGORITHM.value], key_type=EncryptionKeyType.SYMMETRIC, - key_encoding=KeyEncodingType.RAW + key_encoding=KeyEncodingType.RAW, ) signing_key_kwargs = dict( key=item[MetaStoreAttributeNames.INTEGRITY_KEY.value].value, algorithm=item[MetaStoreAttributeNames.INTEGRITY_ALGORITHM.value], key_type=EncryptionKeyType.SYMMETRIC, - key_encoding=KeyEncodingType.RAW + key_encoding=KeyEncodingType.RAW, ) except KeyError: - raise Exception('TODO: Invalid record') + raise Exception("TODO: Invalid record") # TODO: handle if the material type version is not in the item if item[MetaStoreAttributeNames.MATERIAL_TYPE_VERSION.value] != MetaStoreValues.MATERIAL_TYPE_VERSION.value: - raise InvalidVersionError('Unsupported material type: "{}"'.format( - item[MetaStoreAttributeNames.MATERIAL_TYPE_VERSION.value] - )) + raise InvalidVersionError( + 'Unsupported material type: "{}"'.format(item[MetaStoreAttributeNames.MATERIAL_TYPE_VERSION.value]) + ) encryption_key = JceNameLocalDelegatedKey(**encryption_key_kwargs) signing_key = JceNameLocalDelegatedKey(**signing_key_kwargs) @@ -192,26 +175,26 @@ def _save_materials(self, material_name, version, encryption_key, signing_key): MetaStoreAttributeNames.ENCRYPTION_ALGORITHM.value: encryption_key.algorithm, MetaStoreAttributeNames.ENCRYPTION_KEY.value: Binary(encryption_key.key), MetaStoreAttributeNames.INTEGRITY_ALGORITHM.value: signing_key.algorithm, - MetaStoreAttributeNames.INTEGRITY_KEY.value: Binary(signing_key.key) + MetaStoreAttributeNames.INTEGRITY_KEY.value: Binary(signing_key.key), } try: self._encrypted_table.put_item( Item=item, ConditionExpression=( - Attr(MetaStoreAttributeNames.PARTITION.value).not_exists() & - Attr(MetaStoreAttributeNames.SORT.value).not_exists() - ) + Attr(MetaStoreAttributeNames.PARTITION.value).not_exists() + & Attr(MetaStoreAttributeNames.SORT.value).not_exists() + ), ) except botocore.exceptions.ClientError as error: - if error.response['Error']['Code'] == 'ConditionalCheckFailedException': + if error.response["Error"]["Code"] == "ConditionalCheckFailedException": raise VersionAlreadyExistsError('Version already exists: "{}#{}"'.format(material_name, version)) def _save_or_load_materials( - self, - material_name, # type: Text - version, # type: int - encryption_key, # type: JceNameLocalDelegatedKey - signing_key # type: JceNameLocalDelegatedKey + self, + material_name, # type: Text + version, # type: int + encryption_key, # type: JceNameLocalDelegatedKey + signing_key, # type: JceNameLocalDelegatedKey ): # type: (...) -> Tuple[JceNameLocalDelegatedKey, JceNameLocalDelegatedKey] """Attempt to save the materials to the table. @@ -238,7 +221,7 @@ def _material_description(material_name, version): :param str material_name: Material to locate :param int version: Version of material to locate """ - return {_MATERIAL_DESCRIPTION_META_FIELD: '{name}#{version}'.format(name=material_name, version=version)} + return {_MATERIAL_DESCRIPTION_META_FIELD: "{name}#{version}".format(name=material_name, version=version)} def _load_provider_from_table(self, material_name, version): # type: (Text, int) -> CryptographicMaterialsProvider @@ -254,7 +237,7 @@ def _load_provider_from_table(self, material_name, version): signing_key=signing_key, wrapping_key=encryption_key, unwrapping_key=encryption_key, - material_description=self._material_description(material_name, version) + material_description=self._material_description(material_name, version), ) def get_or_create_provider(self, material_name, version): @@ -270,19 +253,17 @@ def get_or_create_provider(self, material_name, version): :raises InvalidVersionError: if the requested version is not available and cannot be created """ encryption_key = JceNameLocalDelegatedKey.generate( - MetaStoreValues.ENCRYPTION_ALGORITHM.value, - MetaStoreValues.KEY_BITS.value + MetaStoreValues.ENCRYPTION_ALGORITHM.value, MetaStoreValues.KEY_BITS.value ) signing_key = JceNameLocalDelegatedKey.generate( - MetaStoreValues.INTEGRITY_ALGORITHM.value, - MetaStoreValues.KEY_BITS.value + MetaStoreValues.INTEGRITY_ALGORITHM.value, MetaStoreValues.KEY_BITS.value ) encryption_key, signing_key = self._save_or_load_materials(material_name, version, encryption_key, signing_key) return WrappedCryptographicMaterialsProvider( signing_key=signing_key, wrapping_key=encryption_key, unwrapping_key=encryption_key, - material_description=self._material_description(material_name, version) + material_description=self._material_description(material_name, version), ) def provider(self, material_name, version=None): @@ -315,12 +296,12 @@ def version_from_material_description(self, material_description): try: info = material_description[_MATERIAL_DESCRIPTION_META_FIELD] except KeyError: - raise Exception('TODO: No info found') + raise Exception("TODO: No info found") try: - return int(info.split('#', 1)[1]) + return int(info.split("#", 1)[1]) except (IndexError, ValueError): - raise Exception('TODO: Malformed info') + raise Exception("TODO: Malformed info") def max_version(self, material_name): # (Text) -> int @@ -334,10 +315,10 @@ def max_version(self, material_name): response = self._encrypted_table.query( KeyConditionExpression=Key(MetaStoreAttributeNames.PARTITION.value).eq(material_name), ScanIndexForward=False, - Limit=1 + Limit=1, ) - if not response['Items']: + if not response["Items"]: raise NoKnownVersionError('No known version for name: "{}"'.format(material_name)) - return int(response['Items'][0][MetaStoreAttributeNames.SORT.value]) + return int(response["Items"][0][MetaStoreAttributeNames.SORT.value]) diff --git a/src/dynamodb_encryption_sdk/material_providers/wrapped.py b/src/dynamodb_encryption_sdk/material_providers/wrapped.py index 7a6c571d..13f6a346 100644 --- a/src/dynamodb_encryption_sdk/material_providers/wrapped.py +++ b/src/dynamodb_encryption_sdk/material_providers/wrapped.py @@ -14,6 +14,14 @@ import attr import six +from dynamodb_encryption_sdk.delegated_keys import DelegatedKey +from dynamodb_encryption_sdk.exceptions import UnwrappingError, WrappingError +from dynamodb_encryption_sdk.internal.validators import dictionary_validator +from dynamodb_encryption_sdk.materials.wrapped import WrappedCryptographicMaterials +from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import + +from . import CryptographicMaterialsProvider + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Dict, Optional, Text # noqa pylint: disable=unused-import except ImportError: # pragma: no cover @@ -26,14 +34,8 @@ # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.delegated_keys import DelegatedKey -from dynamodb_encryption_sdk.exceptions import UnwrappingError, WrappingError -from dynamodb_encryption_sdk.internal.validators import dictionary_validator -from dynamodb_encryption_sdk.materials.wrapped import WrappedCryptographicMaterials -from dynamodb_encryption_sdk.structures import EncryptionContext # noqa pylint: disable=unused-import -from . import CryptographicMaterialsProvider -__all__ = ('WrappedCryptographicMaterialsProvider',) +__all__ = ("WrappedCryptographicMaterialsProvider",) @attr.s(init=False) @@ -56,25 +58,21 @@ class WrappedCryptographicMaterialsProvider(CryptographicMaterialsProvider): """ _signing_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) - _wrapping_key = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), - default=None - ) + _wrapping_key = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), default=None) _unwrapping_key = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), - default=None + validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), default=None ) _material_description = attr.ib( validator=attr.validators.optional(dictionary_validator(six.string_types, six.string_types)), - default=attr.Factory(dict) + default=attr.Factory(dict), ) def __init__( - self, - signing_key, # type: DelegatedKey - wrapping_key=None, # type: Optional[DelegatedKey] - unwrapping_key=None, # type: Optional[DelegatedKey] - material_description=None # type: Optional[Dict[Text, Text]] + self, + signing_key, # type: DelegatedKey + wrapping_key=None, # type: Optional[DelegatedKey] + unwrapping_key=None, # type: Optional[DelegatedKey] + material_description=None, # type: Optional[Dict[Text, Text]] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -103,7 +101,7 @@ def _build_materials(self, encryption_context): wrapping_key=self._wrapping_key, unwrapping_key=self._unwrapping_key, signing_key=self._signing_key, - material_description=material_description + material_description=material_description, ) def encryption_materials(self, encryption_context): @@ -116,7 +114,7 @@ def encryption_materials(self, encryption_context): :raises WrappingError: if no wrapping key is available """ if self._wrapping_key is None: - raise WrappingError('Encryption materials cannot be provided: no wrapping key') + raise WrappingError("Encryption materials cannot be provided: no wrapping key") return self._build_materials(encryption_context) @@ -130,6 +128,6 @@ def decryption_materials(self, encryption_context): :raises UnwrappingError: if no unwrapping key is available """ if self._unwrapping_key is None: - raise UnwrappingError('Decryption materials cannot be provided: no unwrapping key') + raise UnwrappingError("Decryption materials cannot be provided: no unwrapping key") return self._build_materials(encryption_context) diff --git a/src/dynamodb_encryption_sdk/materials/__init__.py b/src/dynamodb_encryption_sdk/materials/__init__.py index d95702a0..ba07d0c8 100644 --- a/src/dynamodb_encryption_sdk/materials/__init__.py +++ b/src/dynamodb_encryption_sdk/materials/__init__.py @@ -13,6 +13,10 @@ """Cryptographic materials are containers that provide delegated keys for cryptographic operations.""" import abc +import six + +from dynamodb_encryption_sdk.delegated_keys import DelegatedKey # noqa pylint: disable=unused-import + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Dict, Text # noqa pylint: disable=unused-import from mypy_extensions import NoReturn # noqa pylint: disable=unused-import @@ -20,11 +24,8 @@ # We only actually need these imports when running the mypy checks pass -import six - -from dynamodb_encryption_sdk.delegated_keys import DelegatedKey # noqa pylint: disable=unused-import -__all__ = ('CryptographicMaterials', 'EncryptionMaterials', 'DecryptionMaterials') +__all__ = ("CryptographicMaterials", "EncryptionMaterials", "DecryptionMaterials") @six.add_metaclass(abc.ABCMeta) @@ -87,7 +88,7 @@ def decryption_key(self): :raises NotImplementedError: because encryption materials do not contain decryption keys """ - raise NotImplementedError('Encryption materials do not provide decryption keys.') + raise NotImplementedError("Encryption materials do not provide decryption keys.") @property def verification_key(self): @@ -96,7 +97,7 @@ def verification_key(self): :raises NotImplementedError: because encryption materials do not contain verification keys """ - raise NotImplementedError('Encryption materials do not provide verification keys.') + raise NotImplementedError("Encryption materials do not provide verification keys.") class DecryptionMaterials(CryptographicMaterials): @@ -109,7 +110,7 @@ def encryption_key(self): :raises NotImplementedError: because decryption materials do not contain encryption keys """ - raise NotImplementedError('Decryption materials do not provide encryption keys.') + raise NotImplementedError("Decryption materials do not provide encryption keys.") @property def signing_key(self): @@ -118,4 +119,4 @@ def signing_key(self): :raises NotImplementedError: because decryption materials do not contain signing keys """ - raise NotImplementedError('Decryption materials do not provide signing keys.') + raise NotImplementedError("Decryption materials do not provide signing keys.") diff --git a/src/dynamodb_encryption_sdk/materials/raw.py b/src/dynamodb_encryption_sdk/materials/raw.py index 05bc9eb1..7c2e85e4 100644 --- a/src/dynamodb_encryption_sdk/materials/raw.py +++ b/src/dynamodb_encryption_sdk/materials/raw.py @@ -27,17 +27,18 @@ import attr import six +from dynamodb_encryption_sdk.delegated_keys import DelegatedKey +from dynamodb_encryption_sdk.internal.validators import dictionary_validator +from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Dict, Optional, Text # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.delegated_keys import DelegatedKey -from dynamodb_encryption_sdk.internal.validators import dictionary_validator -from dynamodb_encryption_sdk.materials import DecryptionMaterials, EncryptionMaterials -__all__ = ('RawEncryptionMaterials', 'RawDecryptionMaterials') +__all__ = ("RawEncryptionMaterials", "RawDecryptionMaterials") @attr.s(init=False) @@ -56,20 +57,19 @@ class RawEncryptionMaterials(EncryptionMaterials): _signing_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) _encryption_key = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), - default=None + validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), default=None ) _material_description = attr.ib( validator=dictionary_validator(six.string_types, six.string_types), converter=copy.deepcopy, - default=attr.Factory(dict) + default=attr.Factory(dict), ) def __init__( - self, - signing_key, # type: DelegatedKey - encryption_key=None, # type: Optional[DelegatedKey] - material_description=None # type: Optional[Dict[Text, Text]] + self, + signing_key, # type: DelegatedKey + encryption_key=None, # type: Optional[DelegatedKey] + material_description=None, # type: Optional[Dict[Text, Text]] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -87,9 +87,11 @@ def __init__( def __attrs_post_init__(self): """Verify that the encryption key is allowed be used for raw materials.""" if self._encryption_key is not None and not self._encryption_key.allowed_for_raw_materials: - raise ValueError('Encryption key type "{}" does not allow use with RawEncryptionMaterials'.format( - type(self._encryption_key) - )) + raise ValueError( + 'Encryption key type "{}" does not allow use with RawEncryptionMaterials'.format( + type(self._encryption_key) + ) + ) @property def material_description(self): @@ -120,7 +122,7 @@ def encryption_key(self): :rtype: DelegatedKey """ if self._encryption_key is None: - raise AttributeError('No encryption key available') + raise AttributeError("No encryption key available") return self._encryption_key @@ -141,20 +143,19 @@ class RawDecryptionMaterials(DecryptionMaterials): _verification_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) _decryption_key = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), - default=None + validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), default=None ) _material_description = attr.ib( validator=dictionary_validator(six.string_types, six.string_types), converter=copy.deepcopy, - default=attr.Factory(dict) + default=attr.Factory(dict), ) def __init__( - self, - verification_key, # type: DelegatedKey - decryption_key=None, # type: Optional[DelegatedKey] - material_description=None # type: Optional[Dict[Text, Text]] + self, + verification_key, # type: DelegatedKey + decryption_key=None, # type: Optional[DelegatedKey] + material_description=None, # type: Optional[Dict[Text, Text]] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -172,9 +173,11 @@ def __init__( def __attrs_post_init__(self): """Verify that the encryption key is allowed be used for raw materials.""" if self._decryption_key is not None and not self._decryption_key.allowed_for_raw_materials: - raise ValueError('Decryption key type "{}" does not allow use with RawDecryptionMaterials'.format( - type(self._decryption_key) - )) + raise ValueError( + 'Decryption key type "{}" does not allow use with RawDecryptionMaterials'.format( + type(self._decryption_key) + ) + ) @property def material_description(self): @@ -205,6 +208,6 @@ def decryption_key(self): :rtype: DelegatedKey """ if self._decryption_key is None: - raise AttributeError('No decryption key available') + raise AttributeError("No decryption key available") return self._decryption_key diff --git a/src/dynamodb_encryption_sdk/materials/wrapped.py b/src/dynamodb_encryption_sdk/materials/wrapped.py index c69b2028..daf8638a 100644 --- a/src/dynamodb_encryption_sdk/materials/wrapped.py +++ b/src/dynamodb_encryption_sdk/materials/wrapped.py @@ -17,12 +17,6 @@ import attr import six -try: # Python 3.5.0 and 3.5.1 have incompatible typing modules - from typing import Dict, Optional, Text # noqa pylint: disable=unused-import -except ImportError: # pragma: no cover - # We only actually need these imports when running the mypy checks - pass - from dynamodb_encryption_sdk.delegated_keys import DelegatedKey from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey from dynamodb_encryption_sdk.exceptions import UnwrappingError, WrappingError @@ -31,12 +25,16 @@ from dynamodb_encryption_sdk.internal.validators import dictionary_validator from dynamodb_encryption_sdk.materials import CryptographicMaterials -__all__ = ('WrappedCryptographicMaterials',) -_DEFAULT_CONTENT_ENCRYPTION_ALGORITHM = 'AES/256' -_WRAPPING_TRANSFORMATION = { - 'AES': 'AESWrap', - 'RSA': 'RSA/ECB/OAEPWithSHA-256AndMGF1Padding' -} +try: # Python 3.5.0 and 3.5.1 have incompatible typing modules + from typing import Dict, Optional, Text # noqa pylint: disable=unused-import +except ImportError: # pragma: no cover + # We only actually need these imports when running the mypy checks + pass + + +__all__ = ("WrappedCryptographicMaterials",) +_DEFAULT_CONTENT_ENCRYPTION_ALGORITHM = "AES/256" +_WRAPPING_TRANSFORMATION = {"AES": "AESWrap", "RSA": "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"} @attr.s(init=False) @@ -61,26 +59,22 @@ class WrappedCryptographicMaterials(CryptographicMaterials): """ _signing_key = attr.ib(validator=attr.validators.instance_of(DelegatedKey)) - _wrapping_key = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), - default=None - ) + _wrapping_key = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), default=None) _unwrapping_key = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), - default=None + validator=attr.validators.optional(attr.validators.instance_of(DelegatedKey)), default=None ) _material_description = attr.ib( validator=dictionary_validator(six.string_types, six.string_types), converter=copy.deepcopy, - default=attr.Factory(dict) + default=attr.Factory(dict), ) def __init__( - self, - signing_key, # type: DelegatedKey - wrapping_key=None, # type: Optional[DelegatedKey] - unwrapping_key=None, # type: Optional[DelegatedKey] - material_description=None # type: Optional[Dict[Text, Text]] + self, + signing_key, # type: DelegatedKey + wrapping_key=None, # type: Optional[DelegatedKey] + unwrapping_key=None, # type: Optional[DelegatedKey] + material_description=None, # type: Optional[Dict[Text, Text]] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -99,14 +93,17 @@ def __init__( def __attrs_post_init__(self): """Prepare the content key.""" self._content_key_algorithm = self.material_description.get( # pylint: disable=attribute-defined-outside-init - MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value, - _DEFAULT_CONTENT_ENCRYPTION_ALGORITHM + MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value, _DEFAULT_CONTENT_ENCRYPTION_ALGORITHM ) if MaterialDescriptionKeys.WRAPPED_DATA_KEY.value in self.material_description: - self._content_key = self._content_key_from_material_description() # noqa pylint: disable=attribute-defined-outside-init + self._content_key = ( + self._content_key_from_material_description() + ) # noqa pylint: disable=attribute-defined-outside-init else: - self._content_key, self._material_description = self._generate_content_key() # noqa pylint: disable=attribute-defined-outside-init + self._content_key, self._material_description = ( + self._generate_content_key() + ) # noqa pylint: disable=attribute-defined-outside-init @staticmethod def _wrapping_transformation(algorithm): @@ -126,23 +123,20 @@ def _content_key_from_material_description(self): """ if self._unwrapping_key is None: raise UnwrappingError( - 'Cryptographic materials cannot be loaded from material description: no unwrapping key' + "Cryptographic materials cannot be loaded from material description: no unwrapping key" ) wrapping_algorithm = self.material_description.get( - MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value, - self._unwrapping_key.algorithm + MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value, self._unwrapping_key.algorithm ) - wrapped_key = base64.b64decode( - self.material_description[MaterialDescriptionKeys.WRAPPED_DATA_KEY.value] - ) - content_key_algorithm = self._content_key_algorithm.split('/', 1)[0] + wrapped_key = base64.b64decode(self.material_description[MaterialDescriptionKeys.WRAPPED_DATA_KEY.value]) + content_key_algorithm = self._content_key_algorithm.split("/", 1)[0] return self._unwrapping_key.unwrap( algorithm=wrapping_algorithm, wrapped_key=wrapped_key, wrapped_key_algorithm=content_key_algorithm, wrapped_key_type=EncryptionKeyType.SYMMETRIC, - additional_associated_data=None + additional_associated_data=None, ) def _generate_content_key(self): @@ -153,33 +147,30 @@ def _generate_content_key(self): :rtype: tuple containing DelegatedKey and dict """ if self._wrapping_key is None: - raise WrappingError('Cryptographic materials cannot be generated: no wrapping key') + raise WrappingError("Cryptographic materials cannot be generated: no wrapping key") wrapping_algorithm = self.material_description.get( MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value, - self._wrapping_transformation(self._wrapping_key.algorithm) + self._wrapping_transformation(self._wrapping_key.algorithm), ) - args = self._content_key_algorithm.split('/', 1) + args = self._content_key_algorithm.split("/", 1) content_algorithm = args[0] try: content_key_length = int(args[1]) except IndexError: content_key_length = None - content_key = JceNameLocalDelegatedKey.generate( - algorithm=content_algorithm, - key_length=content_key_length - ) + content_key = JceNameLocalDelegatedKey.generate(algorithm=content_algorithm, key_length=content_key_length) wrapped_key = self._wrapping_key.wrap( - algorithm=wrapping_algorithm, - content_key=content_key.key, - additional_associated_data=None + algorithm=wrapping_algorithm, content_key=content_key.key, additional_associated_data=None ) new_material_description = self.material_description.copy() - new_material_description.update({ - MaterialDescriptionKeys.WRAPPED_DATA_KEY.value: base64.b64encode(wrapped_key), - MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value: self._content_key_algorithm, - MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value: wrapping_algorithm - }) + new_material_description.update( + { + MaterialDescriptionKeys.WRAPPED_DATA_KEY.value: base64.b64encode(wrapped_key), + MaterialDescriptionKeys.CONTENT_ENCRYPTION_ALGORITHM.value: self._content_key_algorithm, + MaterialDescriptionKeys.CONTENT_KEY_WRAPPING_ALGORITHM.value: wrapping_algorithm, + } + ) return content_key, new_material_description @property diff --git a/src/dynamodb_encryption_sdk/structures.py b/src/dynamodb_encryption_sdk/structures.py index 22c38cdf..7d62ac94 100644 --- a/src/dynamodb_encryption_sdk/structures.py +++ b/src/dynamodb_encryption_sdk/structures.py @@ -16,18 +16,20 @@ import attr import six +from dynamodb_encryption_sdk.exceptions import InvalidArgumentError +from dynamodb_encryption_sdk.internal.identifiers import ReservedAttributes +from dynamodb_encryption_sdk.internal.validators import dictionary_validator, iterable_validator + +from .identifiers import CryptoAction + try: # Python 3.5.0 and 3.5.1 have incompatible typing modules from typing import Dict, Iterable, List, Optional, Set, Text # noqa pylint: disable=unused-import except ImportError: # pragma: no cover # We only actually need these imports when running the mypy checks pass -from dynamodb_encryption_sdk.exceptions import InvalidArgumentError -from dynamodb_encryption_sdk.internal.identifiers import ReservedAttributes -from dynamodb_encryption_sdk.internal.validators import dictionary_validator, iterable_validator -from .identifiers import CryptoAction -__all__ = ('EncryptionContext', 'AttributeActions', 'TableIndex', 'TableInfo') +__all__ = ("EncryptionContext", "AttributeActions", "TableIndex", "TableInfo") def _validate_attribute_values_are_ddb_items(instance, attribute, value): # pylint: disable=unused-argument @@ -57,37 +59,31 @@ class EncryptionContext(object): """ table_name = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), - default=None + validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), default=None ) partition_key_name = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), - default=None + validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), default=None ) sort_key_name = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), - default=None + validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), default=None ) attributes = attr.ib( - validator=( - dictionary_validator(six.string_types, dict), - _validate_attribute_values_are_ddb_items - ), - default=attr.Factory(dict) + validator=(dictionary_validator(six.string_types, dict), _validate_attribute_values_are_ddb_items), + default=attr.Factory(dict), ) material_description = attr.ib( validator=dictionary_validator(six.string_types, six.string_types), converter=copy.deepcopy, - default=attr.Factory(dict) + default=attr.Factory(dict), ) def __init__( - self, - table_name=None, # type: Optional[Text] - partition_key_name=None, # type: Optional[Text] - sort_key_name=None, # type: Optional[Text] - attributes=None, # type: Optional[Dict[Text, Dict]] - material_description=None # type: Optional[Dict[Text, Text]] + self, + table_name=None, # type: Optional[Text] + partition_key_name=None, # type: Optional[Text] + sort_key_name=None, # type: Optional[Text] + attributes=None, # type: Optional[Dict[Text, Dict]] + material_description=None, # type: Optional[Dict[Text, Text]] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -115,19 +111,15 @@ class AttributeActions(object): :param dict attribute_actions: Dictionary mapping attribute names to specific actions """ - default_action = attr.ib( - validator=attr.validators.instance_of(CryptoAction), - default=CryptoAction.ENCRYPT_AND_SIGN - ) + default_action = attr.ib(validator=attr.validators.instance_of(CryptoAction), default=CryptoAction.ENCRYPT_AND_SIGN) attribute_actions = attr.ib( - validator=dictionary_validator(six.string_types, CryptoAction), - default=attr.Factory(dict) + validator=dictionary_validator(six.string_types, CryptoAction), default=attr.Factory(dict) ) def __init__( - self, - default_action=CryptoAction.ENCRYPT_AND_SIGN, # type: Optional[CryptoAction] - attribute_actions=None # type: Optional[Dict[Text, CryptoAction]] + self, + default_action=CryptoAction.ENCRYPT_AND_SIGN, # type: Optional[CryptoAction] + attribute_actions=None, # type: Optional[Dict[Text, CryptoAction]] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -166,10 +158,7 @@ def action(self, attribute_name): def copy(self): # () -> AttributeActions """Return a new copy of this object.""" - return AttributeActions( - default_action=self.default_action, - attribute_actions=self.attribute_actions.copy() - ) + return AttributeActions(default_action=self.default_action, attribute_actions=self.attribute_actions.copy()) def set_index_keys(self, *keys): """Set the appropriate action for the specified indexed attribute names. @@ -218,10 +207,7 @@ def __add__(self, other): attribute_actions = {} for attribute in all_attributes: attribute_actions[attribute] = max(self.action(attribute), other.action(attribute)) - return AttributeActions( - default_action=default_action, - attribute_actions=attribute_actions - ) + return AttributeActions(default_action=default_action, attribute_actions=attribute_actions) @attr.s(init=False) @@ -234,16 +220,9 @@ class TableIndex(object): """ partition = attr.ib(validator=attr.validators.instance_of(six.string_types)) - sort = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), - default=None - ) + sort = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(six.string_types)), default=None) - def __init__( - self, - partition, # type: Text - sort=None # type: Optional[Text] - ): # noqa=D107 + def __init__(self, partition, sort=None): # type: Text # type: Optional[Text] # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 @@ -277,14 +256,8 @@ def from_key_schema(cls, key_schema): :returns: New TableIndex that describes the provided schema :rtype: TableIndex """ - index = { - key['KeyType']: key['AttributeName'] - for key in key_schema - } - return cls( - partition=index['HASH'], - sort=index.get('RANGE', None) - ) + index = {key["KeyType"]: key["AttributeName"] for key in key_schema} + return cls(partition=index["HASH"], sort=index.get("RANGE", None)) @attr.s(init=False) @@ -299,20 +272,14 @@ class TableInfo(object): """ name = attr.ib(validator=attr.validators.instance_of(six.string_types)) - _primary_index = attr.ib( - validator=attr.validators.optional(attr.validators.instance_of(TableIndex)), - default=None - ) - _secondary_indexes = attr.ib( - validator=attr.validators.optional(iterable_validator(list, TableIndex)), - default=None - ) + _primary_index = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(TableIndex)), default=None) + _secondary_indexes = attr.ib(validator=attr.validators.optional(iterable_validator(list, TableIndex)), default=None) def __init__( - self, - name, # type: Text - primary_index=None, # type: Optional[TableIndex] - secondary_indexes=None # type: Optional[List[TableIndex]] + self, + name, # type: Text + primary_index=None, # type: Optional[TableIndex] + secondary_indexes=None, # type: Optional[List[TableIndex]] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. @@ -333,7 +300,7 @@ def primary_index(self): :raises AttributeError: if primary index is unknown """ if self._primary_index is None: - raise AttributeError('Indexes unknown. Run refresh_indexed_attributes') + raise AttributeError("Indexes unknown. Run refresh_indexed_attributes") return self._primary_index @property @@ -346,7 +313,7 @@ def secondary_indexes(self): :raises AttributeError: if secondary indexes are unknown """ if self._secondary_indexes is None: - raise AttributeError('Indexes unknown. Run refresh_indexed_attributes') + raise AttributeError("Indexes unknown. Run refresh_indexed_attributes") return self._secondary_indexes def protected_index_keys(self): @@ -361,12 +328,11 @@ def encryption_context_values(self): :rtype: dict """ - values = {'table_name': self.name} + values = {"table_name": self.name} if self.primary_index is not None: - values.update({ - 'partition_key_name': self.primary_index.partition, - 'sort_key_name': self.primary_index.sort - }) + values.update( + {"partition_key_name": self.primary_index.partition, "sort_key_name": self.primary_index.sort} + ) return values def refresh_indexed_attributes(self, client): @@ -375,13 +341,13 @@ def refresh_indexed_attributes(self, client): :param client: Pre-configured boto3 DynamoDB client :type client: botocore.client.BaseClient """ - table = client.describe_table(TableName=self.name)['Table'] - self._primary_index = TableIndex.from_key_schema(table['KeySchema']) + table = client.describe_table(TableName=self.name)["Table"] + self._primary_index = TableIndex.from_key_schema(table["KeySchema"]) self._secondary_indexes = [] - for group in ('LocalSecondaryIndexes', 'GlobalSecondaryIndexes'): + for group in ("LocalSecondaryIndexes", "GlobalSecondaryIndexes"): try: for index in table[group]: - self._secondary_indexes.append(TableIndex.from_key_schema(index['KeySchema'])) + self._secondary_indexes.append(TableIndex.from_key_schema(index["KeySchema"])) except KeyError: pass # Not all tables will have secondary indexes. diff --git a/src/dynamodb_encryption_sdk/transform.py b/src/dynamodb_encryption_sdk/transform.py index 8541639a..76b22c6c 100644 --- a/src/dynamodb_encryption_sdk/transform.py +++ b/src/dynamodb_encryption_sdk/transform.py @@ -19,7 +19,7 @@ from boto3.dynamodb.types import TypeDeserializer, TypeSerializer -__all__ = ('dict_to_ddb', 'ddb_to_dict') +__all__ = ("dict_to_ddb", "ddb_to_dict") def dict_to_ddb(item): @@ -32,11 +32,7 @@ def dict_to_ddb(item): :rtype: dict """ serializer = TypeSerializer() - return { - key: serializer.serialize(value) - for key, value - in item.items() - } + return {key: serializer.serialize(value) for key, value in item.items()} def ddb_to_dict(item): @@ -49,8 +45,4 @@ def ddb_to_dict(item): :rtype: dict """ deserializer = TypeDeserializer() - return { - key: deserializer.deserialize(value) - for key, value - in item.items() - } + return {key: deserializer.deserialize(value) for key, value in item.items()} diff --git a/src/pylintrc b/src/pylintrc index c45b647c..81cbacba 100644 --- a/src/pylintrc +++ b/src/pylintrc @@ -2,8 +2,11 @@ # Disabling messages that we either don't care about # for tests or are necessary to break for tests. # +# C0330 : bad-continuation (we let black handle this) +# C0412 : ungrouped-imports (we let isort handle this) +# R0205 : useless-object-inheritance (we need to support Python 2, so no, not useless) # R0801 : duplicate-code (causes lots of problems with implementations of common interfaces) -disable = R0801 +disable = C0330, C0412, R0205, R0801 [BASIC] # Allow function names up to 50 characters diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..2f0470af --- /dev/null +++ b/test/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2018 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. +"""Stub to allow relative imports between test groups.""" diff --git a/test/acceptance/acceptance_test_utils.py b/test/acceptance/acceptance_test_utils.py index 8f1c1ee8..5d66bc65 100644 --- a/test/acceptance/acceptance_test_utils.py +++ b/test/acceptance/acceptance_test_utils.py @@ -12,11 +12,10 @@ # language governing permissions and limitations under the License. """Helper tools for use with acceptance tests.""" import base64 -from collections import defaultdict -from functools import partial import json import os -import sys +from collections import defaultdict +from functools import partial import boto3 import pytest @@ -32,30 +31,20 @@ from dynamodb_encryption_sdk.materials.raw import RawDecryptionMaterials from dynamodb_encryption_sdk.structures import AttributeActions -sys.path.append(os.path.join( - os.path.abspath(os.path.dirname(__file__)), - '..', - 'functional' -)) - -# Convenience imports -import functional_test_vector_generators # noqa: E402,I100 pylint: disable=import-error,wrong-import-position +from ..functional import functional_test_vector_generators _ENCRYPTED_ITEM_VECTORS_DIR = os.path.join( - os.path.abspath(os.path.dirname(__file__)), - '..', - 'vectors', - 'encrypted_item' + os.path.abspath(os.path.dirname(__file__)), "..", "vectors", "encrypted_item" ) -_SCENARIO_FILE = os.path.join(_ENCRYPTED_ITEM_VECTORS_DIR, 'scenarios.json') +_SCENARIO_FILE = os.path.join(_ENCRYPTED_ITEM_VECTORS_DIR, "scenarios.json") def _filename_from_uri(uri): parsed = urlparse(uri) - if parsed.scheme != 'file': + if parsed.scheme != "file": raise ValueError('Unsupported URI scheme: "{}"'.format(parsed.scheme)) relative_path = [parsed.netloc] - for part in parsed.path.split('/'): + for part in parsed.path.split("/"): if part: relative_path.append(part) return os.path.join(_ENCRYPTED_ITEM_VECTORS_DIR, *relative_path) @@ -76,38 +65,28 @@ def _build_plaintext_items(plaintext_file, version): plaintext_data = json.load(f) actions = {} - for name, description in plaintext_data['actions'].items(): - default_action = _action(description['default']) + for name, description in plaintext_data["actions"].items(): + default_action = _action(description["default"]) attribute_actions = { attribute_name: _action(attribute_action) - for attribute_name, attribute_action - in description.get('override', {}).items() + for attribute_name, attribute_action in description.get("override", {}).items() } - actions[name.lower()] = AttributeActions( - default_action=default_action, - attribute_actions=attribute_actions - ) + actions[name.lower()] = AttributeActions(default_action=default_action, attribute_actions=attribute_actions) tables = defaultdict(list) - for table_name, table_data in plaintext_data['items'].items(): + for table_name, table_data in plaintext_data["items"].items(): table_items = [] - for item in table_data['items']: - item_actions = actions[item['action']].copy() - item_actions.set_index_keys(*table_data['index'].values()) - attributes = item['attributes'].copy() - if not item.get('exact', False): - for group in plaintext_data['versions'].get(table_name, {}).get(version, []): - attributes.update(plaintext_data['attributes'][group]) + for item in table_data["items"]: + item_actions = actions[item["action"]].copy() + item_actions.set_index_keys(*table_data["index"].values()) + attributes = item["attributes"].copy() + if not item.get("exact", False): + for group in plaintext_data["versions"].get(table_name, {}).get(version, []): + attributes.update(plaintext_data["attributes"][group]) _decode_item(attributes) - table_items.append(dict( - item=attributes, - action=item_actions - )) - - tables[table_name] = dict( - index=table_data['index'], - items=table_items - ) + table_items.append(dict(item=attributes, action=item_actions)) + + tables[table_name] = dict(index=table_data["index"], items=table_items) return tables @@ -129,55 +108,43 @@ def _load_keys(keys_file): _KEY_TYPE = { - 'SYMMETRIC': EncryptionKeyType.SYMMETRIC, - 'PUBLIC': EncryptionKeyType.PUBLIC, - 'PRIVATE': EncryptionKeyType.PRIVATE -} -_KEY_ENCODING = { - 'RAW': KeyEncodingType.RAW, - 'DER': KeyEncodingType.DER + "SYMMETRIC": EncryptionKeyType.SYMMETRIC, + "PUBLIC": EncryptionKeyType.PUBLIC, + "PRIVATE": EncryptionKeyType.PRIVATE, } +_KEY_ENCODING = {"RAW": KeyEncodingType.RAW, "DER": KeyEncodingType.DER} def _load_key(key): - key_material = base64.b64decode(key['material']) - key_type = _KEY_TYPE[key['type'].upper()] - key_encoding = _KEY_ENCODING[key['encoding'].upper()] + key_material = base64.b64decode(key["material"]) + key_type = _KEY_TYPE[key["type"].upper()] + key_encoding = _KEY_ENCODING[key["encoding"].upper()] return JceNameLocalDelegatedKey( - key=key_material, - algorithm=key['algorithm'], - key_type=key_type, - key_encoding=key_encoding + key=key_material, algorithm=key["algorithm"], key_type=key_type, key_encoding=key_encoding ) def _load_signing_key(key): - if key['type'].upper() == 'RSA': - key['type'] = 'RSA' + if key["type"].upper() == "RSA": + key["type"] = "RSA" return _load_key(key) def _build_static_cmp(decrypt_key, verify_key): decryption_key = _load_key(decrypt_key) verification_key = _load_signing_key(verify_key) - decryption_materials = RawDecryptionMaterials( - decryption_key=decryption_key, - verification_key=verification_key - ) + decryption_materials = RawDecryptionMaterials(decryption_key=decryption_key, verification_key=verification_key) return StaticCryptographicMaterialsProvider(decryption_materials=decryption_materials) def _build_wrapped_cmp(decrypt_key, verify_key): unwrapping_key = _load_key(decrypt_key) signing_key = _load_signing_key(verify_key) - return WrappedCryptographicMaterialsProvider( - signing_key=signing_key, - unwrapping_key=unwrapping_key - ) + return WrappedCryptographicMaterialsProvider(signing_key=signing_key, unwrapping_key=unwrapping_key) def _build_aws_kms_cmp(decrypt_key, verify_key): - key_id = decrypt_key['keyId'] + key_id = decrypt_key["keyId"] return AwsKmsCryptographicMaterialsProvider(key_id=key_id) @@ -185,7 +152,7 @@ def _meta_table_prep(table_name, items_filename): if table_name is None: return - client = boto3.client('dynamodb', region_name='us-west-2') + client = boto3.client("dynamodb", region_name="us-west-2") MetaStore.create_table(client, table_name, 100, 100) with open(_filename_from_uri(items_filename)) as f: @@ -196,49 +163,47 @@ def _meta_table_prep(table_name, items_filename): requests = [] for item in items: _decode_item(item) - requests.append({'PutRequest': {'Item': item}}) + requests.append({"PutRequest": {"Item": item}}) request_items[table_name] = requests client.batch_write_item(RequestItems=request_items) def _build_most_recent_cmp(scenario, keys): - table = boto3.resource('dynamodb', region_name='us-west-2').Table(scenario['metastore']['table_name']) - meta_cmp, _, _ = _build_cmp(scenario['metastore'], keys) + table = boto3.resource("dynamodb", region_name="us-west-2").Table(scenario["metastore"]["table_name"]) + meta_cmp, _, _ = _build_cmp(scenario["metastore"], keys) metastore = MetaStore(table=table, materials_provider=meta_cmp()) most_recent_cmp = MostRecentProvider( - provider_store=metastore, - material_name=scenario['material_name'], - version_ttl=600.0 + provider_store=metastore, material_name=scenario["material_name"], version_ttl=600.0 ) return most_recent_cmp _CMP_TYPE_MAP = { - 'STATIC': _build_static_cmp, - 'WRAPPED': _build_wrapped_cmp, - 'AWSKMS': _build_aws_kms_cmp, - 'MOST_RECENT': _build_most_recent_cmp + "STATIC": _build_static_cmp, + "WRAPPED": _build_wrapped_cmp, + "AWSKMS": _build_aws_kms_cmp, + "MOST_RECENT": _build_most_recent_cmp, } def _build_cmp(scenario, keys): try: - cmp_builder = _CMP_TYPE_MAP[scenario['provider'].upper()] + cmp_builder = _CMP_TYPE_MAP[scenario["provider"].upper()] except KeyError: - raise ValueError('Unsupported cryptographic materials provider type: "{}"'.format(scenario['provider'])) + raise ValueError('Unsupported cryptographic materials provider type: "{}"'.format(scenario["provider"])) if cmp_builder is _build_most_recent_cmp: return ( partial(cmp_builder, scenario, keys), - scenario['metastore']['keys']['decrypt'], - scenario['metastore']['keys']['verify'] + scenario["metastore"]["keys"]["decrypt"], + scenario["metastore"]["keys"]["verify"], ) return ( - partial(cmp_builder, keys[scenario['keys']['decrypt']], keys[scenario['keys']['verify']]), - scenario['keys']['decrypt'], - scenario['keys']['verify'] + partial(cmp_builder, keys[scenario["keys"]["decrypt"]], keys[scenario["keys"]["verify"]]), + scenario["keys"]["decrypt"], + scenario["keys"]["verify"], ) @@ -248,13 +213,13 @@ def _index(item, keys): def _expand_items(ciphertext_items, plaintext_items): for table_name, table_items in ciphertext_items.items(): - table_index = plaintext_items[table_name]['index'] + table_index = plaintext_items[table_name]["index"] for ciphertext_item in table_items: - ct_index = _index(ciphertext_item, plaintext_items[table_name]['index'].values()) + ct_index = _index(ciphertext_item, plaintext_items[table_name]["index"].values()) pt_items = [ - item for item - in plaintext_items[table_name]['items'] - if ct_index == _index(item['item'], plaintext_items[table_name]['index'].values()) + item + for item in plaintext_items[table_name]["items"] + if ct_index == _index(item["item"], plaintext_items[table_name]["index"].values()) ] if not pt_items: continue @@ -263,30 +228,30 @@ def _expand_items(ciphertext_items, plaintext_items): raise Exception('TODO: Ciphertext matches multiple plaintext items: "{}"'.format(ct_index)) pt_item = pt_items[0] - yield table_name, table_index, ciphertext_item, pt_item['item'], pt_item['action'] + yield table_name, table_index, ciphertext_item, pt_item["item"], pt_item["action"] def load_scenarios(online): # pylint: disable=too-many-locals with open(_SCENARIO_FILE) as f: scenarios = json.load(f) - keys_file = _filename_from_uri(scenarios['keys']) + keys_file = _filename_from_uri(scenarios["keys"]) keys = _load_keys(keys_file) - for scenario in scenarios['scenarios']: - if (not online and scenario['network']) or (online and not scenario['network']): + for scenario in scenarios["scenarios"]: + if (not online and scenario["network"]) or (online and not scenario["network"]): continue - plaintext_file = _filename_from_uri(scenario['plaintext']) - plaintext_items = _build_plaintext_items(plaintext_file, scenario['version']) + plaintext_file = _filename_from_uri(scenario["plaintext"]) + plaintext_items = _build_plaintext_items(plaintext_file, scenario["version"]) - ciphertext_file = _filename_from_uri(scenario['ciphertext']) + ciphertext_file = _filename_from_uri(scenario["ciphertext"]) ciphertext_items = _load_ciphertext_items(ciphertext_file) materials_provider, decrypt_key_name, verify_key_name = _build_cmp(scenario, keys) items = _expand_items(ciphertext_items, plaintext_items) - metastore_info = scenario.get('metastore', {'table_name': None, 'ciphertext': None}) + metastore_info = scenario.get("metastore", {"table_name": None, "ciphertext": None}) for table_name, table_index, ciphertext_item, plaintext_item, attribute_actions in items: item_index = _index(ciphertext_item, table_index.values()) @@ -297,13 +262,13 @@ def load_scenarios(online): ciphertext_item, plaintext_item, attribute_actions, - partial(_meta_table_prep, metastore_info['table_name'], metastore_info['ciphertext']), - id='{version}-{provider}-{decrypt_key}-{verify_key}-{table}-{index}'.format( - version=scenario['version'], - provider=scenario['provider'], + partial(_meta_table_prep, metastore_info["table_name"], metastore_info["ciphertext"]), + id="{version}-{provider}-{decrypt_key}-{verify_key}-{table}-{index}".format( + version=scenario["version"], + provider=scenario["provider"], decrypt_key=decrypt_key_name, verify_key=verify_key_name, table=table_name, - index=str(item_index) - ) + index=str(item_index), + ), ) diff --git a/test/acceptance/encrypted/test_client.py b/test/acceptance/encrypted/test_client.py index 46b7ae7b..493740a3 100644 --- a/test/acceptance/encrypted/test_client.py +++ b/test/acceptance/encrypted/test_client.py @@ -12,13 +12,14 @@ # language governing permissions and limitations under the License. """Acceptance tests for ``dynamodb_encryption_sdk.encrypted.client``.""" import botocore +import pytest from mock import MagicMock from moto import mock_dynamodb2 -import pytest from dynamodb_encryption_sdk.encrypted.client import EncryptedClient from dynamodb_encryption_sdk.structures import TableIndex, TableInfo from dynamodb_encryption_sdk.transform import dict_to_ddb + from ..acceptance_test_utils import load_scenarios pytestmark = [pytest.mark.accept] @@ -26,150 +27,87 @@ def fake_client(table_name, item): client = MagicMock(__class__=botocore.client.BaseClient) - client.get_item.return_value = {'Item': item.copy()} - client.batch_get_item.return_value = {'Responses': {table_name: [item.copy()]}} + client.get_item.return_value = {"Item": item.copy()} + client.batch_get_item.return_value = {"Responses": {table_name: [item.copy()]}} return client def _compare_item(plaintext_item, decrypted_item): assert set(decrypted_item.keys()) == set(plaintext_item.keys()) for key in decrypted_item: - if key == 'version': + if key == "version": continue assert decrypted_item[key] == plaintext_item[key] -def _client_setup( - materials_provider, - table_name, - table_index, - ciphertext_item, - attribute_actions, - prep -): +def _client_setup(materials_provider, table_name, table_index, ciphertext_item, attribute_actions, prep): prep() # Test scenario setup that needs to happen inside the test cmp = materials_provider() # Some of the materials providers need to be constructed inside the test client = fake_client(table_name, ciphertext_item) table_info = TableInfo( name=table_name, - primary_index=TableIndex( - partition=table_index['partition'], - sort=table_index.get('sort', None) - ) + primary_index=TableIndex(partition=table_index["partition"], sort=table_index.get("sort", None)), ) item_key = {table_info.primary_index.partition: ciphertext_item[table_info.primary_index.partition]} if table_info.primary_index.sort is not None: item_key[table_info.primary_index.sort] = ciphertext_item[table_info.primary_index.sort] e_client = EncryptedClient( - client=client, - materials_provider=cmp, - attribute_actions=attribute_actions, - auto_refresh_table_indexes=False + client=client, materials_provider=cmp, attribute_actions=attribute_actions, auto_refresh_table_indexes=False ) e_client._table_info_cache._all_tables_info[table_name] = table_info return e_client, dict_to_ddb(item_key) -def _item_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep -): +def _item_check(materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep): e_client, item_key = _client_setup( - materials_provider, - table_name, - table_index, - ciphertext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, attribute_actions, prep ) - decrypted_item = e_client.get_item(TableName=table_name, Key=item_key)['Item'] + decrypted_item = e_client.get_item(TableName=table_name, Key=item_key)["Item"] _compare_item(plaintext_item, decrypted_item) def _batch_items_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ): e_client, item_key = _client_setup( - materials_provider, - table_name, - table_index, - ciphertext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, attribute_actions, prep ) - response = e_client.batch_get_item(RequestItems={table_name: {'Keys': [item_key]}}) - decrypted_item = response['Responses'][table_name][0] + response = e_client.batch_get_item(RequestItems={table_name: {"Keys": [item_key]}}) + decrypted_item = response["Responses"][table_name][0] _compare_item(plaintext_item, decrypted_item) def pytest_generate_tests(metafunc): - if 'item_checker' in metafunc.fixturenames: - metafunc.parametrize('item_checker', ( - pytest.param(_item_check, id='single-item'), - pytest.param(_batch_items_check, id='batch-items') - )) + if "item_checker" in metafunc.fixturenames: + metafunc.parametrize( + "item_checker", + (pytest.param(_item_check, id="single-item"), pytest.param(_batch_items_check, id="batch-items")), + ) @mock_dynamodb2 @pytest.mark.local @pytest.mark.parametrize( - 'materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep', - load_scenarios(online=False) + "materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep", + load_scenarios(online=False), ) def test_client_get_offline( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep, - item_checker + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep, item_checker ): return item_checker( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ) @pytest.mark.integ @pytest.mark.parametrize( - 'materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep', - load_scenarios(online=True) + "materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep", + load_scenarios(online=True), ) def test_client_get_online( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep, - item_checker + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep, item_checker ): return item_checker( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ) diff --git a/test/acceptance/encrypted/test_item.py b/test/acceptance/encrypted/test_item.py index 2fe3a745..b7e1b85e 100644 --- a/test/acceptance/encrypted/test_item.py +++ b/test/acceptance/encrypted/test_item.py @@ -11,43 +11,34 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Acceptance tests for ``dynamodb_encryption_sdk.encrypted.item``.""" -from moto import mock_dynamodb2 import pytest +from moto import mock_dynamodb2 from dynamodb_encryption_sdk.encrypted import CryptoConfig from dynamodb_encryption_sdk.encrypted.item import decrypt_dynamodb_item from dynamodb_encryption_sdk.structures import EncryptionContext + from ..acceptance_test_utils import load_scenarios pytestmark = [pytest.mark.accept] -def _item_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep -): +def _item_check(materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep): prep() # Test scenario setup that needs to happen inside the test cmp = materials_provider() # Some of the materials providers need to be constructed inside the test encryption_context = EncryptionContext( table_name=table_name, - partition_key_name=table_index['partition'], - sort_key_name=table_index.get('sort', None), - attributes=ciphertext_item + partition_key_name=table_index["partition"], + sort_key_name=table_index.get("sort", None), + attributes=ciphertext_item, ) crypto_config = CryptoConfig( - materials_provider=cmp, - encryption_context=encryption_context, - attribute_actions=attribute_actions + materials_provider=cmp, encryption_context=encryption_context, attribute_actions=attribute_actions ) decrypted_item = decrypt_dynamodb_item(ciphertext_item.copy(), crypto_config) assert set(decrypted_item.keys()) == set(plaintext_item.keys()) for key in decrypted_item: - if key == 'version': + if key == "version": continue assert decrypted_item[key] == plaintext_item[key] @@ -55,49 +46,25 @@ def _item_check( @mock_dynamodb2 @pytest.mark.local @pytest.mark.parametrize( - 'materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep', - load_scenarios(online=False) + "materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep", + load_scenarios(online=False), ) def test_item_encryptor_offline( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ): return _item_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ) @pytest.mark.integ @pytest.mark.parametrize( - 'materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep', - load_scenarios(online=True) + "materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep", + load_scenarios(online=True), ) def test_item_encryptor_online( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ): return _item_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ) diff --git a/test/acceptance/encrypted/test_resource.py b/test/acceptance/encrypted/test_resource.py index b88c01ea..17da5317 100644 --- a/test/acceptance/encrypted/test_resource.py +++ b/test/acceptance/encrypted/test_resource.py @@ -11,16 +11,17 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Acceptance tests for ``dynamodb_encryption_sdk.encrypted.resource``.""" +import botocore +import pytest from boto3.resources.base import ServiceResource from boto3.resources.collection import CollectionManager -import botocore from mock import MagicMock from moto import mock_dynamodb2 -import pytest from dynamodb_encryption_sdk.encrypted.resource import EncryptedResource from dynamodb_encryption_sdk.structures import TableIndex, TableInfo from dynamodb_encryption_sdk.transform import ddb_to_dict + from ..acceptance_test_utils import load_scenarios pytestmark = [pytest.mark.accept] @@ -28,7 +29,7 @@ def fake_resource(table_name, item): resource = MagicMock(__class__=ServiceResource) - resource.batch_get_item.return_value = {'Responses': {table_name: [item.copy()]}} + resource.batch_get_item.return_value = {"Responses": {table_name: [item.copy()]}} resource.meta.client = MagicMock(__class__=botocore.client.BaseClient) resource.tables = MagicMock(__class__=CollectionManager) return resource @@ -37,113 +38,65 @@ def fake_resource(table_name, item): def _compare_item(plaintext_item, decrypted_item): assert set(decrypted_item.keys()) == set(plaintext_item.keys()) for key in decrypted_item: - if key == 'version': + if key == "version": continue assert decrypted_item[key] == plaintext_item[key] -def _resource_setup( - materials_provider, - table_name, - table_index, - ciphertext_item, - attribute_actions, - prep -): +def _resource_setup(materials_provider, table_name, table_index, ciphertext_item, attribute_actions, prep): prep() # Test scenario setup that needs to happen inside the test cmp = materials_provider() # Some of the materials providers need to be constructed inside the test resource = fake_resource(table_name, ciphertext_item) table_info = TableInfo( name=table_name, - primary_index=TableIndex( - partition=table_index['partition'], - sort=table_index.get('sort', None) - ) + primary_index=TableIndex(partition=table_index["partition"], sort=table_index.get("sort", None)), ) item_key = {table_info.primary_index.partition: ciphertext_item[table_info.primary_index.partition]} if table_info.primary_index.sort is not None: item_key[table_info.primary_index.sort] = ciphertext_item[table_info.primary_index.sort] e_resource = EncryptedResource( - resource=resource, - materials_provider=cmp, - attribute_actions=attribute_actions, - auto_refresh_table_indexes=False + resource=resource, materials_provider=cmp, attribute_actions=attribute_actions, auto_refresh_table_indexes=False ) e_resource._table_info_cache._all_tables_info[table_name] = table_info return e_resource, item_key def _batch_items_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ): plaintext_item = ddb_to_dict(plaintext_item) ciphertext_item = ddb_to_dict(ciphertext_item) e_resource, item_key = _resource_setup( - materials_provider, - table_name, - table_index, - ciphertext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, attribute_actions, prep ) - response = e_resource.batch_get_item(RequestItems={table_name: {'Keys': [item_key]}}) - decrypted_item = response['Responses'][table_name][0] + response = e_resource.batch_get_item(RequestItems={table_name: {"Keys": [item_key]}}) + decrypted_item = response["Responses"][table_name][0] _compare_item(plaintext_item, decrypted_item) @mock_dynamodb2 @pytest.mark.local @pytest.mark.parametrize( - 'materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep', - load_scenarios(online=False) + "materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep", + load_scenarios(online=False), ) def test_client_get_offline( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ): return _batch_items_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ) @pytest.mark.integ @pytest.mark.parametrize( - 'materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep', - load_scenarios(online=True) + "materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep", + load_scenarios(online=True), ) def test_client_get_online( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ): return _batch_items_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ) diff --git a/test/acceptance/encrypted/test_table.py b/test/acceptance/encrypted/test_table.py index 9454cf25..91adc9b5 100644 --- a/test/acceptance/encrypted/test_table.py +++ b/test/acceptance/encrypted/test_table.py @@ -11,14 +11,15 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Acceptance tests for ``dynamodb_encryption_sdk.encrypted.table``.""" +import pytest from boto3.resources.base import ServiceResource from mock import MagicMock from moto import mock_dynamodb2 -import pytest from dynamodb_encryption_sdk.encrypted.table import EncryptedTable from dynamodb_encryption_sdk.structures import TableIndex, TableInfo from dynamodb_encryption_sdk.transform import ddb_to_dict + from ..acceptance_test_utils import load_scenarios pytestmark = [pytest.mark.accept] @@ -26,19 +27,11 @@ def fake_table(item): table = MagicMock(__class__=ServiceResource) - table.get_item.return_value = {'Item': item} + table.get_item.return_value = {"Item": item} return table -def _item_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep -): +def _item_check(materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep): ciphertext_item = ddb_to_dict(ciphertext_item) plaintext_item = ddb_to_dict(plaintext_item) prep() # Test scenario setup that needs to happen inside the test @@ -46,10 +39,7 @@ def _item_check( table = fake_table(ciphertext_item) table_info = TableInfo( name=table_name, - primary_index=TableIndex( - partition=table_index['partition'], - sort=table_index.get('sort', None) - ) + primary_index=TableIndex(partition=table_index["partition"], sort=table_index.get("sort", None)), ) item_key = {table_info.primary_index.partition: ciphertext_item[table_info.primary_index.partition]} if table_info.primary_index.sort is not None: @@ -60,12 +50,12 @@ def _item_check( materials_provider=cmp, table_info=table_info, attribute_actions=attribute_actions, - auto_refresh_table_indexes=False + auto_refresh_table_indexes=False, ) - decrypted_item = e_table.get_item(Key=item_key)['Item'] + decrypted_item = e_table.get_item(Key=item_key)["Item"] assert set(decrypted_item.keys()) == set(plaintext_item.keys()) for key in decrypted_item: - if key == 'version': + if key == "version": continue assert decrypted_item[key] == plaintext_item[key] @@ -73,49 +63,25 @@ def _item_check( @mock_dynamodb2 @pytest.mark.local @pytest.mark.parametrize( - 'materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep', - load_scenarios(online=False) + "materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep", + load_scenarios(online=False), ) def test_table_get_offline( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ): return _item_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ) @pytest.mark.integ @pytest.mark.parametrize( - 'materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep', - load_scenarios(online=True) + "materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep", + load_scenarios(online=True), ) def test_table_get_online( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ): return _item_check( - materials_provider, - table_name, - table_index, - ciphertext_item, - plaintext_item, - attribute_actions, - prep + materials_provider, table_name, table_index, ciphertext_item, plaintext_item, attribute_actions, prep ) diff --git a/test/functional/delegated_keys/test_jce.py b/test/functional/delegated_keys/test_jce.py index 2c88902e..a1e25d80 100644 --- a/test/functional/delegated_keys/test_jce.py +++ b/test/functional/delegated_keys/test_jce.py @@ -11,9 +11,9 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Functional test suite for ``dynamodb_encryption_sdk.delegated_keys.jce``.""" +import pytest from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization -import pytest from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey @@ -29,19 +29,22 @@ def _find_rsa_key_length(key): return loaded_key._key_size -@pytest.mark.parametrize('algorithm, requested_bits, expected_bits, length_finder', ( - ('AES', 256, 256, _find_aes_key_length), - ('AESWrap', 256, 256, _find_aes_key_length), - ('RSA', 4096, 4096, _find_rsa_key_length), - ('HmacSHA512', 256, 256, _find_aes_key_length), - ('HmacSHA256', 256, 256, _find_aes_key_length), - ('HmacSHA384', 256, 256, _find_aes_key_length), - ('HmacSHA224', 256, 256, _find_aes_key_length), - ('SHA512withRSA', 4096, 4096, _find_rsa_key_length), - ('SHA256withRSA', 4096, 4096, _find_rsa_key_length), - ('SHA384withRSA', 4096, 4096, _find_rsa_key_length), - ('SHA224withRSA', 4096, 4096, _find_rsa_key_length) -)) +@pytest.mark.parametrize( + "algorithm, requested_bits, expected_bits, length_finder", + ( + ("AES", 256, 256, _find_aes_key_length), + ("AESWrap", 256, 256, _find_aes_key_length), + ("RSA", 4096, 4096, _find_rsa_key_length), + ("HmacSHA512", 256, 256, _find_aes_key_length), + ("HmacSHA256", 256, 256, _find_aes_key_length), + ("HmacSHA384", 256, 256, _find_aes_key_length), + ("HmacSHA224", 256, 256, _find_aes_key_length), + ("SHA512withRSA", 4096, 4096, _find_rsa_key_length), + ("SHA256withRSA", 4096, 4096, _find_rsa_key_length), + ("SHA384withRSA", 4096, 4096, _find_rsa_key_length), + ("SHA224withRSA", 4096, 4096, _find_rsa_key_length), + ), +) def test_generate_correct_key_length(algorithm, requested_bits, expected_bits, length_finder): test = JceNameLocalDelegatedKey.generate(algorithm, requested_bits) diff --git a/test/functional/encrypted/test_client.py b/test/functional/encrypted/test_client.py index fca79487..f7a76b56 100644 --- a/test/functional/encrypted/test_client.py +++ b/test/functional/encrypted/test_client.py @@ -14,13 +14,17 @@ import hypothesis import pytest +from ..functional_test_utils import example_table # noqa pylint: disable=unused-import from ..functional_test_utils import ( - client_cycle_batch_items_check, client_cycle_batch_items_check_paginators, client_cycle_single_item_check, - set_parametrized_actions, set_parametrized_cmp, set_parametrized_item, - TEST_TABLE_NAME + TEST_TABLE_NAME, + client_cycle_batch_items_check, + client_cycle_batch_items_check_paginators, + client_cycle_single_item_check, + set_parametrized_actions, + set_parametrized_cmp, + set_parametrized_item, ) -from ..functional_test_utils import example_table # noqa pylint: disable=unused-import -from ..hypothesis_strategies import ddb_items, SLOW_SETTINGS, VERY_SLOW_SETTINGS +from ..hypothesis_strategies import SLOW_SETTINGS, VERY_SLOW_SETTINGS, ddb_items pytestmark = [pytest.mark.functional, pytest.mark.local] @@ -33,31 +37,19 @@ def pytest_generate_tests(metafunc): def _client_cycle_single_item_check(materials_provider, initial_actions, initial_item): return client_cycle_single_item_check( - materials_provider, - initial_actions, - initial_item, - TEST_TABLE_NAME, - 'us-west-2' + materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, "us-west-2" ) def _client_cycle_batch_items_check(materials_provider, initial_actions, initial_item): return client_cycle_batch_items_check( - materials_provider, - initial_actions, - initial_item, - TEST_TABLE_NAME, - 'us-west-2' + materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, "us-west-2" ) def _client_cycle_batch_items_check_paginators(materials_provider, initial_actions, initial_item): return client_cycle_batch_items_check_paginators( - materials_provider, - initial_actions, - initial_item, - TEST_TABLE_NAME, - 'us-west-2' + materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, "us-west-2" ) diff --git a/test/functional/encrypted/test_item.py b/test/functional/encrypted/test_item.py index dd475e11..c51e4851 100644 --- a/test/functional/encrypted/test_item.py +++ b/test/functional/encrypted/test_item.py @@ -23,10 +23,15 @@ from dynamodb_encryption_sdk.material_providers.static import StaticCryptographicMaterialsProvider from dynamodb_encryption_sdk.materials.raw import RawDecryptionMaterials, RawEncryptionMaterials from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext + from ..functional_test_utils import ( - build_static_jce_cmp, cycle_item_check, set_parametrized_actions, set_parametrized_cmp, set_parametrized_item + build_static_jce_cmp, + cycle_item_check, + set_parametrized_actions, + set_parametrized_cmp, + set_parametrized_item, ) -from ..hypothesis_strategies import ddb_items, SLOW_SETTINGS, VERY_SLOW_SETTINGS +from ..hypothesis_strategies import SLOW_SETTINGS, VERY_SLOW_SETTINGS, ddb_items pytestmark = [pytest.mark.functional, pytest.mark.local] @@ -40,80 +45,81 @@ def pytest_generate_tests(metafunc): @pytest.fixture def static_cmp_crypto_config(): return CryptoConfig( - materials_provider=build_static_jce_cmp('AES', 256, 'HmacSHA256', 256), + materials_provider=build_static_jce_cmp("AES", 256, "HmacSHA256", 256), encryption_context=EncryptionContext(), - attribute_actions=AttributeActions() + attribute_actions=AttributeActions(), ) def test_unsigned_item(static_cmp_crypto_config): - item = {'test': 'no signature'} + item = {"test": "no signature"} with pytest.raises(DecryptionError) as exc_info: decrypt_python_item(item, static_cmp_crypto_config) - exc_info.match(r'No signature attribute found in item') + exc_info.match(r"No signature attribute found in item") -@pytest.mark.parametrize('item', ( - {reserved.value: 'asdf'} - for reserved in ReservedAttributes -)) +@pytest.mark.parametrize("item", ({reserved.value: "asdf"} for reserved in ReservedAttributes)) def test_reserved_attributes_on_encrypt(static_cmp_crypto_config, item): with pytest.raises(EncryptionError) as exc_info: encrypt_python_item(item, static_cmp_crypto_config) - exc_info.match(r'Reserved attribute name *') + exc_info.match(r"Reserved attribute name *") def test_only_sign_item(parametrized_item): - signing_key = JceNameLocalDelegatedKey.generate('HmacSHA256', 256) + signing_key = JceNameLocalDelegatedKey.generate("HmacSHA256", 256) cmp = StaticCryptographicMaterialsProvider( encryption_materials=RawEncryptionMaterials(signing_key=signing_key), - decryption_materials=RawDecryptionMaterials(verification_key=signing_key) + decryption_materials=RawDecryptionMaterials(verification_key=signing_key), ) actions = AttributeActions(default_action=CryptoAction.SIGN_ONLY) crypto_config = CryptoConfig( - materials_provider=cmp, - encryption_context=EncryptionContext(), - attribute_actions=actions + materials_provider=cmp, encryption_context=EncryptionContext(), attribute_actions=actions ) signed_item = encrypt_python_item(parametrized_item, crypto_config) material_description = signed_item[ReservedAttributes.MATERIAL_DESCRIPTION.value].value - assert MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value.encode('utf-8') not in material_description + assert MaterialDescriptionKeys.ATTRIBUTE_ENCRYPTION_MODE.value.encode("utf-8") not in material_description decrypt_python_item(signed_item, crypto_config) -@pytest.mark.parametrize('actions', ( - AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN), - AttributeActions(default_action=CryptoAction.SIGN_ONLY, attribute_actions={'test': CryptoAction.ENCRYPT_AND_SIGN}), -)) +@pytest.mark.parametrize( + "actions", + ( + AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN), + AttributeActions( + default_action=CryptoAction.SIGN_ONLY, attribute_actions={"test": CryptoAction.ENCRYPT_AND_SIGN} + ), + ), +) def test_no_encryption_key_but_encryption_requested(actions, parametrized_item): - signing_key = JceNameLocalDelegatedKey.generate('HmacSHA256', 256) - cmp = StaticCryptographicMaterialsProvider( - encryption_materials=RawEncryptionMaterials(signing_key=signing_key) - ) + signing_key = JceNameLocalDelegatedKey.generate("HmacSHA256", 256) + cmp = StaticCryptographicMaterialsProvider(encryption_materials=RawEncryptionMaterials(signing_key=signing_key)) crypto_config = CryptoConfig( - materials_provider=cmp, - encryption_context=EncryptionContext(), - attribute_actions=actions + materials_provider=cmp, encryption_context=EncryptionContext(), attribute_actions=actions ) with pytest.raises(EncryptionError) as excinfo: encrypt_python_item(parametrized_item, crypto_config) - excinfo.match('Attribute actions ask for some attributes to be encrypted but no encryption key is available') + excinfo.match("Attribute actions ask for some attributes to be encrypted but no encryption key is available") -@pytest.mark.parametrize('actions', ( - AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN), - AttributeActions(default_action=CryptoAction.SIGN_ONLY, attribute_actions={'test': CryptoAction.ENCRYPT_AND_SIGN}), -)) +@pytest.mark.parametrize( + "actions", + ( + AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN), + AttributeActions( + default_action=CryptoAction.SIGN_ONLY, attribute_actions={"test": CryptoAction.ENCRYPT_AND_SIGN} + ), + ), +) def test_no_decryption_key_but_decryption_requested(actions, parametrized_item): - encryption_key = JceNameLocalDelegatedKey.generate('AES', 256) - signing_key = JceNameLocalDelegatedKey.generate('HmacSHA256', 256) + encryption_key = JceNameLocalDelegatedKey.generate("AES", 256) + signing_key = JceNameLocalDelegatedKey.generate("HmacSHA256", 256) encrypting_cmp = StaticCryptographicMaterialsProvider( encryption_materials=RawEncryptionMaterials(encryption_key=encryption_key, signing_key=signing_key) ) @@ -124,30 +130,26 @@ def test_no_decryption_key_but_decryption_requested(actions, parametrized_item): encrypted_item = encrypt_python_item( parametrized_item, CryptoConfig( - materials_provider=encrypting_cmp, - encryption_context=EncryptionContext(), - attribute_actions=actions - ) + materials_provider=encrypting_cmp, encryption_context=EncryptionContext(), attribute_actions=actions + ), ) with pytest.raises(DecryptionError) as excinfo: decrypt_python_item( encrypted_item, CryptoConfig( - materials_provider=decrypting_cmp, - encryption_context=EncryptionContext(), - attribute_actions=actions - ) + materials_provider=decrypting_cmp, encryption_context=EncryptionContext(), attribute_actions=actions + ), ) - excinfo.match('Attribute actions ask for some attributes to be decrypted but no decryption key is available') + excinfo.match("Attribute actions ask for some attributes to be decrypted but no decryption key is available") def _item_cycle_check(materials_provider, attribute_actions, item): crypto_config = CryptoConfig( materials_provider=materials_provider, encryption_context=EncryptionContext(), - attribute_actions=attribute_actions + attribute_actions=attribute_actions, ) cycle_item_check(item, crypto_config) diff --git a/test/functional/encrypted/test_resource.py b/test/functional/encrypted/test_resource.py index 3b84e933..31e66d7f 100644 --- a/test/functional/encrypted/test_resource.py +++ b/test/functional/encrypted/test_resource.py @@ -13,11 +13,14 @@ """Functional tests for ``dynamodb_encryption_sdk.encrypted.resource``.""" import pytest +from ..functional_test_utils import example_table # noqa pylint: disable=unused-import from ..functional_test_utils import ( - resource_cycle_batch_items_check, set_parametrized_actions, - set_parametrized_cmp, set_parametrized_item, TEST_TABLE_NAME + TEST_TABLE_NAME, + resource_cycle_batch_items_check, + set_parametrized_actions, + set_parametrized_cmp, + set_parametrized_item, ) -from ..functional_test_utils import example_table # noqa pylint: disable=unused-import pytestmark = [pytest.mark.functional, pytest.mark.local] @@ -29,7 +32,7 @@ def pytest_generate_tests(metafunc): def _resource_cycle_batch_items_check(materials_provider, initial_actions, initial_item): - resource_cycle_batch_items_check(materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, 'us-west-2') + resource_cycle_batch_items_check(materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, "us-west-2") def test_ephemeral_batch_item_cycle(example_table, some_cmps, parametrized_actions, parametrized_item): diff --git a/test/functional/encrypted/test_table.py b/test/functional/encrypted/test_table.py index e51a30ed..6a0d1d55 100644 --- a/test/functional/encrypted/test_table.py +++ b/test/functional/encrypted/test_table.py @@ -14,12 +14,16 @@ import hypothesis import pytest +from ..functional_test_utils import example_table # noqa pylint: disable=unused-import from ..functional_test_utils import ( - set_parametrized_actions, set_parametrized_cmp, set_parametrized_item, - table_cycle_batch_writer_check, table_cycle_check, TEST_TABLE_NAME + TEST_TABLE_NAME, + set_parametrized_actions, + set_parametrized_cmp, + set_parametrized_item, + table_cycle_batch_writer_check, + table_cycle_check, ) -from ..functional_test_utils import example_table # noqa pylint: disable=unused-import -from ..hypothesis_strategies import ddb_items, SLOW_SETTINGS, VERY_SLOW_SETTINGS +from ..hypothesis_strategies import SLOW_SETTINGS, VERY_SLOW_SETTINGS, ddb_items pytestmark = [pytest.mark.functional, pytest.mark.local] @@ -31,7 +35,7 @@ def pytest_generate_tests(metafunc): def _table_cycle_check(materials_provider, initial_actions, initial_item): - return table_cycle_check(materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, 'us-west-2') + return table_cycle_check(materials_provider, initial_actions, initial_item, TEST_TABLE_NAME, "us-west-2") def test_ephemeral_item_cycle(example_table, some_cmps, parametrized_actions, parametrized_item): @@ -41,7 +45,7 @@ def test_ephemeral_item_cycle(example_table, some_cmps, parametrized_actions, pa def test_ephemeral_item_cycle_batch_writer(example_table, some_cmps, parametrized_actions, parametrized_item): """Test a small number of curated CMPs against a small number of curated items.""" - table_cycle_batch_writer_check(some_cmps, parametrized_actions, parametrized_item, TEST_TABLE_NAME, 'us-west-2') + table_cycle_batch_writer_check(some_cmps, parametrized_actions, parametrized_item, TEST_TABLE_NAME, "us-west-2") @pytest.mark.slow @@ -53,7 +57,7 @@ def test_ephemeral_item_cycle_slow(example_table, all_the_cmps, parametrized_act @pytest.mark.slow def test_ephemeral_item_cycle_batch_writer_slow(example_table, all_the_cmps, parametrized_actions, parametrized_item): """Test a small number of curated CMPs against a small number of curated items.""" - table_cycle_batch_writer_check(all_the_cmps, parametrized_actions, parametrized_item, TEST_TABLE_NAME, 'us-west-2') + table_cycle_batch_writer_check(all_the_cmps, parametrized_actions, parametrized_item, TEST_TABLE_NAME, "us-west-2") @pytest.mark.slow diff --git a/test/functional/functional_test_utils.py b/test/functional/functional_test_utils.py index b7fb3a2f..4f8f2ec6 100644 --- a/test/functional/functional_test_utils.py +++ b/test/functional/functional_test_utils.py @@ -13,15 +13,15 @@ """Helper tools for use in tests.""" from __future__ import division -from collections import defaultdict import copy -from decimal import Decimal import itertools +from collections import defaultdict +from decimal import Decimal import boto3 +import pytest from boto3.dynamodb.types import Binary from moto import mock_dynamodb2 -import pytest from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey from dynamodb_encryption_sdk.encrypted.client import EncryptedClient @@ -37,103 +37,51 @@ from dynamodb_encryption_sdk.transform import ddb_to_dict, dict_to_ddb _DELEGATED_KEY_CACHE = defaultdict(lambda: defaultdict(dict)) -TEST_TABLE_NAME = 'my_table' +TEST_TABLE_NAME = "my_table" TEST_INDEX = { - 'partition_attribute': { - 'type': 'S', - 'value': 'test_value' - }, - 'sort_attribute': { - 'type': 'N', - 'value': Decimal('99.233') - } + "partition_attribute": {"type": "S", "value": "test_value"}, + "sort_attribute": {"type": "N", "value": Decimal("99.233")}, } SECONARY_INDEX = { - 'secondary_index_1': { - 'type': 'B', - 'value': Binary(b'\x00\x01\x02') - }, - 'secondary_index_1': { - 'type': 'S', - 'value': 'another_value' - } + "secondary_index_1": {"type": "B", "value": Binary(b"\x00\x01\x02")}, + "secondary_index_1": {"type": "S", "value": "another_value"}, } -TEST_KEY = {name: value['value'] for name, value in TEST_INDEX.items()} +TEST_KEY = {name: value["value"] for name, value in TEST_INDEX.items()} TEST_BATCH_INDEXES = [ { - 'partition_attribute': { - 'type': 'S', - 'value': 'test_value' - }, - 'sort_attribute': { - 'type': 'N', - 'value': Decimal('99.233') - } + "partition_attribute": {"type": "S", "value": "test_value"}, + "sort_attribute": {"type": "N", "value": Decimal("99.233")}, }, { - 'partition_attribute': { - 'type': 'S', - 'value': 'test_value' - }, - 'sort_attribute': { - 'type': 'N', - 'value': Decimal('92986745') - } + "partition_attribute": {"type": "S", "value": "test_value"}, + "sort_attribute": {"type": "N", "value": Decimal("92986745")}, }, { - 'partition_attribute': { - 'type': 'S', - 'value': 'test_value' - }, - 'sort_attribute': { - 'type': 'N', - 'value': Decimal('2231.0001') - } + "partition_attribute": {"type": "S", "value": "test_value"}, + "sort_attribute": {"type": "N", "value": Decimal("2231.0001")}, }, { - 'partition_attribute': { - 'type': 'S', - 'value': 'another_test_value' - }, - 'sort_attribute': { - 'type': 'N', - 'value': Decimal('732342') - } - } -] -TEST_BATCH_KEYS = [ - {name: value['value'] for name, value in key.items()} - for key in TEST_BATCH_INDEXES + "partition_attribute": {"type": "S", "value": "another_test_value"}, + "sort_attribute": {"type": "N", "value": Decimal("732342")}, + }, ] +TEST_BATCH_KEYS = [{name: value["value"] for name, value in key.items()} for key in TEST_BATCH_INDEXES] @pytest.fixture def example_table(): mock_dynamodb2().start() - ddb = boto3.client('dynamodb', region_name='us-west-2') + ddb = boto3.client("dynamodb", region_name="us-west-2") ddb.create_table( TableName=TEST_TABLE_NAME, KeySchema=[ - { - 'AttributeName': 'partition_attribute', - 'KeyType': 'HASH' - }, - { - 'AttributeName': 'sort_attribute', - 'KeyType': 'RANGE' - } + {"AttributeName": "partition_attribute", "KeyType": "HASH"}, + {"AttributeName": "sort_attribute", "KeyType": "RANGE"}, ], AttributeDefinitions=[ - { - 'AttributeName': name, - 'AttributeType': value['type'] - } - for name, value in TEST_INDEX.items() + {"AttributeName": name, "AttributeType": value["type"]} for name, value in TEST_INDEX.items() ], - ProvisionedThroughput={ - 'ReadCapacityUnits': 100, - 'WriteCapacityUnits': 100 - } + ProvisionedThroughput={"ReadCapacityUnits": 100, "WriteCapacityUnits": 100}, ) yield ddb.delete_table(TableName=TEST_TABLE_NAME) @@ -143,56 +91,30 @@ def example_table(): @pytest.fixture def table_with_local_seconary_indexes(): mock_dynamodb2().start() - ddb = boto3.client('dynamodb', region_name='us-west-2') + ddb = boto3.client("dynamodb", region_name="us-west-2") ddb.create_table( TableName=TEST_TABLE_NAME, KeySchema=[ - { - 'AttributeName': 'partition_attribute', - 'KeyType': 'HASH' - }, - { - 'AttributeName': 'sort_attribute', - 'KeyType': 'RANGE' - } + {"AttributeName": "partition_attribute", "KeyType": "HASH"}, + {"AttributeName": "sort_attribute", "KeyType": "RANGE"}, ], LocalSecondaryIndexes=[ { - 'IndexName': 'lsi-1', - 'KeySchema': [ - { - 'AttributeName': 'secondary_index_1', - 'KeyType': 'HASH' - } - ], - 'Projection': { - 'ProjectionType': 'ALL' - } + "IndexName": "lsi-1", + "KeySchema": [{"AttributeName": "secondary_index_1", "KeyType": "HASH"}], + "Projection": {"ProjectionType": "ALL"}, }, { - 'IndexName': 'lsi-2', - 'KeySchema': [ - { - 'AttributeName': 'secondary_index_2', - 'KeyType': 'HASH' - } - ], - 'Projection': { - 'ProjectionType': 'ALL' - } - } + "IndexName": "lsi-2", + "KeySchema": [{"AttributeName": "secondary_index_2", "KeyType": "HASH"}], + "Projection": {"ProjectionType": "ALL"}, + }, ], AttributeDefinitions=[ - { - 'AttributeName': name, - 'AttributeType': value['type'] - } + {"AttributeName": name, "AttributeType": value["type"]} for name, value in list(TEST_INDEX.items()) + list(SECONARY_INDEX.items()) ], - ProvisionedThroughput={ - 'ReadCapacityUnits': 100, - 'WriteCapacityUnits': 100 - } + ProvisionedThroughput={"ReadCapacityUnits": 100, "WriteCapacityUnits": 100}, ) yield ddb.delete_table(TableName=TEST_TABLE_NAME) @@ -202,64 +124,32 @@ def table_with_local_seconary_indexes(): @pytest.fixture def table_with_global_seconary_indexes(): mock_dynamodb2().start() - ddb = boto3.client('dynamodb', region_name='us-west-2') + ddb = boto3.client("dynamodb", region_name="us-west-2") ddb.create_table( TableName=TEST_TABLE_NAME, KeySchema=[ - { - 'AttributeName': 'partition_attribute', - 'KeyType': 'HASH' - }, - { - 'AttributeName': 'sort_attribute', - 'KeyType': 'RANGE' - } + {"AttributeName": "partition_attribute", "KeyType": "HASH"}, + {"AttributeName": "sort_attribute", "KeyType": "RANGE"}, ], GlobalSecondaryIndexes=[ { - 'IndexName': 'gsi-1', - 'KeySchema': [ - { - 'AttributeName': 'secondary_index_1', - 'KeyType': 'HASH' - } - ], - 'Projection': { - 'ProjectionType': 'ALL' - }, - 'ProvisionedThroughput': { - 'ReadCapacityUnits': 100, - 'WriteCapacityUnits': 100 - } + "IndexName": "gsi-1", + "KeySchema": [{"AttributeName": "secondary_index_1", "KeyType": "HASH"}], + "Projection": {"ProjectionType": "ALL"}, + "ProvisionedThroughput": {"ReadCapacityUnits": 100, "WriteCapacityUnits": 100}, }, { - 'IndexName': 'gsi-2', - 'KeySchema': [ - { - 'AttributeName': 'secondary_index_2', - 'KeyType': 'HASH' - } - ], - 'Projection': { - 'ProjectionType': 'ALL' - }, - 'ProvisionedThroughput': { - 'ReadCapacityUnits': 100, - 'WriteCapacityUnits': 100 - } - } + "IndexName": "gsi-2", + "KeySchema": [{"AttributeName": "secondary_index_2", "KeyType": "HASH"}], + "Projection": {"ProjectionType": "ALL"}, + "ProvisionedThroughput": {"ReadCapacityUnits": 100, "WriteCapacityUnits": 100}, + }, ], AttributeDefinitions=[ - { - 'AttributeName': name, - 'AttributeType': value['type'] - } + {"AttributeName": name, "AttributeType": value["type"]} for name, value in list(TEST_INDEX.items()) + list(SECONARY_INDEX.items()) ], - ProvisionedThroughput={ - 'ReadCapacityUnits': 100, - 'WriteCapacityUnits': 100 - } + ProvisionedThroughput={"ReadCapacityUnits": 100, "WriteCapacityUnits": 100}, ) yield ddb.delete_table(TableName=TEST_TABLE_NAME) @@ -280,17 +170,10 @@ def build_static_jce_cmp(encryption_algorithm, encryption_key_length, signing_al """Build a StaticCryptographicMaterialsProvider using ephemeral JceNameLocalDelegatedKeys as specified.""" encryption_key = _get_from_cache(JceNameLocalDelegatedKey, encryption_algorithm, encryption_key_length) authentication_key = _get_from_cache(JceNameLocalDelegatedKey, signing_algorithm, signing_key_length) - encryption_materials = RawEncryptionMaterials( - signing_key=authentication_key, - encryption_key=encryption_key - ) - decryption_materials = RawDecryptionMaterials( - verification_key=authentication_key, - decryption_key=encryption_key - ) + encryption_materials = RawEncryptionMaterials(signing_key=authentication_key, encryption_key=encryption_key) + decryption_materials = RawDecryptionMaterials(verification_key=authentication_key, decryption_key=encryption_key) return StaticCryptographicMaterialsProvider( - encryption_materials=encryption_materials, - decryption_materials=decryption_materials + encryption_materials=encryption_materials, decryption_materials=decryption_materials ) @@ -299,31 +182,20 @@ def _build_wrapped_jce_cmp(wrapping_algorithm, wrapping_key_length, signing_algo wrapping_key = _get_from_cache(JceNameLocalDelegatedKey, wrapping_algorithm, wrapping_key_length) signing_key = _get_from_cache(JceNameLocalDelegatedKey, signing_algorithm, signing_key_length) return WrappedCryptographicMaterialsProvider( - wrapping_key=wrapping_key, - unwrapping_key=wrapping_key, - signing_key=signing_key + wrapping_key=wrapping_key, unwrapping_key=wrapping_key, signing_key=signing_key ) def _all_encryption(): """All encryption configurations to test in slow tests.""" - return itertools.chain( - itertools.product(('AES',), (128, 256)), - itertools.product(('RSA',), (1024, 2048, 4096)) - ) + return itertools.chain(itertools.product(("AES",), (128, 256)), itertools.product(("RSA",), (1024, 2048, 4096))) def _all_authentication(): """All authentication configurations to test in slow tests.""" return itertools.chain( - itertools.product( - ('HmacSHA224', 'HmacSHA256', 'HmacSHA384', 'HmacSHA512'), - (128, 256) - ), - itertools.product( - ('SHA224withRSA', 'SHA256withRSA', 'SHA384withRSA', 'SHA512withRSA'), - (1024, 2048, 4096) - ) + itertools.product(("HmacSHA224", "HmacSHA256", "HmacSHA384", "HmacSHA512"), (128, 256)), + itertools.product(("SHA224withRSA", "SHA256withRSA", "SHA384withRSA", "SHA512withRSA"), (1024, 2048, 4096)), ) @@ -335,107 +207,94 @@ def _all_algorithm_pairs(): def _some_algorithm_pairs(): """Cherry-picked set of algorithm pairs (encryption + authentication) to test in fast tests.""" - return ( - ('AES', 256, 'HmacSHA256', 256), - ('AES', 256, 'SHA256withRSA', 4096), - ('RSA', 4096, 'SHA256withRSA', 4096) - ) + return (("AES", 256, "HmacSHA256", 256), ("AES", 256, "SHA256withRSA", 4096), ("RSA", 4096, "SHA256withRSA", 4096)) -_cmp_builders = { - 'static': build_static_jce_cmp, - 'wrapped': _build_wrapped_jce_cmp -} +_cmp_builders = {"static": build_static_jce_cmp, "wrapped": _build_wrapped_jce_cmp} def _all_possible_cmps(algorithm_generator): """Generate all possible cryptographic materials providers based on the supplied generator.""" # The AES combinations do the same thing, but this makes sure that the AESWrap name works as expected. - yield _build_wrapped_jce_cmp('AESWrap', 256, 'HmacSHA256', 256) + yield _build_wrapped_jce_cmp("AESWrap", 256, "HmacSHA256", 256) for builder_info, args in itertools.product(_cmp_builders.items(), algorithm_generator()): builder_type, builder_func = builder_info encryption_algorithm, encryption_key_length, signing_algorithm, signing_key_length = args - if builder_type == 'static' and encryption_algorithm != 'AES': + if builder_type == "static" and encryption_algorithm != "AES": # Only AES keys are allowed to be used with static materials continue - id_string = '{enc_algorithm}/{enc_key_length} {builder_type} {sig_algorithm}/{sig_key_length}'.format( + id_string = "{enc_algorithm}/{enc_key_length} {builder_type} {sig_algorithm}/{sig_key_length}".format( enc_algorithm=encryption_algorithm, enc_key_length=encryption_key_length, builder_type=builder_type, sig_algorithm=signing_algorithm, - sig_key_length=signing_key_length + sig_key_length=signing_key_length, ) yield pytest.param( - builder_func( - encryption_algorithm, - encryption_key_length, - signing_algorithm, - signing_key_length - ), - id=id_string + builder_func(encryption_algorithm, encryption_key_length, signing_algorithm, signing_key_length), + id=id_string, ) def set_parametrized_cmp(metafunc): """Set paramatrized values for cryptographic materials providers.""" - for name, algorithm_generator in (('all_the_cmps', _all_algorithm_pairs), ('some_cmps', _some_algorithm_pairs)): + for name, algorithm_generator in (("all_the_cmps", _all_algorithm_pairs), ("some_cmps", _some_algorithm_pairs)): if name in metafunc.fixturenames: metafunc.parametrize(name, _all_possible_cmps(algorithm_generator)) _ACTIONS = { - 'hypothesis_actions': ( - pytest.param(AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN), id='encrypt all'), - pytest.param(AttributeActions(default_action=CryptoAction.SIGN_ONLY), id='sign only all'), - pytest.param(AttributeActions(default_action=CryptoAction.DO_NOTHING), id='do nothing'), + "hypothesis_actions": ( + pytest.param(AttributeActions(default_action=CryptoAction.ENCRYPT_AND_SIGN), id="encrypt all"), + pytest.param(AttributeActions(default_action=CryptoAction.SIGN_ONLY), id="sign only all"), + pytest.param(AttributeActions(default_action=CryptoAction.DO_NOTHING), id="do nothing"), ) } -_ACTIONS['parametrized_actions'] = _ACTIONS['hypothesis_actions'] + ( +_ACTIONS["parametrized_actions"] = _ACTIONS["hypothesis_actions"] + ( pytest.param( AttributeActions( default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={ - 'number_set': CryptoAction.SIGN_ONLY, - 'string_set': CryptoAction.SIGN_ONLY, - 'binary_set': CryptoAction.SIGN_ONLY - } + "number_set": CryptoAction.SIGN_ONLY, + "string_set": CryptoAction.SIGN_ONLY, + "binary_set": CryptoAction.SIGN_ONLY, + }, ), - id='sign sets, encrypt everything else' + id="sign sets, encrypt everything else", ), pytest.param( AttributeActions( default_action=CryptoAction.ENCRYPT_AND_SIGN, attribute_actions={ - 'number_set': CryptoAction.DO_NOTHING, - 'string_set': CryptoAction.DO_NOTHING, - 'binary_set': CryptoAction.DO_NOTHING - } + "number_set": CryptoAction.DO_NOTHING, + "string_set": CryptoAction.DO_NOTHING, + "binary_set": CryptoAction.DO_NOTHING, + }, ), - id='ignore sets, encrypt everything else' + id="ignore sets, encrypt everything else", ), pytest.param( AttributeActions( - default_action=CryptoAction.DO_NOTHING, - attribute_actions={'map': CryptoAction.ENCRYPT_AND_SIGN} + default_action=CryptoAction.DO_NOTHING, attribute_actions={"map": CryptoAction.ENCRYPT_AND_SIGN} ), - id='encrypt map, ignore everything else' + id="encrypt map, ignore everything else", ), pytest.param( AttributeActions( default_action=CryptoAction.SIGN_ONLY, attribute_actions={ - 'number_set': CryptoAction.DO_NOTHING, - 'string_set': CryptoAction.DO_NOTHING, - 'binary_set': CryptoAction.DO_NOTHING, - 'map': CryptoAction.ENCRYPT_AND_SIGN - } + "number_set": CryptoAction.DO_NOTHING, + "string_set": CryptoAction.DO_NOTHING, + "binary_set": CryptoAction.DO_NOTHING, + "map": CryptoAction.ENCRYPT_AND_SIGN, + }, ), - id='ignore sets, encrypt map, sign everything else' - ) + id="ignore sets, encrypt map, sign everything else", + ), ) @@ -448,27 +307,22 @@ def set_parametrized_actions(metafunc): def set_parametrized_item(metafunc): """Set parametrized values for items to cycle.""" - if 'parametrized_item' in metafunc.fixturenames: - metafunc.parametrize( - 'parametrized_item', - ( - pytest.param(diverse_item(), id='diverse item'), - ) - ) + if "parametrized_item" in metafunc.fixturenames: + metafunc.parametrize("parametrized_item", (pytest.param(diverse_item(), id="diverse item"),)) def diverse_item(): base_item = { - 'int': 5, - 'decimal': Decimal('123.456'), - 'string': 'this is a string', - 'binary': b'this is a bytestring! \x01', - 'number_set': set([5, 4, 3]), - 'string_set': set(['abc', 'def', 'geh']), - 'binary_set': set([b'\x00\x00\x00', b'\x00\x01\x00', b'\x00\x00\x02']) + "int": 5, + "decimal": Decimal("123.456"), + "string": "this is a string", + "binary": b"this is a bytestring! \x01", + "number_set": set([5, 4, 3]), + "string_set": set(["abc", "def", "geh"]), + "binary_set": set([b"\x00\x00\x00", b"\x00\x01\x00", b"\x00\x00\x02"]), } - base_item['list'] = [copy.copy(i) for i in base_item.values()] - base_item['map'] = copy.deepcopy(base_item) + base_item["list"] = [copy.copy(i) for i in base_item.values()] + base_item["map"] = copy.deepcopy(base_item) return copy.deepcopy(base_item) @@ -500,9 +354,10 @@ def check_encrypted_item(plaintext_item, ciphertext_item, attribute_actions): def _matching_key(actual_item, expected): expected_item = [ - i for i in expected - if i['partition_attribute'] == actual_item['partition_attribute'] and - i['sort_attribute'] == actual_item['sort_attribute'] + i + for i in expected + if i["partition_attribute"] == actual_item["partition_attribute"] + and i["sort_attribute"] == actual_item["sort_attribute"] ] assert len(expected_item) == 1 return expected_item[0] @@ -528,7 +383,7 @@ def check_many_encrypted_items(actual, expected, attribute_actions, transformer= check_encrypted_item( plaintext_item=transformer(expected_item), ciphertext_item=transformer(actual_item), - attribute_actions=attribute_actions + attribute_actions=attribute_actions, ) @@ -544,24 +399,19 @@ def _generate_items(initial_item, write_transformer): def _cleanup_items(encrypted, write_transformer, table_name=TEST_TABLE_NAME): ddb_keys = [write_transformer(key) for key in TEST_BATCH_KEYS] _delete_result = encrypted.batch_write_item( # noqa - RequestItems={ - table_name: [ - {'DeleteRequest': {'Key': _key}} - for _key in ddb_keys - ] - } + RequestItems={table_name: [{"DeleteRequest": {"Key": _key}} for _key in ddb_keys]} ) def cycle_batch_item_check( - raw, - encrypted, - initial_actions, - initial_item, - write_transformer=_nop_transformer, - read_transformer=_nop_transformer, - table_name=TEST_TABLE_NAME, - delete_items=True + raw, + encrypted, + initial_actions, + initial_item, + write_transformer=_nop_transformer, + read_transformer=_nop_transformer, + table_name=TEST_TABLE_NAME, + delete_items=True, ): """Check that cycling (plaintext->encrypted->decrypted) item batch has the expected results.""" check_attribute_actions = initial_actions.copy() @@ -569,40 +419,21 @@ def cycle_batch_item_check( items = _generate_items(initial_item, write_transformer) _put_result = encrypted.batch_write_item( # noqa - RequestItems={ - table_name: [ - {'PutRequest': {'Item': _item}} - for _item in items - ] - } + RequestItems={table_name: [{"PutRequest": {"Item": _item}} for _item in items]} ) ddb_keys = [write_transformer(key) for key in TEST_BATCH_KEYS] - encrypted_result = raw.batch_get_item( - RequestItems={ - table_name: { - 'Keys': ddb_keys - } - } - ) + encrypted_result = raw.batch_get_item(RequestItems={table_name: {"Keys": ddb_keys}}) check_many_encrypted_items( - actual=encrypted_result['Responses'][table_name], + actual=encrypted_result["Responses"][table_name], expected=items, attribute_actions=check_attribute_actions, - transformer=read_transformer + transformer=read_transformer, ) - decrypted_result = encrypted.batch_get_item( - RequestItems={ - table_name: { - 'Keys': ddb_keys - } - } - ) + decrypted_result = encrypted.batch_get_item(RequestItems={table_name: {"Keys": ddb_keys}}) assert_equal_lists_of_items( - actual=decrypted_result['Responses'][table_name], - expected=items, - transformer=read_transformer + actual=decrypted_result["Responses"][table_name], expected=items, transformer=read_transformer ) if delete_items: @@ -625,26 +456,13 @@ def cycle_batch_writer_check(raw_table, encrypted_table, initial_actions, initia writer.put_item(item) ddb_keys = [key for key in TEST_BATCH_KEYS] - encrypted_items = [ - raw_table.get_item(Key=key, ConsistentRead=True)['Item'] - for key in ddb_keys - ] + encrypted_items = [raw_table.get_item(Key=key, ConsistentRead=True)["Item"] for key in ddb_keys] check_many_encrypted_items( - actual=encrypted_items, - expected=items, - attribute_actions=check_attribute_actions, - transformer=_nop_transformer + actual=encrypted_items, expected=items, attribute_actions=check_attribute_actions, transformer=_nop_transformer ) - decrypted_result = [ - encrypted_table.get_item(Key=key, ConsistentRead=True)['Item'] - for key in ddb_keys - ] - assert_equal_lists_of_items( - actual=decrypted_result, - expected=items, - transformer=_nop_transformer - ) + decrypted_result = [encrypted_table.get_item(Key=key, ConsistentRead=True)["Item"] for key in ddb_keys] + assert_equal_lists_of_items(actual=decrypted_result, expected=items, transformer=_nop_transformer) with encrypted_table.batch_writer() as writer: for key in ddb_keys: @@ -675,21 +493,17 @@ def table_cycle_check(materials_provider, initial_actions, initial_item, table_n kwargs = {} if region_name is not None: - kwargs['region_name'] = region_name - table = boto3.resource('dynamodb', **kwargs).Table(table_name) - e_table = EncryptedTable( - table=table, - materials_provider=materials_provider, - attribute_actions=initial_actions, - ) + kwargs["region_name"] = region_name + table = boto3.resource("dynamodb", **kwargs).Table(table_name) + e_table = EncryptedTable(table=table, materials_provider=materials_provider, attribute_actions=initial_actions) _put_result = e_table.put_item(Item=item) # noqa encrypted_result = table.get_item(Key=TEST_KEY, ConsistentRead=True) - check_encrypted_item(item, encrypted_result['Item'], check_attribute_actions) + check_encrypted_item(item, encrypted_result["Item"], check_attribute_actions) decrypted_result = e_table.get_item(Key=TEST_KEY, ConsistentRead=True) - assert decrypted_result['Item'] == item + assert decrypted_result["Item"] == item e_table.delete_item(Key=TEST_KEY) del item @@ -699,13 +513,9 @@ def table_cycle_check(materials_provider, initial_actions, initial_item, table_n def table_cycle_batch_writer_check(materials_provider, initial_actions, initial_item, table_name, region_name=None): kwargs = {} if region_name is not None: - kwargs['region_name'] = region_name - table = boto3.resource('dynamodb', **kwargs).Table(table_name) - e_table = EncryptedTable( - table=table, - materials_provider=materials_provider, - attribute_actions=initial_actions, - ) + kwargs["region_name"] = region_name + table = boto3.resource("dynamodb", **kwargs).Table(table_name) + e_table = EncryptedTable(table=table, materials_provider=materials_provider, attribute_actions=initial_actions) cycle_batch_writer_check(table, e_table, initial_actions, initial_item) @@ -713,12 +523,10 @@ def table_cycle_batch_writer_check(materials_provider, initial_actions, initial_ def resource_cycle_batch_items_check(materials_provider, initial_actions, initial_item, table_name, region_name=None): kwargs = {} if region_name is not None: - kwargs['region_name'] = region_name - resource = boto3.resource('dynamodb', **kwargs) + kwargs["region_name"] = region_name + resource = boto3.resource("dynamodb", **kwargs) e_resource = EncryptedResource( - resource=resource, - materials_provider=materials_provider, - attribute_actions=initial_actions + resource=resource, materials_provider=materials_provider, attribute_actions=initial_actions ) cycle_batch_item_check( @@ -726,13 +534,13 @@ def resource_cycle_batch_items_check(materials_provider, initial_actions, initia encrypted=e_resource, initial_actions=initial_actions, initial_item=initial_item, - table_name=table_name + table_name=table_name, ) raw_scan_result = resource.Table(table_name).scan(ConsistentRead=True) e_scan_result = e_resource.Table(table_name).scan(ConsistentRead=True) - assert not raw_scan_result['Items'] - assert not e_scan_result['Items'] + assert not raw_scan_result["Items"] + assert not e_scan_result["Items"] def client_cycle_single_item_check(materials_provider, initial_actions, initial_item, table_name, region_name=None): @@ -745,37 +553,19 @@ def client_cycle_single_item_check(materials_provider, initial_actions, initial_ kwargs = {} if region_name is not None: - kwargs['region_name'] = region_name - client = boto3.client('dynamodb', **kwargs) - e_client = EncryptedClient( - client=client, - materials_provider=materials_provider, - attribute_actions=initial_actions - ) + kwargs["region_name"] = region_name + client = boto3.client("dynamodb", **kwargs) + e_client = EncryptedClient(client=client, materials_provider=materials_provider, attribute_actions=initial_actions) - _put_result = e_client.put_item( # noqa - TableName=table_name, - Item=ddb_item - ) + _put_result = e_client.put_item(TableName=table_name, Item=ddb_item) # noqa - encrypted_result = client.get_item( - TableName=table_name, - Key=ddb_key, - ConsistentRead=True - ) - check_encrypted_item(item, ddb_to_dict(encrypted_result['Item']), check_attribute_actions) + encrypted_result = client.get_item(TableName=table_name, Key=ddb_key, ConsistentRead=True) + check_encrypted_item(item, ddb_to_dict(encrypted_result["Item"]), check_attribute_actions) - decrypted_result = e_client.get_item( - TableName=table_name, - Key=ddb_key, - ConsistentRead=True - ) - assert ddb_to_dict(decrypted_result['Item']) == item + decrypted_result = e_client.get_item(TableName=table_name, Key=ddb_key, ConsistentRead=True) + assert ddb_to_dict(decrypted_result["Item"]) == item - e_client.delete_item( - TableName=table_name, - Key=ddb_key - ) + e_client.delete_item(TableName=table_name, Key=ddb_key) del item del check_attribute_actions @@ -783,13 +573,9 @@ def client_cycle_single_item_check(materials_provider, initial_actions, initial_ def client_cycle_batch_items_check(materials_provider, initial_actions, initial_item, table_name, region_name=None): kwargs = {} if region_name is not None: - kwargs['region_name'] = region_name - client = boto3.client('dynamodb', **kwargs) - e_client = EncryptedClient( - client=client, - materials_provider=materials_provider, - attribute_actions=initial_actions - ) + kwargs["region_name"] = region_name + client = boto3.client("dynamodb", **kwargs) + e_client = EncryptedClient(client=client, materials_provider=materials_provider, attribute_actions=initial_actions) cycle_batch_item_check( raw=client, @@ -798,31 +584,23 @@ def client_cycle_batch_items_check(materials_provider, initial_actions, initial_ initial_item=initial_item, write_transformer=dict_to_ddb, read_transformer=ddb_to_dict, - table_name=table_name + table_name=table_name, ) raw_scan_result = client.scan(TableName=table_name, ConsistentRead=True) e_scan_result = e_client.scan(TableName=table_name, ConsistentRead=True) - assert not raw_scan_result['Items'] - assert not e_scan_result['Items'] + assert not raw_scan_result["Items"] + assert not e_scan_result["Items"] def client_cycle_batch_items_check_paginators( - materials_provider, - initial_actions, - initial_item, - table_name, - region_name=None + materials_provider, initial_actions, initial_item, table_name, region_name=None ): kwargs = {} if region_name is not None: - kwargs['region_name'] = region_name - client = boto3.client('dynamodb', **kwargs) - e_client = EncryptedClient( - client=client, - materials_provider=materials_provider, - attribute_actions=initial_actions - ) + kwargs["region_name"] = region_name + client = boto3.client("dynamodb", **kwargs) + e_client = EncryptedClient(client=client, materials_provider=materials_provider, attribute_actions=initial_actions) cycle_batch_item_check( raw=client, @@ -832,18 +610,18 @@ def client_cycle_batch_items_check_paginators( write_transformer=dict_to_ddb, read_transformer=ddb_to_dict, table_name=table_name, - delete_items=False + delete_items=False, ) encrypted_items = [] - raw_paginator = client.get_paginator('scan') + raw_paginator = client.get_paginator("scan") for page in raw_paginator.paginate(TableName=table_name, ConsistentRead=True): - encrypted_items.extend(page['Items']) + encrypted_items.extend(page["Items"]) decrypted_items = [] - encrypted_paginator = e_client.get_paginator('scan') + encrypted_paginator = e_client.get_paginator("scan") for page in encrypted_paginator.paginate(TableName=table_name, ConsistentRead=True): - decrypted_items.extend(page['Items']) + decrypted_items.extend(page["Items"]) print(encrypted_items) print(decrypted_items) @@ -854,16 +632,12 @@ def client_cycle_batch_items_check_paginators( actual=encrypted_items, expected=decrypted_items, attribute_actions=check_attribute_actions, - transformer=ddb_to_dict + transformer=ddb_to_dict, ) - _cleanup_items( - encrypted=e_client, - write_transformer=dict_to_ddb, - table_name=table_name - ) + _cleanup_items(encrypted=e_client, write_transformer=dict_to_ddb, table_name=table_name) raw_scan_result = client.scan(TableName=table_name, ConsistentRead=True) e_scan_result = e_client.scan(TableName=table_name, ConsistentRead=True) - assert not raw_scan_result['Items'] - assert not e_scan_result['Items'] + assert not raw_scan_result["Items"] + assert not e_scan_result["Items"] diff --git a/test/functional/functional_test_vector_generators.py b/test/functional/functional_test_vector_generators.py index 88a7fd4e..02906f35 100644 --- a/test/functional/functional_test_vector_generators.py +++ b/test/functional/functional_test_vector_generators.py @@ -13,9 +13,9 @@ """Helper tools for collecting test vectors for use in functional tests.""" import base64 import codecs -from decimal import Decimal import json import os +from decimal import Decimal from boto3.dynamodb.types import Binary @@ -23,22 +23,13 @@ from dynamodb_encryption_sdk.structures import AttributeActions _ATTRIBUTE_TEST_VECTOR_FILE_TEMPLATE = os.path.join( - os.path.abspath(os.path.dirname(__file__)), - '..', - 'vectors', - '{mode}_attribute.json' + os.path.abspath(os.path.dirname(__file__)), "..", "vectors", "{mode}_attribute.json" ) _MATERIAL_DESCRIPTION_TEST_VECTORS_FILE = os.path.join( - os.path.abspath(os.path.dirname(__file__)), - '..', - 'vectors', - 'material_description.json' + os.path.abspath(os.path.dirname(__file__)), "..", "vectors", "material_description.json" ) _STRING_TO_SIGN_TEST_VECTORS_FILE = os.path.join( - os.path.abspath(os.path.dirname(__file__)), - '..', - 'vectors', - 'string_to_sign.json' + os.path.abspath(os.path.dirname(__file__)), "..", "vectors", "string_to_sign.json" ) @@ -47,7 +38,7 @@ def _decode_string(_value): return _value def _decode_number(_value): - return '{0:f}'.format(Decimal(_value)) + return "{0:f}".format(Decimal(_value)) def _decode_binary(_value): raw = base64.b64decode(_value) @@ -91,14 +82,14 @@ def _decode_map(_value): return decoded_value _decode_mapping = { - 'S': _decode_string, - 'B': _decode_binary, - 'SS': _decode_string_set, - 'BS': _decode_binary_set, - 'L': _decode_list, - 'M': _decode_map, - 'N': _decode_number, - 'NS': _decode_number_set + "S": _decode_string, + "B": _decode_binary, + "SS": _decode_string_set, + "BS": _decode_binary_set, + "L": _decode_list, + "M": _decode_map, + "N": _decode_number, + "NS": _decode_number_set, } def _decode_complex_value(_value): @@ -116,26 +107,20 @@ def attribute_test_vectors(mode): with open(filepath) as f: vectors = json.load(f) for vector in vectors: - yield ( - decode_value(vector['attribute']), - base64.b64decode(codecs.encode(vector['serialized'], 'utf-8')) - ) + yield (decode_value(vector["attribute"]), base64.b64decode(codecs.encode(vector["serialized"], "utf-8"))) def material_description_test_vectors(): with open(_MATERIAL_DESCRIPTION_TEST_VECTORS_FILE) as f: vectors = json.load(f) for vector in vectors: - yield ( - vector['material_description'], - decode_value({'B': codecs.encode(vector['serialized'], 'utf-8')}) - ) + yield (vector["material_description"], decode_value({"B": codecs.encode(vector["serialized"], "utf-8")})) ACTION_MAP = { - 'encrypt': CryptoAction.ENCRYPT_AND_SIGN, - 'sign': CryptoAction.SIGN_ONLY, - 'nothing': CryptoAction.DO_NOTHING + "encrypt": CryptoAction.ENCRYPT_AND_SIGN, + "sign": CryptoAction.SIGN_ONLY, + "nothing": CryptoAction.DO_NOTHING, } @@ -143,18 +128,12 @@ def string_to_sign_test_vectors(): with open(_STRING_TO_SIGN_TEST_VECTORS_FILE) as f: vectors = json.load(f) for vector in vectors: - item = { - key: decode_value(value['value']) - for key, value in vector['item'].items() - } - bare_actions = {key: ACTION_MAP[value['action']] for key, value in vector['item'].items()} - attribute_actions = AttributeActions( - default_action=CryptoAction.DO_NOTHING, - attribute_actions=bare_actions - ) + item = {key: decode_value(value["value"]) for key, value in vector["item"].items()} + bare_actions = {key: ACTION_MAP[value["action"]] for key, value in vector["item"].items()} + attribute_actions = AttributeActions(default_action=CryptoAction.DO_NOTHING, attribute_actions=bare_actions) yield ( item, - vector['table'], + vector["table"], attribute_actions, - base64.b64decode(codecs.encode(vector['string_to_sign'], 'utf-8')) + base64.b64decode(codecs.encode(vector["string_to_sign"], "utf-8")), ) diff --git a/test/functional/hypothesis_strategies.py b/test/functional/hypothesis_strategies.py index 170b9389..8e230375 100644 --- a/test/functional/hypothesis_strategies.py +++ b/test/functional/hypothesis_strategies.py @@ -14,27 +14,21 @@ """Hypothesis strategies for use in tests.""" from decimal import Decimal -from boto3.dynamodb.types import Binary import hypothesis -from hypothesis.strategies import ( - binary, booleans, deferred, dictionaries, fractions, just, lists, none, sets, text -) +from boto3.dynamodb.types import Binary +from hypothesis.strategies import binary, booleans, deferred, dictionaries, fractions, just, lists, none, sets, text SLOW_SETTINGS = hypothesis.settings( suppress_health_check=( hypothesis.HealthCheck.too_slow, hypothesis.HealthCheck.data_too_large, hypothesis.HealthCheck.hung_test, - hypothesis.HealthCheck.large_base_example + hypothesis.HealthCheck.large_base_example, ), timeout=hypothesis.unlimited, - deadline=None -) -VERY_SLOW_SETTINGS = hypothesis.settings( - SLOW_SETTINGS, - max_examples=1000, - max_iterations=1500 + deadline=None, ) +VERY_SLOW_SETTINGS = hypothesis.settings(SLOW_SETTINGS, max_examples=1000, max_iterations=1500) MAX_ITEM_BYTES = 400 * 1024 * 1024 # _MIN_NUMBER = Decimal('1E-128') # The DDB min is 1E-130, but DYNAMODB_CONTEXT Emin is -128 @@ -42,14 +36,11 @@ # TODO: I would like to test the full range of possible number values, but boto3 does not # correctly handle conversion of large edge case values at this time. We will work to fix # that, but in the meantime, we will just use the happy path numbers. -_MIN_NUMBER = Decimal('1E-38') -_MAX_NUMBER = Decimal('9.{}E37'.format('9' * 37)) +_MIN_NUMBER = Decimal("1E-38") +_MAX_NUMBER = Decimal("9.{}E37".format("9" * 37)) -ddb_string = text( - min_size=1, - max_size=MAX_ITEM_BYTES -) +ddb_string = text(min_size=1, max_size=MAX_ITEM_BYTES) ddb_string_set = sets(ddb_string, min_size=1) @@ -59,16 +50,13 @@ def _ddb_fraction_to_decimal(val): def _negative(val): - return val * Decimal('-1') + return val * Decimal("-1") -ddb_positive_numbers = fractions( - min_value=_MIN_NUMBER, - max_value=_MAX_NUMBER -).map(_ddb_fraction_to_decimal) +ddb_positive_numbers = fractions(min_value=_MIN_NUMBER, max_value=_MAX_NUMBER).map(_ddb_fraction_to_decimal) ddb_negative_numbers = ddb_positive_numbers.map(_negative) -ddb_number = ddb_negative_numbers | just(Decimal('0')) | ddb_positive_numbers +ddb_number = ddb_negative_numbers | just(Decimal("0")) | ddb_positive_numbers ddb_number_set = sets(ddb_number, min_size=1) ddb_binary = binary(min_size=1, max_size=MAX_ITEM_BYTES).map(Binary) @@ -77,54 +65,22 @@ def _negative(val): ddb_boolean = booleans() ddb_null = none() -ddb_scalar_types = ( - ddb_string | - ddb_number | - ddb_binary | - ddb_boolean | - ddb_null -) +ddb_scalar_types = ddb_string | ddb_number | ddb_binary | ddb_boolean | ddb_null -ddb_set_types = ( - ddb_string_set | - ddb_number_set | - ddb_binary_set -) -ddb_attribute_names = text( - min_size=1, - max_size=255 -) +ddb_set_types = ddb_string_set | ddb_number_set | ddb_binary_set +ddb_attribute_names = text(min_size=1, max_size=255) # TODO: List and Map types have a max depth of 32 -ddb_map_type = deferred(lambda: dictionaries( - keys=ddb_attribute_names, - values=( - ddb_scalar_types | - ddb_set_types | - ddb_list_type | - ddb_map_type - ), - min_size=1 -)) -ddb_list_type = deferred(lambda: lists( - ddb_scalar_types | - ddb_set_types | - ddb_list_type | - ddb_map_type, - min_size=1 -)) +ddb_map_type = deferred( + lambda: dictionaries( + keys=ddb_attribute_names, values=(ddb_scalar_types | ddb_set_types | ddb_list_type | ddb_map_type), min_size=1 + ) +) +ddb_list_type = deferred(lambda: lists(ddb_scalar_types | ddb_set_types | ddb_list_type | ddb_map_type, min_size=1)) ddb_document_types = ddb_map_type | ddb_list_type ddb_attribute_values = ddb_scalar_types | ddb_set_types | ddb_list_type -ddb_items = dictionaries( - keys=ddb_attribute_names, - values=ddb_attribute_values, - min_size=1 -) +ddb_items = dictionaries(keys=ddb_attribute_names, values=ddb_attribute_values, min_size=1) -material_descriptions = deferred(lambda: dictionaries( - keys=text(), - values=text(), - min_size=1 -)) +material_descriptions = deferred(lambda: dictionaries(keys=text(), values=text(), min_size=1)) diff --git a/test/functional/internal/crypto/test_authentication.py b/test/functional/internal/crypto/test_authentication.py index c17a078c..ef0abbc3 100644 --- a/test/functional/internal/crypto/test_authentication.py +++ b/test/functional/internal/crypto/test_authentication.py @@ -14,12 +14,13 @@ import pytest from dynamodb_encryption_sdk.internal.crypto.authentication import _string_to_sign + from ...functional_test_vector_generators import string_to_sign_test_vectors pytestmark = [pytest.mark.functional, pytest.mark.local] -@pytest.mark.parametrize('item, table_name, attribute_actions, expected_result', string_to_sign_test_vectors()) +@pytest.mark.parametrize("item, table_name, attribute_actions, expected_result", string_to_sign_test_vectors()) def test_string_to_sign(item, table_name, attribute_actions, expected_result): generated_string = _string_to_sign(item, table_name, attribute_actions) assert generated_string == expected_result diff --git a/test/functional/internal/formatting/test_attribute.py b/test/functional/internal/formatting/test_attribute.py index 6b726f46..3cdac26a 100644 --- a/test/functional/internal/formatting/test_attribute.py +++ b/test/functional/internal/formatting/test_attribute.py @@ -11,32 +11,36 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Functional tests for attribute de/serialization.""" -from boto3.dynamodb.types import TypeDeserializer, TypeSerializer import hypothesis import pytest +from boto3.dynamodb.types import TypeDeserializer, TypeSerializer from dynamodb_encryption_sdk.exceptions import DeserializationError, SerializationError from dynamodb_encryption_sdk.internal.formatting.deserialize.attribute import deserialize_attribute from dynamodb_encryption_sdk.internal.formatting.serialize.attribute import serialize_attribute from dynamodb_encryption_sdk.transform import ddb_to_dict, dict_to_ddb + from ...functional_test_vector_generators import attribute_test_vectors -from ...hypothesis_strategies import ddb_attribute_values, ddb_items, SLOW_SETTINGS, VERY_SLOW_SETTINGS +from ...hypothesis_strategies import SLOW_SETTINGS, VERY_SLOW_SETTINGS, ddb_attribute_values, ddb_items pytestmark = [pytest.mark.functional, pytest.mark.local] -@pytest.mark.parametrize('attribute, serialized', attribute_test_vectors('serialize')) +@pytest.mark.parametrize("attribute, serialized", attribute_test_vectors("serialize")) def test_serialize_attribute(attribute, serialized): serialized_attribute = serialize_attribute(attribute) assert serialized_attribute == serialized -@pytest.mark.parametrize('attribute, expected_type, expected_message', ( - ({'_': None}, SerializationError, r'Unsupported DynamoDB data type: *'), - ({}, SerializationError, r'cannot serialize attribute: incorrect number of members *'), - ({'a': None, 'b': None}, SerializationError, r'cannot serialize attribute: incorrect number of members *'), - (None, TypeError, r'Invalid attribute type *') -)) +@pytest.mark.parametrize( + "attribute, expected_type, expected_message", + ( + ({"_": None}, SerializationError, r"Unsupported DynamoDB data type: *"), + ({}, SerializationError, r"cannot serialize attribute: incorrect number of members *"), + ({"a": None, "b": None}, SerializationError, r"cannot serialize attribute: incorrect number of members *"), + (None, TypeError, r"Invalid attribute type *"), + ), +) def test_serialize_attribute_errors(attribute, expected_type, expected_message): with pytest.raises(expected_type) as excinfo: serialize_attribute(attribute) @@ -44,19 +48,22 @@ def test_serialize_attribute_errors(attribute, expected_type, expected_message): excinfo.match(expected_message) -@pytest.mark.parametrize('attribute, serialized', attribute_test_vectors('deserialize')) +@pytest.mark.parametrize("attribute, serialized", attribute_test_vectors("deserialize")) def test_deserialize_attribute(attribute, serialized): deserialized_attribute = deserialize_attribute(serialized) assert deserialized_attribute == attribute -@pytest.mark.parametrize('data, expected_type, expected_message', ( - (b'', DeserializationError, r'Empty serialized attribute data'), - (b'_', DeserializationError, r'Malformed serialized data'), - (b'\x00_', DeserializationError, r'Unsupported tag: *'), - (b'__', DeserializationError, r'Invalid tag: reserved byte is not null'), - (b'\x00M\x00\x00\x00\x01\x00\x00', DeserializationError, r'Malformed serialized map: *') -)) +@pytest.mark.parametrize( + "data, expected_type, expected_message", + ( + (b"", DeserializationError, r"Empty serialized attribute data"), + (b"_", DeserializationError, r"Malformed serialized data"), + (b"\x00_", DeserializationError, r"Unsupported tag: *"), + (b"__", DeserializationError, r"Invalid tag: reserved byte is not null"), + (b"\x00M\x00\x00\x00\x01\x00\x00", DeserializationError, r"Malformed serialized map: *"), + ), +) def test_deserialize_attribute_errors(data, expected_type, expected_message): with pytest.raises(expected_type) as exc_info: deserialize_attribute(data) diff --git a/test/functional/internal/formatting/test_material_description.py b/test/functional/internal/formatting/test_material_description.py index 1fbf219d..a951b518 100644 --- a/test/functional/internal/formatting/test_material_description.py +++ b/test/functional/internal/formatting/test_material_description.py @@ -16,24 +16,29 @@ from dynamodb_encryption_sdk.exceptions import InvalidMaterialDescriptionError, InvalidMaterialDescriptionVersionError from dynamodb_encryption_sdk.internal.formatting.material_description import ( - deserialize as deserialize_material_description, serialize as serialize_material_description + deserialize as deserialize_material_description, + serialize as serialize_material_description, ) + from ...functional_test_vector_generators import material_description_test_vectors -from ...hypothesis_strategies import material_descriptions, SLOW_SETTINGS, VERY_SLOW_SETTINGS +from ...hypothesis_strategies import SLOW_SETTINGS, VERY_SLOW_SETTINGS, material_descriptions pytestmark = [pytest.mark.functional, pytest.mark.local] -@pytest.mark.parametrize('material_description, serialized', material_description_test_vectors()) +@pytest.mark.parametrize("material_description, serialized", material_description_test_vectors()) def test_serialize_material_description(material_description, serialized): serialized_material_description = serialize_material_description(material_description) assert serialized_material_description == serialized -@pytest.mark.parametrize('data, expected_type, expected_message', ( - ({'test': 5}, InvalidMaterialDescriptionError, 'Invalid name or value in material description: *'), - ({5: 'test'}, InvalidMaterialDescriptionError, 'Invalid name or value in material description: *'), -)) +@pytest.mark.parametrize( + "data, expected_type, expected_message", + ( + ({"test": 5}, InvalidMaterialDescriptionError, "Invalid name or value in material description: *"), + ({5: "test"}, InvalidMaterialDescriptionError, "Invalid name or value in material description: *"), + ), +) def test_serialize_material_description_errors(data, expected_type, expected_message): with pytest.raises(expected_type) as exc_info: serialize_material_description(data) @@ -41,28 +46,35 @@ def test_serialize_material_description_errors(data, expected_type, expected_mes exc_info.match(expected_message) -@pytest.mark.parametrize('material_description, serialized', material_description_test_vectors()) +@pytest.mark.parametrize("material_description, serialized", material_description_test_vectors()) def test_deserialize_material_description(material_description, serialized): deserialized_material_description = deserialize_material_description(serialized) assert deserialized_material_description == material_description -@pytest.mark.parametrize('data, expected_type, expected_message', ( - # Invalid version - ({'B': b'\x00\x00\x00\x01'}, InvalidMaterialDescriptionVersionError, r'Invalid material description version: *'), - # Malformed version - ({'B': b'\x00\x00\x00'}, InvalidMaterialDescriptionError, r'Malformed material description version'), - # Invalid attribute type - ({'S': 'not bytes'}, InvalidMaterialDescriptionError, r'Invalid material description'), - # Invalid data: not a DDB attribute - (b'bare bytes', InvalidMaterialDescriptionError, r'Invalid material description'), - # Partial entry +@pytest.mark.parametrize( + "data, expected_type, expected_message", ( - {'B': b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01A\x00\x00\x00\x01'}, - InvalidMaterialDescriptionError, - r'Invalid material description' - ) -)) + # Invalid version + ( + {"B": b"\x00\x00\x00\x01"}, + InvalidMaterialDescriptionVersionError, + r"Invalid material description version: *", + ), + # Malformed version + ({"B": b"\x00\x00\x00"}, InvalidMaterialDescriptionError, r"Malformed material description version"), + # Invalid attribute type + ({"S": "not bytes"}, InvalidMaterialDescriptionError, r"Invalid material description"), + # Invalid data: not a DDB attribute + (b"bare bytes", InvalidMaterialDescriptionError, r"Invalid material description"), + # Partial entry + ( + {"B": b"\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01A\x00\x00\x00\x01"}, + InvalidMaterialDescriptionError, + r"Invalid material description", + ), + ), +) def test_deserialize_material_description_errors(data, expected_type, expected_message): with pytest.raises(expected_type) as exc_info: deserialize_material_description(data) diff --git a/test/functional/internal/test_str_ops.py b/test/functional/internal/test_str_ops.py index 785c5b93..704e3e3f 100644 --- a/test/functional/internal/test_str_ops.py +++ b/test/functional/internal/test_str_ops.py @@ -21,24 +21,30 @@ pytestmark = [pytest.mark.functional, pytest.mark.local] -@pytest.mark.parametrize('data, expected_output', ( - ('asdf', 'asdf'), - (b'asdf', 'asdf'), - (codecs.encode(u'Предисловие', 'utf-8'), u'Предисловие'), - (u'Предисловие', u'Предисловие') -)) +@pytest.mark.parametrize( + "data, expected_output", + ( + ("asdf", "asdf"), + (b"asdf", "asdf"), + (codecs.encode(u"Предисловие", "utf-8"), u"Предисловие"), + (u"Предисловие", u"Предисловие"), + ), +) def test_to_str(data, expected_output): test = to_str(data) assert test == expected_output -@pytest.mark.parametrize('data, expected_output', ( - ('asdf', b'asdf'), - (b'asdf', b'asdf'), - (b'\x3a\x00\x99', b'\x3a\x00\x99'), - (u'Предисловие', codecs.encode(u'Предисловие', 'utf-8')), - (codecs.encode(u'Предисловие', 'utf-8'), codecs.encode(u'Предисловие', 'utf-8')) -)) +@pytest.mark.parametrize( + "data, expected_output", + ( + ("asdf", b"asdf"), + (b"asdf", b"asdf"), + (b"\x3a\x00\x99", b"\x3a\x00\x99"), + (u"Предисловие", codecs.encode(u"Предисловие", "utf-8")), + (codecs.encode(u"Предисловие", "utf-8"), codecs.encode(u"Предисловие", "utf-8")), + ), +) def test_to_bytes(data, expected_output): test = to_bytes(data) assert test == expected_output diff --git a/test/functional/material_providers/store/test_meta.py b/test/functional/material_providers/store/test_meta.py index 7d2ea0c1..ac2706d9 100644 --- a/test/functional/material_providers/store/test_meta.py +++ b/test/functional/material_providers/store/test_meta.py @@ -15,8 +15,8 @@ import os import boto3 -from moto import mock_dynamodb2 import pytest +from moto import mock_dynamodb2 from dynamodb_encryption_sdk.material_providers.store.meta import MetaStore @@ -25,13 +25,13 @@ @mock_dynamodb2 def test_create_table(): - client = boto3.client('dynamodb', region_name='us-west-2') - table_name = base64.b64encode(os.urandom(32)).decode('utf-8') + client = boto3.client("dynamodb", region_name="us-west-2") + table_name = base64.b64encode(os.urandom(32)).decode("utf-8") MetaStore.create_table(client, table_name, 1, 1) - waiter = client.get_waiter('table_exists') + waiter = client.get_waiter("table_exists") waiter.wait(TableName=table_name) client.delete_table(TableName=table_name) - waiter = client.get_waiter('table_not_exists') + waiter = client.get_waiter("table_not_exists") waiter.wait(TableName=table_name) diff --git a/test/functional/material_providers/test_most_recent.py b/test/functional/material_providers/test_most_recent.py index 399a7d11..60b082f5 100644 --- a/test/functional/material_providers/test_most_recent.py +++ b/test/functional/material_providers/test_most_recent.py @@ -11,8 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Functional tests for ``dynamodb_encryption_sdk.material_providers.most_recent``.""" -from mock import MagicMock, sentinel import pytest +from mock import MagicMock, sentinel from dynamodb_encryption_sdk.material_providers.most_recent import MostRecentProvider from dynamodb_encryption_sdk.material_providers.store import ProviderStore @@ -22,11 +22,7 @@ def test_failed_lock_acquisition(): store = MagicMock(__class__=ProviderStore) - provider = MostRecentProvider( - provider_store=store, - material_name='my material', - version_ttl=10.0 - ) + provider = MostRecentProvider(provider_store=store, material_name="my material", version_ttl=10.0) provider._version = 9 provider._cache.put(provider._version, sentinel.nine) diff --git a/test/functional/materials/test_raw.py b/test/functional/materials/test_raw.py index 0cc07735..f093f811 100644 --- a/test/functional/materials/test_raw.py +++ b/test/functional/materials/test_raw.py @@ -20,20 +20,20 @@ def test_no_encryption_key(): - signing_key = JceNameLocalDelegatedKey.generate('HmacSHA512', 256) + signing_key = JceNameLocalDelegatedKey.generate("HmacSHA512", 256) encryption_materials = RawEncryptionMaterials(signing_key=signing_key) with pytest.raises(AttributeError) as excinfo: encryption_materials.encryption_key - excinfo.match('No encryption key available') + excinfo.match("No encryption key available") def test_no_decryption_key(): - verification_key = JceNameLocalDelegatedKey.generate('HmacSHA512', 256) + verification_key = JceNameLocalDelegatedKey.generate("HmacSHA512", 256) decryption_materials = RawDecryptionMaterials(verification_key=verification_key) with pytest.raises(AttributeError) as excinfo: decryption_materials.decryption_key - excinfo.match('No decryption key available') + excinfo.match("No decryption key available") diff --git a/test/functional/test_hypothesis_strategies.py b/test/functional/test_hypothesis_strategies.py index d2c7ec2e..3a8d560e 100644 --- a/test/functional/test_hypothesis_strategies.py +++ b/test/functional/test_hypothesis_strategies.py @@ -11,14 +11,15 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Tests to verify that our advanced hypothesis strategies are behaving as expected.""" -from boto3.dynamodb.types import DYNAMODB_CONTEXT import hypothesis import pytest +from boto3.dynamodb.types import DYNAMODB_CONTEXT from dynamodb_encryption_sdk.internal.formatting.deserialize.attribute import deserialize_attribute from dynamodb_encryption_sdk.internal.formatting.serialize.attribute import serialize_attribute -from dynamodb_encryption_sdk.transform import dict_to_ddb, ddb_to_dict -from .hypothesis_strategies import ddb_items, ddb_negative_numbers, ddb_number, ddb_positive_numbers, VERY_SLOW_SETTINGS +from dynamodb_encryption_sdk.transform import ddb_to_dict, dict_to_ddb + +from .hypothesis_strategies import VERY_SLOW_SETTINGS, ddb_items, ddb_negative_numbers, ddb_number, ddb_positive_numbers pytestmark = [pytest.mark.functional, pytest.mark.slow, pytest.mark.local, pytest.mark.hypothesis_strategy] diff --git a/test/functional/test_identifiers.py b/test/functional/test_identifiers.py index 7baa7a59..7693489b 100644 --- a/test/functional/test_identifiers.py +++ b/test/functional/test_identifiers.py @@ -20,49 +20,58 @@ pytestmark = [pytest.mark.functional, pytest.mark.local] -@pytest.mark.parametrize('left, right, expected', ( - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN), - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY, CryptoAction.ENCRYPT_AND_SIGN), - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN), - (CryptoAction.SIGN_ONLY, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN), - (CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY), - (CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING, CryptoAction.SIGN_ONLY), - (CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN), - (CryptoAction.DO_NOTHING, CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY), - (CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING), -)) +@pytest.mark.parametrize( + "left, right, expected", + ( + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN), + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY, CryptoAction.ENCRYPT_AND_SIGN), + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN), + (CryptoAction.SIGN_ONLY, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN), + (CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY), + (CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING, CryptoAction.SIGN_ONLY), + (CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN), + (CryptoAction.DO_NOTHING, CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY), + (CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING), + ), +) def test_item_action_max(left, right, expected): assert max(left, right) == expected -@pytest.mark.parametrize('left, right, expected', ( - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN), - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY), - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING), - (CryptoAction.SIGN_ONLY, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY), - (CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY), - (CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING), - (CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.DO_NOTHING), - (CryptoAction.DO_NOTHING, CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING), - (CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING), -)) +@pytest.mark.parametrize( + "left, right, expected", + ( + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN), + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY), + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING), + (CryptoAction.SIGN_ONLY, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY), + (CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY), + (CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING), + (CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.DO_NOTHING), + (CryptoAction.DO_NOTHING, CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING), + (CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING), + ), +) def test_item_action_min(left, right, expected): assert min(left, right) == expected -@pytest.mark.parametrize('left, right, expected_comparison', ( - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN, operator.eq), - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY, operator.ne), - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY, operator.gt), - (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.DO_NOTHING, operator.gt), - (CryptoAction.SIGN_ONLY, CryptoAction.ENCRYPT_AND_SIGN, operator.lt), - (CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY, operator.eq), - (CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING, operator.ne), - (CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING, operator.gt), - (CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN, operator.lt), - (CryptoAction.DO_NOTHING, CryptoAction.SIGN_ONLY, operator.lt), - (CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING, operator.eq), - (CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN, operator.ne) -)) +@pytest.mark.parametrize( + "left, right, expected_comparison", + ( + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.ENCRYPT_AND_SIGN, operator.eq), + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY, operator.ne), + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.SIGN_ONLY, operator.gt), + (CryptoAction.ENCRYPT_AND_SIGN, CryptoAction.DO_NOTHING, operator.gt), + (CryptoAction.SIGN_ONLY, CryptoAction.ENCRYPT_AND_SIGN, operator.lt), + (CryptoAction.SIGN_ONLY, CryptoAction.SIGN_ONLY, operator.eq), + (CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING, operator.ne), + (CryptoAction.SIGN_ONLY, CryptoAction.DO_NOTHING, operator.gt), + (CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN, operator.lt), + (CryptoAction.DO_NOTHING, CryptoAction.SIGN_ONLY, operator.lt), + (CryptoAction.DO_NOTHING, CryptoAction.DO_NOTHING, operator.eq), + (CryptoAction.DO_NOTHING, CryptoAction.ENCRYPT_AND_SIGN, operator.ne), + ), +) def test_item_action_comp(left, right, expected_comparison): assert expected_comparison(left, right) diff --git a/test/functional/test_structures.py b/test/functional/test_structures.py index 60810961..afe5c0f2 100644 --- a/test/functional/test_structures.py +++ b/test/functional/test_structures.py @@ -17,8 +17,12 @@ from dynamodb_encryption_sdk.exceptions import InvalidArgumentError from dynamodb_encryption_sdk.identifiers import CryptoAction from dynamodb_encryption_sdk.structures import AttributeActions, TableIndex, TableInfo + from .functional_test_utils import ( - example_table, table_with_global_seconary_indexes, table_with_local_seconary_indexes, TEST_TABLE_NAME + TEST_TABLE_NAME, + example_table, + table_with_global_seconary_indexes, + table_with_local_seconary_indexes, ) pytestmark = [pytest.mark.functional, pytest.mark.local] @@ -27,59 +31,51 @@ # TODO: There is a way to parameterize fixtures, but the existing docs on that are very unclear. # This will get us what we need for now, but we should come back to this to clean this up later. def test_tableinfo_refresh_indexes_no_secondary_indexes(example_table): - client = boto3.client('dynamodb', region_name='us-west-2') + client = boto3.client("dynamodb", region_name="us-west-2") table = TableInfo(name=TEST_TABLE_NAME) table.refresh_indexed_attributes(client) def test_tableinfo_refresh_indexes_with_gsis(table_with_global_seconary_indexes): - client = boto3.client('dynamodb', region_name='us-west-2') + client = boto3.client("dynamodb", region_name="us-west-2") table = TableInfo(name=TEST_TABLE_NAME) table.refresh_indexed_attributes(client) def test_tableinfo_refresh_indexes_with_lsis(table_with_local_seconary_indexes): - client = boto3.client('dynamodb', region_name='us-west-2') + client = boto3.client("dynamodb", region_name="us-west-2") table = TableInfo(name=TEST_TABLE_NAME) table.refresh_indexed_attributes(client) -@pytest.mark.parametrize('kwargs, expected_attributes', ( - (dict(partition='partition_name'), set(['partition_name'])), - (dict(partition='partition_name', sort='sort_name'), set(['partition_name', 'sort_name'])) -)) +@pytest.mark.parametrize( + "kwargs, expected_attributes", + ( + (dict(partition="partition_name"), set(["partition_name"])), + (dict(partition="partition_name", sort="sort_name"), set(["partition_name", "sort_name"])), + ), +) def test_tableindex_attributes(kwargs, expected_attributes): index = TableIndex(**kwargs) assert index.attributes == expected_attributes -@pytest.mark.parametrize('key_schema, expected_kwargs', ( +@pytest.mark.parametrize( + "key_schema, expected_kwargs", ( - [ - { - 'KeyType': 'HASH', - 'AttributeName': 'partition_name' - } - ], - dict(partition='partition_name') + ([{"KeyType": "HASH", "AttributeName": "partition_name"}], dict(partition="partition_name")), + ( + [ + {"KeyType": "HASH", "AttributeName": "partition_name"}, + {"KeyType": "RANGE", "AttributeName": "sort_name"}, + ], + dict(partition="partition_name", sort="sort_name"), + ), ), - ( - [ - { - 'KeyType': 'HASH', - 'AttributeName': 'partition_name' - }, - { - 'KeyType': 'RANGE', - 'AttributeName': 'sort_name' - } - ], - dict(partition='partition_name', sort='sort_name') - ) -)) +) def test_tableindex_from_key_schema(key_schema, expected_kwargs): index = TableIndex.from_key_schema(key_schema) expected_index = TableIndex(**expected_kwargs) @@ -87,32 +83,34 @@ def test_tableindex_from_key_schema(key_schema, expected_kwargs): assert index == expected_index -@pytest.mark.parametrize('default, overrides, expected_result', ( - (CryptoAction.ENCRYPT_AND_SIGN, {}, CryptoAction.SIGN_ONLY), - (CryptoAction.SIGN_ONLY, {}, CryptoAction.SIGN_ONLY), - (CryptoAction.DO_NOTHING, {}, CryptoAction.DO_NOTHING), - (CryptoAction.ENCRYPT_AND_SIGN, {'indexed_attribute': CryptoAction.SIGN_ONLY}, CryptoAction.SIGN_ONLY), - (CryptoAction.ENCRYPT_AND_SIGN, {'indexed_attribute': CryptoAction.DO_NOTHING}, CryptoAction.DO_NOTHING), - (CryptoAction.SIGN_ONLY, {'indexed_attribute': CryptoAction.SIGN_ONLY}, CryptoAction.SIGN_ONLY), - (CryptoAction.SIGN_ONLY, {'indexed_attribute': CryptoAction.DO_NOTHING}, CryptoAction.DO_NOTHING), - (CryptoAction.DO_NOTHING, {'indexed_attribute': CryptoAction.SIGN_ONLY}, CryptoAction.SIGN_ONLY), - (CryptoAction.DO_NOTHING, {'indexed_attribute': CryptoAction.DO_NOTHING}, CryptoAction.DO_NOTHING) -)) +@pytest.mark.parametrize( + "default, overrides, expected_result", + ( + (CryptoAction.ENCRYPT_AND_SIGN, {}, CryptoAction.SIGN_ONLY), + (CryptoAction.SIGN_ONLY, {}, CryptoAction.SIGN_ONLY), + (CryptoAction.DO_NOTHING, {}, CryptoAction.DO_NOTHING), + (CryptoAction.ENCRYPT_AND_SIGN, {"indexed_attribute": CryptoAction.SIGN_ONLY}, CryptoAction.SIGN_ONLY), + (CryptoAction.ENCRYPT_AND_SIGN, {"indexed_attribute": CryptoAction.DO_NOTHING}, CryptoAction.DO_NOTHING), + (CryptoAction.SIGN_ONLY, {"indexed_attribute": CryptoAction.SIGN_ONLY}, CryptoAction.SIGN_ONLY), + (CryptoAction.SIGN_ONLY, {"indexed_attribute": CryptoAction.DO_NOTHING}, CryptoAction.DO_NOTHING), + (CryptoAction.DO_NOTHING, {"indexed_attribute": CryptoAction.SIGN_ONLY}, CryptoAction.SIGN_ONLY), + (CryptoAction.DO_NOTHING, {"indexed_attribute": CryptoAction.DO_NOTHING}, CryptoAction.DO_NOTHING), + ), +) def test_attribute_actions_index_override(default, overrides, expected_result): test = AttributeActions(default_action=default, attribute_actions=overrides) - test.set_index_keys('indexed_attribute') + test.set_index_keys("indexed_attribute") - assert test.action('indexed_attribute') is expected_result + assert test.action("indexed_attribute") is expected_result -@pytest.mark.parametrize('default', CryptoAction) +@pytest.mark.parametrize("default", CryptoAction) def test_attribute_actions_index_override_fail(default): test = AttributeActions( - default_action=default, - attribute_actions={'indexed_attribute': CryptoAction.ENCRYPT_AND_SIGN} + default_action=default, attribute_actions={"indexed_attribute": CryptoAction.ENCRYPT_AND_SIGN} ) with pytest.raises(InvalidArgumentError) as excinfo: - test.set_index_keys('indexed_attribute') + test.set_index_keys("indexed_attribute") - excinfo.match(r'Cannot overwrite a previously requested action on indexed attribute: *') + excinfo.match(r"Cannot overwrite a previously requested action on indexed attribute: *") diff --git a/test/integration/encrypted/test_client.py b/test/integration/encrypted/test_client.py index 965baa41..4d75d248 100644 --- a/test/integration/encrypted/test_client.py +++ b/test/integration/encrypted/test_client.py @@ -13,8 +13,11 @@ """Integration tests for ``dynamodb_encryption_sdk.encrypted.client``.""" import pytest -from ..integration_test_utils import aws_kms_cmp, ddb_table_name # noqa pylint: disable=unused-import -from ..integration_test_utils import functional_test_utils +from ..integration_test_utils import ( # noqa pylint: disable=unused-import + aws_kms_cmp, + ddb_table_name, + functional_test_utils, +) pytestmark = [pytest.mark.integ, pytest.mark.ddb_integ] @@ -28,38 +31,26 @@ def pytest_generate_tests(metafunc): def test_ephemeral_item_cycle(ddb_table_name, some_cmps, parametrized_actions, parametrized_item): """Test a small number of curated CMPs against a small number of curated items.""" functional_test_utils.client_cycle_single_item_check( - some_cmps, - parametrized_actions, - parametrized_item, - ddb_table_name + some_cmps, parametrized_actions, parametrized_item, ddb_table_name ) def test_ephemeral_item_cycle_kms(ddb_table_name, aws_kms_cmp, parametrized_actions, parametrized_item): """Test the AWS KMS CMP against a small number of curated items.""" functional_test_utils.client_cycle_single_item_check( - aws_kms_cmp, - parametrized_actions, - parametrized_item, - ddb_table_name + aws_kms_cmp, parametrized_actions, parametrized_item, ddb_table_name ) def test_ephemeral_batch_item_cycle(ddb_table_name, some_cmps, parametrized_actions, parametrized_item): """Test a small number of curated CMPs against a small number of curated items.""" functional_test_utils.client_cycle_batch_items_check( - some_cmps, - parametrized_actions, - parametrized_item, - ddb_table_name + some_cmps, parametrized_actions, parametrized_item, ddb_table_name ) def test_ephemeral_batch_item_cycle_kms(ddb_table_name, aws_kms_cmp, parametrized_actions, parametrized_item): """Test the AWS KMS CMP against a small number of curated items.""" functional_test_utils.client_cycle_batch_items_check( - aws_kms_cmp, - parametrized_actions, - parametrized_item, - ddb_table_name + aws_kms_cmp, parametrized_actions, parametrized_item, ddb_table_name ) diff --git a/test/integration/encrypted/test_resource.py b/test/integration/encrypted/test_resource.py index 7f712950..a96ef764 100644 --- a/test/integration/encrypted/test_resource.py +++ b/test/integration/encrypted/test_resource.py @@ -13,8 +13,11 @@ """Integration tests for ``dynamodb_encryption_sdk.encrypted.resource``.""" import pytest -from ..integration_test_utils import aws_kms_cmp, ddb_table_name # noqa pylint: disable=unused-import -from ..integration_test_utils import functional_test_utils +from ..integration_test_utils import ( # noqa pylint: disable=unused-import + aws_kms_cmp, + ddb_table_name, + functional_test_utils, +) pytestmark = [pytest.mark.integ, pytest.mark.ddb_integ] @@ -28,18 +31,12 @@ def pytest_generate_tests(metafunc): def test_ephemeral_batch_item_cycle(ddb_table_name, some_cmps, parametrized_actions, parametrized_item): """Test a small number of curated CMPs against a small number of curated items.""" functional_test_utils.resource_cycle_batch_items_check( - some_cmps, - parametrized_actions, - parametrized_item, - ddb_table_name + some_cmps, parametrized_actions, parametrized_item, ddb_table_name ) def test_ephemeral_batch_item_cycle_kms(ddb_table_name, aws_kms_cmp, parametrized_actions, parametrized_item): """Test the AWS KMS CMP against a small number of curated items.""" functional_test_utils.resource_cycle_batch_items_check( - aws_kms_cmp, - parametrized_actions, - parametrized_item, - ddb_table_name + aws_kms_cmp, parametrized_actions, parametrized_item, ddb_table_name ) diff --git a/test/integration/encrypted/test_table.py b/test/integration/encrypted/test_table.py index 0881cc9b..5bb17661 100644 --- a/test/integration/encrypted/test_table.py +++ b/test/integration/encrypted/test_table.py @@ -13,8 +13,11 @@ """Integration tests for ``dynamodb_encryption_sdk.encrypted.table``.""" import pytest -from ..integration_test_utils import aws_kms_cmp, ddb_table_name # noqa pylint: disable=unused-import -from ..integration_test_utils import functional_test_utils +from ..integration_test_utils import ( # noqa pylint: disable=unused-import + aws_kms_cmp, + ddb_table_name, + functional_test_utils, +) pytestmark = [pytest.mark.integ, pytest.mark.ddb_integ] @@ -33,10 +36,7 @@ def test_ephemeral_item_cycle(ddb_table_name, some_cmps, parametrized_actions, p def test_ephemeral_item_cycle_batch_writer(ddb_table_name, some_cmps, parametrized_actions, parametrized_item): """Test a small number of curated CMPs against a small number of curated items.""" functional_test_utils.table_cycle_batch_writer_check( - some_cmps, - parametrized_actions, - parametrized_item, - ddb_table_name + some_cmps, parametrized_actions, parametrized_item, ddb_table_name ) diff --git a/test/integration/integration_test_utils.py b/test/integration/integration_test_utils.py index edd699f7..f63729e8 100644 --- a/test/integration/integration_test_utils.py +++ b/test/integration/integration_test_utils.py @@ -12,24 +12,16 @@ # language governing permissions and limitations under the License. """Helper utilities for integration tests.""" import os -import sys import pytest from dynamodb_encryption_sdk.material_providers.aws_kms import AwsKmsCryptographicMaterialsProvider -sys.path.append(os.path.join( - os.path.abspath(os.path.dirname(__file__)), - '..', - 'functional' -)) +# convenience imports +from ..functional import functional_test_utils, hypothesis_strategies -# Convenience imports -import functional_test_utils # noqa: E402,F401,I100 pylint: disable=import-error,unused-import,wrong-import-position -import hypothesis_strategies # noqa: E402,F401,I100 pylint: disable=import-error,unused-import,wrong-import-position - -AWS_KMS_KEY_ID = 'AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID' -DDB_TABLE_NAME = 'DDB_ENCRYPTION_CLIENT_TEST_TABLE_NAME' +AWS_KMS_KEY_ID = "AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID" +DDB_TABLE_NAME = "DDB_ENCRYPTION_CLIENT_TEST_TABLE_NAME" @pytest.fixture @@ -42,9 +34,9 @@ def cmk_arn(): AWS_KMS_KEY_ID ) ) - if arn.startswith('arn:') and ':alias/' not in arn: + if arn.startswith("arn:") and ":alias/" not in arn: return arn - raise ValueError('KMS CMK ARN provided for integration tests must be a key not an alias') + raise ValueError("KMS CMK ARN provided for integration tests must be a key not an alias") @pytest.fixture @@ -61,8 +53,6 @@ def ddb_table_name(): raise ValueError( ( 'Environment variable "{}" must be set to the correct DynamoDB table name' - ' for integration tests to run' - ).format( - AWS_KMS_KEY_ID - ) + " for integration tests to run" + ).format(AWS_KMS_KEY_ID) ) diff --git a/test/integration/material_providers/store/test_meta.py b/test/integration/material_providers/store/test_meta.py index 075f0c38..f2cceb2b 100644 --- a/test/integration/material_providers/store/test_meta.py +++ b/test/integration/material_providers/store/test_meta.py @@ -19,5 +19,3 @@ from dynamodb_encryption_sdk.material_providers.store.meta import MetaStore pytestmark = [pytest.mark.integ] - - diff --git a/test/integration/material_providers/test_aws_kms.py b/test/integration/material_providers/test_aws_kms.py index dd4b5b5a..0a6e93d9 100644 --- a/test/integration/material_providers/test_aws_kms.py +++ b/test/integration/material_providers/test_aws_kms.py @@ -11,23 +11,24 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Integration tests for ``dynamodb_encryption_sdk.material_providers.aws_kms``.""" -import logging import itertools +import logging -from boto3.dynamodb.types import Binary import hypothesis import pytest +from boto3.dynamodb.types import Binary from dynamodb_encryption_sdk.encrypted import CryptoConfig -from dynamodb_encryption_sdk.identifiers import CryptoAction, USER_AGENT_SUFFIX +from dynamodb_encryption_sdk.identifiers import USER_AGENT_SUFFIX, CryptoAction from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext from dynamodb_encryption_sdk.transform import dict_to_ddb + from ..integration_test_utils import aws_kms_cmp # noqa pylint: disable=unused-import from ..integration_test_utils import functional_test_utils, hypothesis_strategies pytestmark = pytest.mark.integ -_primary_key_names = ('partition_key', 'sort_key') +_primary_key_names = ("partition_key", "sort_key") def pytest_generate_tests(metafunc): @@ -44,39 +45,32 @@ def test_verify_user_agent(aws_kms_cmp, caplog): def _many_items(): - values = ('a string', 1234, Binary(b'binary \x00\x88 value')) - partition_keys = (('partition_key', value) for value in values) - sort_keys = (('sort_key', value) for value in values) + values = ("a string", 1234, Binary(b"binary \x00\x88 value")) + partition_keys = (("partition_key", value) for value in values) + sort_keys = (("sort_key", value) for value in values) for pairs in itertools.product(partition_keys, sort_keys): item = dict(pairs) yield pytest.param(item, id=str(item)) -@pytest.mark.parametrize('item', _many_items()) +@pytest.mark.parametrize("item", _many_items()) def test_aws_kms_diverse_indexes(aws_kms_cmp, item): """Verify that AWS KMS cycle works for items with all possible combinations for primary index attribute types.""" crypto_config = CryptoConfig( materials_provider=aws_kms_cmp, encryption_context=EncryptionContext( - partition_key_name='partition_key', - sort_key_name='sort_key', - attributes=dict_to_ddb(item) + partition_key_name="partition_key", sort_key_name="sort_key", attributes=dict_to_ddb(item) ), attribute_actions=AttributeActions( - attribute_actions={ - key: CryptoAction.SIGN_ONLY - for key in _primary_key_names - } - ) + attribute_actions={key: CryptoAction.SIGN_ONLY for key in _primary_key_names} + ), ) functional_test_utils.cycle_item_check(item, crypto_config) def test_aws_kms_item_cycle(aws_kms_cmp, parametrized_actions, parametrized_item): crypto_config = CryptoConfig( - materials_provider=aws_kms_cmp, - encryption_context=EncryptionContext(), - attribute_actions=parametrized_actions + materials_provider=aws_kms_cmp, encryption_context=EncryptionContext(), attribute_actions=parametrized_actions ) functional_test_utils.cycle_item_check(parametrized_item, crypto_config) @@ -86,9 +80,7 @@ def test_aws_kms_item_cycle(aws_kms_cmp, parametrized_actions, parametrized_item @hypothesis.given(item=hypothesis_strategies.ddb_items) def test_aws_kms_item_cycle_hypothesis_slow(aws_kms_cmp, hypothesis_actions, item): crypto_config = CryptoConfig( - materials_provider=aws_kms_cmp, - encryption_context=EncryptionContext(), - attribute_actions=hypothesis_actions + materials_provider=aws_kms_cmp, encryption_context=EncryptionContext(), attribute_actions=hypothesis_actions ) functional_test_utils.cycle_item_check(item, crypto_config) @@ -98,8 +90,6 @@ def test_aws_kms_item_cycle_hypothesis_slow(aws_kms_cmp, hypothesis_actions, ite @hypothesis.given(item=hypothesis_strategies.ddb_items) def test_aws_kms_item_cycle_hypothesis_veryslow(aws_kms_cmp, hypothesis_actions, item): crypto_config = CryptoConfig( - materials_provider=aws_kms_cmp, - encryption_context=EncryptionContext(), - attribute_actions=hypothesis_actions + materials_provider=aws_kms_cmp, encryption_context=EncryptionContext(), attribute_actions=hypothesis_actions ) functional_test_utils.cycle_item_check(item, crypto_config) diff --git a/test/pylintrc b/test/pylintrc index 27d82a9c..bb25d2dc 100644 --- a/test/pylintrc +++ b/test/pylintrc @@ -4,12 +4,15 @@ # # C0103 : invalid-name (we prefer long, descriptive, names for tests) # C0111 : missing-docstring (we don't write docstrings for tests) +# C0330 : bad-continuation (we let black handle this) +# C0412 : ungrouped-imports (we let isort handle this) # E1101 : no-member (raised on patched objects with mock checks) +# R0205 : useless-object-inheritance (we need to support Python 2, so no, not useless) # R0801 : duplicate-code (unit tests for similar things tend to be similar) # W0212 : protected-access (raised when calling _ methods) # W0621 : redefined-outer-name (raised when using pytest-mock) # W0613 : unused-argument (raised when patches and fixtures are needed but not called) -disable = C0103, C0111, E1101, R0801, W0212, W0621, W0613 +disable = C0103, C0111, C0330, C0412, E1101, R0205, R0801, W0212, W0621, W0613 [DESIGN] max-args = 10 diff --git a/test/unit/encrypted/test_encrypted.py b/test/unit/encrypted/test_encrypted.py index ec736f9e..9c2917aa 100644 --- a/test/unit/encrypted/test_encrypted.py +++ b/test/unit/encrypted/test_encrypted.py @@ -15,6 +15,7 @@ from dynamodb_encryption_sdk.encrypted import CryptoConfig from dynamodb_encryption_sdk.structures import AttributeActions, EncryptionContext + from ..unit_test_utils import wrapped_cmp # noqa pylint: disable=unused-import pytestmark = [pytest.mark.unit, pytest.mark.local] @@ -24,12 +25,9 @@ def test_with_item(wrapped_cmp): config = CryptoConfig( materials_provider=wrapped_cmp, encryption_context=EncryptionContext(attributes={}), - attribute_actions=AttributeActions() + attribute_actions=AttributeActions(), ) - item = { - 'test': 'item', - 'with': 'some data' - } + item = {"test": "item", "with": "some data"} new_config = config.with_item(item) assert config.encryption_context.attributes == {} diff --git a/test/unit/internal/formatting/test_attribute.py b/test/unit/internal/formatting/test_attribute.py index fae2b415..71235e0c 100644 --- a/test/unit/internal/formatting/test_attribute.py +++ b/test/unit/internal/formatting/test_attribute.py @@ -20,25 +20,22 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] -@pytest.mark.parametrize('initial, expected, transform', ( +@pytest.mark.parametrize( + "initial, expected, transform", ( - { - 'test': 'value', - 'zzz': 'another', - 'aaa': 'qqq', - '?>?>?': 5, - b'\x00\x00': None - }, - [ - (b'\x00\x00', None, b'\x00\x00'), - (b'?>?>?', 5, '?>?>?'), - (b'aaa', 'qqq', 'aaa'), - (b'test', 'value', 'test'), - (b'zzz', 'another', 'zzz') - ], - to_bytes + ( + {"test": "value", "zzz": "another", "aaa": "qqq", "?>?>?": 5, b"\x00\x00": None}, + [ + (b"\x00\x00", None, b"\x00\x00"), + (b"?>?>?", 5, "?>?>?"), + (b"aaa", "qqq", "aaa"), + (b"test", "value", "test"), + (b"zzz", "another", "zzz"), + ], + to_bytes, + ), ), -)) +) def test_sorted_key_map(initial, expected, transform): actual = _sorted_key_map(initial, transform) diff --git a/test/unit/material_providers/__init__.py b/test/unit/material_providers/__init__.py index 5eb0e67b..48f88c23 100644 --- a/test/unit/material_providers/__init__.py +++ b/test/unit/material_providers/__init__.py @@ -18,15 +18,15 @@ pytestmark = [pytest.mark.unit, pytest.mark.local] -@pytest.mark.parametrize('method, message', ( - ('decryption_materials', 'No decryption materials available'), - ('encryption_materials', 'No encryption materials available') -)) +@pytest.mark.parametrize( + "method, message", + ( + ("decryption_materials", "No decryption materials available"), + ("encryption_materials", "No encryption materials available"), + ), +) def test_no_materials(method, message): - empty_cmp = CryptographicMaterialsProvider( - decryption_materials=None, - encryption_materials=None - ) + empty_cmp = CryptographicMaterialsProvider(decryption_materials=None, encryption_materials=None) with pytest.raises(AttributeError) as excinfo: getattr(empty_cmp, method)(None) diff --git a/test/unit/material_providers/test_aws_kms.py b/test/unit/material_providers/test_aws_kms.py index c8109a4e..a1166dba 100644 --- a/test/unit/material_providers/test_aws_kms.py +++ b/test/unit/material_providers/test_aws_kms.py @@ -12,136 +12,131 @@ # language governing permissions and limitations under the License. """Unit tests for ``dynamodb_encryption_sdk.material_providers.aws_kms``.""" import base64 -from mock import MagicMock, sentinel import boto3 import botocore -from moto import mock_kms import pytest +from mock import MagicMock, sentinel +from moto import mock_kms from pytest_mock import mocker # noqa pylint: disable=unused-import +import dynamodb_encryption_sdk.material_providers.aws_kms from dynamodb_encryption_sdk.delegated_keys.jce import JceNameLocalDelegatedKey from dynamodb_encryption_sdk.exceptions import UnknownRegionError, UnwrappingError, WrappingError from dynamodb_encryption_sdk.identifiers import EncryptionKeyType, KeyEncodingType -import dynamodb_encryption_sdk.material_providers.aws_kms from dynamodb_encryption_sdk.material_providers.aws_kms import ( - _DEFAULT_CONTENT_ENCRYPTION_ALGORITHM, _DEFAULT_SIGNING_ALGORITHM, - AwsKmsCryptographicMaterialsProvider, KeyInfo + _DEFAULT_CONTENT_ENCRYPTION_ALGORITHM, + _DEFAULT_SIGNING_ALGORITHM, + AwsKmsCryptographicMaterialsProvider, + KeyInfo, ) from dynamodb_encryption_sdk.structures import EncryptionContext pytestmark = [pytest.mark.unit, pytest.mark.local] -_VALID_KEY_INFO_KWARGS = dict( - description='some string', - algorithm='algorithm name', - length=1234 -) -_REGION = 'fake-region' -_KEY_ID = 'arn:aws:kms:{}:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab'.format(_REGION) +_VALID_KEY_INFO_KWARGS = dict(description="some string", algorithm="algorithm name", length=1234) +_REGION = "fake-region" +_KEY_ID = "arn:aws:kms:{}:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab".format(_REGION) _DERIVED_KEYS = { - 'initial_material': b'\xafx2"\xb5\xd5`\xc6\x8d\xaa\xfe\xc10E3x?D\x18\x93$<\x161\xcb\x99\xef\xc0Z\x1a\x1b]', - 'encrypted_initial_material': ( + "initial_material": b'\xafx2"\xb5\xd5`\xc6\x8d\xaa\xfe\xc10E3x?D\x18\x93$<\x161\xcb\x99\xef\xc0Z\x1a\x1b]', + "encrypted_initial_material": ( b"\x01\x01\x02\x00x@\xf3\x8c'^1\tt\x16\xc1\x07)QPW\x19d\xad\xa3\xef\x1c!\xe9L\x8b\xa0\xbd\xbc\x9d\x0f\xb4\x14" b"\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" b"\x1e\x06\t`\x86H\x01e\x03\x04\x01.0\x11\x04\x0c-\xc0&\x1f\xeb_\xdek\xca/$y\x02\x01\x10\x80;!\x99z\xbek3|\x8b" b"\x98\x1b\xba\x91H<\xb1X\x8c\xc7vGv\x84*\xe1\xf1B\xd4\xe5&\xa2\xa3)\x04\x1f\xad\t\x07\x90\x14\xbeQo\xa0\xff" b"\x1a\xc2\xa5(i\x0c4\x10\xe8\xe2\xf3\x17}\t\xd6" ), # encrypted using our public-test CMK in us-west-2 - 'encryption_key': b'\xb3~{,Z\x80\x7f\x82I\xe5