Example #1
0
    def validate_timestamps(self):
        """
        Verifies that the document is valid according to Conditions Element

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

        :returns: True if the condition is valid, False otherwise
        :rtype: bool
        """
        conditions_nodes = self.__query_assertion('/saml:Conditions')

        for conditions_node in conditions_nodes:
            nb_attr = conditions_node.get('NotBefore')
            nooa_attr = conditions_node.get('NotOnOrAfter')
            if nb_attr and OneLogin_Saml2_Utils.parse_SAML_to_time(
                    nb_attr) > OneLogin_Saml2_Utils.now(
                    ) + OneLogin_Saml2_Constants.ALLOWED_CLOCK_DRIFT:
                raise OneLogin_Saml2_ValidationError(
                    'Could not validate timestamp: not yet valid. Check system clock.',
                    OneLogin_Saml2_ValidationError.ASSERTION_TOO_EARLY)
            if nooa_attr and OneLogin_Saml2_Utils.parse_SAML_to_time(
                    nooa_attr
            ) + OneLogin_Saml2_Constants.ALLOWED_CLOCK_DRIFT <= OneLogin_Saml2_Utils.now(
            ):
                raise OneLogin_Saml2_ValidationError(
                    'Could not validate timestamp: expired. Check system clock.',
                    OneLogin_Saml2_ValidationError.ASSERTION_EXPIRED)
        return True
Example #2
0
    def get_issuers(self):
        """
        Gets the issuers (from message and from assertion)

        :returns: The issuers
        :rtype: list
        """
        issuers = []

        message_issuer_nodes = OneLogin_Saml2_Utils.query(
            self.document, '/samlp:Response/saml:Issuer')
        if len(message_issuer_nodes) > 0:
            if len(message_issuer_nodes) == 1:
                issuers.append(
                    OneLogin_Saml2_Utils.element_text(message_issuer_nodes[0]))
            else:
                raise OneLogin_Saml2_ValidationError(
                    'Issuer of the Response is multiple.',
                    OneLogin_Saml2_ValidationError.ISSUER_MULTIPLE_IN_RESPONSE)

        assertion_issuer_nodes = self.__query_assertion('/saml:Issuer')
        if len(assertion_issuer_nodes) == 1:
            issuers.append(
                OneLogin_Saml2_Utils.element_text(assertion_issuer_nodes[0]))
        else:
            raise OneLogin_Saml2_ValidationError(
                'Issuer of the Assertion not found or multiple.',
                OneLogin_Saml2_ValidationError.ISSUER_NOT_FOUND_IN_ASSERTION)

        return list(set(issuers))
Example #3
0
    def __decrypt_assertion(self, dom):
        """
        Decrypts the Assertion

        :raises: Exception if no private key available

        :param dom: Encrypted Assertion
        :type dom: Element

        :returns: Decrypted Assertion
        :rtype: Element
        """
        key = self.__settings.get_sp_key()
        debug = self.__settings.is_debug_active()

        if not key:
            raise OneLogin_Saml2_Error(
                'No private key available to decrypt the assertion, check settings',
                OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND
            )

        encrypted_assertion_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/saml:EncryptedAssertion')
        if encrypted_assertion_nodes:
            encrypted_data_nodes = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData')
            if encrypted_data_nodes:
                keyinfo = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo')
                if not keyinfo:
                    raise OneLogin_Saml2_ValidationError(
                        'No KeyInfo present, invalid Assertion',
                        OneLogin_Saml2_ValidationError.KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA
                    )
                keyinfo = keyinfo[0]
                children = keyinfo.getchildren()
                if not children:
                    raise OneLogin_Saml2_ValidationError(
                        'KeyInfo has no children nodes, invalid Assertion',
                        OneLogin_Saml2_ValidationError.CHILDREN_NODE_NOT_FOUND_IN_KEYINFO
                    )
                for child in children:
                    if 'RetrievalMethod' in child.tag:
                        if child.attrib['Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey':
                            raise OneLogin_Saml2_ValidationError(
                                'Unsupported Retrieval Method found',
                                OneLogin_Saml2_ValidationError.UNSUPPORTED_RETRIEVAL_METHOD
                            )
                        uri = child.attrib['URI']
                        if not uri.startswith('#'):
                            break
                        uri = uri.split('#')[1]
                        encrypted_key = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id="' + uri + '"]')
                        if encrypted_key:
                            keyinfo.append(encrypted_key[0])

                encrypted_data = encrypted_data_nodes[0]
                decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug=debug, inplace=True)
                dom.replace(encrypted_assertion_nodes[0], decrypted)

        return dom
Example #4
0
    def get_nameid_data(self):
        """
        Gets the NameID Data provided by the SAML Response from the IdP

        :returns: Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
        :rtype: dict
        """
        nameid = None
        nameid_data = {}

        encrypted_id_data_nodes = self.__query_assertion(
            '/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
        if encrypted_id_data_nodes:
            encrypted_data = encrypted_id_data_nodes[0]
            key = self.__settings.get_sp_key()
            nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key)
        else:
            nameid_nodes = self.__query_assertion('/saml:Subject/saml:NameID')
            if nameid_nodes:
                nameid = nameid_nodes[0]

        is_strict = self.__settings.is_strict()
        want_nameid = self.__settings.get_security_data().get(
            'wantNameId', True)
        if nameid is None:
            if is_strict and want_nameid:
                raise OneLogin_Saml2_ValidationError(
                    'NameID not found in the assertion of the Response',
                    OneLogin_Saml2_ValidationError.NO_NAMEID)
        else:
            if is_strict and want_nameid and not OneLogin_Saml2_Utils.element_text(
                    nameid):
                raise OneLogin_Saml2_ValidationError(
                    'An empty NameID value found',
                    OneLogin_Saml2_ValidationError.EMPTY_NAMEID)

            nameid_data = {'Value': OneLogin_Saml2_Utils.element_text(nameid)}
            for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
                value = nameid.get(attr, None)
                if value:
                    if is_strict and attr == 'SPNameQualifier':
                        sp_data = self.__settings.get_sp_data()
                        sp_entity_id = sp_data.get('entityId', '')
                        if sp_entity_id != value:
                            raise OneLogin_Saml2_ValidationError(
                                'The SPNameQualifier value mistmatch the SP entityID value.',
                                OneLogin_Saml2_ValidationError.
                                SP_NAME_QUALIFIER_NAME_MISMATCH)

                    nameid_data[attr] = value
        return nameid_data
Example #5
0
    def get_status(dom):
        """
        Gets Status from a Response.

        :param dom: The Response as XML
        :type: Document

        :returns: The Status, an array with the code and a message.
        :rtype: dict
        """
        status = {}

        status_entry = OneLogin_Saml2_XML.query(
            dom, '/samlp:Response/samlp:Status')
        if len(status_entry) != 1:
            raise OneLogin_Saml2_ValidationError(
                'Missing Status on response',
                OneLogin_Saml2_ValidationError.MISSING_STATUS)

        code_entry = OneLogin_Saml2_XML.query(
            dom, '/samlp:Response/samlp:Status/samlp:StatusCode',
            status_entry[0])
        if len(code_entry) != 1:
            raise OneLogin_Saml2_ValidationError(
                'Missing Status Code on response',
                OneLogin_Saml2_ValidationError.MISSING_STATUS_CODE)
        code = code_entry[0].values()[0]

        if not code:
            raise OneLogin_Saml2_ValidationError(
                'Missing Status Code on response',
                OneLogin_Saml2_ValidationError.MISSING_STATUS_CODE)

        status['code'] = code

        status['msg'] = ''
        message_entry = OneLogin_Saml2_XML.query(
            dom, '/samlp:Response/samlp:Status/samlp:StatusMessage',
            status_entry[0])
        if len(message_entry) == 0:
            subcode_entry = OneLogin_Saml2_XML.query(
                dom,
                '/samlp:Response/samlp:Status/samlp:StatusCode/samlp:StatusCode',
                status_entry[0])
            if len(subcode_entry) == 1:
                status['msg'] = subcode_entry[0].values()[0]
        elif len(message_entry) == 1:
            status['msg'] = OneLogin_Saml2_XML.element_text(message_entry[0])

        return status
Example #6
0
    def get_issuer(dom):
        issuer = {}
        issuer_entry = OneLogin_Saml2_XML.query(dom,
                                                '/samlp:Response/saml:Issuer')
        if len(issuer_entry) != 1:
            raise OneLogin_Saml2_ValidationError(
                'Missing Issuer on response',
                OneLogin_Saml2_ValidationError.MISSING_ISSUER)
        if not issuer_entry[0].text:
            raise OneLogin_Saml2_ValidationError(
                'Missing Issuer on response',
                OneLogin_Saml2_ValidationError.MISSING_ISSUER)

        issuer['Format'] = issuer_entry[0].get('Format', None)
        return issuer
Example #7
0
 def get_assertion_id(self):
     """
     :returns: the ID of the assertion in the response
     :rtype: string
     """
     if not self.validate_num_assertions():
         raise OneLogin_Saml2_ValidationError(
             'SAML Response must contain 1 assertion',
             OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS)
     return self.__query_assertion('')[0].get('ID', None)
Example #8
0
    def validate_signed_elements(self, signed_elements):
        """
        Verifies that the document has the expected signed nodes.

        :param signed_elements: The signed elements to be checked
        :type signed_elements: list

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if len(signed_elements) > 2:
            return False

        response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP
        assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML

        if (response_tag in signed_elements and signed_elements.count(response_tag) > 1) or \
           (assertion_tag in signed_elements and signed_elements.count(assertion_tag) > 1) or \
           (response_tag not in signed_elements and assertion_tag not in signed_elements):
            return False

        # Check that the signed elements found here, are the ones that will be verified
        # by OneLogin_Saml2_Utils.validate_sign
        if response_tag in signed_elements:
            expected_signature_nodes = OneLogin_Saml2_Utils.query(
                self.document, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH)
            if len(expected_signature_nodes) != 1:
                raise OneLogin_Saml2_ValidationError(
                    'Unexpected number of Response signatures found. SAML Response rejected.',
                    OneLogin_Saml2_ValidationError.
                    WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE)

        if assertion_tag in signed_elements:
            expected_signature_nodes = self.__query(
                OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)
            if len(expected_signature_nodes) != 1:
                raise OneLogin_Saml2_ValidationError(
                    'Unexpected number of Assertion signatures found. SAML Response rejected.',
                    OneLogin_Saml2_ValidationError.
                    WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION)

        return True
Example #9
0
    def get_nameid_data(request, key=None):
        """
        Gets the NameID Data of the the Logout Request
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :param key: The SP key
        :type key: string
        :return: Name ID Data (Value, Format, NameQualifier, SPNameQualifier)
        :rtype: dict
        """
        if isinstance(request, etree._Element):
            elem = request
        else:
            if isinstance(request, Document):
                request = request.toxml()
            elem = fromstring(request, forbid_dtd=True)

        name_id = None
        encrypted_entries = OneLogin_Saml2_Utils.query(
            elem, '/samlp:LogoutRequest/saml:EncryptedID')

        if len(encrypted_entries) == 1:
            if key is None:
                raise OneLogin_Saml2_Error(
                    'Private Key is required in order to decrypt the NameID, check settings',
                    OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND)

            encrypted_data_nodes = OneLogin_Saml2_Utils.query(
                elem,
                '/samlp:LogoutRequest/saml:EncryptedID/xenc:EncryptedData')
            if len(encrypted_data_nodes) == 1:
                encrypted_data = encrypted_data_nodes[0]
                name_id = OneLogin_Saml2_Utils.decrypt_element(
                    encrypted_data, key)
        else:
            entries = OneLogin_Saml2_Utils.query(
                elem, '/samlp:LogoutRequest/saml:NameID')
            if len(entries) == 1:
                name_id = entries[0]

        if name_id is None:
            raise OneLogin_Saml2_ValidationError(
                'NameID not found in the Logout Request',
                OneLogin_Saml2_ValidationError.NO_NAMEID)

        name_id_data = {'Value': OneLogin_Saml2_Utils.element_text(name_id)}
        for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
            if attr in name_id.attrib.keys():
                name_id_data[attr] = name_id.attrib[attr]

        return name_id_data
Example #10
0
    def check_status(self):
        """
        Check if the status of the response is success or not

        :raises: Exception. If the status is not success
        """
        status = OneLogin_Saml2_Utils.get_status(self.document)
        code = status.get('code', None)
        if code and code != OneLogin_Saml2_Constants.STATUS_SUCCESS:
            splited_code = code.split(':')
            printable_code = splited_code.pop()
            status_exception_msg = 'The status code of the Response was not Success, was %s' % printable_code
            status_msg = status.get('msg', None)
            if status_msg:
                status_exception_msg += ' -> ' + status_msg
            raise OneLogin_Saml2_ValidationError(
                status_exception_msg,
                OneLogin_Saml2_ValidationError.STATUS_CODE_IS_NOT_SUCCESS)
Example #11
0
    def get_friendlyname_attributes(self):
        """
        Gets the Attributes from the AttributeStatement element indexed by FiendlyName.
        EncryptedAttributes are not supported
        """
        attributes = {}
        attribute_nodes = self.__query_assertion(
            '/saml:AttributeStatement/saml:Attribute')
        for attribute_node in attribute_nodes:
            attr_friendlyname = attribute_node.get('FriendlyName')
            if attr_friendlyname:
                if attr_friendlyname in attributes.keys():
                    raise OneLogin_Saml2_ValidationError(
                        'Found an Attribute element with duplicated FriendlyName',
                        OneLogin_Saml2_ValidationError.
                        DUPLICATED_ATTRIBUTE_NAME_FOUND)

                values = []
                for attr in attribute_node.iterchildren(
                        '{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP[
                            OneLogin_Saml2_Constants.NS_PREFIX_SAML]):
                    # Remove any whitespace (which may be present where attributes are
                    # nested inside NameID children).
                    attr_text = OneLogin_Saml2_Utils.element_text(attr)
                    if attr_text:
                        attr_text = attr_text.strip()
                        if attr_text:
                            values.append(attr_text)

                    # Parse any nested NameID children
                    for nameid in attr.iterchildren(
                            '{%s}NameID' % OneLogin_Saml2_Constants.NSMAP[
                                OneLogin_Saml2_Constants.NS_PREFIX_SAML]):
                        values.append({
                            'NameID': {
                                'Format': nameid.get('Format'),
                                'NameQualifier': nameid.get('NameQualifier'),
                                'value':
                                OneLogin_Saml2_Utils.element_text(nameid)
                            }
                        })

                attributes[attr_friendlyname] = values
        return attributes
Example #12
0
    def validate_sign(xml,
                      cert=None,
                      fingerprint=None,
                      fingerprintalg='sha1',
                      validatecert=False,
                      debug=False,
                      xpath=None,
                      multicerts=None):
        """
        Validates a signature (Message or Assertion).

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The pubic cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param xpath: The xpath of the signed element
        :type: string

        :param multicerts: Multiple public certs
        :type: list

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if xml is None or xml == '':
            raise Exception('Empty string supplied as input')
        elif isinstance(xml, etree._Element):
            elem = xml
        elif isinstance(xml, Document):
            xml = xml.toxml()
            elem = fromstring(str(xml))
        elif isinstance(xml, Element):
            xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAMLP),
                               'xmlns:samlp',
                               unicode(OneLogin_Saml2_Constants.NS_SAMLP))
            xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAML),
                               'xmlns:saml',
                               unicode(OneLogin_Saml2_Constants.NS_SAML))
            xml = xml.toxml()
            elem = fromstring(str(xml))
        elif isinstance(xml, basestring):
            elem = fromstring(str(xml))
        else:
            raise Exception('Error parsing xml string')

        error_callback_method = None
        if debug:
            error_callback_method = print_xmlsec_errors
        xmlsec.set_error_callback(error_callback_method)

        xmlsec.addIDs(elem, ["ID"])

        if xpath:
            signature_nodes = OneLogin_Saml2_Utils.query(elem, xpath)
        else:
            signature_nodes = OneLogin_Saml2_Utils.query(
                elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH)

            if len(signature_nodes) == 0:
                signature_nodes = OneLogin_Saml2_Utils.query(
                    elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)

        if len(signature_nodes) == 1:
            signature_node = signature_nodes[0]

            if not multicerts:
                return OneLogin_Saml2_Utils.validate_node_sign(
                    signature_node,
                    elem,
                    cert,
                    fingerprint,
                    fingerprintalg,
                    validatecert,
                    debug,
                    raise_exceptions=True)
            else:
                # If multiple certs are provided, I may ignore cert and
                # fingerprint provided by the method and just check the
                # certs multicerts
                fingerprint = fingerprintalg = None
                for cert in multicerts:
                    if OneLogin_Saml2_Utils.validate_node_sign(
                            signature_node,
                            elem,
                            cert,
                            fingerprint,
                            fingerprintalg,
                            validatecert,
                            False,
                            raise_exceptions=False):
                        return True
                raise OneLogin_Saml2_ValidationError(
                    'Signature validation failed. SAML Response rejected.')
        else:
            raise OneLogin_Saml2_ValidationError(
                'Expected exactly one signature node; got {}.'.format(
                    len(signature_nodes)),
                OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_SIGNATURES)
Example #13
0
    def validate_node_sign(signature_node,
                           elem,
                           cert=None,
                           fingerprint=None,
                           fingerprintalg='sha1',
                           validatecert=False,
                           debug=False):
        """
        Validates a signature node.

        :param signature_node: The signature node
        :type: Node

        :param xml: The element we should validate
        :type: Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        error_callback_method = None
        if debug:
            error_callback_method = print_xmlsec_errors
        xmlsec.set_error_callback(error_callback_method)

        xmlsec.addIDs(elem, ["ID"])

        if (cert is None or cert == '') and fingerprint:
            x509_certificate_nodes = OneLogin_Saml2_Utils.query(
                signature_node,
                '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate')
            if len(x509_certificate_nodes) > 0:
                x509_certificate_node = x509_certificate_nodes[0]
                x509_cert_value = x509_certificate_node.text
                x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint(
                    x509_cert_value, fingerprintalg)
                if fingerprint == x509_fingerprint_value:
                    cert = OneLogin_Saml2_Utils.format_cert(x509_cert_value)

        # Check if Reference URI is empty
        # reference_elem = OneLogin_Saml2_Utils.query(signature_node, '//ds:Reference')
        # if len(reference_elem) > 0:
        #    if reference_elem[0].get('URI') == '':
        #        reference_elem[0].set('URI', '#%s' % signature_node.getparent().get('ID'))

        if cert is None or cert == '':
            raise OneLogin_Saml2_Error(
                'Could not validate node signature: No certificate provided.',
                OneLogin_Saml2_Error.CERT_NOT_FOUND)

        file_cert = OneLogin_Saml2_Utils.write_temp_file(cert)

        if validatecert:
            mngr = xmlsec.KeysMngr()
            mngr.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem,
                          xmlsec.KeyDataTypeTrusted)
            dsig_ctx = xmlsec.DSigCtx(mngr)
        else:
            dsig_ctx = xmlsec.DSigCtx()
            dsig_ctx.signKey = xmlsec.Key.load(file_cert.name,
                                               xmlsec.KeyDataFormatCertPem,
                                               None)

        file_cert.close()

        dsig_ctx.setEnabledKeyData([xmlsec.KeyDataX509])

        try:
            dsig_ctx.verify(signature_node)
        except Exception as err:
            raise OneLogin_Saml2_ValidationError(
                'Signature validation failed. SAML Response rejected. %s',
                OneLogin_Saml2_ValidationError.INVALID_SIGNATURE,
                err.__str__())

        return True
Example #14
0
    def __validate_signature(self, data, saml_type, raise_exceptions=False):
        """
        Validate Signature

        :param data: The Request data
        :type data: dict

        :param cert: The certificate to check signature
        :type cert: str

        :param saml_type: The target URL the user should be redirected to
        :type saml_type: string  SAMLRequest | SAMLResponse

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        try:
            signature = data.get('Signature', None)
            if signature is None:
                if self.__settings.is_strict(
                ) and self.__settings.get_security_data().get(
                        'wantMessagesSigned', False):
                    raise OneLogin_Saml2_ValidationError(
                        'The %s is not signed. Rejected.' % saml_type,
                        OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE)
                return True

            idp_data = self.get_settings().get_idp_data()

            exists_x509cert = 'x509cert' in idp_data and idp_data['x509cert']
            exists_multix509sign = 'x509certMulti' in idp_data and \
                                   'signing' in idp_data['x509certMulti'] and \
                                   idp_data['x509certMulti']['signing']

            if not (exists_x509cert or exists_multix509sign):
                error_msg = 'In order to validate the sign on the %s, the x509cert of the IdP is required' % saml_type
                self.__errors.append(error_msg)
                raise OneLogin_Saml2_Error(error_msg,
                                           OneLogin_Saml2_Error.CERT_NOT_FOUND)

            sign_alg = data.get('SigAlg', OneLogin_Saml2_Constants.RSA_SHA1)
            if isinstance(sign_alg, bytes):
                sign_alg = sign_alg.decode('utf8')

            lowercase_urlencoding = False
            if 'lowercase_urlencoding' in self.__request_data.keys():
                lowercase_urlencoding = self.__request_data[
                    'lowercase_urlencoding']

            signed_query = self.__build_sign_query(
                data[saml_type], data.get('RelayState', None), sign_alg,
                saml_type, lowercase_urlencoding)

            if exists_multix509sign:
                for cert in idp_data['x509certMulti']['signing']:
                    if OneLogin_Saml2_Utils.validate_binary_sign(
                            signed_query,
                            OneLogin_Saml2_Utils.b64decode(signature), cert,
                            sign_alg):
                        return True
                raise OneLogin_Saml2_ValidationError(
                    'Signature validation failed. %s rejected' % saml_type,
                    OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)
            else:
                cert = idp_data['x509cert']

                if not OneLogin_Saml2_Utils.validate_binary_sign(
                        signed_query,
                        OneLogin_Saml2_Utils.b64decode(signature), cert,
                        sign_alg, self.__settings.is_debug_active()):
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. %s rejected' % saml_type,
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)
            return True
        except Exception as e:
            self.__error_reason = str(e)
            if raise_exceptions:
                raise e
            return False
Example #15
0
    def validate_sign(xml,
                      cert=None,
                      fingerprint=None,
                      fingerprintalg='sha1',
                      validatecert=False,
                      debug=False,
                      xpath=None):
        """
        Validates a signature (Message or Assertion).

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param xpath: The xpath of the signed element
        :type: string

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if xml is None or xml == '':
            raise Exception('Empty string supplied as input')

        elem = OneLogin_Saml2_XML.to_etree(xml)
        xmlsec.enable_debug_trace(debug)
        xmlsec.tree.add_ids(elem, ["ID"])

        if xpath:
            signature_nodes = OneLogin_Saml2_XML.query(elem, xpath)
        else:
            signature_nodes = OneLogin_Saml2_XML.query(
                elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH)

            if len(signature_nodes) == 0:
                signature_nodes = OneLogin_Saml2_XML.query(
                    elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)

        if len(signature_nodes) == 1:
            signature_node = signature_nodes[0]
            # Raises expection if invalid
            return OneLogin_Saml2_Utils.validate_node_sign(
                signature_node,
                elem,
                cert,
                fingerprint,
                fingerprintalg,
                validatecert,
                debug,
                raise_exceptions=True)
        else:
            raise OneLogin_Saml2_ValidationError(
                'Expected exactly one signature node; got {}.'.format(
                    len(signature_nodes)),
                OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_SIGNATURES)
Example #16
0
    def is_valid(self, request_data, raise_exceptions=False):
        """
        Checks if the Logout Request received is valid
        :param request_data: Request Data
        :type request_data: dict
        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        :return: If the Logout Request is or not valid
        :rtype: boolean
        """
        self.__error = None
        lowercase_urlencoding = False
        try:
            dom = fromstring(self.__logout_request, forbid_dtd=True)

            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data['entityId']

            if 'get_data' in request_data.keys():
                get_data = request_data['get_data']
            else:
                get_data = {}

            if 'lowercase_urlencoding' in request_data.keys():
                lowercase_urlencoding = request_data['lowercase_urlencoding']

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

                security = self.__settings.get_security_data()

                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(
                    request_data)

                # Check NotOnOrAfter
                if dom.get('NotOnOrAfter', None):
                    na = OneLogin_Saml2_Utils.parse_SAML_to_time(
                        dom.get('NotOnOrAfter'))
                    if na <= OneLogin_Saml2_Utils.now():
                        raise OneLogin_Saml2_ValidationError(
                            'Could not validate timestamp: expired. Check system clock.',
                            OneLogin_Saml2_ValidationError.RESPONSE_EXPIRED)

                # Check destination
                destination = dom.get('Destination')
                if destination:
                    if not OneLogin_Saml2_Utils.normalize_url(
                            url=destination).startswith(
                                OneLogin_Saml2_Utils.normalize_url(
                                    url=current_url)):
                        raise Exception(
                            'The LogoutRequest was received at '
                            '%(currentURL)s instead of %(destination)s' % {
                                'currentURL': current_url,
                                'destination': destination,
                            },
                            OneLogin_Saml2_ValidationError.WRONG_DESTINATION)

                # Check issuer
                issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom)
                if issuer is not None and issuer != idp_entity_id:
                    raise OneLogin_Saml2_ValidationError(
                        'Invalid issuer in the Logout Request (expected %(idpEntityId)s, got %(issuer)s)'
                        % {
                            'idpEntityId': idp_entity_id,
                            'issuer': issuer
                        }, OneLogin_Saml2_ValidationError.WRONG_ISSUER)

                if security['wantMessagesSigned']:
                    if 'Signature' not in get_data:
                        raise OneLogin_Saml2_ValidationError(
                            'The Message of the Logout Request is not signed and the SP require it',
                            OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE)

            if 'Signature' in get_data:
                if 'SigAlg' not in get_data:
                    sign_alg = OneLogin_Saml2_Constants.RSA_SHA1
                else:
                    sign_alg = get_data['SigAlg']

                signed_query = 'SAMLRequest=%s' % OneLogin_Saml2_Utils.get_encoded_parameter(
                    get_data,
                    'SAMLRequest',
                    lowercase_urlencoding=lowercase_urlencoding)
                if 'RelayState' in get_data:
                    signed_query = '%s&RelayState=%s' % (
                        signed_query,
                        OneLogin_Saml2_Utils.get_encoded_parameter(
                            get_data,
                            'RelayState',
                            lowercase_urlencoding=lowercase_urlencoding))
                signed_query = '%s&SigAlg=%s' % (
                    signed_query,
                    OneLogin_Saml2_Utils.get_encoded_parameter(
                        get_data,
                        'SigAlg',
                        OneLogin_Saml2_Constants.RSA_SHA1,
                        lowercase_urlencoding=lowercase_urlencoding))

                exists_x509cert = 'x509cert' in idp_data and idp_data[
                    'x509cert']
                exists_multix509sign = 'x509certMulti' in idp_data and \
                    'signing' in idp_data['x509certMulti'] and \
                    idp_data['x509certMulti']['signing']

                if not (exists_x509cert or exists_multix509sign):
                    raise OneLogin_Saml2_Error(
                        'In order to validate the sign on the Logout Request, the x509cert of the IdP is required',
                        OneLogin_Saml2_Error.CERT_NOT_FOUND)
                if exists_multix509sign:
                    for cert in idp_data['x509certMulti']['signing']:
                        if OneLogin_Saml2_Utils.validate_binary_sign(
                                signed_query, b64decode(get_data['Signature']),
                                cert, sign_alg):
                            return True
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. Logout Request rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)
                else:
                    cert = idp_data['x509cert']

                    if not OneLogin_Saml2_Utils.validate_binary_sign(
                            signed_query, b64decode(get_data['Signature']),
                            cert, sign_alg):
                        raise OneLogin_Saml2_ValidationError(
                            'Signature validation failed. Logout Request rejected',
                            OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)
            return True
        except Exception as err:
            # pylint: disable=R0801sign_alg
            self.__error = err.__str__()
            debug = self.__settings.is_debug_active()
            if debug:
                print(err.__str__())
            if raise_exceptions:
                raise err
            return False
    def is_valid(self, request_data, request_id=None, raise_exceptions=False):
        """
        Determines if the SAML LogoutResponse is valid
        :param request_id: The ID of the LogoutRequest 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
        :return: Returns if the SAML LogoutResponse is or not valid
        :rtype: boolean
        """
        self.__error = None
        lowercase_urlencoding = False
        try:
            idp_data = self.__settings.get_idp_data()
            idp_entity_id = idp_data['entityId']
            get_data = request_data['get_data']

            if 'lowercase_urlencoding' in request_data.keys():
                lowercase_urlencoding = request_data['lowercase_urlencoding']

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

                security = self.__settings.get_security_data()

                # Check if the InResponseTo of the Logout Response matches the ID of the Logout Request (requestId) if provided
                if request_id is not None and self.document.documentElement.hasAttribute('InResponseTo'):
                    in_response_to = self.document.documentElement.getAttribute('InResponseTo')
                    if request_id != in_response_to:
                        raise OneLogin_Saml2_ValidationError(
                            'The InResponseTo of the Logout Response: %s, does not match the ID of the Logout request sent by the SP: %s' % (in_response_to, request_id),
                            OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO
                        )

                # Check issuer
                issuer = self.get_issuer()
                if issuer is not None and issuer != idp_entity_id:
                    raise OneLogin_Saml2_ValidationError(
                        'Invalid issuer in the Logout Request',
                        OneLogin_Saml2_ValidationError.WRONG_ISSUER
                    )

                current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)

                # Check destination
                if self.document.documentElement.hasAttribute('Destination'):
                    destination = self.document.documentElement.getAttribute('Destination')
                    if destination != '':
                        if current_url not in destination:
                            raise OneLogin_Saml2_ValidationError(
                                'The LogoutResponse was received at %s instead of %s' % (current_url, destination),
                                OneLogin_Saml2_ValidationError.WRONG_DESTINATION
                            )

                if security['wantMessagesSigned']:
                    if security['wantValidMessageSignature']:
                        if 'Signature' not in get_data:
                            raise OneLogin_Saml2_ValidationError(
                                'The Message of the Logout Response is not signed and the SP require it',
                                OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE
                            )

            if 'Signature' in get_data:
                if 'SigAlg' not in get_data:
                    sign_alg = OneLogin_Saml2_Constants.RSA_SHA1
                else:
                    sign_alg = get_data['SigAlg']

                signed_query = 'SAMLResponse=%s' % OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SAMLResponse', lowercase_urlencoding=lowercase_urlencoding)
                if 'RelayState' in get_data:
                    signed_query = '%s&RelayState=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'RelayState', lowercase_urlencoding=lowercase_urlencoding))
                signed_query = '%s&SigAlg=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1, lowercase_urlencoding=lowercase_urlencoding))

                exists_x509cert = 'x509cert' in idp_data and idp_data['x509cert']
                exists_multix509sign = 'x509certMulti' in idp_data and \
                    'signing' in idp_data['x509certMulti'] and \
                    idp_data['x509certMulti']['signing']

                if not (exists_x509cert or exists_multix509sign):
                    raise OneLogin_Saml2_Error(
                        'In order to validate the sign on the Logout Response, the x509cert of the IdP is required',
                        OneLogin_Saml2_Error.CERT_NOT_FOUND
                    )
                if exists_multix509sign:
                    for cert in idp_data['x509certMulti']['signing']:
                        if OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg):
                            return True
                    raise OneLogin_Saml2_ValidationError(
                        'Signature validation failed. Logout Response rejected',
                        OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
                    )
                else:
                    cert = idp_data['x509cert']

                    if not OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg):
                        raise OneLogin_Saml2_ValidationError(
                            'Signature validation failed. Logout Response rejected',
                            OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
                        )

            return True
        # pylint: disable=R0801
        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
Example #18
0
    def process_signed_elements(self):
        """
        Verifies the signature nodes:
         - Checks that are Response or Assertion
         - Check that IDs and reference URI are unique and consistent.

        :returns: The signed elements tag names
        :rtype: list
        """
        sign_nodes = self.__query('//ds:Signature')

        signed_elements = []
        verified_seis = []
        verified_ids = []
        response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP
        assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML

        for sign_node in sign_nodes:
            signed_element = sign_node.getparent().tag
            if signed_element != response_tag and signed_element != assertion_tag:
                raise OneLogin_Saml2_ValidationError(
                    'Invalid Signature Element %s SAML Response rejected' %
                    signed_element,
                    OneLogin_Saml2_ValidationError.WRONG_SIGNED_ELEMENT)

            if not sign_node.getparent().get('ID'):
                raise OneLogin_Saml2_ValidationError(
                    'Signed Element must contain an ID. SAML Response rejected',
                    OneLogin_Saml2_ValidationError.
                    ID_NOT_FOUND_IN_SIGNED_ELEMENT)

            id_value = sign_node.getparent().get('ID')
            if id_value in verified_ids:
                raise OneLogin_Saml2_ValidationError(
                    'Duplicated ID. SAML Response rejected',
                    OneLogin_Saml2_ValidationError.
                    DUPLICATED_ID_IN_SIGNED_ELEMENTS)
            verified_ids.append(id_value)

            # Check that reference URI matches the parent ID and no duplicate References or IDs
            ref = OneLogin_Saml2_Utils.query(sign_node, './/ds:Reference')
            if ref:
                ref = ref[0]
                if ref.get('URI'):
                    sei = ref.get('URI')[1:]

                    if sei != id_value:
                        raise OneLogin_Saml2_ValidationError(
                            'Found an invalid Signed Element. SAML Response rejected',
                            OneLogin_Saml2_ValidationError.
                            INVALID_SIGNED_ELEMENT)

                    if sei in verified_seis:
                        raise OneLogin_Saml2_ValidationError(
                            'Duplicated Reference URI. SAML Response rejected',
                            OneLogin_Saml2_ValidationError.
                            DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS)
                    verified_seis.append(sei)

            signed_elements.append(signed_element)

        if signed_elements:
            if not self.validate_signed_elements(signed_elements,
                                                 raise_exceptions=True):
                raise OneLogin_Saml2_ValidationError(
                    'Found an unexpected Signature Element. SAML Response rejected',
                    OneLogin_Saml2_ValidationError.UNEXPECTED_SIGNED_ELEMENT)
        return signed_elements
Example #19
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
Example #20
0
    def validate_node_sign(signature_node,
                           elem,
                           cert=None,
                           fingerprint=None,
                           fingerprintalg='sha1',
                           validatecert=False,
                           debug=False):
        """
        Validates a signature node.

        :param signature_node: The signature node
        :type: Node

        :param xml: The element we should validate
        :type: Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if (cert is None or cert == '') and fingerprint:
            x509_certificate_nodes = OneLogin_Saml2_XML.query(
                signature_node,
                '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate')
            if len(x509_certificate_nodes) > 0:
                x509_certificate_node = x509_certificate_nodes[0]
                x509_cert_value = OneLogin_Saml2_XML.element_text(
                    x509_certificate_node)
                x509_cert_value_formatted = OneLogin_Saml2_Utils.format_cert(
                    x509_cert_value)
                x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint(
                    x509_cert_value_formatted, fingerprintalg)
                if fingerprint == x509_fingerprint_value:
                    cert = x509_cert_value_formatted

        if cert is None or cert == '':
            raise OneLogin_Saml2_Error(
                'Could not validate node signature: No certificate provided.',
                OneLogin_Saml2_Error.CERT_NOT_FOUND)

        # Check if Reference URI is empty
        reference_elem = OneLogin_Saml2_XML.query(signature_node,
                                                  '//ds:Reference')
        if len(reference_elem) > 0:
            if reference_elem[0].get('URI') == '':
                reference_elem[0].set(
                    'URI', '#%s' % signature_node.getparent().get('ID'))

        if validatecert:
            manager = xmlsec.KeysManager()
            manager.load_cert_from_memory(cert, xmlsec.KeyFormat.CERT_PEM,
                                          xmlsec.KeyDataType.TRUSTED)
            dsig_ctx = xmlsec.SignatureContext(manager)
        else:
            dsig_ctx = xmlsec.SignatureContext()
            dsig_ctx.key = xmlsec.Key.from_memory(cert,
                                                  xmlsec.KeyFormat.CERT_PEM,
                                                  None)

        dsig_ctx.set_enabled_key_data([xmlsec.KeyData.X509])

        try:
            dsig_ctx.verify(signature_node)
        except Exception as err:
            raise OneLogin_Saml2_ValidationError(
                'Signature validation failed. SAML Response rejected. %s',
                OneLogin_Saml2_ValidationError.INVALID_SIGNATURE, str(err))

        return True
Example #21
0
    def validate_sign(xml,
                      cert=None,
                      fingerprint=None,
                      fingerprintalg='sha1',
                      validatecert=False,
                      debug=False,
                      xpath=None,
                      multicerts=None):
        """
        Validates a signature (Message or Assertion).

        :param xml: The element we should validate
        :type: string | Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param xpath: The xpath of the signed element
        :type: string

        :param multicerts: Multiple public certs
        :type: list

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if xml is None or xml == '':
            raise Exception('Empty string supplied as input')

        elem = OneLogin_Saml2_XML.to_etree(xml)
        xmlsec.enable_debug_trace(debug)
        xmlsec.tree.add_ids(elem, ["ID"])

        if xpath:
            signature_nodes = OneLogin_Saml2_XML.query(elem, xpath)
        else:
            signature_nodes = OneLogin_Saml2_XML.query(
                elem, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH)

            if len(signature_nodes) == 0:
                signature_nodes = OneLogin_Saml2_XML.query(
                    elem, OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH)

        if len(signature_nodes) == 1:
            signature_node = signature_nodes[0]

            if not multicerts:
                return OneLogin_Saml2_Utils.validate_node_sign(
                    signature_node,
                    elem,
                    cert,
                    fingerprint,
                    fingerprintalg,
                    validatecert,
                    debug,
                    raise_exceptions=True)
            else:
                # If multiple certs are provided, I may ignore cert and
                # fingerprint provided by the method and just check the
                # certs multicerts
                fingerprint = fingerprintalg = None
                for cert in multicerts:
                    if OneLogin_Saml2_Utils.validate_node_sign(
                            signature_node,
                            elem,
                            cert,
                            fingerprint,
                            fingerprintalg,
                            validatecert,
                            False,
                            raise_exceptions=False):
                        return True
                raise OneLogin_Saml2_ValidationError(
                    'Signature validation failed. SAML Response rejected.',
                    OneLogin_Saml2_ValidationError.INVALID_SIGNATURE)
        else:
            raise OneLogin_Saml2_ValidationError(
                'Expected exactly one signature node; got {}.'.format(
                    len(signature_nodes)),
                OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_SIGNATURES)