class TestKMIPClient(TestCase): def setUp(self): super(TestKMIPClient, self).setUp() self.attr_factory = AttributeFactory() self.cred_factory = CredentialFactory() self.secret_factory = SecretFactory() self.client = KMIPProxy() KMIP_PORT = 9090 CA_CERTS_PATH = os.path.normpath(os.path.join(os.path.dirname( os.path.abspath(__file__)), '../utils/certs/server.crt')) self.mock_client = KMIPProxy(host="IP_ADDR_1, IP_ADDR_2", port=KMIP_PORT, ca_certs=CA_CERTS_PATH) self.mock_client.socket = mock.MagicMock() self.mock_client.socket.connect = mock.MagicMock() self.mock_client.socket.close = mock.MagicMock() def tearDown(self): super(TestKMIPClient, self).tearDown() def test_close(self): """ Test that calling close on the client works as expected. """ c = KMIPProxy( host="IP_ADDR_1, IP_ADDR_2", port=9090, ca_certs=None ) c.socket = mock.MagicMock() c_socket = c.socket c.socket.shutdown.assert_not_called() c.socket.close.assert_not_called() c.close() self.assertEqual(None, c.socket) c_socket.shutdown.assert_called_once_with(socket.SHUT_RDWR) c_socket.close.assert_called_once() def test_close_with_shutdown_error(self): """ Test that calling close on an unconnected client does not trigger an exception. """ c = KMIPProxy( host="IP_ADDR_1, IP_ADDR_2", port=9090, ca_certs=None ) c.socket = mock.MagicMock() c_socket = c.socket c.socket.shutdown.side_effect = OSError c.socket.shutdown.assert_not_called() c.socket.close.assert_not_called() c.close() self.assertEqual(None, c.socket) c_socket.shutdown.assert_called_once_with(socket.SHUT_RDWR) c_socket.close.assert_not_called() # TODO (peter-hamilton) Modify for credential type and/or add new test def test_build_credential(self): username = '******' password = '******' cred_type = CredentialType.USERNAME_AND_PASSWORD self.client.username = username self.client.password = password credential = self.client._build_credential() message = utils.build_er_error(credential.__class__, 'type', cred_type, credential.credential_type.value, 'value') self.assertEqual(CredentialType.USERNAME_AND_PASSWORD, credential.credential_type.value, message) message = utils.build_er_error( credential.__class__, 'type', username, credential.credential_value.username.value, 'value') self.assertEqual(username, credential.credential_value.username.value, message) message = utils.build_er_error( credential.__class__, 'type', password, credential.credential_value.password.value, 'value') self.assertEqual(password, credential.credential_value.password.value, message) def test_build_credential_no_username(self): username = None password = '******' self.client.username = username self.client.password = password exception = self.assertRaises(ValueError, self.client._build_credential) self.assertEqual('cannot build credential, username is None', str(exception)) def test_build_credential_no_password(self): username = '******' password = None self.client.username = username self.client.password = password exception = self.assertRaises(ValueError, self.client._build_credential) self.assertEqual('cannot build credential, password is None', str(exception)) def test_build_credential_no_creds(self): self.client.username = None self.client.password = None credential = self.client._build_credential() self.assertEqual(None, credential) def _test_build_create_key_pair_batch_item(self, common, private, public): batch_item = self.client._build_create_key_pair_batch_item( common_template_attribute=common, private_key_template_attribute=private, public_key_template_attribute=public) base = "expected {0}, received {1}" msg = base.format(RequestBatchItem, batch_item) self.assertIsInstance(batch_item, RequestBatchItem, msg) operation = batch_item.operation msg = base.format(Operation, operation) self.assertIsInstance(operation, Operation, msg) operation_enum = operation.value msg = base.format(OperationEnum.CREATE_KEY_PAIR, operation_enum) self.assertEqual(OperationEnum.CREATE_KEY_PAIR, operation_enum, msg) payload = batch_item.request_payload msg = base.format(payloads.CreateKeyPairRequestPayload, payload) self.assertIsInstance( payload, payloads.CreateKeyPairRequestPayload, msg ) common_observed = payload.common_template_attribute private_observed = payload.private_key_template_attribute public_observed = payload.public_key_template_attribute msg = base.format(common, common_observed) self.assertEqual(common, common_observed, msg) msg = base.format(private, private_observed) self.assertEqual(private, private_observed, msg) msg = base.format(public, public_observed) self.assertEqual(public, public_observed) def test_build_create_key_pair_batch_item_with_input(self): self._test_build_create_key_pair_batch_item( CommonTemplateAttribute(), PrivateKeyTemplateAttribute(), PublicKeyTemplateAttribute()) def test_build_create_key_pair_batch_item_no_input(self): self._test_build_create_key_pair_batch_item(None, None, None) def _test_build_rekey_key_pair_batch_item(self, uuid, offset, common, private, public): batch_item = self.client._build_rekey_key_pair_batch_item( private_key_uuid=uuid, offset=offset, common_template_attribute=common, private_key_template_attribute=private, public_key_template_attribute=public) base = "expected {0}, received {1}" msg = base.format(RequestBatchItem, batch_item) self.assertIsInstance(batch_item, RequestBatchItem, msg) operation = batch_item.operation msg = base.format(Operation, operation) self.assertIsInstance(operation, Operation, msg) operation_enum = operation.value msg = base.format(OperationEnum.REKEY_KEY_PAIR, operation_enum) self.assertEqual(OperationEnum.REKEY_KEY_PAIR, operation_enum, msg) payload = batch_item.request_payload msg = base.format(payloads.RekeyKeyPairRequestPayload, payload) self.assertIsInstance( payload, payloads.RekeyKeyPairRequestPayload, msg ) private_key_uuid_observed = payload.private_key_uuid offset_observed = payload.offset common_observed = payload.common_template_attribute private_observed = payload.private_key_template_attribute public_observed = payload.public_key_template_attribute msg = base.format(uuid, private_key_uuid_observed) self.assertEqual(uuid, private_key_uuid_observed, msg) msg = base.format(offset, offset_observed) self.assertEqual(offset, offset_observed, msg) msg = base.format(common, common_observed) self.assertEqual(common, common_observed, msg) msg = base.format(private, private_observed) self.assertEqual(private, private_observed, msg) msg = base.format(public, public_observed) self.assertEqual(public, public_observed) def test_build_rekey_key_pair_batch_item_with_input(self): self._test_build_rekey_key_pair_batch_item( PrivateKeyUniqueIdentifier(), Offset(), CommonTemplateAttribute(), PrivateKeyTemplateAttribute(), PublicKeyTemplateAttribute()) def test_build_rekey_key_pair_batch_item_no_input(self): self._test_build_rekey_key_pair_batch_item( None, None, None, None, None) def _test_build_query_batch_item(self, query_functions): batch_item = self.client._build_query_batch_item(query_functions) base = "expected {0}, received {1}" msg = base.format(RequestBatchItem, batch_item) self.assertIsInstance(batch_item, RequestBatchItem, msg) operation = batch_item.operation msg = base.format(Operation, operation) self.assertIsInstance(operation, Operation, msg) operation_enum = operation.value msg = base.format(OperationEnum.QUERY, operation_enum) self.assertEqual(OperationEnum.QUERY, operation_enum, msg) payload = batch_item.request_payload if query_functions is None: query_functions = list() msg = base.format(payloads.QueryRequestPayload, payload) self.assertIsInstance(payload, payloads.QueryRequestPayload, msg) query_functions_observed = payload.query_functions self.assertEqual(query_functions, query_functions_observed) def test_build_query_batch_item_with_input(self): self._test_build_query_batch_item( [QueryFunction(QueryFunctionEnum.QUERY_OBJECTS)]) def test_build_query_batch_item_without_input(self): self._test_build_query_batch_item(None) def _test_build_discover_versions_batch_item(self, protocol_versions): batch_item = self.client._build_discover_versions_batch_item( protocol_versions) base = "expected {0}, received {1}" msg = base.format(RequestBatchItem, batch_item) self.assertIsInstance(batch_item, RequestBatchItem, msg) operation = batch_item.operation msg = base.format(Operation, operation) self.assertIsInstance(operation, Operation, msg) operation_enum = operation.value msg = base.format(OperationEnum.DISCOVER_VERSIONS, operation_enum) self.assertEqual(OperationEnum.DISCOVER_VERSIONS, operation_enum, msg) payload = batch_item.request_payload if protocol_versions is None: protocol_versions = list() msg = base.format(payloads.DiscoverVersionsRequestPayload, payload) self.assertIsInstance( payload, payloads.DiscoverVersionsRequestPayload, msg ) observed = payload.protocol_versions msg = base.format(protocol_versions, observed) self.assertEqual(protocol_versions, observed, msg) def test_build_discover_versions_batch_item_with_input(self): protocol_versions = [ProtocolVersion.create(1, 0)] self._test_build_discover_versions_batch_item(protocol_versions) def test_build_discover_versions_batch_item_no_input(self): protocol_versions = None self._test_build_discover_versions_batch_item(protocol_versions) def test_build_get_attributes_batch_item(self): uuid = '00000000-1111-2222-3333-444444444444' attribute_names = [ 'Name', 'Object Type' ] batch_item = self.client._build_get_attributes_batch_item( uuid, attribute_names ) self.assertIsInstance(batch_item, RequestBatchItem) self.assertIsInstance(batch_item.operation, Operation) self.assertEqual( OperationEnum.GET_ATTRIBUTES, batch_item.operation.value ) self.assertIsInstance( batch_item.request_payload, payloads.GetAttributesRequestPayload ) self.assertEqual(uuid, batch_item.request_payload.unique_identifier) self.assertEqual( attribute_names, batch_item.request_payload.attribute_names ) def test_build_get_attribute_list_batch_item(self): uid = '00000000-1111-2222-3333-444444444444' batch_item = self.client._build_get_attribute_list_batch_item(uid) self.assertIsInstance(batch_item, RequestBatchItem) self.assertIsInstance(batch_item.operation, Operation) self.assertEqual( OperationEnum.GET_ATTRIBUTE_LIST, batch_item.operation.value) self.assertIsInstance( batch_item.request_payload, payloads.GetAttributeListRequestPayload) self.assertEqual(uid, batch_item.request_payload.unique_identifier) def test_process_batch_items(self): batch_item = ResponseBatchItem( operation=Operation(OperationEnum.CREATE_KEY_PAIR), response_payload=payloads.CreateKeyPairResponsePayload()) response = ResponseMessage(batch_items=[batch_item, batch_item]) results = self.client._process_batch_items(response) base = "expected {0}, received {1}" msg = base.format(list, results) self.assertIsInstance(results, list, msg) msg = "number of results " + base.format(2, len(results)) self.assertEqual(2, len(results), msg) for result in results: msg = base.format(CreateKeyPairResult, result) self.assertIsInstance(result, CreateKeyPairResult, msg) def test_process_batch_items_no_batch_items(self): response = ResponseMessage(batch_items=[]) results = self.client._process_batch_items(response) base = "expected {0}, received {1}" msg = base.format(list, results) self.assertIsInstance(results, list, msg) msg = "number of results " + base.format(0, len(results)) self.assertEqual(0, len(results), msg) def test_process_batch_item_with_error(self): result_status = ResultStatus(ResultStatusEnum.OPERATION_FAILED) result_reason = ResultReason(ResultReasonEnum.INVALID_MESSAGE) result_message = ResultMessage("message") batch_item = ResponseBatchItem( result_status=result_status, result_reason=result_reason, result_message=result_message) response = ResponseMessage(batch_items=[batch_item]) results = self.client._process_batch_items(response) base = "expected {0}, received {1}" msg = "number of results " + base.format(1, len(results)) self.assertEqual(1, len(results), msg) result = results[0] self.assertIsInstance(result, OperationResult) self.assertEqual(result.result_status, result_status) self.assertEqual(result.result_reason, result_reason) self.assertEqual(result.result_message.value, "message") def test_get_batch_item_processor(self): base = "expected {0}, received {1}" expected = self.client._process_create_key_pair_batch_item observed = self.client._get_batch_item_processor( OperationEnum.CREATE_KEY_PAIR) msg = base.format(expected, observed) self.assertEqual(expected, observed, msg) expected = self.client._process_rekey_key_pair_batch_item observed = self.client._get_batch_item_processor( OperationEnum.REKEY_KEY_PAIR) msg = base.format(expected, observed) self.assertEqual(expected, observed, msg) self.assertRaisesRegexp(ValueError, "no processor for operation", self.client._get_batch_item_processor, 0xA5A5A5A5) expected = self.client._process_get_attributes_batch_item observed = self.client._get_batch_item_processor( OperationEnum.GET_ATTRIBUTES ) self.assertEqual(expected, observed) expected = self.client._process_get_attribute_list_batch_item observed = self.client._get_batch_item_processor( OperationEnum.GET_ATTRIBUTE_LIST) self.assertEqual(expected, observed) def _test_equality(self, expected, observed): msg = "expected {0}, observed {1}".format(expected, observed) self.assertEqual(expected, observed, msg) def test_process_create_key_pair_batch_item(self): batch_item = ResponseBatchItem( operation=Operation(OperationEnum.CREATE_KEY_PAIR), response_payload=payloads.CreateKeyPairResponsePayload()) result = self.client._process_create_key_pair_batch_item(batch_item) msg = "expected {0}, received {1}".format(CreateKeyPairResult, result) self.assertIsInstance(result, CreateKeyPairResult, msg) def test_process_rekey_key_pair_batch_item(self): batch_item = ResponseBatchItem( operation=Operation(OperationEnum.REKEY_KEY_PAIR), response_payload=payloads.RekeyKeyPairResponsePayload()) result = self.client._process_rekey_key_pair_batch_item(batch_item) msg = "expected {0}, received {1}".format(RekeyKeyPairResult, result) self.assertIsInstance(result, RekeyKeyPairResult, msg) def _test_process_query_batch_item( self, operations, object_types, vendor_identification, server_information, application_namespaces, extension_information): payload = payloads.QueryResponsePayload( operations, object_types, vendor_identification, server_information, application_namespaces, extension_information) batch_item = ResponseBatchItem( operation=Operation(OperationEnum.QUERY), response_payload=payload) result = self.client._process_query_batch_item(batch_item) base = "expected {0}, observed {1}" msg = base.format(QueryResult, result) self.assertIsInstance(result, QueryResult, msg) # The payload maps the following inputs to empty lists on None. if operations is None: operations = list() if object_types is None: object_types = list() if application_namespaces is None: application_namespaces = list() if extension_information is None: extension_information = list() self._test_equality(operations, result.operations) self._test_equality(object_types, result.object_types) self._test_equality( vendor_identification, result.vendor_identification) self._test_equality(server_information, result.server_information) self._test_equality( application_namespaces, result.application_namespaces) self._test_equality( extension_information, result.extension_information) def test_process_query_batch_item_with_results(self): self._test_process_query_batch_item( list(), list(), VendorIdentification(), ServerInformation(), list(), list()) def test_process_query_batch_item_without_results(self): self._test_process_query_batch_item(None, None, None, None, None, None) def _test_process_discover_versions_batch_item(self, protocol_versions): batch_item = ResponseBatchItem( operation=Operation(OperationEnum.DISCOVER_VERSIONS), response_payload=payloads.DiscoverVersionsResponsePayload( protocol_versions)) result = self.client._process_discover_versions_batch_item(batch_item) base = "expected {0}, received {1}" msg = base.format(DiscoverVersionsResult, result) self.assertIsInstance(result, DiscoverVersionsResult, msg) # The payload maps protocol_versions to an empty list on None if protocol_versions is None: protocol_versions = list() msg = base.format(protocol_versions, result.protocol_versions) self.assertEqual(protocol_versions, result.protocol_versions, msg) def test_process_discover_versions_batch_item_with_results(self): protocol_versions = [ProtocolVersion.create(1, 0)] self._test_process_discover_versions_batch_item(protocol_versions) def test_process_discover_versions_batch_item_no_results(self): protocol_versions = None self._test_process_discover_versions_batch_item(protocol_versions) def test_process_get_attributes_batch_item(self): uuid = '00000000-1111-2222-3333-444444444444' attributes = [] payload = payloads.GetAttributesResponsePayload( unique_identifier=uuid, attributes=attributes ) batch_item = ResponseBatchItem( operation=Operation(OperationEnum.GET_ATTRIBUTES), response_payload=payload ) result = self.client._process_get_attributes_batch_item(batch_item) self.assertIsInstance(result, GetAttributesResult) self.assertEqual(uuid, result.uuid) self.assertEqual(attributes, result.attributes) def test_process_get_attribute_list_batch_item(self): uid = '00000000-1111-2222-3333-444444444444' names = ['Cryptographic Algorithm', 'Cryptographic Length'] payload = payloads.GetAttributeListResponsePayload( unique_identifier=uid, attribute_names=names) batch_item = ResponseBatchItem( operation=Operation(OperationEnum.GET_ATTRIBUTE_LIST), response_payload=payload) result = self.client._process_get_attribute_list_batch_item(batch_item) self.assertIsInstance(result, GetAttributeListResult) self.assertEqual(uid, result.uid) self.assertEqual(names, result.names) def test_host_list_import_string(self): """ This test verifies that the client can process a string with multiple IP addresses specified in it. It also tests that unnecessary spaces are ignored. """ host_list_string = '127.0.0.1,127.0.0.3, 127.0.0.5' host_list_expected = ['127.0.0.1', '127.0.0.3', '127.0.0.5'] self.client._set_variables(host=host_list_string, port=None, keyfile=None, certfile=None, cert_reqs=None, ssl_version=None, ca_certs=None, do_handshake_on_connect=False, suppress_ragged_eofs=None, username=None, password=None, timeout=None) self.assertEqual(host_list_expected, self.client.host_list) def test_host_is_invalid_input(self): """ This test verifies that invalid values are not processed when setting the client object parameters """ host = 1337 expected_error = TypeError kwargs = {'host': host, 'port': None, 'keyfile': None, 'certfile': None, 'cert_reqs': None, 'ssl_version': None, 'ca_certs': None, 'do_handshake_on_connect': False, 'suppress_ragged_eofs': None, 'username': None, 'password': None, 'timeout': None} self.assertRaises(expected_error, self.client._set_variables, **kwargs) @mock.patch.object(KMIPProxy, '_create_socket') def test_open_server_conn_failover_fail(self, mock_create_socket): """ This test verifies that the KMIP client throws an exception if no servers are available for connection """ mock_create_socket.return_value = mock.MagicMock() # Assumes both IP addresses fail connection attempts self.mock_client.socket.connect.side_effect = [Exception, Exception] self.assertRaises(Exception, self.mock_client.open) @mock.patch.object(KMIPProxy, '_create_socket') def test_open_server_conn_failover_succeed(self, mock_create_socket): """ This test verifies that the KMIP client can setup a connection if at least one connection is established """ mock_create_socket.return_value = mock.MagicMock() # Assumes IP_ADDR_1 is a bad address and IP_ADDR_2 is a good address self.mock_client.socket.connect.side_effect = [Exception, None] self.mock_client.open() self.assertEqual('IP_ADDR_2', self.mock_client.host) def test_socket_ssl_wrap(self): """ This test tests that the KMIP socket is successfully wrapped into an ssl socket """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.client._create_socket(sock) self.assertEqual(ssl.SSLSocket, type(self.client.socket)) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._build_request_message' ) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._send_and_receive_message' ) def test_derive_key(self, send_mock, build_mock): """ Test that the client can derive a key. """ payload = payloads.DeriveKeyResponsePayload( unique_identifier='1', ) batch_item = ResponseBatchItem( operation=Operation(OperationEnum.DERIVE_KEY), result_status=ResultStatus(ResultStatusEnum.SUCCESS), response_payload=payload ) response = ResponseMessage(batch_items=[batch_item]) build_mock.return_value = None send_mock.return_value = response result = self.client.derive_key( object_type=enums.ObjectType.SYMMETRIC_KEY, unique_identifiers=['2', '3'], derivation_method=enums.DerivationMethod.ENCRYPT, derivation_parameters=DerivationParameters( cryptographic_parameters=CryptographicParameters( block_cipher_mode=enums.BlockCipherMode.CBC, padding_method=enums.PaddingMethod.PKCS1v15, cryptographic_algorithm=enums.CryptographicAlgorithm.AES ), initialization_vector=b'\x01\x02\x03\x04', derivation_data=b'\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8' ), template_attribute=TemplateAttribute( attributes=[ self.attr_factory.create_attribute( 'Cryptographic Length', 128 ), self.attr_factory.create_attribute( 'Cryptographic Algorithm', enums.CryptographicAlgorithm.AES ) ] ), ) self.assertEqual('1', result.get('unique_identifier')) self.assertEqual( ResultStatusEnum.SUCCESS, result.get('result_status') ) self.assertEqual(None, result.get('result_reason')) self.assertEqual(None, result.get('result_message')) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._build_request_message' ) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._send_and_receive_message' ) def test_encrypt(self, send_mock, build_mock): """ Test that the client can encrypt data. """ payload = payloads.EncryptResponsePayload( unique_identifier='1', data=( b'\x6B\x77\xB4\xD6\x30\x06\xDE\xE6' b'\x05\xB1\x56\xE2\x74\x03\x97\x93' b'\x58\xDE\xB9\xE7\x15\x46\x16\xD9' b'\x74\x9D\xEC\xBE\xC0\x5D\x26\x4B' ) ) batch_item = ResponseBatchItem( operation=Operation(OperationEnum.ENCRYPT), result_status=ResultStatus(ResultStatusEnum.SUCCESS), response_payload=payload ) response = ResponseMessage(batch_items=[batch_item]) build_mock.return_value = None send_mock.return_value = response result = self.client.encrypt( ( b'\x37\x36\x35\x34\x33\x32\x31\x20' b'\x4E\x6F\x77\x20\x69\x73\x20\x74' b'\x68\x65\x20\x74\x69\x6D\x65\x20' b'\x66\x6F\x72\x20\x00' ), unique_identifier='1', cryptographic_parameters=CryptographicParameters( block_cipher_mode=enums.BlockCipherMode.CBC, padding_method=enums.PaddingMethod.PKCS5, cryptographic_algorithm=enums.CryptographicAlgorithm.BLOWFISH ), iv_counter_nonce=b'\xFE\xDC\xBA\x98\x76\x54\x32\x10' ) self.assertEqual('1', result.get('unique_identifier')) self.assertEqual( ( b'\x6B\x77\xB4\xD6\x30\x06\xDE\xE6' b'\x05\xB1\x56\xE2\x74\x03\x97\x93' b'\x58\xDE\xB9\xE7\x15\x46\x16\xD9' b'\x74\x9D\xEC\xBE\xC0\x5D\x26\x4B' ), result.get('data') ) self.assertEqual(None, result.get('iv_counter_nonce')) self.assertEqual( ResultStatusEnum.SUCCESS, result.get('result_status') ) self.assertEqual(None, result.get('result_reason')) self.assertEqual(None, result.get('result_message')) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._build_request_message' ) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._send_and_receive_message' ) def test_decrypt(self, send_mock, build_mock): """ Test that the client can decrypt data. """ payload = payloads.DecryptResponsePayload( unique_identifier='1', data=( b'\x37\x36\x35\x34\x33\x32\x31\x20' b'\x4E\x6F\x77\x20\x69\x73\x20\x74' b'\x68\x65\x20\x74\x69\x6D\x65\x20' b'\x66\x6F\x72\x20\x00' ) ) batch_item = ResponseBatchItem( operation=Operation(OperationEnum.DECRYPT), result_status=ResultStatus(ResultStatusEnum.SUCCESS), response_payload=payload ) response = ResponseMessage(batch_items=[batch_item]) build_mock.return_value = None send_mock.return_value = response result = self.client.decrypt( ( b'\x6B\x77\xB4\xD6\x30\x06\xDE\xE6' b'\x05\xB1\x56\xE2\x74\x03\x97\x93' b'\x58\xDE\xB9\xE7\x15\x46\x16\xD9' b'\x74\x9D\xEC\xBE\xC0\x5D\x26\x4B' ), unique_identifier='1', cryptographic_parameters=CryptographicParameters( block_cipher_mode=enums.BlockCipherMode.CBC, padding_method=enums.PaddingMethod.PKCS5, cryptographic_algorithm=enums.CryptographicAlgorithm.BLOWFISH ), iv_counter_nonce=b'\xFE\xDC\xBA\x98\x76\x54\x32\x10' ) self.assertEqual('1', result.get('unique_identifier')) self.assertEqual( ( b'\x37\x36\x35\x34\x33\x32\x31\x20' b'\x4E\x6F\x77\x20\x69\x73\x20\x74' b'\x68\x65\x20\x74\x69\x6D\x65\x20' b'\x66\x6F\x72\x20\x00' ), result.get('data') ) self.assertEqual( ResultStatusEnum.SUCCESS, result.get('result_status') ) self.assertEqual(None, result.get('result_reason')) self.assertEqual(None, result.get('result_message')) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._build_request_message' ) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._send_and_receive_message' ) def test_signature_verify(self, send_mock, build_mock): """ Test that the client can verify a signature. """ payload = payloads.SignatureVerifyResponsePayload( unique_identifier='1', validity_indicator=enums.ValidityIndicator.INVALID ) batch_item = ResponseBatchItem( operation=Operation(OperationEnum.SIGNATURE_VERIFY), result_status=ResultStatus(ResultStatusEnum.SUCCESS), response_payload=payload ) response = ResponseMessage(batch_items=[batch_item]) build_mock.return_value = None send_mock.return_value = response result = self.client.signature_verify( ( b'\x6B\x77\xB4\xD6\x30\x06\xDE\xE6' b'\x05\xB1\x56\xE2\x74\x03\x97\x93' b'\x58\xDE\xB9\xE7\x15\x46\x16\xD9' b'\x74\x9D\xEC\xBE\xC0\x5D\x26\x4B' ), ( b'\x11\x11\x11\x11\x11\x11\x11\x11' ), unique_identifier='1', cryptographic_parameters=CryptographicParameters( padding_method=enums.PaddingMethod.PKCS1v15, cryptographic_algorithm=enums.CryptographicAlgorithm.RSA, hashing_algorithm=enums.HashingAlgorithm.SHA_224 ) ) self.assertEqual('1', result.get('unique_identifier')) self.assertEqual( enums.ValidityIndicator.INVALID, result.get('validity_indicator') ) self.assertEqual( ResultStatusEnum.SUCCESS, result.get('result_status') ) self.assertEqual(None, result.get('result_reason')) self.assertEqual(None, result.get('result_message')) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._build_request_message' ) @mock.patch( 'kmip.services.kmip_client.KMIPProxy._send_and_receive_message' ) def test_sign(self, send_mock, build_mock): """ Test that the client can sign data """ payload = payloads.SignResponsePayload( unique_identifier='1', signature_data=b'aaaaaaaaaaaaaaaa' ) batch_item = ResponseBatchItem( operation=Operation(OperationEnum.SIGN), result_status=ResultStatus(ResultStatusEnum.SUCCESS), response_payload=payload ) response = ResponseMessage(batch_items=[batch_item]) build_mock.return_value = None send_mock.return_value = response result = self.client.sign( b'\x11\x11\x11\x11\x11\x11\x11\x11', unique_identifier='1', cryptographic_parameters=CryptographicParameters( padding_method=enums.PaddingMethod.PKCS1v15, cryptographic_algorithm=enums.CryptographicAlgorithm.RSA, hashing_algorithm=enums.HashingAlgorithm.SHA_224 ) ) self.assertEqual('1', result.get('unique_identifier')) self.assertEqual( b'aaaaaaaaaaaaaaaa', result.get('signature') ) self.assertEqual( ResultStatusEnum.SUCCESS, result.get('result_status') ) self.assertEqual(None, result.get('result_reason')) self.assertEqual(None, result.get('result_message')) @mock.patch('kmip.services.kmip_client.KMIPProxy._send_message', mock.MagicMock()) @mock.patch('kmip.services.kmip_client.KMIPProxy._receive_message', mock.MagicMock()) def test_mac(self): from kmip.core.utils import BytearrayStream request_expected = ( b'\x42\x00\x78\x01\x00\x00\x00\xa0\x42\x00\x77\x01\x00\x00\x00\x38' b'\x42\x00\x69\x01\x00\x00\x00\x20\x42\x00\x6a\x02\x00\x00\x00\x04' b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x6b\x02\x00\x00\x00\x04' b'\x00\x00\x00\x02\x00\x00\x00\x00\x42\x00\x0d\x02\x00\x00\x00\x04' b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x0f\x01\x00\x00\x00\x58' b'\x42\x00\x5c\x05\x00\x00\x00\x04\x00\x00\x00\x23\x00\x00\x00\x00' b'\x42\x00\x79\x01\x00\x00\x00\x40\x42\x00\x94\x07\x00\x00\x00\x01' b'\x31\x00\x00\x00\x00\x00\x00\x00\x42\x00\x2b\x01\x00\x00\x00\x10' b'\x42\x00\x28\x05\x00\x00\x00\x04\x00\x00\x00\x0b\x00\x00\x00\x00' b'\x42\x00\xc2\x08\x00\x00\x00\x10\x00\x01\x02\x03\x04\x05\x06\x07' b'\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f') response = ( b'\x42\x00\x7b\x01\x00\x00\x00\xd8\x42\x00\x7a\x01\x00\x00\x00\x48' b'\x42\x00\x69\x01\x00\x00\x00\x20\x42\x00\x6a\x02\x00\x00\x00\x04' b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x6b\x02\x00\x00\x00\x04' b'\x00\x00\x00\x02\x00\x00\x00\x00\x42\x00\x92\x09\x00\x00\x00\x08' b'\x00\x00\x00\x00\x58\x8a\x3f\x23\x42\x00\x0d\x02\x00\x00\x00\x04' b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x0f\x01\x00\x00\x00\x80' b'\x42\x00\x5c\x05\x00\x00\x00\x04\x00\x00\x00\x23\x00\x00\x00\x00' b'\x42\x00\x7f\x05\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00' b'\x42\x00\x7c\x01\x00\x00\x00\x58\x42\x00\x94\x07\x00\x00\x00\x01' b'\x31\x00\x00\x00\x00\x00\x00\x00\x42\x00\xc6\x08\x00\x00\x00\x40' b'\x99\x8b\x55\x59\x90\x9b\x85\x87\x5b\x90\x63\x13\x12\xbb\x32\x9f' b'\x6a\xc4\xed\x97\x6e\xac\x99\xe5\x21\x53\xc4\x19\x28\xf2\x2a\x5b' b'\xef\x79\xa4\xbe\x05\x3b\x31\x49\x19\xe0\x75\x23\xb9\xbe\xc8\x23' b'\x35\x60\x7e\x49\xba\xa9\x7e\xe0\x9e\x6b\x3d\x55\xf4\x51\xff\x7c' ) response_no_payload = ( b'\x42\x00\x7b\x01\x00\x00\x00\x78\x42\x00\x7a\x01\x00\x00\x00\x48' b'\x42\x00\x69\x01\x00\x00\x00\x20\x42\x00\x6a\x02\x00\x00\x00\x04' b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x6b\x02\x00\x00\x00\x04' b'\x00\x00\x00\x02\x00\x00\x00\x00\x42\x00\x92\x09\x00\x00\x00\x08' b'\x00\x00\x00\x00\x58\x8a\x3f\x23\x42\x00\x0d\x02\x00\x00\x00\x04' b'\x00\x00\x00\x01\x00\x00\x00\x00\x42\x00\x0f\x01\x00\x00\x00\x80' b'\x42\x00\x5c\x05\x00\x00\x00\x04\x00\x00\x00\x23\x00\x00\x00\x00' b'\x42\x00\x7f\x05\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00' ) data = (b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B' b'\x0C\x0D\x0E\x0F') mdata = (b'\x99\x8b\x55\x59\x90\x9b\x85\x87\x5b\x90\x63\x13' b'\x12\xbb\x32\x9f' b'\x6a\xc4\xed\x97\x6e\xac\x99\xe5\x21\x53\xc4\x19' b'\x28\xf2\x2a\x5b' b'\xef\x79\xa4\xbe\x05\x3b\x31\x49\x19\xe0\x75\x23' b'\xb9\xbe\xc8\x23' b'\x35\x60\x7e\x49\xba\xa9\x7e\xe0\x9e\x6b\x3d\x55' b'\xf4\x51\xff\x7c') def verify_request(message): stream = BytearrayStream() message.write(stream) self.assertEqual(stream.buffer, request_expected) uuid = '1' cryptographic_parameters = CryptographicParameters( cryptographic_algorithm=CryptographicAlgorithmEnum.HMAC_SHA512 ) self.client._send_message.side_effect = verify_request self.client._receive_message.return_value = BytearrayStream(response) result = self.client.mac(data, uuid, cryptographic_parameters) self.assertEqual(result.uuid.value, uuid) self.assertEqual(result.mac_data.value, mdata) self.client._receive_message.return_value = \ BytearrayStream(response_no_payload) result = self.client.mac(data, uuid, cryptographic_parameters) self.assertEqual(result.uuid, None) self.assertEqual(result.mac_data, None)
class ProxyKmipClient(api.KmipClient): """ A simplified KMIP client for conducting KMIP operations. The ProxyKmipClient is a simpler KMIP client supporting various KMIP operations. It wraps the original KMIPProxy, reducing the boilerplate needed to deploy PyKMIP in client applications. The underlying proxy client is responsible for setting up the underlying socket connection and for writing/reading data to/from the socket. Like the KMIPProxy, the ProxyKmipClient is not thread-safe. """ def __init__(self, hostname=None, port=None, cert=None, key=None, ca=None, ssl_version=None, username=None, password=None, config='client'): """ Construct a ProxyKmipClient. Args: hostname (string): The host or IP address of a KMIP appliance. Optional, defaults to None. port (int): The port number used to establish a connection to a KMIP appliance. Usually 5696 for KMIP applications. Optional, defaults to None. cert (string): The path to the client's certificate. Optional, defaults to None. key (string): The path to the key for the client's certificate. Optional, defaults to None. ca (string): The path to the CA certificate used to verify the server's certificate. Optional, defaults to None. ssl_version (string): The name of the ssl version to use for the connection. Example: 'PROTOCOL_SSLv23'. Optional, defaults to None. username (string): The username of the KMIP appliance account to use for operations. Optional, defaults to None. password (string): The password of the KMIP appliance account to use for operations. Optional, defaults to None. config (string): The name of a section in the PyKMIP configuration file. Use to load a specific set of configuration settings from the configuration file, instead of specifying them manually. Optional, defaults to the default client section, 'client'. """ self.logger = logging.getLogger() self.attribute_factory = attributes.AttributeFactory() self.object_factory = factory.ObjectFactory() # TODO (peter-hamilton) Consider adding validation checks for inputs. self.proxy = KMIPProxy(host=hostname, port=port, certfile=cert, keyfile=key, ca_certs=ca, ssl_version=ssl_version, username=username, password=password, config=config) # TODO (peter-hamilton) Add a multiprocessing lock for synchronization. self._is_open = False def open(self): """ Open the client connection. Raises: ClientConnectionFailure: if the client connection is already open Exception: if an error occurs while trying to open the connection """ if self._is_open: raise exceptions.ClientConnectionFailure( "client connection already open") else: try: self.proxy.open() self._is_open = True except Exception as e: self.logger.exception("could not open client connection", e) raise e def close(self): """ Close the client connection. Raises: ClientConnectionNotOpen: if the client connection is not open Exception: if an error occurs while trying to close the connection """ if not self._is_open: raise exceptions.ClientConnectionNotOpen() else: try: self.proxy.close() self._is_open = False except Exception as e: self.logger.exception("could not close client connection", e) raise e def create(self, algorithm, length, operation_policy_name=None, name=None): """ Create a symmetric key on a KMIP appliance. Args: algorithm (CryptographicAlgorithm): An enumeration defining the algorithm to use to generate the symmetric key. length (int): The length in bits for the symmetric key. operation_policy_name (string): The name of the operation policy to use for the new symmetric key. Optional, defaults to None name (string): The name to give the key. Optional, defaults to None Returns: string: The uid of the newly created symmetric key. Raises: ClientConnectionNotOpen: if the client connection is unusable KmipOperationFailure: if the operation result is a failure TypeError: if the input arguments are invalid """ # Check inputs if not isinstance(algorithm, enums.CryptographicAlgorithm): raise TypeError( "algorithm must be a CryptographicAlgorithm enumeration") elif not isinstance(length, six.integer_types) or length <= 0: raise TypeError("length must be a positive integer") # Verify that operations can be given at this time if not self._is_open: raise exceptions.ClientConnectionNotOpen() # Create the template containing the attributes common_attributes = self._build_common_attributes( operation_policy_name) key_attributes = self._build_key_attributes(algorithm, length) key_attributes.extend(common_attributes) if name: key_attributes.extend(self._build_name_attribute(name)) template = cobjects.TemplateAttribute(attributes=key_attributes) # Create the symmetric key and handle the results result = self.proxy.create(enums.ObjectType.SYMMETRIC_KEY, template) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: uid = result.uuid.value return uid else: reason = result.result_reason.value message = result.result_message.value raise exceptions.KmipOperationFailure(status, reason, message) def create_key_pair(self, algorithm, length, operation_policy_name=None, public_name=None, private_name=None): """ Create an asymmetric key pair on a KMIP appliance. Args: algorithm (CryptographicAlgorithm): An enumeration defining the algorithm to use to generate the key pair. length (int): The length in bits for the key pair. operation_policy_name (string): The name of the operation policy to use for the new key pair. Optional, defaults to None. public_name (string): The name to give the public key. Optional, defaults to None. private_name (string): The name to give the public key. Optional, defaults to None. Returns: string: The uid of the newly created public key. string: The uid of the newly created private key. Raises: ClientConnectionNotOpen: if the client connection is unusable KmipOperationFailure: if the operation result is a failure TypeError: if the input arguments are invalid """ # Check inputs if not isinstance(algorithm, enums.CryptographicAlgorithm): raise TypeError( "algorithm must be a CryptographicAlgorithm enumeration") elif not isinstance(length, six.integer_types) or length <= 0: raise TypeError("length must be a positive integer") # Verify that operations can be given at this time if not self._is_open: raise exceptions.ClientConnectionNotOpen() # Create the common attributes that are shared common_attributes = self._build_common_attributes( operation_policy_name) key_attributes = self._build_key_attributes(algorithm, length) key_attributes.extend(common_attributes) template = cobjects.CommonTemplateAttribute(attributes=key_attributes) # Create public / private specific attributes public_template = None if public_name: name_attr = self._build_name_attribute(name=public_name) public_template = cobjects.PublicKeyTemplateAttribute( names=name_attr) private_template = None if private_name: name_attr = self._build_name_attribute(name=private_name) private_template = cobjects.PrivateKeyTemplateAttribute( names=name_attr) # Create the asymmetric key pair and handle the results result = self.proxy.create_key_pair( common_template_attribute=template, private_key_template_attribute=private_template, public_key_template_attribute=public_template) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: public_uid = result.public_key_uuid.value private_uid = result.private_key_uuid.value return public_uid, private_uid else: reason = result.result_reason.value message = result.result_message.value raise exceptions.KmipOperationFailure(status, reason, message) def register(self, managed_object): """ Register a managed object with a KMIP appliance. Args: managed_object (ManagedObject): A managed object to register. An instantiatable subclass of ManagedObject from the Pie API. Returns: string: The uid of the newly registered managed object. Raises: ClientConnectionNotOpen: if the client connection is unusable KmipOperationFailure: if the operation result is a failure TypeError: if the input argument is invalid """ # Check input if not isinstance(managed_object, pobjects.ManagedObject): raise TypeError("managed object must be a Pie ManagedObject") # Verify that operations can be given at this time if not self._is_open: raise exceptions.ClientConnectionNotOpen() # Extract and create attributes object_attributes = list() if hasattr(managed_object, 'cryptographic_usage_masks'): if managed_object.cryptographic_usage_masks is not None: mask_attribute = self.attribute_factory.create_attribute( enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, managed_object.cryptographic_usage_masks) object_attributes.append(mask_attribute) if hasattr(managed_object, 'operation_policy_name'): if managed_object.operation_policy_name is not None: opn_attribute = self.attribute_factory.create_attribute( enums.AttributeType.OPERATION_POLICY_NAME, managed_object.operation_policy_name) object_attributes.append(opn_attribute) template = cobjects.TemplateAttribute(attributes=object_attributes) object_type = managed_object.object_type # Register the managed object and handle the results secret = self.object_factory.convert(managed_object) result = self.proxy.register(object_type, template, secret) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: uid = result.uuid.value return uid else: reason = result.result_reason.value message = result.result_message.value raise exceptions.KmipOperationFailure(status, reason, message) def get(self, uid): """ Get a managed object from a KMIP appliance. Args: uid (string): The unique ID of the managed object to retrieve. Returns: ManagedObject: The retrieved managed object object. Raises: ClientConnectionNotOpen: if the client connection is unusable KmipOperationFailure: if the operation result is a failure TypeError: if the input argument is invalid """ # Check input if not isinstance(uid, six.string_types): raise TypeError("uid must be a string") # Verify that operations can be given at this time if not self._is_open: raise exceptions.ClientConnectionNotOpen() # Get the managed object and handle the results result = self.proxy.get(uid) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: managed_object = self.object_factory.convert(result.secret) return managed_object else: reason = result.result_reason.value message = result.result_message.value raise exceptions.KmipOperationFailure(status, reason, message) def get_attributes(self, uid=None, attribute_names=None): """ Get the attributes associated with a managed object. If the uid is not specified, the appliance will use the ID placeholder by default. If the attribute_names list is not specified, the appliance will return all viable attributes for the managed object. Args: uid (string): The unique ID of the managed object with which the retrieved attributes should be associated. Optional, defaults to None. attribute_names (list): A list of string attribute names indicating which attributes should be retrieved. Optional, defaults to None. """ # Check input if uid is not None: if not isinstance(uid, six.string_types): raise TypeError("uid must be a string") if attribute_names is not None: if not isinstance(attribute_names, list): raise TypeError("attribute_names must be a list of strings") else: for attribute_name in attribute_names: if not isinstance(attribute_name, six.string_types): raise TypeError( "attribute_names must be a list of strings") # Verify that operations can be given at this time if not self._is_open: raise exceptions.ClientConnectionNotOpen() # Get the list of attributes for a managed object result = self.proxy.get_attributes(uid, attribute_names) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: return result.uuid, result.attributes else: reason = result.result_reason.value message = result.result_message.value raise exceptions.KmipOperationFailure(status, reason, message) def get_attribute_list(self, uid=None): """ Get the names of the attributes associated with a managed object. If the uid is not specified, the appliance will use the ID placeholder by default. Args: uid (string): The unique ID of the managed object with which the retrieved attribute names should be associated. Optional, defaults to None. """ # Check input if uid is not None: if not isinstance(uid, six.string_types): raise TypeError("uid must be a string") # Verify that operations can be given at this time if not self._is_open: raise exceptions.ClientConnectionNotOpen() # Get the list of attribute names for a managed object. result = self.proxy.get_attribute_list(uid) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: attribute_names = sorted(result.names) return attribute_names else: reason = result.result_reason.value message = result.result_message.value raise exceptions.KmipOperationFailure(status, reason, message) def destroy(self, uid): """ Destroy a managed object stored by a KMIP appliance. Args: uid (string): The unique ID of the managed object to destroy. Returns: None Raises: ClientConnectionNotOpen: if the client connection is unusable KmipOperationFailure: if the operation result is a failure TypeError: if the input argument is invalid """ # Check input if not isinstance(uid, six.string_types): raise TypeError("uid must be a string") # Verify that operations can be given at this time if not self._is_open: raise exceptions.ClientConnectionNotOpen() # Destroy the managed object and handle the results result = self.proxy.destroy(uid) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: return else: reason = result.result_reason.value message = result.result_message.value raise exceptions.KmipOperationFailure(status, reason, message) def mac(self, uid, algorithm, data): """ Get the message authentication code for data. Args: uid (string): The unique ID of the managed object that is the key to use for the MAC operation. algorithm (CryptographicAlgorithm): An enumeration defining the algorithm to use to generate the MAC. data (string): The data to be MACed. Returns: string: The unique ID of the managed object that is the key to use for the MAC operation. string: The data MACed Raises: ClientConnectionNotOpen: if the client connection is unusable KmipOperationFailure: if the operation result is a failure TypeError: if the input arguments are invalid """ # Check inputs if not isinstance(uid, six.string_types): raise TypeError("uid must be a string") if not isinstance(algorithm, enums.CryptographicAlgorithm): raise TypeError( "algorithm must be a CryptographicAlgorithm enumeration") if not isinstance(data, six.binary_type): raise TypeError("data must be bytes") # Verify that operations can be given at this time if not self._is_open: raise exceptions.ClientConnectionNotOpen() parameters_attribute = CryptographicParameters( cryptographic_algorithm=CryptographicAlgorithm(algorithm)) # Create the symmetric key and handle the results result = self.proxy.mac(uid, parameters_attribute, data) status = result.result_status.value if status == enums.ResultStatus.SUCCESS: uid = result.uuid.value mac_data = result.mac_data.value return uid, mac_data else: reason = result.result_reason.value message = result.result_message.value raise exceptions.KmipOperationFailure(status, reason, message) def _build_key_attributes(self, algorithm, length): # Build a list of core key attributes. algorithm_attribute = self.attribute_factory.create_attribute( enums.AttributeType.CRYPTOGRAPHIC_ALGORITHM, algorithm) length_attribute = self.attribute_factory.create_attribute( enums.AttributeType.CRYPTOGRAPHIC_LENGTH, length) mask_attribute = self.attribute_factory.create_attribute( enums.AttributeType.CRYPTOGRAPHIC_USAGE_MASK, [ enums.CryptographicUsageMask.ENCRYPT, enums.CryptographicUsageMask.DECRYPT ]) return [algorithm_attribute, length_attribute, mask_attribute] def _build_common_attributes(self, operation_policy_name=None): ''' Build a list of common attributes that are shared across symmetric as well as asymmetric objects ''' common_attributes = [] if operation_policy_name: common_attributes.append( self.attribute_factory.create_attribute( enums.AttributeType.OPERATION_POLICY_NAME, operation_policy_name)) return common_attributes def _build_name_attribute(self, name=None): ''' Build a name attribute, returned in a list for ease of use in the caller ''' name_list = [] if name: name_list.append( self.attribute_factory.create_attribute( enums.AttributeType.NAME, name)) return name_list def __enter__(self): self.open() return self def __exit__(self, exc_type, exc_value, traceback): self.close()