示例#1
0
def saml_validate_post_response(response):
    issuer = 'urn:rea:sshephalopod'
    request_id_expiration_period_ms = 3600000

    xml = ET.fromstring(response)
    path = "/*[local-name()='Response']/@InResponseTo"
    in_response_to = xml.xpath(path)
    in_response_to = in_response_to[0] if in_response_to else ""

    path = ".//*[local-name()='X509Certificate']/text()"
    cert = xml.xpath(path)[0]
    cert = "-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----".format(cert)

    if not OneLogin_Saml2_Utils.validate_sign(xml, cert, validatecert=True):
        raise Exception("Top level signature is not valid!")

    assertions = xml.xpath("/*[local-name()='Response']/*[local-name()='Assertion']")
    encrypted_assertions = xml.xpath("/*[local-name()='Response']/*[local-name()='EncryptedAssertion']")
    if len(assertions) + len(encrypted_assertions) > 1:
        # There's apparently no reason we want to handle multiple assertions
        raise Exception("Too many assertions!")

    if len(assertions) == 1:
        if not OneLogin_Saml2_Utils.validate_sign(assertions[0], cert, validatecert=True):
            raise Exception("Invalid signature in assertion")
        return process_validly_signed_assertion(ET.tostring(assertions[0]))

    raise NotImplementedError("Sorry, only know about if there is an assertion in the response")
示例#2
0
    def is_valid(self, request_data, request_id=None, raise_exceptions=False):
        """
        Validates the response object.

        :param request_data: Request Data
        :type request_data: dict

        :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP
        :type request_id: string

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean

        :returns: True if the SAML Response is valid, False if not
        :rtype: bool
        """
        self.__error = None
        try:
            # Checks SAML version
            if self.document.get('Version', None) != '2.0':
                raise OneLogin_Saml2_ValidationError(
                    'Unsupported SAML version',
                    OneLogin_Saml2_ValidationError.UNSUPPORTED_SAML_VERSION)

            # Checks that ID exists
            if self.document.get('ID', None) is None:
                raise OneLogin_Saml2_ValidationError(
                    'Missing ID attribute on SAML Response',
                    OneLogin_Saml2_ValidationError.MISSING_ID)

            # Checks that the response has the SUCCESS status
            self.check_status()

            # Checks that the response only has one assertion
            if not self.validate_num_assertions():
                raise OneLogin_Saml2_ValidationError(
                    'SAML Response must contain 1 assertion',
                    OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS)

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data.get('entityId', '')
            sp_data = self.__settings.get_sp_data()
            sp_entity_id = sp_data.get('entityId', '')

            signed_elements = self.process_signed_elements()

            has_signed_response = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements
            has_signed_assertion = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML in signed_elements

            if self.__settings.is_strict():
                no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd'
                res = OneLogin_Saml2_Utils.validate_xml(
                    tostring(self.document), 'saml-schema-protocol-2.0.xsd',
                    self.__settings.is_debug_active())
                if not isinstance(res, Document):
                    raise OneLogin_Saml2_ValidationError(
                        no_valid_xml_msg,
                        OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT)

                # If encrypted, check also the decrypted document
                if self.encrypted:
                    res = OneLogin_Saml2_Utils.validate_xml(
                        tostring(self.decrypted_document),
                        'saml-schema-protocol-2.0.xsd',
                        self.__settings.is_debug_active())
                    if not isinstance(res, Document):
                        raise OneLogin_Saml2_ValidationError(
                            no_valid_xml_msg,
                            OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT)

                security = self.__settings.get_security_data()
                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(
                    request_data)

                in_response_to = self.document.get('InResponseTo', None)
                if request_id is None and in_response_to is not None and security.get(
                        'rejectUnsolicitedResponsesWithInResponseTo', False):
                    raise OneLogin_Saml2_ValidationError(
                        'The Response has an InResponseTo attribute: %s while no InResponseTo was expected'
                        % in_response_to,
                        OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO)

                # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
                if request_id is not None and in_response_to != request_id:
                    raise OneLogin_Saml2_ValidationError(
                        'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s'
                        % (in_response_to, request_id),
                        OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO)

                if not self.encrypted and security.get(
                        'wantAssertionsEncrypted', False):
                    raise OneLogin_Saml2_ValidationError(
                        'The assertion of the Response is not encrypted and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_ENCRYPTED_ASSERTION)

                if security.get('wantNameIdEncrypted', False):
                    encrypted_nameid_nodes = self.__query_assertion(
                        '/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
                    if len(encrypted_nameid_nodes) != 1:
                        raise OneLogin_Saml2_ValidationError(
                            'The NameID of the Response is not encrypted and the SP require it',
                            OneLogin_Saml2_ValidationError.NO_ENCRYPTED_NAMEID)

                # Checks that a Conditions element exists
                if not self.check_one_condition():
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion must include a Conditions element',
                        OneLogin_Saml2_ValidationError.MISSING_CONDITIONS)

                # Validates Assertion timestamps
                self.validate_timestamps(raise_exceptions=True)

                # Checks that an AuthnStatement element exists and is unique
                if not self.check_one_authnstatement():
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion must include an AuthnStatement element',
                        OneLogin_Saml2_ValidationError.
                        WRONG_NUMBER_OF_AUTHSTATEMENTS)

                # Checks that there is at least one AttributeStatement if required
                attribute_statement_nodes = self.__query_assertion(
                    '/saml:AttributeStatement')
                if security.get('wantAttributeStatement',
                                True) and not attribute_statement_nodes:
                    raise OneLogin_Saml2_ValidationError(
                        'There is no AttributeStatement on the Response',
                        OneLogin_Saml2_ValidationError.NO_ATTRIBUTESTATEMENT)

                encrypted_attributes_nodes = self.__query_assertion(
                    '/saml:AttributeStatement/saml:EncryptedAttribute')
                if encrypted_attributes_nodes:
                    raise OneLogin_Saml2_ValidationError(
                        'There is an EncryptedAttribute in the Response and this SP not support them',
                        OneLogin_Saml2_ValidationError.ENCRYPTED_ATTRIBUTES)

                # Checks destination
                destination = self.document.get('Destination', None)
                if destination:
                    if not destination.startswith(current_url):
                        # TODO: Review if following lines are required, since we can control the
                        # request_data
                        #  current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
                        #  if not destination.startswith(current_url_routed):
                        raise OneLogin_Saml2_ValidationError(
                            'The response was received at %s instead of %s' %
                            (current_url, destination),
                            OneLogin_Saml2_ValidationError.WRONG_DESTINATION)
                elif destination == '':
                    raise OneLogin_Saml2_ValidationError(
                        'The response has an empty Destination value',
                        OneLogin_Saml2_ValidationError.EMPTY_DESTINATION)

                # Checks audience
                valid_audiences = self.get_audiences()
                if valid_audiences and sp_entity_id not in valid_audiences:
                    raise OneLogin_Saml2_ValidationError(
                        '%s is not a valid audience for this Response' %
                        sp_entity_id,
                        OneLogin_Saml2_ValidationError.WRONG_AUDIENCE)

                # Checks the issuers
                issuers = self.get_issuers()
                for issuer in issuers:
                    if issuer is None or issuer != idp_entity_id:
                        raise OneLogin_Saml2_ValidationError(
                            'Invalid issuer in the Assertion/Response (expected %(idpEntityId)s, got %(issuer)s)'
                            % {
                                'idpEntityId': idp_entity_id,
                                'issuer': issuer
                            }, OneLogin_Saml2_ValidationError.WRONG_ISSUER)

                # Checks the session Expiration
                session_expiration = self.get_session_not_on_or_after()
                if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now(
                ):
                    raise OneLogin_Saml2_ValidationError(
                        'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response',
                        OneLogin_Saml2_ValidationError.SESSION_EXPIRED)

                # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
                any_subject_confirmation = False
                subject_confirmation_nodes = self.__query_assertion(
                    '/saml:Subject/saml:SubjectConfirmation')

                for scn in subject_confirmation_nodes:
                    method = scn.get('Method', None)
                    if method and method != OneLogin_Saml2_Constants.CM_BEARER:
                        continue
                    sc_data = scn.find(
                        'saml:SubjectConfirmationData',
                        namespaces=OneLogin_Saml2_Constants.NSMAP)
                    if sc_data is None:
                        continue
                    else:
                        irt = sc_data.get('InResponseTo', None)
                        if (in_response_to is None and irt is not None and
                           security.get('rejectUnsolicitedResponsesWithInResponseTo', False)) or \
                           in_response_to and irt and irt != in_response_to:
                            continue
                        recipient = sc_data.get('Recipient', None)
                        if recipient and current_url not in recipient:
                            continue
                        nooa = sc_data.get('NotOnOrAfter', None)
                        if nooa:
                            parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nooa)
                            if parsed_nooa <= OneLogin_Saml2_Utils.now():
                                continue
                        nb = sc_data.get('NotBefore', None)
                        if nb:
                            parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nb)
                            if parsed_nb > OneLogin_Saml2_Utils.now():
                                continue

                        if nooa:
                            self.valid_scd_not_on_or_after = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nooa)

                        any_subject_confirmation = True
                        break

                if not any_subject_confirmation:
                    raise OneLogin_Saml2_ValidationError(
                        'A valid SubjectConfirmation was not found on this Response',
                        OneLogin_Saml2_ValidationError.
                        WRONG_SUBJECTCONFIRMATION)

                if security.get('wantAssertionsSigned',
                                False) and not has_signed_assertion:
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion of the Response is not signed and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_SIGNED_ASSERTION)

                if security.get('wantMessagesSigned',
                                False) and not has_signed_response:
                    raise OneLogin_Saml2_ValidationError(
                        'The Message of the Response is not signed and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE)

            if not signed_elements or (not has_signed_response
                                       and not has_signed_assertion):
                raise OneLogin_Saml2_ValidationError(
                    'No Signature found. SAML Response rejected',
                    OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND)
            else:
                cert = idp_data.get('x509cert', None)
                fingerprint = idp_data.get('certFingerprint', None)
                fingerprintalg = idp_data.get('certFingerprintAlgorithm', None)

                multicerts = None
                if 'x509certMulti' in idp_data and 'signing' in idp_data[
                        'x509certMulti'] and idp_data['x509certMulti'][
                            'signing']:
                    multicerts = idp_data['x509certMulti']['signing']

                # If find a Signature on the Response, validates it checking the original response
                if has_signed_response and not OneLogin_Saml2_Utils.validate_sign(
                        self.document,
                        cert,
                        fingerprint,
                        fingerprintalg,
                        xpath=OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH,
                        multicerts=multicerts,
                        raise_exceptions=False):
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. SAML Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)

                document_check_assertion = self.decrypted_document if self.encrypted else self.document
                if has_signed_assertion and not OneLogin_Saml2_Utils.validate_sign(
                        document_check_assertion,
                        cert,
                        fingerprint,
                        fingerprintalg,
                        xpath=OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH,
                        multicerts=multicerts,
                        raise_exceptions=False):
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. SAML Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)

            return True
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print err.__str__()
            if raise_exceptions:
                raise err
            return False
示例#3
0
    def is_valid(self, request_data, request_id=None):
        """
        Validates the response object.

        :param request_data: Request Data
        :type request_data: dict

        :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP
        :type request_id: string

        :returns: True if the SAML Response is valid, False if not
        :rtype: bool
        """
        self.__error = None
        try:
            # Checks SAML version
            if self.document.get('Version', None) != '2.0':
                raise Exception('Unsupported SAML version')

            # Checks that ID exists
            if self.document.get('ID', None) is None:
                raise Exception('Missing ID attribute on SAML Response')

            # Checks that the response only has one assertion
            if not self.validate_num_assertions():
                raise Exception('SAML Response must contain 1 assertion')

            # Checks that the response has the SUCCESS status
            self.check_status()

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data.get('entityId', '')
            sp_data = self.__settings.get_sp_data()
            sp_entity_id = sp_data.get('entityId', '')

            signed_elements = self.process_signed_elements()

            if self.__settings.is_strict():
                no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd'
                res = OneLogin_Saml2_Utils.validate_xml(etree.tostring(self.document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
                if not isinstance(res, Document):
                    raise Exception(no_valid_xml_msg)

                # If encrypted, check also the decrypted document
                if self.encrypted:
                    res = OneLogin_Saml2_Utils.validate_xml(etree.tostring(self.decrypted_document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
                    if not isinstance(res, Document):
                        raise Exception(no_valid_xml_msg)

                security = self.__settings.get_security_data()
                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)

                # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
                in_response_to = self.document.get('InResponseTo', None)
                if in_response_to and request_id:
                    if in_response_to != request_id:
                        raise Exception('The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id))

                if not self.encrypted and security.get('wantAssertionsEncrypted', False):
                    raise Exception('The assertion of the Response is not encrypted and the SP require it')

                if security.get('wantNameIdEncrypted', False):
                    encrypted_nameid_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
                    if len(encrypted_nameid_nodes) == 0:
                        raise Exception('The NameID of the Response is not encrypted and the SP require it')

                # Checks that there is at least one AttributeStatement if required
                attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement')
                if security.get('wantAttributeStatement', True) and not attribute_statement_nodes:
                    raise Exception('There is no AttributeStatement on the Response')

                # Validates Assertion timestamps
                if not self.validate_timestamps():
                    raise Exception('Timing issues (please check your clock settings)')

                encrypted_attributes_nodes = self.__query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute')
                if encrypted_attributes_nodes:
                    raise Exception('There is an EncryptedAttribute in the Response and this SP not support them')

                # Checks destination
                destination = self.document.get('Destination', '')
                if destination:
                    if not destination.startswith(current_url):
                        # TODO: Review if following lines are required, since we can control the
                        # request_data
                        #  current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
                        #  if not destination.startswith(current_url_routed):
                        raise Exception('The response was received at %s instead of %s' % (current_url, destination))

                # Checks audience
                valid_audiences = self.get_audiences()
                if valid_audiences and sp_entity_id not in valid_audiences:
                    raise Exception('%s is not a valid audience for this Response' % sp_entity_id)

                # Checks the issuers
                issuers = self.get_issuers()
                for issuer in issuers:
                    if issuer is None or issuer != idp_entity_id:
                        raise Exception('Invalid issuer in the Assertion/Response')

                # Checks the session Expiration
                session_expiration = self.get_session_not_on_or_after()
                if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now():
                    raise Exception('The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response')

                # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
                any_subject_confirmation = False
                subject_confirmation_nodes = self.__query_assertion('/saml:Subject/saml:SubjectConfirmation')

                for scn in subject_confirmation_nodes:
                    method = scn.get('Method', None)
                    if method and method != OneLogin_Saml2_Constants.CM_BEARER:
                        continue
                    sc_data = scn.find('saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP)
                    if sc_data is None:
                        continue
                    else:
                        irt = sc_data.get('InResponseTo', None)
                        if in_response_to and irt and irt != in_response_to:
                            continue
                        recipient = sc_data.get('Recipient', None)
                        if recipient and current_url not in recipient:
                            continue
                        nooa = sc_data.get('NotOnOrAfter', None)
                        if nooa:
                            parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa)
                            if parsed_nooa <= OneLogin_Saml2_Utils.now():
                                continue
                        nb = sc_data.get('NotBefore', None)
                        if nb:
                            parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(nb)
                            if parsed_nb > OneLogin_Saml2_Utils.now():
                                continue
                        any_subject_confirmation = True
                        break

                if not any_subject_confirmation:
                    raise Exception('A valid SubjectConfirmation was not found on this Response')

                if security.get('wantAssertionsSigned', False) and ('{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML) not in signed_elements:
                    raise Exception('The Assertion of the Response is not signed and the SP require it')

                if security.get('wantMessagesSigned', False) and ('{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP) not in signed_elements:
                    raise Exception('The Message of the Response is not signed and the SP require it')

            if len(signed_elements) > 0:
                if len(signed_elements) > 2:
                    raise Exception('Too many Signatures found. SAML Response rejected')
                certs = idp_data.get('x509certs', [])
                fingerprint = idp_data.get('certFingerprint', None)
                fingerprintalg = idp_data.get('certFingerprintAlgorithm', None)

                # If find a Signature on the Response, validates it checking the original response
                if '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements:
                    document_to_validate = self.document
                # Otherwise validates the assertion (decrypted assertion if was encrypted)
                else:
                    if self.encrypted:
                        document_to_validate = self.decrypted_document
                    else:
                        document_to_validate = self.document
                if certs:
                    validated = any(OneLogin_Saml2_Utils.validate_sign(document_to_validate, cert, fingerprint, fingerprintalg)
                                    for cert in certs)
                else:
                    validated = OneLogin_Saml2_Utils.validate_sign(document_to_validate, None, fingerprint, fingerprintalg)
                if not validated:
                    raise Exception('Signature validation failed. SAML Response rejected')
            else:
                raise Exception('No Signature found. SAML Response rejected')

            return True
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print err.__str__()
            return False
示例#4
0
    def testValidateSign(self):
        """
        Tests the validate_sign method of the OneLogin_Saml2_Utils
        """
        settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
        idp_data = settings.get_idp_data()
        cert = idp_data['x509cert']

        settings_2 = OneLogin_Saml2_Settings(
            self.loadSettingsJSON('settings2.json'))
        idp_data2 = settings_2.get_idp_data()
        cert_2 = idp_data2['x509cert']
        fingerprint_2 = OneLogin_Saml2_Utils.calculate_x509_fingerprint(cert_2)
        fingerprint_2_256 = OneLogin_Saml2_Utils.calculate_x509_fingerprint(
            cert_2, 'sha256')

        try:
            self.assertFalse(OneLogin_Saml2_Utils.validate_sign('', cert))
        except Exception as e:
            self.assertEqual('Empty string supplied as input', str(e))

        # expired cert
        xml_metadata_signed = self.file_contents(
            join(self.data_path, 'metadata', 'signed_metadata_settings1.xml'))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_metadata_sign(
                xml_metadata_signed, cert))
        # expired cert, verified it
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed,
                                                        cert,
                                                        validatecert=True))

        xml_metadata_signed_2 = self.file_contents(
            join(self.data_path, 'metadata', 'signed_metadata_settings2.xml'))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_metadata_sign(
                xml_metadata_signed_2, cert_2))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_metadata_sign(
                xml_metadata_signed_2, None, fingerprint_2))

        xml_response_msg_signed = b64decode(
            self.file_contents(
                join(self.data_path, 'responses',
                     'signed_message_response.xml.base64')))

        # expired cert
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert))
        # expired cert, verified it
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed,
                                               cert,
                                               validatecert=True))

        # modified cert
        other_cert_path = join(dirname(__file__), '..', '..', '..', 'certs')
        f = open(other_cert_path + '/certificate1', 'r')
        cert_x = f.read()
        f.close()
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed,
                                               cert_x))
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed,
                                               cert_x,
                                               validatecert=True))

        xml_response_msg_signed_2 = b64decode(
            self.file_contents(
                join(self.data_path, 'responses',
                     'signed_message_response2.xml.base64')))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2,
                                               cert_2))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None,
                                               fingerprint_2))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None,
                                               fingerprint_2, 'sha1'))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None,
                                               fingerprint_2_256, 'sha256'))

        xml_response_assert_signed = b64decode(
            self.file_contents(
                join(self.data_path, 'responses',
                     'signed_assertion_response.xml.base64')))

        # expired cert
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed,
                                               cert))
        # expired cert, verified it
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed,
                                               cert,
                                               validatecert=True))

        xml_response_assert_signed_2 = b64decode(
            self.file_contents(
                join(self.data_path, 'responses',
                     'signed_assertion_response2.xml.base64')))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed_2,
                                               cert_2))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed_2,
                                               None, fingerprint_2))

        xml_response_double_signed = b64decode(
            self.file_contents(
                join(self.data_path, 'responses',
                     'double_signed_response.xml.base64')))

        # expired cert
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed,
                                               cert))
        # expired cert, verified it
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed,
                                               cert,
                                               validatecert=True))

        xml_response_double_signed_2 = b64decode(
            self.file_contents(
                join(self.data_path, 'responses',
                     'double_signed_response2.xml.base64')))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed_2,
                                               cert_2))
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed_2,
                                               None, fingerprint_2))

        dom = parseString(xml_response_msg_signed_2)
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(
            dom.toxml(), cert_2))

        dom.firstChild.firstChild.firstChild.nodeValue = 'https://idp.example.com/simplesaml/saml2/idp/metadata.php'

        dom.firstChild.getAttributeNode(
            'ID').nodeValue = u'_34fg27g212d63k1f923845324475802ac0fc24530b'
        # Reference validation failed
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_sign(dom.toxml(), cert_2))

        invalid_fingerprint = 'afe71c34ef740bc87434be13a2263d31271da1f9'
        # Wrong fingerprint
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_metadata_sign(
                xml_metadata_signed_2, None, invalid_fingerprint))

        dom_2 = parseString(xml_response_double_signed_2)
        self.assertTrue(
            OneLogin_Saml2_Utils.validate_sign(dom_2.toxml(), cert_2))
        dom_2.firstChild.firstChild.firstChild.nodeValue = 'https://example.com/other-idp'
        # Modified message
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_sign(dom_2.toxml(), cert_2))

        # Try to validate directly the Assertion
        dom_3 = parseString(xml_response_double_signed_2)
        assert_elem_3 = dom_3.firstChild.firstChild.nextSibling.nextSibling.nextSibling
        assert_elem_3.setAttributeNS(OneLogin_Saml2_Constants.NS_SAML,
                                     'xmlns:saml',
                                     OneLogin_Saml2_Constants.NS_SAML)
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_sign(assert_elem_3.toxml(), cert_2))

        # Wrong scheme
        no_signed = b64decode(
            self.file_contents(
                join(self.data_path, 'responses', 'invalids',
                     'no_signature.xml.base64')))
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(no_signed, cert))

        no_key = b64decode(
            self.file_contents(
                join(self.data_path, 'responses', 'invalids',
                     'no_key.xml.base64')))
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(no_key, cert))

        # Signature Wrapping attack
        wrapping_attack1 = b64decode(
            self.file_contents(
                join(self.data_path, 'responses', 'invalids',
                     'signature_wrapping_attack.xml.base64')))
        self.assertFalse(
            OneLogin_Saml2_Utils.validate_sign(wrapping_attack1, cert))
示例#5
0
    def is_valid(self, request_data, request_id=None):
        """
        Validates the response object.

        :param request_data: Request Data
        :type request_data: dict

        :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP
        :type request_id: string

        :returns: True if the SAML Response is valid, False if not
        :rtype: bool
        """
        self.__error = None
        try:
            # Checks SAML version
            if self.document.get('Version', None) != '2.0':
                raise Exception('Unsupported SAML version')

            # Checks that ID exists
            if self.document.get('ID', None) is None:
                raise Exception('Missing ID attribute on SAML Response')

            # Checks that the response only has one assertion
            if not self.validate_num_assertions():
                raise Exception('SAML Response must contain 1 assertion')

            # Checks that the response has the SUCCESS status
            self.check_status()

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data.get('entityId', '')
            sp_data = self.__settings.get_sp_data()
            sp_entity_id = sp_data.get('entityId', '')

            sign_nodes = self.__query('//ds:Signature')

            signed_elements = []
            for sign_node in sign_nodes:
                signed_elements.append(sign_node.getparent().tag)

            if self.__settings.is_strict():
                res = OneLogin_Saml2_Utils.validate_xml(
                    etree.tostring(self.document),
                    'saml-schema-protocol-2.0.xsd',
                    self.__settings.is_debug_active())
                if not isinstance(res, Document):
                    raise Exception(
                        'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd'
                    )

                security = self.__settings.get_security_data()
                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(
                    request_data)

                # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
                in_response_to = self.document.get('InResponseTo', None)
                if in_response_to and request_id:
                    if in_response_to != request_id:
                        raise Exception(
                            'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s'
                            % (in_response_to, request_id))

                if not self.encrypted and security.get(
                        'wantAssertionsEncrypted', False):
                    raise Exception(
                        'The assertion of the Response is not encrypted and the SP require it'
                    )

                if security.get('wantNameIdEncrypted', False):
                    encrypted_nameid_nodes = self.__query_assertion(
                        '/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
                    if len(encrypted_nameid_nodes) == 0:
                        raise Exception(
                            'The NameID of the Response is not encrypted and the SP require it'
                        )

                # Checks that there is at least one AttributeStatement if required
                attribute_statement_nodes = self.__query_assertion(
                    '/saml:AttributeStatement')
                if security.get('wantAttributeStatement',
                                True) and not attribute_statement_nodes:
                    raise Exception(
                        'There is no AttributeStatement on the Response')

                # Validates Assertion timestamps
                if not self.validate_timestamps():
                    raise Exception(
                        'Timing issues (please check your clock settings)')

                encrypted_attributes_nodes = self.__query_assertion(
                    '/saml:AttributeStatement/saml:EncryptedAttribute')
                if encrypted_attributes_nodes:
                    raise Exception(
                        'There is an EncryptedAttribute in the Response and this SP not support them'
                    )

                # Checks destination
                destination = self.document.get('Destination', '')
                if destination:
                    if not destination.startswith(current_url):
                        # TODO: Review if following lines are required, since we can control the
                        # request_data
                        #  current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
                        #  if not destination.startswith(current_url_routed):
                        raise Exception(
                            'The response was received at %s instead of %s' %
                            (current_url, destination))

                # Checks audience
                valid_audiences = self.get_audiences()
                if valid_audiences and sp_entity_id not in valid_audiences:
                    raise Exception(
                        '%s is not a valid audience for this Response' %
                        sp_entity_id)

                # Checks the issuers
                issuers = self.get_issuers()
                for issuer in issuers:
                    if issuer is None or issuer != idp_entity_id:
                        raise Exception(
                            'Invalid issuer in the Assertion/Response')

                # Checks the session Expiration
                session_expiration = self.get_session_not_on_or_after()
                if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now(
                ):
                    raise Exception(
                        'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response'
                    )

                # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
                any_subject_confirmation = False
                subject_confirmation_nodes = self.__query_assertion(
                    '/saml:Subject/saml:SubjectConfirmation')

                for scn in subject_confirmation_nodes:
                    method = scn.get('Method', None)
                    if method and method != OneLogin_Saml2_Constants.CM_BEARER:
                        continue
                    sc_data = scn.find(
                        'saml:SubjectConfirmationData',
                        namespaces=OneLogin_Saml2_Constants.NSMAP)
                    if sc_data is None:
                        continue
                    else:
                        irt = sc_data.get('InResponseTo', None)
                        if irt != in_response_to:
                            continue
                        recipient = sc_data.get('Recipient', None)
                        if recipient and current_url not in recipient:
                            continue
                        nooa = sc_data.get('NotOnOrAfter', None)
                        if nooa:
                            parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nooa)
                            if parsed_nooa <= OneLogin_Saml2_Utils.now():
                                continue
                        nb = sc_data.get('NotBefore', None)
                        if nb:
                            parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(
                                nb)
                            if parsed_nb > OneLogin_Saml2_Utils.now():
                                continue
                        any_subject_confirmation = True
                        break

                if not any_subject_confirmation:
                    raise Exception(
                        'A valid SubjectConfirmation was not found on this Response'
                    )

                if security.get('wantAssertionsSigned', False) and (
                        '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML
                ) not in signed_elements:
                    raise Exception(
                        'The Assertion of the Response is not signed and the SP require it'
                    )

                if security.get('wantMessagesSigned', False) and (
                        '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP
                ) not in signed_elements:
                    raise Exception(
                        'The Message of the Response is not signed and the SP require it'
                    )

                if len(signed_elements) > 0:
                    if len(signed_elements) > 2:
                        raise Exception(
                            'Too many Signatures found. SAML Response rejected'
                        )
                    cert = idp_data.get('x509cert', None)
                    fingerprint = idp_data.get('certFingerprint', None)
                    fingerprintalg = idp_data.get('certFingerprintAlgorithm',
                                                  None)

                    # If find a Signature on the Response, validates it checking the original response
                    if '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements:
                        document_to_validate = self.document
                    # Otherwise validates the assertion (decrypted assertion if was encrypted)
                    else:
                        if self.encrypted:
                            document_to_validate = self.decrypted_document
                        else:
                            document_to_validate = self.document
                    if not OneLogin_Saml2_Utils.validate_sign(
                            document_to_validate, cert, fingerprint,
                            fingerprintalg):
                        raise Exception(
                            'Signature validation failed. SAML Response rejected'
                        )
                else:
                    raise Exception(
                        'No Signature found. SAML Response rejected')

            return True
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print err.__str__()
            return False
示例#6
0
    def testValidateSign(self):
        """
        Tests the validate_sign method of the OneLogin_Saml2_Utils
        """
        settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
        idp_data = settings.get_idp_data()
        cert = idp_data['x509cert']

        settings_2 = OneLogin_Saml2_Settings(self.loadSettingsJSON('settings2.json'))
        idp_data2 = settings_2.get_idp_data()
        cert_2 = idp_data2['x509cert']
        fingerprint_2 = OneLogin_Saml2_Utils.calculate_x509_fingerprint(cert_2)
        fingerprint_2_256 = OneLogin_Saml2_Utils.calculate_x509_fingerprint(cert_2, 'sha256')

        try:
            self.assertFalse(OneLogin_Saml2_Utils.validate_sign('', cert))
        except Exception as e:
            self.assertEqual('Empty string supplied as input', e.message)

        try:
            self.assertFalse(OneLogin_Saml2_Utils.validate_sign(1, cert))
        except Exception as e:
            self.assertEqual('Error parsing xml string', e.message)

        # expired cert
        xml_metadata_signed = self.file_contents(join(self.data_path, 'metadata', 'signed_metadata_settings1.xml'))
        self.assertTrue(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed, cert))
        # expired cert, verified it
        self.assertFalse(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed, cert, validatecert=True))

        xml_metadata_signed_2 = self.file_contents(join(self.data_path, 'metadata', 'signed_metadata_settings2.xml'))
        self.assertTrue(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed_2, cert_2))
        self.assertTrue(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed_2, None, fingerprint_2))

        xml_response_msg_signed = b64decode(self.file_contents(join(self.data_path, 'responses', 'signed_message_response.xml.base64')))

        # expired cert
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert))
        # expired cert, verified it
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert, validatecert=True))

        # modified cert
        other_cert_path = join(dirname(__file__), '..', '..', '..', 'certs')
        f = open(other_cert_path + '/certificate1', 'r')
        cert_x = f.read()
        f.close()
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert_x))
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert_x, validatecert=True))

        xml_response_msg_signed_2 = b64decode(self.file_contents(join(self.data_path, 'responses', 'signed_message_response2.xml.base64')))
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, cert_2))
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None, fingerprint_2))
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None, fingerprint_2, 'sha1'))
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None, fingerprint_2_256, 'sha256'))

        xml_response_assert_signed = b64decode(self.file_contents(join(self.data_path, 'responses', 'signed_assertion_response.xml.base64')))

        # expired cert
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed, cert))
        # expired cert, verified it
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed, cert, validatecert=True))

        xml_response_assert_signed_2 = b64decode(self.file_contents(join(self.data_path, 'responses', 'signed_assertion_response2.xml.base64')))
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed_2, cert_2))
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed_2, None, fingerprint_2))

        xml_response_double_signed = b64decode(self.file_contents(join(self.data_path, 'responses', 'double_signed_response.xml.base64')))

        # expired cert
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed, cert))
        # expired cert, verified it
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed, cert, validatecert=True))

        xml_response_double_signed_2 = b64decode(self.file_contents(join(self.data_path, 'responses', 'double_signed_response2.xml.base64')))
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed_2, cert_2))
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed_2, None, fingerprint_2))

        dom = parseString(xml_response_msg_signed_2)
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(dom, cert_2))

        dom.firstChild.firstChild.firstChild.nodeValue = 'https://idp.example.com/simplesaml/saml2/idp/metadata.php'

        dom.firstChild.getAttributeNode('ID').nodeValue = u'_34fg27g212d63k1f923845324475802ac0fc24530b'
        # Reference validation failed
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(dom, cert_2))

        invalid_fingerprint = 'afe71c34ef740bc87434be13a2263d31271da1f9'
        # Wrong fingerprint
        self.assertFalse(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed_2, None, invalid_fingerprint))

        dom_2 = parseString(xml_response_double_signed_2)
        self.assertTrue(OneLogin_Saml2_Utils.validate_sign(dom_2, cert_2))
        dom_2.firstChild.firstChild.firstChild.nodeValue = 'https://example.com/other-idp'
        # Modified message
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(dom_2, cert_2))

        # Try to validate directly the Assertion
        dom_3 = parseString(xml_response_double_signed_2)
        assert_elem_3 = dom_3.firstChild.firstChild.nextSibling.nextSibling.nextSibling
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(assert_elem_3, cert_2))

        # Wrong scheme
        no_signed = b64decode(self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_signature.xml.base64')))
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(no_signed, cert))

        no_key = b64decode(self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_key.xml.base64')))
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(no_key, cert))

        # Signature Wrapping attack
        wrapping_attack1 = b64decode(self.file_contents(join(self.data_path, 'responses', 'invalids', 'signature_wrapping_attack.xml.base64')))
        self.assertFalse(OneLogin_Saml2_Utils.validate_sign(wrapping_attack1, cert))
示例#7
0
    def is_valid(self, request_data, request_id=None, raise_exceptions=False):
        """
        Validates the response object.

        :param request_data: Request Data
        :type request_data: dict

        :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP
        :type request_id: string

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean

        :returns: True if the SAML Response is valid, False if not
        :rtype: bool
        """
        self.__error = None
        try:
            # Checks SAML version
            if self.document.get('Version', None) != '2.0':
                raise OneLogin_Saml2_ValidationError(
                    'Unsupported SAML version',
                    OneLogin_Saml2_ValidationError.UNSUPPORTED_SAML_VERSION
                )

            # Checks that ID exists
            if self.document.get('ID', None) is None:
                raise OneLogin_Saml2_ValidationError(
                    'Missing ID attribute on SAML Response',
                    OneLogin_Saml2_ValidationError.MISSING_ID
                )

            # Checks that the response has the SUCCESS status
            self.check_status()

            # Checks that the response only has one assertion
            if not self.validate_num_assertions():
                raise OneLogin_Saml2_ValidationError(
                    'SAML Response must contain 1 assertion',
                    OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS
                )

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data.get('entityId', '')
            sp_data = self.__settings.get_sp_data()
            sp_entity_id = sp_data.get('entityId', '')

            signed_elements = self.process_signed_elements()

            has_signed_response = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements
            has_signed_assertion = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML in signed_elements

            if self.__settings.is_strict():
                no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd'
                res = OneLogin_Saml2_Utils.validate_xml(
                    tostring(self.document),
                    'saml-schema-protocol-2.0.xsd',
                    self.__settings.is_debug_active()
                )
                if not isinstance(res, Document):
                    raise OneLogin_Saml2_ValidationError(
                        no_valid_xml_msg,
                        OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT
                    )

                # If encrypted, check also the decrypted document
                if self.encrypted:
                    res = OneLogin_Saml2_Utils.validate_xml(
                        tostring(self.decrypted_document),
                        'saml-schema-protocol-2.0.xsd',
                        self.__settings.is_debug_active()
                    )
                    if not isinstance(res, Document):
                        raise OneLogin_Saml2_ValidationError(
                            no_valid_xml_msg,
                            OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT
                        )

                security = self.__settings.get_security_data()
                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)

                in_response_to = self.document.get('InResponseTo', None)
                if request_id is None and in_response_to is not None and security.get('rejectUnsolicitedResponsesWithInResponseTo', False):
                    raise OneLogin_Saml2_ValidationError(
                        'The Response has an InResponseTo attribute: %s while no InResponseTo was expected' % in_response_to,
                        OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO
                    )

                # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided
                if request_id is not None and in_response_to != request_id:
                    raise OneLogin_Saml2_ValidationError(
                        'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id),
                        OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO
                    )

                if not self.encrypted and security.get('wantAssertionsEncrypted', False):
                    raise OneLogin_Saml2_ValidationError(
                        'The assertion of the Response is not encrypted and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_ENCRYPTED_ASSERTION
                    )

                if security.get('wantNameIdEncrypted', False):
                    encrypted_nameid_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
                    if len(encrypted_nameid_nodes) != 1:
                        raise OneLogin_Saml2_ValidationError(
                            'The NameID of the Response is not encrypted and the SP require it',
                            OneLogin_Saml2_ValidationError.NO_ENCRYPTED_NAMEID
                        )

                # Checks that a Conditions element exists
                if not self.check_one_condition():
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion must include a Conditions element',
                        OneLogin_Saml2_ValidationError.MISSING_CONDITIONS
                    )

                # Validates Assertion timestamps
                self.validate_timestamps(raise_exceptions=True)

                # Checks that an AuthnStatement element exists and is unique
                if not self.check_one_authnstatement():
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion must include an AuthnStatement element',
                        OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_AUTHSTATEMENTS
                    )

                # Checks that the response has all of the AuthnContexts that we provided in the request.
                # Only check if failOnAuthnContextMismatch is true and requestedAuthnContext is set to a list.
                requested_authn_contexts = security.get('requestedAuthnContext', True)

                if security.get('failOnAuthnContextMismatch', False) and requested_authn_contexts and requested_authn_contexts is not True:
                    authn_contexts = self.get_authn_contexts()
                    unmatched_contexts = set(requested_authn_contexts).difference(authn_contexts)
                    if unmatched_contexts:
                        raise OneLogin_Saml2_ValidationError(
                            'The AuthnContext "%s" didn\'t include requested context "%s"' % (', '.join(authn_contexts), ', '.join(unmatched_contexts)),
                            OneLogin_Saml2_ValidationError.AUTHN_CONTEXT_MISMATCH
                        )

                # Checks that there is at least one AttributeStatement if required
                attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement')
                if security.get('wantAttributeStatement', True) and not attribute_statement_nodes:
                    raise OneLogin_Saml2_ValidationError(
                        'There is no AttributeStatement on the Response',
                        OneLogin_Saml2_ValidationError.NO_ATTRIBUTESTATEMENT
                    )

                encrypted_attributes_nodes = self.__query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute')
                if encrypted_attributes_nodes:
                    raise OneLogin_Saml2_ValidationError(
                        'There is an EncryptedAttribute in the Response and this SP not support them',
                        OneLogin_Saml2_ValidationError.ENCRYPTED_ATTRIBUTES
                    )

                # Checks destination
                destination = self.document.get('Destination', None)
                if destination:
                    if not destination.startswith(current_url):
                        # TODO: Review if following lines are required, since we can control the
                        # request_data
                        #  current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data)
                        #  if not destination.startswith(current_url_routed):
                        raise OneLogin_Saml2_ValidationError(
                            'The response was received at %s instead of %s' % (current_url, destination),
                            OneLogin_Saml2_ValidationError.WRONG_DESTINATION
                        )
                elif destination == '':
                    raise OneLogin_Saml2_ValidationError(
                        'The response has an empty Destination value',
                        OneLogin_Saml2_ValidationError.EMPTY_DESTINATION
                    )

                # Checks audience
                valid_audiences = self.get_audiences()
                if valid_audiences and sp_entity_id not in valid_audiences:
                    raise OneLogin_Saml2_ValidationError(
                        '%s is not a valid audience for this Response' % sp_entity_id,
                        OneLogin_Saml2_ValidationError.WRONG_AUDIENCE
                    )

                # Checks the issuers
                issuers = self.get_issuers()
                for issuer in issuers:
                    if issuer is None or issuer != idp_entity_id:
                        raise OneLogin_Saml2_ValidationError(
                            'Invalid issuer in the Assertion/Response (expected %(idpEntityId)s, got %(issuer)s)' %
                            {
                                'idpEntityId': idp_entity_id,
                                'issuer': issuer
                            },
                            OneLogin_Saml2_ValidationError.WRONG_ISSUER
                        )

                # Checks the session Expiration
                session_expiration = self.get_session_not_on_or_after()
                if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now():
                    raise OneLogin_Saml2_ValidationError(
                        'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response',
                        OneLogin_Saml2_ValidationError.SESSION_EXPIRED
                    )

                # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid
                any_subject_confirmation = False
                subject_confirmation_nodes = self.__query_assertion('/saml:Subject/saml:SubjectConfirmation')

                for scn in subject_confirmation_nodes:
                    method = scn.get('Method', None)
                    if method and method != OneLogin_Saml2_Constants.CM_BEARER:
                        continue
                    sc_data = scn.find('saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP)
                    if sc_data is None:
                        continue
                    else:
                        irt = sc_data.get('InResponseTo', None)
                        if (in_response_to is None and irt is not None and
                           security.get('rejectUnsolicitedResponsesWithInResponseTo', False)) or \
                           in_response_to and irt and irt != in_response_to:
                            continue
                        recipient = sc_data.get('Recipient', None)
                        if recipient and current_url not in recipient:
                            continue
                        nooa = sc_data.get('NotOnOrAfter', None)
                        if nooa:
                            parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa)
                            if parsed_nooa <= OneLogin_Saml2_Utils.now():
                                continue
                        nb = sc_data.get('NotBefore', None)
                        if nb:
                            parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(nb)
                            if parsed_nb > OneLogin_Saml2_Utils.now():
                                continue

                        if nooa:
                            self.valid_scd_not_on_or_after = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa)

                        any_subject_confirmation = True
                        break

                if not any_subject_confirmation:
                    raise OneLogin_Saml2_ValidationError(
                        'A valid SubjectConfirmation was not found on this Response',
                        OneLogin_Saml2_ValidationError.WRONG_SUBJECTCONFIRMATION
                    )

                if security.get('wantAssertionsSigned', False) and not has_signed_assertion:
                    raise OneLogin_Saml2_ValidationError(
                        'The Assertion of the Response is not signed and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_SIGNED_ASSERTION
                    )

                if security.get('wantMessagesSigned', False) and not has_signed_response:
                    raise OneLogin_Saml2_ValidationError(
                        'The Message of the Response is not signed and the SP require it',
                        OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE
                    )

            if not signed_elements or (not has_signed_response and not has_signed_assertion):
                raise OneLogin_Saml2_ValidationError(
                    'No Signature found. SAML Response rejected',
                    OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND
                )
            else:
                cert = idp_data.get('x509cert', None)
                fingerprint = idp_data.get('certFingerprint', None)
                fingerprintalg = idp_data.get('certFingerprintAlgorithm', None)

                multicerts = None
                if 'x509certMulti' in idp_data and 'signing' in idp_data['x509certMulti'] and idp_data['x509certMulti']['signing']:
                    multicerts = idp_data['x509certMulti']['signing']

                # If find a Signature on the Response, validates it checking the original response
                if has_signed_response and not OneLogin_Saml2_Utils.validate_sign(self.document, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False):
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. SAML Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
                    )

                document_check_assertion = self.decrypted_document if self.encrypted else self.document
                if has_signed_assertion and not OneLogin_Saml2_Utils.validate_sign(document_check_assertion, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False):
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. SAML Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
                    )

            return True
        except Exception as err:
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print(err.__str__())
            if raise_exceptions:
                raise err
            return False
示例#8
0
def validateAssertion(xml, fingerprint=None, fingerprintalg=None):

    result = {
        'schemaValidate': False,
        'signCheck': False,
        'certValidity': False,
        'certAllowed': True,
        'error': 0,
        'msg': '',
        'assertionName': None
    }

    assert isinstance(xml, compat.text_types)

    if len(xml) == 0:
        result['error'] = 1
        result['msg'] = 'Empty string supplied as input'
        return result

    xml = xmlRemoveDeclaration(xml)
    parsedassertion = etree.fromstring(xml)

    # assertion name path
    assertionNameXpath = "local-name(/*)"
    assertionName = parsedassertion.xpath(assertionNameXpath)
    assertionName = str(assertionName)

    # find assertion schema
    if assertionName == 'EntityDescriptor':
        asscertionShema = 'saml-schema-metadata-2.0.xsd'
    elif assertionName == 'Response':
        asscertionShema = 'saml-schema-protocol-2.0.xsd'
    elif assertionName == 'AuthnRequest':
        asscertionShema = 'saml-schema-protocol-2.0.xsd'
    else:
        result['error'] = 2
        result['msg'] = 'Assertion unknown'
        return result

    # siganture node path
    signatureNodeXpath = ".//*[local-name()='Signature']"

    if assertionName == 'Response':
        signatureNodeXpath = "*[local-name(/*)='Response']//*[local-name()='Signature']"

    result['assertionName'] = assertionName
    # get certificate signing
    signingcert = easyspid.lib.easyspid.get_signature_cert(xml)

    # validate xml against its schema
    schemaCheck = OneLogin_Saml2_XML.validate_xml(xml, asscertionShema, False)
    if isinstance(schemaCheck, str):
        result['msg'] = schemaCheck
        result['schemaValidate'] = False
    else:
        result['schemaValidate'] = True

    # check signature
    signingfingerprintalg = 'sha1'
    if fingerprintalg is not None:
        signingfingerprintalg = fingerprintalg

    signingfingerprint = (easyspid.lib.easyspid.calcCertFingerprint(
        signingcert, signingfingerprintalg))['result']

    if assertionName == 'EntityDescriptor' and fingerprint is None:
        allowedCert = easyspid.lib.easyspid.get_metadata_allowed_cert(xml)
        allowedfingerprint = (easyspid.lib.easyspid.calcCertFingerprint(
            allowedCert, signingfingerprintalg))['result']

    elif assertionName != 'EntityDescriptor' and fingerprint is None:
        allowedfingerprint = signingfingerprint

    if fingerprint is not None:
        allowedfingerprint = fingerprint

    signCheck = OneLogin_Saml2_Utils.validate_sign(
        xml,
        cert=signingcert,
        fingerprint=signingfingerprint,
        fingerprintalg=signingfingerprintalg,
        validatecert=False,
        debug=False,
        xpath=signatureNodeXpath,
        multicerts=None)

    if signCheck:
        result['signCheck'] = True

    # checktime validity certificate
    certTimeValdity = easyspid.lib.easyspid.timeValidateCert(signingcert)
    if certTimeValdity:
        result['certValidity'] = True

    # checktime certificate allow
    if allowedfingerprint != signingfingerprint:
        result['certAllowed'] = False

    return result