Source code for dynamodb_encryption_sdk.materials.wrapped

# 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.
"""Cryptographic materials to use ephemeral content encryption keys wrapped by delegated keys."""
import base64
import copy

import attr
import six

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
from dynamodb_encryption_sdk.identifiers import EncryptionKeyType
from dynamodb_encryption_sdk.internal.identifiers import MaterialDescriptionKeys
from dynamodb_encryption_sdk.internal.validators import dictionary_validator
from dynamodb_encryption_sdk.materials import CryptographicMaterials

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


[docs]@attr.s(init=False) class WrappedCryptographicMaterials(CryptographicMaterials): """Encryption/decryption key is a content key stored in the material description, wrapped by the wrapping key. :param DelegatedKey signing_key: Delegated key used as signing and verification key :param DelegatedKey wrapping_key: Delegated key used to wrap content key .. note:: ``wrapping_key`` must be provided if material description contains a wrapped content key :param DelegatedKey unwrapping_key: Delegated key used to unwrap content key .. note:: ``unwrapping_key`` must be provided if material description does not contain a wrapped content key :param dict material_description: Material description to use with these cryptographic materials """ _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) _unwrapping_key = attr.ib( 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), ) 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]] ): # noqa=D107 # type: (...) -> None # Workaround pending resolution of attrs/mypy interaction. # https://github.com/python/mypy/issues/2088 # https://github.com/python-attrs/attrs/issues/215 if material_description is None: material_description = {} self._signing_key = signing_key self._wrapping_key = wrapping_key self._unwrapping_key = unwrapping_key self._material_description = material_description attr.validate(self) self.__attrs_post_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 ) 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 else: ( self._content_key, self._material_description, ) = self._generate_content_key() # noqa pylint: disable=attribute-defined-outside-init @staticmethod def _wrapping_transformation(algorithm): """Convert the specified algorithm name to the desired wrapping algorithm transformation. :param str algorithm: Algorithm name :returns: Algorithm transformation for wrapping with algorithm :rtype: str """ return _WRAPPING_TRANSFORMATION.get(algorithm, algorithm) def _content_key_from_material_description(self): """Load the content key from material description and unwrap it for use. :returns: Unwrapped content key :rtype: DelegatedKey """ if self._unwrapping_key is None: raise UnwrappingError( "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 ) 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, ) def _generate_content_key(self): """Generate the content encryption key and create a new material description containing necessary information about the content and wrapping keys. :returns content key and new material description :rtype: tuple containing DelegatedKey and dict """ if self._wrapping_key is None: 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), ) 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) wrapped_key = self._wrapping_key.wrap( 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, } ) return content_key, new_material_description @property def material_description(self): # type: () -> Dict[Text, Text] """Material description to use with these cryptographic materials. :returns: Material description :rtype: dict """ return self._material_description @property def encryption_key(self): """Content key used for encrypting attributes. :returns: Encryption key :rtype: DelegatedKey """ return self._content_key @property def decryption_key(self): """Content key used for decrypting attributes. :returns: Decryption key :rtype: DelegatedKey """ return self._content_key @property def signing_key(self): """Delegated key used for calculating digital signatures. :returns: Signing key :rtype: DelegatedKey """ return self._signing_key @property def verification_key(self): """Delegated key used for verifying digital signatures. :returns: Verification key :rtype: DelegatedKey """ return self._signing_key