From 6fa4999cc5bfb3d385ba08d8d2e34fd24b777d39 Mon Sep 17 00:00:00 2001 From: Peter Hamilton Date: Sat, 30 Sep 2017 12:11:19 -0400 Subject: [PATCH] Updating the ProxyKmipClient to support getting wrapped keys This change updates the ProxyKmipClient, allowing Get operation calls to retrieve wrapped keys by providing key wrapping specification information with the request. Unit tests have been added and updated to reflect this change. --- kmip/pie/client.py | 138 ++++++++++++++++++- kmip/services/kmip_client.py | 6 +- kmip/tests/unit/pie/test_client.py | 207 ++++++++++++++++++++++++++++- 3 files changed, 343 insertions(+), 8 deletions(-) diff --git a/kmip/pie/client.py b/kmip/pie/client.py index fe5b631..790986e 100644 --- a/kmip/pie/client.py +++ b/kmip/pie/client.py @@ -559,12 +559,15 @@ class ProxyKmipClient(api.KmipClient): raise exceptions.KmipOperationFailure(status, reason, message) @is_connected - def get(self, uid=None): + def get(self, uid=None, key_wrapping_specification=None): """ Get a managed object from a KMIP appliance. Args: uid (string): The unique ID of the managed object to retrieve. + key_wrapping_specification (dict): A dictionary containing various + settings to be used when wrapping the key during retrieval. + See Note below. Optional, defaults to None. Returns: ManagedObject: The retrieved managed object object. @@ -573,14 +576,48 @@ class ProxyKmipClient(api.KmipClient): ClientConnectionNotOpen: if the client connection is unusable KmipOperationFailure: if the operation result is a failure TypeError: if the input argument is invalid + + Notes: + The derivation_parameters argument is a dictionary that can + contain the following key/value pairs: + + Key | Value + --------------------------------|--------------------------------- + 'wrapping_method' | A WrappingMethod enumeration + | that specifies how the object + | should be wrapped. + 'encryption_key_information' | A dictionary containing the ID + | of the wrapping key and + | associated cryptographic + | parameters. + 'mac_signature_key_information' | A dictionary containing the ID + | of the wrapping key and + | associated cryptographic + | parameters. + 'attribute_names' | A list of strings representing + | the names of attributes that + | should be included with the + | wrapped object. + 'encoding_option' | An EncodingOption enumeration + | that specifies the encoding of + | the object before it is wrapped. """ # Check input if uid is not None: if not isinstance(uid, six.string_types): raise TypeError("uid must be a string") + if key_wrapping_specification is not None: + if not isinstance(key_wrapping_specification, dict): + raise TypeError( + "Key wrapping specification must be a dictionary." + ) + + spec = self._build_key_wrapping_specification( + key_wrapping_specification + ) # Get the managed object and handle the results - result = self.proxy.get(uid) + result = self.proxy.get(uid, key_wrapping_specification=spec) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: @@ -1226,6 +1263,103 @@ class ProxyKmipClient(api.KmipClient): ) return cryptographic_parameters + def _build_encryption_key_information(self, value): + """ + Build an EncryptionKeyInformation struct from a dictionary. + + Args: + value (dict): A dictionary containing the key/value pairs for a + EncryptionKeyInformation struct. + + Returns: + EncryptionKeyInformation: an EncryptionKeyInformation struct + + Raises: + TypeError: if the input argument is invalid + """ + if value is None: + return None + if not isinstance(value, dict): + raise TypeError("Encryption key information must be a dictionary.") + + cryptographic_parameters = value.get('cryptographic_parameters') + if cryptographic_parameters: + cryptographic_parameters = self._build_cryptographic_parameters( + cryptographic_parameters + ) + encryption_key_information = cobjects.EncryptionKeyInformation( + unique_identifier=value.get('unique_identifier'), + cryptographic_parameters=cryptographic_parameters + ) + return encryption_key_information + + def _build_mac_signature_key_information(self, value): + """ + Build an MACSignatureKeyInformation struct from a dictionary. + + Args: + value (dict): A dictionary containing the key/value pairs for a + MACSignatureKeyInformation struct. + + Returns: + MACSignatureInformation: a MACSignatureKeyInformation struct + + Raises: + TypeError: if the input argument is invalid + """ + if value is None: + return None + if not isinstance(value, dict): + raise TypeError( + "MAC/signature key information must be a dictionary." + ) + + cryptographic_parameters = value.get('cryptographic_parameters') + if cryptographic_parameters: + cryptographic_parameters = self._build_cryptographic_parameters( + cryptographic_parameters + ) + mac_signature_key_information = cobjects.MACSignatureKeyInformation( + unique_identifier=value.get('unique_identifier'), + cryptographic_parameters=cryptographic_parameters + ) + return mac_signature_key_information + + def _build_key_wrapping_specification(self, value): + """ + Build a KeyWrappingSpecification struct from a dictionary. + + Args: + value (dict): A dictionary containing the key/value pairs for a + KeyWrappingSpecification struct. + + Returns: + KeyWrappingSpecification: a KeyWrappingSpecification struct + + Raises: + TypeError: if the input argument is invalid + """ + if value is None: + return None + if not isinstance(value, dict): + raise TypeError("Key wrapping specification must be a dictionary.") + + encryption_key_info = self._build_encryption_key_information( + value.get('encryption_key_information') + ) + mac_signature_key_info = self._build_mac_signature_key_information( + value.get('mac_signature_key_information') + ) + + key_wrapping_specification = cobjects.KeyWrappingSpecification( + wrapping_method=value.get('wrapping_method'), + encryption_key_information=encryption_key_info, + mac_signature_key_information=mac_signature_key_info, + attribute_names=value.get('attribute_names'), + encoding_option=value.get('encoding_option') + ) + return key_wrapping_specification + def _build_common_attributes(self, operation_policy_name=None): ''' Build a list of common attributes that are shared across diff --git a/kmip/services/kmip_client.py b/kmip/services/kmip_client.py index 4a97355..d8ce3ea 100644 --- a/kmip/services/kmip_client.py +++ b/kmip/services/kmip_client.py @@ -1059,18 +1059,14 @@ class KMIPProxy(KMIP): credential=None): operation = Operation(OperationEnum.GET) - kws = None - if key_format_type is not None: key_format_type = key_format_type.value - if key_wrapping_specification is not None: - kws = objects.KeyWrappingSpecification(key_wrapping_specification) req_pl = payloads.GetRequestPayload( unique_identifier=unique_identifier, key_format_type=key_format_type, key_compression_type=key_compression_type, - key_wrapping_specification=kws + key_wrapping_specification=key_wrapping_specification ) batch_item = messages.RequestBatchItem(operation=operation, diff --git a/kmip/tests/unit/pie/test_client.py b/kmip/tests/unit/pie/test_client.py index 242ef01..c5aab34 100644 --- a/kmip/tests/unit/pie/test_client.py +++ b/kmip/tests/unit/pie/test_client.py @@ -743,7 +743,9 @@ class TestProxyKmipClient(testtools.TestCase): result = client.get('aaaaaaaa-1111-2222-3333-ffffffffffff') client.proxy.get.assert_called_with( - 'aaaaaaaa-1111-2222-3333-ffffffffffff') + 'aaaaaaaa-1111-2222-3333-ffffffffffff', + key_wrapping_specification=None + ) self.assertIsInstance(result, objects.SymmetricKey) self.assertEqual(result, secret) @@ -758,6 +760,24 @@ class TestProxyKmipClient(testtools.TestCase): with ProxyKmipClient() as client: self.assertRaises(TypeError, client.get, *args) + @mock.patch('kmip.pie.client.KMIPProxy', + mock.MagicMock(spec_set=KMIPProxy)) + def test_get_on_invalid_key_wrapping_specification(self): + """ + Test that a TypeError exception is raised when trying to retrieve a + secret with an invalid key wrapping specification. + """ + args = ['1'] + kwargs = {'key_wrapping_specification': 'invalid'} + with ProxyKmipClient() as client: + self.assertRaisesRegexp( + TypeError, + "Key wrapping specification must be a dictionary.", + client.get, + *args, + **kwargs + ) + @mock.patch('kmip.pie.client.KMIPProxy', mock.MagicMock(spec_set=KMIPProxy)) def test_get_on_closed(self): @@ -2594,3 +2614,188 @@ class TestProxyKmipClient(testtools.TestCase): client._build_cryptographic_parameters, *args ) + + def test_build_encryption_key_information(self): + """ + Test that an EncryptionKeyInformation struct can be built from a + dictionary. + """ + client = ProxyKmipClient() + + # Test with no value + result = client._build_encryption_key_information(None) + + self.assertEqual(None, result) + + # Test with a value + result = client._build_encryption_key_information( + { + 'unique_identifier': 'test', + 'cryptographic_parameters': { + 'block_cipher_mode': enums.BlockCipherMode.CBC + } + } + ) + + self.assertIsInstance(result, obj.EncryptionKeyInformation) + self.assertEqual('test', result.unique_identifier) + self.assertIsInstance( + result.cryptographic_parameters, + obj.CryptographicParameters + ) + self.assertEqual( + enums.BlockCipherMode.CBC, + result.cryptographic_parameters.block_cipher_mode + ) + + def test_build_encryption_key_information_invalid(self): + """ + Test that the right error is raised when attempting to build + an EncryptionKeyInformation struct with an invalid value. + """ + client = ProxyKmipClient() + args = ['invalid'] + + self.assertRaisesRegexp( + TypeError, + "Encryption key information must be a dictionary.", + client._build_encryption_key_information, + *args + ) + + def test_build_mac_signature_key_information(self): + """ + Test that a MACSignatureKeyInformation struct can be built from a + dictionary. + """ + client = ProxyKmipClient() + + # Test with no value + result = client._build_mac_signature_key_information(None) + + self.assertEqual(None, result) + + # Test with a value + result = client._build_mac_signature_key_information( + { + 'unique_identifier': '1', + 'cryptographic_parameters': { + 'cryptographic_algorithm': enums.CryptographicAlgorithm.AES + } + } + ) + + self.assertIsInstance(result, obj.MACSignatureKeyInformation) + self.assertEqual('1', result.unique_identifier) + self.assertIsInstance( + result.cryptographic_parameters, + obj.CryptographicParameters + ) + self.assertEqual( + enums.CryptographicAlgorithm.AES, + result.cryptographic_parameters.cryptographic_algorithm + ) + + def test_build_mac_signature_key_information_invalid(self): + """ + Test that the right error is raised when attempting to build + a MACSignatureKeyInformation struct with an invalid value. + """ + client = ProxyKmipClient() + args = ['invalid'] + + self.assertRaisesRegexp( + TypeError, + "MAC/signature key information must be a dictionary.", + client._build_mac_signature_key_information, + *args + ) + + def test_build_key_wrapping_specification(self): + """ + Test that a KeyWrappingSpecification can be built from a dictionary. + """ + client = ProxyKmipClient() + + # Test with no value + result = client._build_key_wrapping_specification(None) + + self.assertEqual(None, result) + + # Test with a value + result = client._build_key_wrapping_specification( + { + 'wrapping_method': enums.WrappingMethod.ENCRYPT, + 'encryption_key_information': { + 'unique_identifier': '1', + 'cryptographic_parameters': { + 'cryptographic_algorithm': + enums.CryptographicAlgorithm.AES + } + }, + 'mac_signature_key_information': { + 'unique_identifier': '2', + 'cryptographic_parameters': { + 'padding_method': enums.PaddingMethod.PKCS5 + } + }, + 'attribute_names': [ + 'Cryptographic Algorithm', + 'Cryptographic Length' + ], + 'encoding_option': enums.EncodingOption.NO_ENCODING + } + ) + + self.assertIsInstance(result, obj.KeyWrappingSpecification) + self.assertIsInstance( + result.encryption_key_information, + obj.EncryptionKeyInformation + ) + info = result.encryption_key_information + self.assertEqual('1', info.unique_identifier) + self.assertIsInstance( + info.cryptographic_parameters, + obj.CryptographicParameters + ) + self.assertEqual( + enums.CryptographicAlgorithm.AES, + info.cryptographic_parameters.cryptographic_algorithm + ) + self.assertIsInstance( + result.mac_signature_key_information, + obj.MACSignatureKeyInformation + ) + info = result.mac_signature_key_information + self.assertEqual('2', info.unique_identifier) + self.assertIsInstance( + info.cryptographic_parameters, + obj.CryptographicParameters + ) + self.assertEqual( + enums.PaddingMethod.PKCS5, + info.cryptographic_parameters.padding_method + ) + self.assertIsInstance(result.attribute_names, list) + self.assertEqual(2, len(result.attribute_names)) + self.assertIn('Cryptographic Algorithm', result.attribute_names) + self.assertIn('Cryptographic Length', result.attribute_names) + self.assertEqual( + enums.EncodingOption.NO_ENCODING, + result.encoding_option + ) + + def test_build_key_wrapping_specification_invalid(self): + """ + Test that the right error is raised when attempting to build + a KeyWrappingSpecification struct with an invalid value. + """ + client = ProxyKmipClient() + args = ['invalid'] + + self.assertRaisesRegexp( + TypeError, + "Key wrapping specification must be a dictionary.", + client._build_key_wrapping_specification, + *args + )