Beispiel #1
0
    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
Beispiel #2
0
    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."
        )
Beispiel #3
0
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.")
Beispiel #4
0
    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')
Beispiel #5
0
    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)
Beispiel #6
0
    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)
Beispiel #7
0
    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.")
Beispiel #8
0
    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)
Beispiel #9
0
    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
Beispiel #10
0
    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
Beispiel #11
0
    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)