mirror of https://github.com/OpenKMIP/PyKMIP.git
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:
parent
9d02201178
commit
53d6b1776e
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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',
|
||||
]
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
)
|
|
@ -1,3 +1,4 @@
|
|||
cryptography>=1.1
|
||||
enum34
|
||||
six>=1.9.0
|
||||
|
||||
|
|
Loading…
Reference in New Issue