From f71500446f8c517c6c9cdcbfcb77b20f19895b1a Mon Sep 17 00:00:00 2001 From: Peter Hamilton Date: Fri, 21 Jul 2017 16:10:53 -0400 Subject: [PATCH] Add key wrapping support to the cryptography engine This change adds key wrapping support to the CryptographyEngine, supporting RFC 3394, AES Key Wrap, only. Numerous unit tests from using test vectors from RFC 3394 are included. --- kmip/services/server/crypto/engine.py | 75 ++++++- .../services/server/crypto/test_engine.py | 186 ++++++++++++++++++ 2 files changed, 253 insertions(+), 8 deletions(-) diff --git a/kmip/services/server/crypto/engine.py b/kmip/services/server/crypto/engine.py index 458df9d..ada09df 100644 --- a/kmip/services/server/crypto/engine.py +++ b/kmip/services/server/crypto/engine.py @@ -22,7 +22,7 @@ from cryptography.hazmat.primitives import padding as symmetric_padding from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import padding as \ asymmetric_padding -from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives import ciphers, keywrap from cryptography.hazmat.primitives.ciphers import algorithms, modes from cryptography.hazmat.primitives.kdf import hkdf from cryptography.hazmat.primitives.kdf import kbkdf @@ -736,13 +736,6 @@ class CryptographyEngine(api.CryptographicEngine): info=derivation_data, backend=default_backend() ) -# df = hmac.HMAC( -# key_material, -# algorithm=hashing_algorithm(), -# backend=default_backend() -# ) -# df.update(derivation_data) -# derived_data = df.finalize() derived_data = df.derive(key_material) return derived_data elif derivation_method == enums.DerivationMethod.HASH: @@ -808,3 +801,69 @@ class CryptographyEngine(api.CryptographicEngine): "Derivation method '{0}' is not a supported key " "derivation method.".format(derivation_method) ) + + def wrap_key(self, + key_material, + wrapping_method, + encryption_algorithm, + encryption_key): + """ + Args: + key_material (bytes): The bytes of the key to wrap. Required. + wrapping_method (WrappingMethod): A WrappingMethod enumeration + specifying what wrapping technique to use to wrap the key + material. Required. + encryption_algorithm (CryptographicAlgorithm): A + CryptographicAlgorithm enumeration specifying the encryption + algorithm to use to encrypt the key material. Required. + encryption_key (bytes): The bytes of the encryption key to use + to encrypt the key material. Required. + + Returns: + bytes: the bytes of the wrapped key + + Raises: + CryptographicFailure: Raised when an error occurs during key + wrapping. + InvalidField: Raised when an unsupported wrapping or encryption + algorithm is specified. + + Example: + >>> engine = CryptographyEngine() + >>> result = engine.wrap_key( + ... key_material=( + ... b'\x00\x11\x22\x33\x44\x55\x66\x77' + ... b'\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF' + ... ) + ... wrapping_method=enums.WrappingMethod.ENCRYPT, + ... encryption_algorithm=enums.CryptographicAlgorithm.AES, + ... encryption_key=( + ... b'\x00\x01\x02\x03\x04\x05\x06\x07' + ... b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + ... ) + ... ) + >>> result + b'\x1f\xa6\x8b\n\x81\x12\xb4G\xae\xf3K\xd8\xfbZ{\x82\x9d>\x86#q + \xd2\xcf\xe5' + """ + if wrapping_method == enums.WrappingMethod.ENCRYPT: + if encryption_algorithm == enums.CryptographicAlgorithm.AES: + try: + wrapped_key = keywrap.aes_key_wrap( + encryption_key, + key_material, + default_backend() + ) + return wrapped_key + except Exception as e: + raise exceptions.CryptographicFailure(str(e)) + else: + raise exceptions.InvalidField( + "Encryption algorithm '{0}' is not a supported key " + "wrapping algorithm.".format(encryption_algorithm) + ) + else: + raise exceptions.InvalidField( + "Wrapping method '{0}' is not a supported key wrapping " + "method.".format(wrapping_method) + ) diff --git a/kmip/tests/unit/services/server/crypto/test_engine.py b/kmip/tests/unit/services/server/crypto/test_engine.py index 8b16878..e624f4d 100644 --- a/kmip/tests/unit/services/server/crypto/test_engine.py +++ b/kmip/tests/unit/services/server/crypto/test_engine.py @@ -670,6 +670,57 @@ class TestCryptographyEngine(testtools.TestCase): **kwargs ) + def test_wrap_key_invalid_wrapping_method(self): + """ + Test that the right error is raised when an invalid wrapping method + is specified for key wrapping. + """ + engine = crypto.CryptographyEngine() + + args = (b'', 'invalid', enums.CryptographicAlgorithm.AES, b'') + self.assertRaisesRegexp( + exceptions.InvalidField, + "Wrapping method 'invalid' is not a supported key wrapping " + "method.", + engine.wrap_key, + *args + ) + + def test_wrap_key_invalid_encryption_algorithm(self): + """ + Test that the right error is raised when an invalid encryption + algorithm is specified for encryption-based key wrapping. + """ + engine = crypto.CryptographyEngine() + + args = (b'', enums.WrappingMethod.ENCRYPT, 'invalid', b'') + self.assertRaisesRegexp( + exceptions.InvalidField, + "Encryption algorithm 'invalid' is not a supported key wrapping " + "algorithm.", + engine.wrap_key, + *args + ) + + def test_wrap_key_cryptographic_error(self): + """ + Test that the right error is raised when an error occurs during the + key wrapping process. + """ + engine = crypto.CryptographyEngine() + + args = ( + b'', + enums.WrappingMethod.ENCRYPT, + enums.CryptographicAlgorithm.AES, + b'' + ) + self.assertRaises( + exceptions.CryptographicFailure, + engine.wrap_key, + *args + ) + # TODO(peter-hamilton): Replace this with actual fixture files from NIST CAPV. # Most of these test vectors were obtained from the pyca/cryptography test @@ -1581,3 +1632,138 @@ def test_derive_key(derivation_parameters): ) assert derivation_parameters.get('derived_data') == result + + +# AES Key Wrap test vectors were obtained from IETF RFC 3394: +# +# https://www.ietf.org/rfc/rfc3394.txt +@pytest.fixture( + scope='function', + params=[ + {'key_material': ( + b'\x00\x11\x22\x33\x44\x55\x66\x77' + b'\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF' + ), + 'wrapping_method': enums.WrappingMethod.ENCRYPT, + 'encryption_algorithm': enums.CryptographicAlgorithm.AES, + 'encryption_key': ( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + ), + 'wrapped_data': ( + b'\x1F\xA6\x8B\x0A\x81\x12\xB4\x47' + b'\xAE\xF3\x4B\xD8\xFB\x5A\x7B\x82' + b'\x9D\x3E\x86\x23\x71\xD2\xCF\xE5' + )}, + {'key_material': ( + b'\x00\x11\x22\x33\x44\x55\x66\x77' + b'\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF' + ), + 'wrapping_method': enums.WrappingMethod.ENCRYPT, + 'encryption_algorithm': enums.CryptographicAlgorithm.AES, + 'encryption_key': ( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + b'\x10\x11\x12\x13\x14\x15\x16\x17' + ), + 'wrapped_data': ( + b'\x96\x77\x8B\x25\xAE\x6C\xA4\x35' + b'\xF9\x2B\x5B\x97\xC0\x50\xAE\xD2' + b'\x46\x8A\xB8\xA1\x7A\xD8\x4E\x5D' + )}, + {'key_material': ( + b'\x00\x11\x22\x33\x44\x55\x66\x77' + b'\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF' + ), + 'wrapping_method': enums.WrappingMethod.ENCRYPT, + 'encryption_algorithm': enums.CryptographicAlgorithm.AES, + 'encryption_key': ( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + b'\x10\x11\x12\x13\x14\x15\x16\x17' + b'\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F' + ), + 'wrapped_data': ( + b'\x64\xE8\xC3\xF9\xCE\x0F\x5B\xA2' + b'\x63\xE9\x77\x79\x05\x81\x8A\x2A' + b'\x93\xC8\x19\x1E\x7D\x6E\x8A\xE7' + )}, + {'key_material': ( + b'\x00\x11\x22\x33\x44\x55\x66\x77' + b'\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF' + b'\x00\x01\x02\x03\x04\x05\x06\x07' + ), + 'wrapping_method': enums.WrappingMethod.ENCRYPT, + 'encryption_algorithm': enums.CryptographicAlgorithm.AES, + 'encryption_key': ( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + b'\x10\x11\x12\x13\x14\x15\x16\x17' + ), + 'wrapped_data': ( + b'\x03\x1D\x33\x26\x4E\x15\xD3\x32' + b'\x68\xF2\x4E\xC2\x60\x74\x3E\xDC' + b'\xE1\xC6\xC7\xDD\xEE\x72\x5A\x93' + b'\x6B\xA8\x14\x91\x5C\x67\x62\xD2' + )}, + {'key_material': ( + b'\x00\x11\x22\x33\x44\x55\x66\x77' + b'\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF' + b'\x00\x01\x02\x03\x04\x05\x06\x07' + ), + 'wrapping_method': enums.WrappingMethod.ENCRYPT, + 'encryption_algorithm': enums.CryptographicAlgorithm.AES, + 'encryption_key': ( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + b'\x10\x11\x12\x13\x14\x15\x16\x17' + b'\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F' + ), + 'wrapped_data': ( + b'\xA8\xF9\xBC\x16\x12\xC6\x8B\x3F' + b'\xF6\xE6\xF4\xFB\xE3\x0E\x71\xE4' + b'\x76\x9C\x8B\x80\xA3\x2C\xB8\x95' + b'\x8C\xD5\xD1\x7D\x6B\x25\x4D\xA1' + )}, + {'key_material': ( + b'\x00\x11\x22\x33\x44\x55\x66\x77' + b'\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF' + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + ), + 'wrapping_method': enums.WrappingMethod.ENCRYPT, + 'encryption_algorithm': enums.CryptographicAlgorithm.AES, + 'encryption_key': ( + b'\x00\x01\x02\x03\x04\x05\x06\x07' + b'\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F' + b'\x10\x11\x12\x13\x14\x15\x16\x17' + b'\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F' + ), + 'wrapped_data': ( + b'\x28\xC9\xF4\x04\xC4\xB8\x10\xF4' + b'\xCB\xCC\xB3\x5C\xFB\x87\xF8\x26' + b'\x3F\x57\x86\xE2\xD8\x0E\xD3\x26' + b'\xCB\xC7\xF0\xE7\x1A\x99\xF4\x3B' + b'\xFB\x98\x8B\x9B\x7A\x02\xDD\x21' + )} + ] +) +def wrapping_parameters(request): + return request.param + + +def test_wrap_key(wrapping_parameters): + """ + Test that various wrapping methods and settings can be used to correctly + wrap key data. + """ + engine = crypto.CryptographyEngine() + + result = engine.wrap_key( + wrapping_parameters.get('key_material'), + wrapping_parameters.get('wrapping_method'), + wrapping_parameters.get('encryption_algorithm'), + wrapping_parameters.get('encryption_key') + ) + + assert wrapping_parameters.get('wrapped_data') == result