def _process_encrypt_patched(self, payload): self._logger.info("Processing operation: Encrypt") unique_identifier = self._id_placeholder if payload.unique_identifier: unique_identifier = payload.unique_identifier # The KMIP spec does not indicate that the Encrypt operation should # have it's own operation policy entry. Rather, the cryptographic # usage mask should be used to determine if the object can be used # to encrypt data (see below). managed_object = self._get_object_with_access_controls( unique_identifier, enums.Operation.GET) cryptographic_parameters = payload.cryptographic_parameters if cryptographic_parameters is None: # Monkey patched here -- rather than exception, we set to default params. default_crypto_params = CryptographicParameters( block_cipher_mode=enums.BlockCipherMode.CBC, padding_method=enums.PaddingMethod.PKCS5, cryptographic_algorithm=enums.CryptographicAlgorithm.AES, ) cryptographic_parameters = default_crypto_params if managed_object._object_type != enums.ObjectType.SYMMETRIC_KEY: raise exceptions.PermissionDenied( "The requested encryption key is not a symmetric key. " "Only symmetric encryption is currently supported.") if managed_object.state != enums.State.ACTIVE: raise exceptions.PermissionDenied( "The encryption key must be in the Active state to be used " "for encryption.") masks = managed_object.cryptographic_usage_masks if enums.CryptographicUsageMask.ENCRYPT not in masks: raise exceptions.PermissionDenied( "The Encrypt bit must be set in the encryption key's " "cryptographic usage mask.") result = self._cryptography_engine.encrypt( cryptographic_parameters.cryptographic_algorithm, managed_object.value, payload.data, cipher_mode=cryptographic_parameters.block_cipher_mode, padding_method=cryptographic_parameters.padding_method, iv_nonce=payload.iv_counter_nonce, auth_additional_data=payload.auth_additional_data, auth_tag_length=cryptographic_parameters.tag_length, ) response_payload = payloads.EncryptResponsePayload( unique_identifier, result.get("cipher_text"), result.get("iv_nonce"), result.get("auth_tag"), ) return response_payload
def _get_client_identity(self): certificate_data = self._connection.getpeercert(binary_form=True) try: certificate = x509.load_der_x509_certificate( certificate_data, backends.default_backend() ) except Exception: # This should never get raised "in theory," as the ssl socket # should fail to connect non-TLS connections before the session # gets created. This is a failsafe in case that protection fails. raise exceptions.PermissionDenied( "Failure loading the client certificate from the session " "connection. Could not retrieve client identity." ) try: extended_key_usage = certificate.extensions.get_extension_for_oid( x509.oid.ExtensionOID.EXTENDED_KEY_USAGE ).value except x509.ExtensionNotFound: raise exceptions.PermissionDenied( "The extended key usage extension is missing from the client " "certificate. Session client identity unavailable." ) if x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH in extended_key_usage: client_identities = certificate.subject.get_attributes_for_oid( x509.oid.NameOID.COMMON_NAME ) if len(client_identities) > 0: if len(client_identities) > 1: self._logger.warning( "Multiple client identities found. Using the first " "one processed." ) client_identity = client_identities[0].value self._logger.info( "Session client identity: {0}".format(client_identity) ) return client_identity else: raise exceptions.PermissionDenied( "The client certificate does not define a subject common " "name. Session client identity unavailable." ) raise exceptions.PermissionDenied( "The extended key usage extension is not marked for client " "authentication in the client certificate. Session client " "identity unavailable." )
def get_client_identity_from_certificate(certificate): """ Given an X.509 certificate, extract and return the client identity. """ client_ids = get_common_names_from_certificate(certificate) if len(client_ids) > 0: if len(client_ids) > 1: raise exceptions.PermissionDenied( "Multiple client identities found.") return client_ids[0] else: raise exceptions.PermissionDenied( "The certificate does not define any subject common names. " "Client identity unavailable.")
def authenticate(self, connection_certificate=None, connection_info=None, request_credentials=None): """ Query the configured SLUGS service with the provided credentials. Args: connection_certificate (cryptography.x509.Certificate): An X.509 certificate object obtained from the connection being authenticated. Required for SLUGS authentication. connection_info (tuple): A tuple of information pertaining to the connection being authenticated, including the source IP address and a timestamp (e.g., ('127.0.0.1', 1519759267.467451)). Optional, defaults to None. Ignored for SLUGS authentication. request_credentials (list): A list of KMIP Credential structures containing credential information to use for authentication. Optional, defaults to None. Ignored for SLUGS authentication. """ if (self.users_url is None) or (self.groups_url is None): raise exceptions.ConfigurationError( "The SLUGS URL must be specified." ) user_id = utils.get_client_identity_from_certificate( connection_certificate ) try: response = requests.get(self.users_url.format(user_id)) except Exception: raise exceptions.ConfigurationError( "A connection could not be established using the SLUGS URL." ) if response.status_code == 404: raise exceptions.PermissionDenied( "Unrecognized user ID: {}".format(user_id) ) response = requests.get(self.groups_url.format(user_id)) if response.status_code == 404: raise exceptions.PermissionDenied( "Group information could not be retrieved for user ID: " "{}".format(user_id) ) return user_id, response.json().get('groups')
def test_handle_message_loop_with_authentication_failure( self, request_mock, cert_mock): """ Test that the correct logging and error handling occurs when an authentication error is generated while processing a request. """ data = utils.BytearrayStream(()) cert_mock.return_value = 'test_certificate' kmip_engine = engine.KmipEngine() kmip_engine._logger = mock.MagicMock() kmip_session = session.KmipSession(kmip_engine, None, None, name='name', enable_tls_client_auth=False) kmip_session.authenticate = mock.MagicMock() kmip_session.authenticate.side_effect = exceptions.PermissionDenied( "Authentication failed.") kmip_session._engine = mock.MagicMock() kmip_session._engine.default_protocol_version = \ kmip_engine.default_protocol_version kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() fake_version = contents.ProtocolVersion(1, 2) fake_credential = objects.Credential( credential_type=enums.CredentialType.USERNAME_AND_PASSWORD, credential_value=objects.UsernamePasswordCredential( username="******", password="******")) fake_header = messages.RequestHeader( protocol_version=fake_version, authentication=contents.Authentication( credentials=[fake_credential])) fake_request = messages.RequestMessage() fake_request.request_header = fake_header fake_request.read = mock.MagicMock() request_mock.return_value = fake_request kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() fake_request.read.assert_called_once_with( data, kmip_version=enums.KMIPVersion.KMIP_1_2) kmip_session.authenticate.assert_called_once_with( "test_certificate", fake_request) kmip_session._logger.warning.assert_called_once_with( "Authentication failed.") kmip_session._engine.build_error_response.assert_called_once_with( fake_version, enums.ResultReason.AUTHENTICATION_NOT_SUCCESSFUL, "An error occurred during client authentication. " "See server logs for more information.") kmip_session._logger.exception.assert_not_called() self.assertTrue(kmip_session._send_response.called)
def test_authenticate_against_slugs_with_failure(self, mock_connector): """ Test that the session correctly handles a SLUGS authentication error. """ mock_instance = mock.MagicMock() test_exception = exceptions.PermissionDenied( "Unrecognized user ID: John Doe" ) mock_instance.authenticate.side_effect = test_exception mock_connector.return_value = mock_instance kmip_session = session.KmipSession( None, None, ("127.0.0.1", 48026), name='TestSession', auth_settings=[( "auth:slugs", {"enabled": "True", "url": "test_url"} )] ) kmip_session._logger = mock.MagicMock() fake_credential = objects.Credential( credential_type=enums.CredentialType.USERNAME_AND_PASSWORD, credential_value=objects.UsernamePasswordCredential( username="******", password="******" ) ) fake_request = messages.RequestMessage( request_header=messages.RequestHeader( authentication=contents.Authentication( credentials=[fake_credential] ) ) ) args = ("fake_certificate", fake_request) self.assertRaisesRegexp( exceptions.PermissionDenied, "Authentication failed.", kmip_session.authenticate, *args ) mock_connector.assert_any_call("test_url") kmip_session._logger.debug.assert_any_call( "Authenticating with plugin: auth:slugs" ) kmip_session._logger.warning.assert_any_call("Authentication failed.") kmip_session._logger.exception.assert_any_call(test_exception)
def authenticate(self, certificate, request): credentials = [] if request.request_header.authentication is not None: credentials = request.request_header.authentication.credentials plugin_enabled = False for auth_settings in self._auth_settings: plugin_name, plugin_config = auth_settings if plugin_name.startswith("auth:slugs"): if plugin_config.get("enabled") == "True": plugin_enabled = True plugin = auth.SLUGSConnector(plugin_config.get("url")) self._logger.debug( "Authenticating with plugin: {}".format(plugin_name)) try: client_identity = plugin.authenticate( certificate, (self._address, self._session_time), credentials) except Exception as e: self._logger.warning("Authentication failed.") self._logger.error(e) self._logger.exception(e) else: self._logger.debug( "Authentication succeeded for client identity: " "{}".format(client_identity[0])) return client_identity else: self._logger.warning("Authentication plugin '{}' is not " "supported.".format(plugin_name)) if not plugin_enabled: self._logger.debug( "No authentication plugins are enabled. The client identity " "will be extracted from the client certificate.") try: client_identity = auth.get_client_identity_from_certificate( certificate) except Exception as e: self._logger.warning("Client identity extraction failed.") self._logger.exception(e) else: self._logger.debug( "Extraction succeeded for client identity: {}".format( client_identity)) return tuple([client_identity, None]) raise exceptions.PermissionDenied("Authentication failed.")
def test_handle_message_loop_invalid_certificate_extension(self, request_mock, cert_mock, ext_mock): """ Test that the correct logging and error handling occurs when an invalid certificate is encountered while processing a request. """ data = utils.BytearrayStream(()) cert_mock.return_value = 'test_certificate' ext_mock.return_value = [] kmip_engine = engine.KmipEngine() kmip_engine._logger = mock.MagicMock() kmip_session = session.KmipSession( kmip_engine, None, None, name='name', enable_tls_client_auth=True ) kmip_session.authenticate = mock.MagicMock() kmip_session._engine = mock.MagicMock() kmip_session._logger = mock.MagicMock() kmip_session._connection = mock.MagicMock() kmip_session._receive_request = mock.MagicMock(return_value=data) kmip_session._send_response = mock.MagicMock() kmip_session._handle_message_loop() kmip_session._receive_request.assert_called_once_with() kmip_session._logger.warning( "Failure verifying the client certificate." ) kmip_session._logger.exception.assert_called_once_with( exceptions.PermissionDenied( "The extended key usage extension is not marked for client " "authentication in the client certificate." ) ) kmip_session._engine.build_error_response.assert_called_once_with( contents.ProtocolVersion(1, 0), enums.ResultReason.AUTHENTICATION_NOT_SUCCESSFUL, "Error verifying the client certificate. " "See server logs for more information." ) self.assertTrue(kmip_session._send_response.called)
def _process_activate(self, payload): self._logger.info("Processing operation: Activate") if payload.unique_identifier: unique_identifier = payload.unique_identifier.value else: unique_identifier = self._id_placeholder object_type = self._get_object_type(unique_identifier) managed_object = self._data_session.query(object_type).filter( object_type.unique_identifier == unique_identifier).one() # Determine if the request should be carried out under the object's # operation policy. If not, feign ignorance of the object. is_allowed = self._is_allowed_by_operation_policy( managed_object.operation_policy_name, self._client_identity, managed_object._owner, managed_object._object_type, enums.Operation.ACTIVATE) if not is_allowed: raise exceptions.ItemNotFound( "Could not locate object: {0}".format(unique_identifier)) object_type = managed_object._object_type if not hasattr(managed_object, 'state'): raise exceptions.IllegalOperation( "An {0} object has no state and cannot be activated.".format( ''.join([ x.capitalize() for x in object_type.name.split('_') ]))) if managed_object.state != enums.State.PRE_ACTIVE: raise exceptions.PermissionDenied( "The object state is not pre-active and cannot be activated.") managed_object.state = enums.State.ACTIVE self._data_session.commit() response_payload = activate.ActivateResponsePayload( unique_identifier=attributes.UniqueIdentifier(unique_identifier)) return response_payload
def _process_get(self, payload): self._logger.info("Processing operation: Get") unique_identifier = self._id_placeholder if payload.unique_identifier: unique_identifier = payload.unique_identifier.value key_format_type = None if payload.key_format_type: key_format_type = payload.key_format_type.value if payload.key_compression_type: raise exceptions.KeyCompressionTypeNotSupported( "Key compression is not supported.") if payload.key_wrapping_specification: raise exceptions.PermissionDenied("Key wrapping is not supported.") # TODO (peterhamilton) Process key wrapping information # 1. Error check wrapping keys for accessibility and usability object_type = self._get_object_type(unique_identifier) managed_object = self._data_session.query(object_type).filter( object_type.unique_identifier == unique_identifier).one() # Determine if the request should be carried out under the object's # operation policy. If not, feign ignorance of the object. is_allowed = self._is_allowed_by_operation_policy( managed_object.operation_policy_name, self._client_identity, managed_object._owner, managed_object._object_type, enums.Operation.GET) if not is_allowed: raise exceptions.ItemNotFound( "Could not locate object: {0}".format(unique_identifier)) if key_format_type: if not hasattr(managed_object, 'key_format_type'): raise exceptions.KeyFormatTypeNotSupported( "Key format is not applicable to the specified object.") # TODO (peterhamilton) Convert key to desired format if possible if key_format_type != managed_object.key_format_type: raise exceptions.KeyFormatTypeNotSupported( "Key format conversion from {0} to {1} is " "unsupported.".format(managed_object.key_format_type.name, key_format_type.name)) object_type = managed_object.object_type.name self._logger.info("Getting a {0} with ID: {1}".format( ''.join([x.capitalize() for x in object_type.split('_')]), managed_object.unique_identifier)) core_secret = self._build_core_object(managed_object) response_payload = get.GetResponsePayload( object_type=attributes.ObjectType(managed_object._object_type), unique_identifier=attributes.UniqueIdentifier(unique_identifier), secret=core_secret) return response_payload
def _handle_message_loop(self): request_data = self._receive_request() request = messages.RequestMessage() max_size = self._max_response_size kmip_version = contents.protocol_version_to_kmip_version( self._engine.default_protocol_version) try: if hasattr(self._connection, 'shared_ciphers'): shared_ciphers = self._connection.shared_ciphers() self._logger.debug("Possible session ciphers: {0}".format( len(shared_ciphers))) for cipher in shared_ciphers: self._logger.debug(cipher) self._logger.debug("Session cipher selected: {0}".format( self._connection.cipher())) certificate = auth.get_certificate_from_connection( self._connection) if certificate is None: raise exceptions.PermissionDenied( "The client certificate could not be loaded from the " "session connection.") if self._enable_tls_client_auth: extension = auth.get_extended_key_usage_from_certificate( certificate) if extension is None: raise exceptions.PermissionDenied( "The extended key usage extension is missing from " "the client certificate.") if x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH not in extension: raise exceptions.PermissionDenied( "The extended key usage extension is not marked for " "client authentication in the client certificate.") request.read(request_data, kmip_version=kmip_version) except exceptions.PermissionDenied as e: self._logger.warning("Failure verifying the client certificate.") self._logger.exception(e) response = self._engine.build_error_response( contents.ProtocolVersion(1, 0), enums.ResultReason.AUTHENTICATION_NOT_SUCCESSFUL, "Error verifying the client certificate. " "See server logs for more information.") except Exception as e: self._logger.warning("Failure parsing request message.") self._logger.exception(e) response = self._engine.build_error_response( contents.ProtocolVersion(1, 0), enums.ResultReason.INVALID_MESSAGE, "Error parsing request message. See server logs for more " "information.") else: try: client_identity = self.authenticate(certificate, request) self._logger.info("Session client identity: {}".format( client_identity[0])) except Exception: self._logger.warning("Authentication failed.") response = self._engine.build_error_response( request.request_header.protocol_version, enums.ResultReason.AUTHENTICATION_NOT_SUCCESSFUL, "An error occurred during client authentication. " "See server logs for more information.") else: try: results = self._engine.process_request( request, client_identity) response, max_response_size, protocol_version = results kmip_version = contents.protocol_version_to_kmip_version( protocol_version) if max_response_size: max_size = max_response_size except exceptions.KmipError as e: response = self._engine.build_error_response( request.request_header.protocol_version, e.reason, str(e)) except Exception as e: self._logger.warning( "An unexpected error occurred while processing " "request.") self._logger.exception(e) response = self._engine.build_error_response( request.request_header.protocol_version, enums.ResultReason.GENERAL_FAILURE, "An unexpected error occurred while processing " "request. See server logs for more information.") response_data = utils.BytearrayStream() response.write(response_data, kmip_version=kmip_version) if len(response_data) > max_size: self._logger.warning("Response message length too large: " "{0} bytes, max {1} bytes".format( len(response_data), self._max_response_size)) response = self._engine.build_error_response( request.request_header.protocol_version, enums.ResultReason.RESPONSE_TOO_LARGE, "Response message length too large. See server logs for " "more information.") response_data = utils.BytearrayStream() response.write(response_data, kmip_version=kmip_version) self._send_response(response_data.buffer)