Source code for dynamodb_encryption_sdk.delegated_keys.jce

# 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.
"""Delegated key that uses JCE StandardName values to determine behavior."""
from __future__ import division

import logging
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

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
except ImportError:  # pragma: no cover
    # We only actually need these imports when running the mypy checks
    pass


__all__ = ("JceNameLocalDelegatedKey",)
_LOGGER = logging.getLogger(LOGGER_NAME)


def _generate_symmetric_key(key_length):
    """Generate a new AES key.

    :param int key_length: Required key length in bits
    :returns: raw key, symmetric key identifier, and RAW encoding identifier
    :rtype: tuple(bytes, :class:`EncryptionKeyType`, :class:`KeyEncodingType`)
    """
    return os.urandom(key_length // 8), EncryptionKeyType.SYMMETRIC, KeyEncodingType.RAW


def _generate_rsa_key(key_length):
    """Generate a new RSA private key.

    :param int key_length: Required key length in bits
    :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())
    key_bytes = private_key.private_bytes(
        encoding=serialization.Encoding.DER,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )
    return key_bytes, EncryptionKeyType.PRIVATE, KeyEncodingType.DER


_ALGORITHM_GENERATE_MAP = {"SYMMETRIC": _generate_symmetric_key, "RSA": _generate_rsa_key}


[docs]@attr.s(init=False) class JceNameLocalDelegatedKey(DelegatedKey): # pylint: disable=too-many-instance-attributes """Delegated key that uses JCE StandardName values to determine behavior. Accepted algorithm names for this include: * `JCE Mac names`_ (for a signing key) * **HmacSHA512** * **HmacSHA256** * **HmacSHA384** * **HmacSHA224** * `JCE Signature names`_ (for a signing key) * **SHA512withRSA** * **SHA256withRSA** * **SHA384withRSA** * **SHA224withRSA** * `JCE Cipher names`_ (for an encryption key) * **RSA** * **AES** * **AESWrap** .. _JCE Mac names: https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Mac .. _JCE Signature names: https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature .. _JCE Cipher names: https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher :param bytes key: Raw key bytes :param str algorithm: JCE Standard Algorithm Name :param EncryptionKeyType key_type: Identifies what type of key is being provided :param KeyEncodingType key_encoding: Identifies how the provided key is encoded """ key = attr.ib(validator=attr.validators.instance_of(bytes), repr=False) _algorithm = attr.ib(validator=attr.validators.instance_of(six.string_types)) _key_type = attr.ib(validator=attr.validators.instance_of(EncryptionKeyType)) _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 ): # 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 self.key = key self._algorithm = algorithm self._key_type = key_type self._key_encoding = key_encoding attr.validate(self) self.__attrs_post_init__() @property def algorithm(self): # type: () -> Text """Text description of algorithm used by this delegated key.""" return self._algorithm def _enable_authentication(self): # () -> None """Enable authentication methods for keys that support them.""" self.sign = self._sign self.verify = self._verify self.signing_algorithm = self._signing_algorithm def _enable_encryption(self): # () -> None """Enable encryption methods for keys that support them.""" self.encrypt = self._encrypt self.decrypt = self._decrypt def _enable_wrap(self): # () -> None """Enable key wrapping methods for keys that support them.""" self.wrap = self._wrap self.unwrap = self._unwrap def __attrs_post_init__(self): # () -> None """Identify the correct key handler class for the requested algorithm and load the provided key.""" # First try for encryption ciphers # https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html try: key_transformer = primitives.JAVA_ENCRYPTION_ALGORITHM[self.algorithm] except KeyError: 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._enable_encryption() self._enable_wrap() return # Now try for authenticators # https://docs.oracle.com/javase/8/docs/api/javax/crypto/Mac.html # https://docs.oracle.com/javase/8/docs/api/java/security/Signature.html try: key_transformer = authentication.JAVA_AUTHENTICATOR[self.algorithm] except KeyError: 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._enable_authentication() return raise JceTransformationError('Unknown algorithm: "{}"'.format(self.algorithm))
[docs] @classmethod def generate(cls, algorithm, key_length=None): # type: (Text, Optional[int]) -> JceNameLocalDelegatedKey """Generate an instance of this :class:`DelegatedKey` using the specified algorithm and key length. :param str algorithm: Text description of algorithm to be used :param int key_length: Size in bits of key to generate :returns: Generated delegated key :rtype: DelegatedKey """ # 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" try: key_generator = _ALGORITHM_GENERATE_MAP[algorithm_lookup] except KeyError: 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)
@property def allowed_for_raw_materials(self): # type: () -> bool """Only :class:`JceNameLocalDelegatedKey` backed by AES keys are allowed to be used with :class:`RawDecryptionMaterials` or :class:`RawEncryptionMaterials`. :returns: decision :rtype: bool """ return self.algorithm == "AES" def _encrypt(self, algorithm, name, plaintext, additional_associated_data=None): # type: (Text, Text, bytes, Optional[Dict[Text, Text]]) -> bytes # pylint: disable=unused-argument """ Encrypt data. :param str algorithm: Java StandardName transformation string of algorithm to use to encrypt data https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html :param str name: Name associated with plaintext data :param bytes plaintext: Plaintext data to encrypt :param dict additional_associated_data: Not used by all delegated keys, but if it is, then if it is provided on encrypt it must be required on decrypt. :returns: Encrypted ciphertext :rtype: bytes """ encryptor = encryption.JavaCipher.from_transformation(algorithm) return encryptor.encrypt(self.__key, plaintext) def _decrypt(self, algorithm, name, ciphertext, additional_associated_data=None): # type: (Text, Text, bytes, Optional[Dict[Text, Text]]) -> bytes # pylint: disable=unused-argument """Encrypt data. :param str algorithm: Java StandardName transformation string of algorithm to use to decrypt data https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html :param str name: Name associated with ciphertext data :param bytes ciphertext: Ciphertext data to decrypt :param dict additional_associated_data: Not used by :class:`JceNameLocalDelegatedKey` :returns: Decrypted plaintext :rtype: bytes """ decryptor = encryption.JavaCipher.from_transformation(algorithm) return decryptor.decrypt(self.__key, ciphertext) def _wrap(self, algorithm, content_key, additional_associated_data=None): # type: (Text, bytes, Optional[Dict[Text, Text]]) -> bytes # pylint: disable=unused-argument """Wrap content key. :param str algorithm: Text description of algorithm to use to wrap key :param bytes content_key: Raw content key to wrap :param dict additional_associated_data: Not used by :class:`JceNameLocalDelegatedKey` :returns: Wrapped key :rtype: bytes """ wrapper = encryption.JavaCipher.from_transformation(algorithm) 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 # pylint: disable=unused-argument """Wrap content key. :param str algorithm: Text description of algorithm to use to unwrap key :param bytes content_key: Raw content key to wrap :param str wrapped_key_algorithm: Text description of algorithm for unwrapped key to use :param EncryptionKeyType wrapped_key_type: Type of key to treat key as once unwrapped :param dict additional_associated_data: Not used by :class:`JceNameLocalDelegatedKey` :returns: Delegated key using unwrapped key :rtype: DelegatedKey """ if wrapped_key_type is not EncryptionKeyType.SYMMETRIC: 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) return JceNameLocalDelegatedKey( key=unwrapped_key, algorithm=wrapped_key_algorithm, key_type=wrapped_key_type, key_encoding=KeyEncodingType.RAW, ) def _sign(self, algorithm, data): # type: (Text, bytes) -> bytes """Sign data. :param str algorithm: Text description of algorithm to use to sign data :param bytes data: Data to sign :returns: Signature value :rtype: bytes """ signer = authentication.JAVA_AUTHENTICATOR[algorithm] return signer.sign(self.__key, data) def _verify(self, algorithm, signature, data): # type: (Text, bytes, bytes) -> None """Sign data. :param str algorithm: Text description of algorithm to use to verify signature :param bytes signature: Signature to verify :param bytes data: Data over which to verify signature """ verifier = authentication.JAVA_AUTHENTICATOR[algorithm] verifier.verify(self.__key, signature, data) def _signing_algorithm(self): # type: () -> Text """Provide a description that can inform an appropriate cryptographic materials provider about how to build a ``JceNameLocalDelegatedKey`` for signature verification. The return value of this method is included in the material description written to the encrypted item. :returns: Signing algorithm identifier :rtype: str """ return self.algorithm