Source code for dynamodb_encryption_sdk.internal.crypto.authentication

# 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.
"""Functions to handle calculating and verifying signatures of encrypted items.

.. warning::
    No guarantee is provided on the modules and APIs within this
    namespace staying consistent. Directly reference at your own risk.
"""
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
except ImportError:  # pragma: no cover
    # We only actually need these imports when running the mypy checks
    pass


__all__ = ("sign_item", "verify_item_signature")


[docs]def sign_item(encrypted_item, signing_key, crypto_config): # type: (dynamodb_types.ITEM, DelegatedKey, CryptoConfig) -> dynamodb_types.BINARY_ATTRIBUTE """Generate the signature DynamoDB atttribute. :param dict encrypted_item: Encrypted DynamoDB item :param DelegatedKey signing_key: DelegatedKey to use to calculate the signature :param CryptoConfig crypto_config: Cryptographic configuration :returns: Item signature DynamoDB attribute value :rtype: dict """ signature = signing_key.sign( algorithm=signing_key.algorithm, data=_string_to_sign( item=encrypted_item, table_name=crypto_config.encryption_context.table_name, attribute_actions=crypto_config.attribute_actions, ), ) # for some reason pylint can't follow the Enum member attributes return {Tag.BINARY.dynamodb_tag: signature} # pylint: disable=no-member
[docs]def verify_item_signature(signature_attribute, encrypted_item, verification_key, crypto_config): # type: (dynamodb_types.BINARY_ATTRIBUTE, dynamodb_types.ITEM, DelegatedKey, CryptoConfig) -> None """Verify the item signature. :param dict signature_attribute: Item signature DynamoDB attribute value :param dict encrypted_item: Encrypted DynamoDB item :param DelegatedKey verification_key: DelegatedKey to use to calculate the signature :param CryptoConfig crypto_config: Cryptographic configuration """ # for some reason pylint can't follow the Enum member attributes signature = signature_attribute[Tag.BINARY.dynamodb_tag] # pylint: disable=no-member verification_key.verify( algorithm=verification_key.algorithm, signature=signature, data=_string_to_sign( item=encrypted_item, table_name=crypto_config.encryption_context.table_name, attribute_actions=crypto_config.attribute_actions, ), )
def _string_to_sign(item, table_name, attribute_actions): # type: (dynamodb_types.ITEM, Text, AttributeActions) -> bytes """Generate the string to sign from an encrypted item and configuration. :param dict item: Encrypted DynamoDB item :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()) data_to_sign = bytearray() data_to_sign.extend(_hash_data(hasher=hasher, data="TABLE>{}<TABLE".format(table_name).encode(TEXT_ENCODING))) for key in sorted(item.keys()): action = attribute_actions.action(key) if action is CryptoAction.DO_NOTHING: continue data_to_sign.extend(_hash_data(hasher=hasher, data=key.encode(TEXT_ENCODING))) # for some reason pylint can't follow the Enum member attributes if action is CryptoAction.SIGN_ONLY: data_to_sign.extend(SignatureValues.PLAINTEXT.sha256) # pylint: disable=no-member else: data_to_sign.extend(SignatureValues.ENCRYPTED.sha256) # pylint: disable=no-member data_to_sign.extend(_hash_data(hasher=hasher, data=serialize_attribute(item[key]))) return bytes(data_to_sign) def _hash_data(hasher, data): """Generate hash of data using provided hash type. :param hasher: Hasher instance to use as a base for calculating hash :type hasher: cryptography.hazmat.primitives.hashes.Hash :param bytes data: Data to sign :returns: Hash of data :rtype: bytes """ _hasher = hasher.copy() _hasher.update(data) return _hasher.finalize()