Adding the CryptographyEngine

This changes adds the CryptographyEngine, which uses pyca/cryptography
to create symmetric and asymmetric keys. A test suite is included.
This commit is contained in:
Peter 2016-01-21 15:17:59 -05:00
parent 9d02201178
commit 53d6b1776e
7 changed files with 428 additions and 0 deletions

View File

@ -13,6 +13,65 @@
# License for the specific language governing permissions and limitations
# under the License.
from kmip.core import enums
class KmipError(Exception):
"""
A generic KMIP error that is the base for the KMIP error hierarchy.
"""
def __init__(self,
status=enums.ResultStatus.OPERATION_FAILED,
reason=enums.ResultReason.GENERAL_FAILURE,
message='A general failure occurred.'):
"""
Create a KmipError exception.
Args:
status (ResultStatus): An enumeration detailing the result outcome.
reason (ResultReason): An enumeration giving the status rationale.
message (string): A string containing more information about the
error.
"""
super(KmipError, self).__init__(message)
self.status = status
self.reason = reason
class CryptographicFailure(KmipError):
"""
An error generated when problems occur with cryptographic operations.
"""
def __init__(self, message):
"""
Create a CryptographicFailure exception.
Args:
message (string): A string containing information about the error.
"""
super(CryptographicFailure, self).__init__(
reason=enums.ResultReason.CRYPTOGRAPHIC_FAILURE,
message=message)
class InvalidField(KmipError):
"""
An error generated when an invalid field value is processed.
"""
def __init__(self, message):
"""
Create an InvalidField exception.
Args:
message (string): A string containing information about the error.
"""
super(InvalidField, self).__init__(
reason=enums.ResultReason.INVALID_FIELD,
message=message)
class InvalidKmipEncoding(Exception):
"""

View File

@ -12,3 +12,11 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from kmip.services.server.crypto.api import CryptographicEngine
from kmip.services.server.crypto.engine import CryptographyEngine
__all__ = [
'CryptographicEngine',
'CryptographyEngine',
]

View File

@ -0,0 +1,212 @@
# 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(__name__)
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

View File

@ -0,0 +1,148 @@
# 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 testtools
from kmip.core import enums
from kmip.core import exceptions
from kmip.services.server import crypto
class TestCryptographyEngine(testtools.TestCase):
"""
Test suite for the CryptographyEngine.
"""
def setUp(self):
super(TestCryptographyEngine, self).setUp()
def tearDown(self):
super(TestCryptographyEngine, self).tearDown()
def test_init(self):
"""
Test that a CryptographyEngine can be constructed.
"""
crypto.CryptographyEngine()
def test_create_symmetric_key(self):
"""
Test that a symmetric key can be created with valid arguments.
"""
engine = crypto.CryptographyEngine()
key = engine.create_symmetric_key(
enums.CryptographicAlgorithm.AES,
256
)
self.assertIn('value', key)
self.assertIn('format', key)
self.assertEqual(enums.KeyFormatType.RAW, key.get('format'))
def test_create_symmetric_key_with_invalid_algorithm(self):
"""
Test that an InvalidField error is raised when creating a symmetric
key with an invalid algorithm.
"""
engine = crypto.CryptographyEngine()
args = ['invalid', 256]
self.assertRaises(
exceptions.InvalidField,
engine.create_symmetric_key,
*args
)
def test_create_symmetric_key_with_invalid_length(self):
"""
Test that an InvalidField error is raised when creating a symmetric
key with an invalid length.
"""
engine = crypto.CryptographyEngine()
args = [enums.CryptographicAlgorithm.AES, 'invalid']
self.assertRaises(
exceptions.InvalidField,
engine.create_symmetric_key,
*args
)
def test_create_symmetric_key_with_cryptographic_failure(self):
"""
Test that a CryptographicFailure error is raised when the symmetric
key generation process fails.
"""
# Create a dummy algorithm that always fails on instantiation.
class DummyAlgorithm(object):
key_sizes = [0]
def __init__(self, key_bytes):
raise Exception()
engine = crypto.CryptographyEngine()
engine._symmetric_key_algorithms.update([(
enums.CryptographicAlgorithm.AES,
DummyAlgorithm
)])
args = [enums.CryptographicAlgorithm.AES, 0]
self.assertRaises(
exceptions.CryptographicFailure,
engine.create_symmetric_key,
*args
)
def test_create_asymmetric_key(self):
"""
Test that an asymmetric key pair can be created with valid arguments.
"""
engine = crypto.CryptographyEngine()
public_key, private_key = engine.create_asymmetric_key_pair(
enums.CryptographicAlgorithm.RSA,
2048
)
self.assertIn('value', public_key)
self.assertIn('format', public_key)
self.assertIn('value', private_key)
self.assertIn('format', private_key)
def test_create_asymmetric_key_with_invalid_algorithm(self):
"""
Test that an InvalidField error is raised when creating an asymmetric
key pair with an invalid algorithm.
"""
engine = crypto.CryptographyEngine()
args = ['invalid', 2048]
self.assertRaises(
exceptions.InvalidField,
engine.create_asymmetric_key_pair,
*args
)
def test_create_asymmetric_key_with_invalid_length(self):
"""
Test that an CryptographicFailure error is raised when creating an
asymmetric key pair with an invalid length.
"""
engine = crypto.CryptographyEngine()
args = [enums.CryptographicAlgorithm.RSA, 0]
self.assertRaises(
exceptions.CryptographicFailure,
engine.create_asymmetric_key_pair,
*args
)

View File

@ -1,3 +1,4 @@
cryptography>=1.1
enum34
six>=1.9.0