# Copyright (c) 2016 The Johns Hopkins University/Applied Physics Laboratory # 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. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License 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. import logging import os from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.ciphers import algorithms from kmip.core import enums from kmip.core import exceptions from kmip.services.server.crypto import api class CryptographyEngine(api.CryptographicEngine): """ A cryptographic engine that uses pyca/cryptography to generate cryptographic objects and conduct cryptographic operations. """ def __init__(self): """ Construct a CryptographyEngine. """ self.logger = logging.getLogger('kmip.server.engine.cryptography') self._symmetric_key_algorithms = { enums.CryptographicAlgorithm.TRIPLE_DES: algorithms.TripleDES, enums.CryptographicAlgorithm.AES: algorithms.AES, enums.CryptographicAlgorithm.BLOWFISH: algorithms.Blowfish, enums.CryptographicAlgorithm.CAMELLIA: algorithms.Camellia, enums.CryptographicAlgorithm.CAST5: algorithms.CAST5, enums.CryptographicAlgorithm.IDEA: algorithms.IDEA, enums.CryptographicAlgorithm.RC4: algorithms.ARC4 } self._asymetric_key_algorithms = { enums.CryptographicAlgorithm.RSA: self._create_rsa_key_pair } def create_symmetric_key(self, algorithm, length): """ Create a symmetric key. Args: algorithm(CryptographicAlgorithm): An enumeration specifying the algorithm for which the created key will be compliant. length(int): The length of the key to be created. This value must be compliant with the constraints of the provided algorithm. Returns: dict: A dictionary containing the key data, with the following key/value fields: * value - the bytes of the key * format - a KeyFormatType enumeration for the bytes format Raises: InvalidField: Raised when the algorithm is unsupported or the length is incompatible with the algorithm. CryptographicFailure: Raised when the key generation process fails. Example: >>> engine = CryptographyEngine() >>> key = engine.create_symmetric_key( ... CryptographicAlgorithm.AES, 256) """ if algorithm not in self._symmetric_key_algorithms.keys(): raise exceptions.InvalidField( "The cryptographic algorithm {0} is not a supported symmetric " "key algorithm.".format(algorithm) ) cryptography_algorithm = self._symmetric_key_algorithms.get(algorithm) if length not in cryptography_algorithm.key_sizes: raise exceptions.InvalidField( "The cryptographic length ({0}) is not valid for " "the cryptographic algorithm ({1}).".format( length, algorithm.name ) ) self.logger.info( "Generating a {0} symmetric key with length: {1}".format( algorithm.name, length ) ) key_bytes = os.urandom(length // 8) try: cryptography_algorithm(key_bytes) except Exception as e: self.logger.exception(e) raise exceptions.CryptographicFailure( "Invalid bytes for the provided cryptographic algorithm.") return {'value': key_bytes, 'format': enums.KeyFormatType.RAW} def create_asymmetric_key_pair(self, algorithm, length): """ Create an asymmetric key pair. Args: algorithm(CryptographicAlgorithm): An enumeration specifying the algorithm for which the created keys will be compliant. length(int): The length of the keys to be created. This value must be compliant with the constraints of the provided algorithm. Returns: dict: A dictionary containing the public key data, with at least the following key/value fields: * value - the bytes of the key * format - a KeyFormatType enumeration for the bytes format dict: A dictionary containing the private key data, identical in structure to the one above. Raises: InvalidField: Raised when the algorithm is unsupported or the length is incompatible with the algorithm. CryptographicFailure: Raised when the key generation process fails. Example: >>> engine = CryptographyEngine() >>> key = engine.create_asymmetric_key( ... CryptographicAlgorithm.RSA, 2048) """ if algorithm not in self._asymetric_key_algorithms.keys(): raise exceptions.InvalidField( "The cryptographic algorithm ({0}) is not a supported " "asymmetric key algorithm.".format(algorithm) ) engine_method = self._asymetric_key_algorithms.get(algorithm) return engine_method(length) def _create_rsa_key_pair(self, length, public_exponent=65537): """ Create an RSA key pair. Args: length(int): The length of the keys to be created. This value must be compliant with the constraints of the provided algorithm. public_exponent(int): The value of the public exponent needed to generate the keys. Usually a small Fermat prime number. Optional, defaults to 65537. Returns: dict: A dictionary containing the public key data, with the following key/value fields: * value - the bytes of the key * format - a KeyFormatType enumeration for the bytes format * public_exponent - the public exponent integer dict: A dictionary containing the private key data, identical in structure to the one above. Raises: CryptographicFailure: Raised when the key generation process fails. """ self.logger.info( "Generating an RSA key pair with length: {0}, and " "public_exponent: {1}".format( length, public_exponent ) ) try: private_key = rsa.generate_private_key( public_exponent=public_exponent, key_size=length, backend=default_backend()) public_key = private_key.public_key() private_bytes = private_key.private_bytes( serialization.Encoding.DER, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()) public_bytes = public_key.public_bytes( serialization.Encoding.DER, serialization.PublicFormat.PKCS1) except Exception as e: self.logger.exception(e) raise exceptions.CryptographicFailure( "An error occurred while generating the RSA key pair. " "See the server log for more information." ) public_key = { 'value': public_bytes, 'format': enums.KeyFormatType.PKCS_1, 'public_exponent': public_exponent } private_key = { 'value': private_bytes, 'format': enums.KeyFormatType.PKCS_8, 'public_exponent': public_exponent } return public_key, private_key