Exemple #1
0
    def testDecryptElementInplace(self):
        """
        Tests the decrypt_element method of the OneLogin_Saml2_Utils with inplace=True
        """
        settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())

        key = settings.get_sp_key()

        xml_nameid_enc = b64decode(self.file_contents(join(self.data_path, 'responses', 'response_encrypted_nameid.xml.base64')))
        dom = fromstring(xml_nameid_enc)
        encrypted_node = dom.xpath('//saml:EncryptedID/xenc:EncryptedData', namespaces=OneLogin_Saml2_Constants.NSMAP)[0]

        # can be decrypted twice when copy the node first
        for _ in range(2):
            decrypted_nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_node, key, inplace=False)
            self.assertIn('NameID', decrypted_nameid.tag)
            self.assertEqual('2de11defd199f8d5bb63f9b7deb265ba5c675c10', decrypted_nameid.text)

        # can only be decrypted once in place
        decrypted_nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_node, key, inplace=True)
        self.assertIn('NameID', decrypted_nameid.tag)
        self.assertEqual('2de11defd199f8d5bb63f9b7deb265ba5c675c10', decrypted_nameid.text)

        # can't be decrypted twice since it has been decrypted inplace
        with self.assertRaisesRegex(Exception, "(1, 'failed to decrypt')"):
            OneLogin_Saml2_Utils.decrypt_element(encrypted_node, key, inplace=True)
Exemple #2
0
    def __init__(self, settings, response):
        """
        Constructs the response object.

        :param settings: The setting info
        :type settings: OneLogin_Saml2_Setting object

        :param response: The base64 encoded, XML string containing the samlp:Response
        :type response: string
        """
        self.__settings = settings
        self.__error = None
        self.response = b64decode(response)
        self.document = fromstring(self.response, forbid_dtd=True)
        self.decrypted_document = None
        self.encrypted = None
        self.valid_scd_not_on_or_after = None

        # Quick check for the presence of EncryptedAssertion
        encrypted_assertion_nodes = self.__query(
            '/samlp:Response/saml:EncryptedAssertion')
        if encrypted_assertion_nodes:
            decrypted_document = deepcopy(self.document)
            self.encrypted = True
            self.decrypted_document = self.__decrypt_assertion(
                decrypted_document)
    def test_start_authentication(self, _, service_provider,
                                  identity_providers):
        configuration = create_autospec(spec=SAMLConfiguration)
        configuration.service_provider_debug_mode = MagicMock(
            return_value=False)
        configuration.service_provider_strict_mode = MagicMock(
            return_value=False)
        configuration.get_service_provider = MagicMock(
            return_value=service_provider)
        configuration.get_identity_providers = MagicMock(
            return_value=identity_providers)
        onelogin_configuration = SAMLOneLoginConfiguration(configuration)
        subject_parser = SAMLSubjectParser()
        parser = DSLParser()
        visitor = DSLEvaluationVisitor()
        evaluator = DSLEvaluator(parser, visitor)
        subject_filter = SAMLSubjectFilter(evaluator)
        authentication_manager = SAMLAuthenticationManager(
            onelogin_configuration, subject_parser, subject_filter)

        with self.app.test_request_context("/"):
            result = authentication_manager.start_authentication(
                self._db, fixtures.IDP_1_ENTITY_ID, "")

            query_items = parse_qs(urlsplit(result).query)
            saml_request = query_items["SAMLRequest"][0]
            decoded_saml_request = OneLogin_Saml2_Utils.decode_base64_and_inflate(
                saml_request)

            validation_result = OneLogin_Saml2_XML.validate_xml(
                decoded_saml_request, "saml-schema-protocol-2.0.xsd", False)
            assert isinstance(validation_result,
                              OneLogin_Saml2_XML._element_class)

            saml_request_dom = fromstring(decoded_saml_request)

            acs_url = saml_request_dom.get("AssertionConsumerServiceURL")
            assert acs_url == SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.acs_service.url

            acs_binding = saml_request_dom.get("ProtocolBinding")
            assert (acs_binding == SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.
                    acs_service.binding.value)

            sso_url = saml_request_dom.get("Destination")
            assert sso_url == IDENTITY_PROVIDERS[0].sso_service.url

            name_id_policy_nodes = OneLogin_Saml2_XML.query(
                saml_request_dom, "./samlp:NameIDPolicy")

            assert name_id_policy_nodes is not None
            assert len(name_id_policy_nodes) == 1

            name_id_policy_node = name_id_policy_nodes[0]
            name_id_format = name_id_policy_node.get("Format")

            assert (name_id_format ==
                    SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.name_id_format)
Exemple #4
0
 def __query(self, query):
     """
     Extracts a node from the DOMDocument (Logout Response Menssage)
     :param query: Xpath Expresion
     :type query: string
     :return: The queried node
     :rtype: DOMNodeList
     """
     # Switch to lxml for querying
     xml = self.document.toxml()
     return OneLogin_Saml2_Utils.query(fromstring(xml, forbid_dtd=True), query)
Exemple #5
0
    def decrypt_element(encrypted_data, key, debug=False, inplace=False):
        """
        Decrypts an encrypted element.

        :param encrypted_data: The encrypted data.
        :type: lxml.etree.Element | DOMElement | basestring

        :param key: The key.
        :type: string

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

        :param inplace: update passed data with decrypted result
        :type: bool

        :returns: The decrypted element.
        :rtype: lxml.etree.Element
        """
        if isinstance(encrypted_data, Element):
            encrypted_data = fromstring(str(encrypted_data.toxml()),
                                        forbid_dtd=True)
        elif isinstance(encrypted_data, basestring):
            encrypted_data = fromstring(str(encrypted_data), forbid_dtd=True)
        elif not inplace and isinstance(encrypted_data, etree._Element):
            encrypted_data = deepcopy(encrypted_data)

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

        mngr = xmlsec.KeysMngr()

        key = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None)
        mngr.addKey(key)
        enc_ctx = xmlsec.EncCtx(mngr)

        return enc_ctx.decrypt(encrypted_data)
Exemple #6
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
Exemple #7
0
 def get_id(request):
     """
     Returns the ID of the Logout Request
     :param request: Logout Request Message
     :type request: string|DOMDocument
     :return: string ID
     :rtype: str object
     """
     if isinstance(request, etree._Element):
         elem = request
     else:
         if isinstance(request, Document):
             request = request.toxml()
         elem = fromstring(request, forbid_dtd=True)
     return elem.get('ID', None)
    def validate(self, federation, metadata):
        """Verify that federated SAML metadata has not expired.

        :param federation: SAML federation
        :type federation: api.saml.metadata.federations.model.SAMLFederation

        :param metadata: SAML federation's aggregated metadata
        :type metadata: str

        :raises SAMLFederatedMetadataValidationError: in the case of validation errors
        """
        self._logger.info(
            "Started validating the expiration time of the metadata belonging to {0}"
            .format(federation))

        try:
            root = fromstring(metadata.encode("utf-8"))
        except Exception as exception:
            raise SAMLFederatedMetadataValidationError(
                "Metadata's XML is not valid", str(exception))

        if "EntitiesDescriptor" not in root.tag:
            raise SAMLFederatedMetadataValidationError(
                'Metadata\'s root element is not "EntitiesDescriptor"')

        valid_until = root.get("validUntil", None)
        if not valid_until:
            raise SAMLFederatedMetadataValidationError(
                'Metadata does not contain "validUntil" attribute')

        valid_until = self._parse_saml_date_time(valid_until)
        now = utc_now()

        if valid_until < now and (now - valid_until) > self.MAX_CLOCK_SKEW:
            raise SAMLFederatedMetadataValidationError(
                "Metadata has already expired. "
                '"validUntil" is {0} while the current time is {1}'.format(
                    valid_until, now))

        if valid_until > now and (valid_until - now) > self.MAX_VALID_TIME:
            raise SAMLFederatedMetadataValidationError(
                "Expiration time is unexpectedly far into the future. "
                '"validUntil" is {0} while the current time is {1}'.format(
                    valid_until, now))

        self._logger.info(
            "Finished validating the expiration time of the metadata belonging to {0}"
            .format(federation))
Exemple #9
0
    def validate_xml(xml, schema, debug=False):
        """
        Validates a xml against a schema
        :param xml: The xml that will be validated
        :type: string|DomDocument
        :param schema: The schema
        :type: string
        :param debug: If debug is active, the parse-errors will be showed
        :type: bool
        :returns: Error code or the DomDocument of the xml
        :rtype: string
        """
        assert isinstance(xml, basestring) or isinstance(
            xml, Document) or isinstance(xml, etree._Element)
        assert isinstance(schema, basestring)

        if isinstance(xml, Document):
            xml = xml.toxml()
        elif isinstance(xml, etree._Element):
            xml = tostring(xml, encoding='unicode')

        # Switch to lxml for schema validation
        try:
            dom = fromstring(xml.encode('utf-8'), forbid_dtd=True)
        except Exception:
            return 'unloaded_xml'

        schema_file = join(dirname(__file__), 'schemas', schema)
        f_schema = open(schema_file, 'r')
        schema_doc = etree.parse(f_schema)
        f_schema.close()
        xmlschema = etree.XMLSchema(schema_doc)

        if not xmlschema.validate(dom):
            if debug:
                stderr.write('Errors validating the metadata')
                stderr.write(':\n\n')
                for error in xmlschema.error_log:
                    stderr.write('%s\n' % error.message)

            return 'invalid_xml'

        return parseString(tostring(dom, encoding='unicode').encode('utf-8'),
                           forbid_dtd=True,
                           forbid_entities=True,
                           forbid_external=True)
Exemple #10
0
    def get_issuer(request):
        """
        Gets the Issuer of the Logout Request Message
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :return: The Issuer
        :rtype: string
        """
        if isinstance(request, etree._Element):
            elem = request
        else:
            if isinstance(request, Document):
                request = request.toxml()
            elem = fromstring(request, forbid_dtd=True)

        issuer = None
        issuer_nodes = OneLogin_Saml2_Utils.query(
            elem, '/samlp:LogoutRequest/saml:Issuer')
        if len(issuer_nodes) == 1:
            issuer = OneLogin_Saml2_Utils.element_text(issuer_nodes[0])
        return issuer
Exemple #11
0
    def get_session_indexes(request):
        """
        Gets the SessionIndexes from the Logout Request
        :param request: Logout Request Message
        :type request: string|DOMDocument
        :return: The SessionIndex value
        :rtype: list
        """
        if isinstance(request, etree._Element):
            elem = request
        else:
            if isinstance(request, Document):
                request = request.toxml()
            elem = fromstring(request, forbid_dtd=True)

        session_indexes = []
        session_index_nodes = OneLogin_Saml2_Utils.query(
            elem, '/samlp:LogoutRequest/samlp:SessionIndex')
        for session_index_node in session_index_nodes:
            session_indexes.append(
                OneLogin_Saml2_Utils.element_text(session_index_node))
        return session_indexes
    def get_metadata(url, validate_cert=True):
        """
        Gets the metadata XML from the provided URL

        :param url: Url where the XML of the Identity Provider Metadata is published.
        :type url: string

        :param validate_cert: If the url uses https schema, that flag enables or not the verification of the associated certificate.
        :type validate_cert: bool

        :returns: metadata XML
        :rtype: string
        """
        valid = False
        if validate_cert:
            response = urllib2.urlopen(url)
        else:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
            response = urllib2.urlopen(url, context=ctx)
        xml = response.read()

        if xml:
            try:
                dom = fromstring(xml, forbid_dtd=True)
                idp_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, '//md:IDPSSODescriptor')
                if idp_descriptor_nodes:
                    valid = True
            except Exception:
                pass

        if not valid:
            raise Exception('Not valid IdP XML found from URL: %s' % (url))

        return xml
Exemple #13
0
    def add_sign(xml,
                 key,
                 cert,
                 debug=False,
                 sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1,
                 digest_algorithm=OneLogin_Saml2_Constants.SHA1):
        """
        Adds signature key and senders certificate to an element (Message or
        Assertion).

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

        :param key: The private key
        :type: string

        :param cert: The public
        :type: string

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

        :param sign_algorithm: Signature algorithm method
        :type sign_algorithm: string

        :param digest_algorithm: Digest algorithm method
        :type digest_algorithm: string

        :returns: Signed XML
        :rtype: string
        """
        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(xml.encode('utf-8'), forbid_dtd=True)
        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(xml.encode('utf-8'), forbid_dtd=True)
        elif isinstance(xml, basestring):
            elem = fromstring(xml.encode('utf-8'), forbid_dtd=True)
        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)

        sign_algorithm_transform_map = {
            OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1,
            OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1,
            OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256,
            OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384,
            OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512
        }
        sign_algorithm_transform = sign_algorithm_transform_map.get(
            sign_algorithm, xmlsec.TransformRsaSha1)

        signature = Signature(xmlsec.TransformExclC14N,
                              sign_algorithm_transform,
                              nsPrefix='ds')

        issuer = OneLogin_Saml2_Utils.query(elem, '//saml:Issuer')
        if len(issuer) > 0:
            issuer = issuer[0]
            issuer.addnext(signature)
            elem_to_sign = issuer.getparent()
        else:
            entity_descriptor = OneLogin_Saml2_Utils.query(
                elem, '//md:EntityDescriptor')
            if len(entity_descriptor) > 0:
                elem.insert(0, signature)
            else:
                elem[0].insert(0, signature)
            elem_to_sign = elem

        elem_id = elem_to_sign.get('ID', None)
        if elem_id is not None:
            if elem_id:
                elem_id = '#' + elem_id
        else:
            generated_id = generated_id = OneLogin_Saml2_Utils.generate_unique_id(
            )
            elem_id = '#' + generated_id
            elem_to_sign.attrib['ID'] = generated_id

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

        digest_algorithm_transform_map = {
            OneLogin_Saml2_Constants.SHA1: xmlsec.TransformSha1,
            OneLogin_Saml2_Constants.SHA256: xmlsec.TransformSha256,
            OneLogin_Saml2_Constants.SHA384: xmlsec.TransformSha384,
            OneLogin_Saml2_Constants.SHA512: xmlsec.TransformSha512
        }
        digest_algorithm_transform = digest_algorithm_transform_map.get(
            digest_algorithm, xmlsec.TransformSha1)

        ref = signature.addReference(digest_algorithm_transform)
        if elem_id:
            ref.attrib['URI'] = elem_id

        ref.addTransform(xmlsec.TransformEnveloped)
        ref.addTransform(xmlsec.TransformExclC14N)

        key_info = signature.ensureKeyInfo()
        key_info.addX509Data()

        dsig_ctx = xmlsec.DSigCtx()
        sign_key = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None)

        file_cert = OneLogin_Saml2_Utils.write_temp_file(cert)
        sign_key.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem)
        file_cert.close()

        dsig_ctx.signKey = sign_key
        dsig_ctx.sign(signature)

        return tostring(elem, encoding='unicode').encode('utf-8')
Exemple #14
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 parse(
            idp_metadata,
            required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
            entity_id=None):
        """
        Parse the Identity Provider metadata and return a dict with extracted data.

        If there are multiple <IDPSSODescriptor> tags, parse only the first.

        Parse only those SSO endpoints with the same binding as given by
        the `required_sso_binding` parameter.

        Parse only those SLO endpoints with the same binding as given by
        the `required_slo_binding` parameter.

        If the metadata specifies multiple SSO endpoints with the required
        binding, extract only the first (the same holds true for SLO
        endpoints).

        :param idp_metadata: XML of the Identity Provider Metadata.
        :type idp_metadata: string

        :param required_sso_binding: Parse only POST or REDIRECT SSO endpoints.
        :type required_sso_binding: one of OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
            or OneLogin_Saml2_Constants.BINDING_HTTP_POST

        :param required_slo_binding: Parse only POST or REDIRECT SLO endpoints.
        :type required_slo_binding: one of OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
            or OneLogin_Saml2_Constants.BINDING_HTTP_POST

        :param entity_id: Specify the entity_id of the EntityDescriptor that you want to parse a XML
                          that contains multiple EntityDescriptor.
        :type entity_id: string

        :returns: settings dict with extracted data
        :rtype: dict
        """
        data = {}

        dom = fromstring(idp_metadata, forbid_dtd=True)

        entity_desc_path = '//md:EntityDescriptor'
        if entity_id:
            entity_desc_path += "[@entityID='%s']" % entity_id

        entity_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, entity_desc_path)

        idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None

        if len(entity_descriptor_nodes) > 0:
            entity_descriptor_node = entity_descriptor_nodes[0]
            idp_descriptor_nodes = OneLogin_Saml2_Utils.query(entity_descriptor_node, './md:IDPSSODescriptor')
            if len(idp_descriptor_nodes) > 0:
                idp_descriptor_node = idp_descriptor_nodes[0]

                idp_entity_id = entity_descriptor_node.get('entityID', None)

                want_authn_requests_signed = entity_descriptor_node.get('WantAuthnRequestsSigned', None)

                name_id_format_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, './md:NameIDFormat')
                if len(name_id_format_nodes) > 0:
                    idp_name_id_format = OneLogin_Saml2_Utils.element_text(name_id_format_nodes[0])

                sso_nodes = OneLogin_Saml2_Utils.query(
                    idp_descriptor_node,
                    "./md:SingleSignOnService[@Binding='%s']" % required_sso_binding
                )

                if len(sso_nodes) > 0:
                    idp_sso_url = sso_nodes[0].get('Location', None)

                slo_nodes = OneLogin_Saml2_Utils.query(
                    idp_descriptor_node,
                    "./md:SingleLogoutService[@Binding='%s']" % required_slo_binding
                )
                if len(slo_nodes) > 0:
                    idp_slo_url = slo_nodes[0].get('Location', None)

                signing_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
                encryption_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate")

                if len(signing_nodes) > 0 or len(encryption_nodes) > 0:
                    certs = {}
                    if len(signing_nodes) > 0:
                        certs['signing'] = []
                        for cert_node in signing_nodes:
                            certs['signing'].append(''.join(OneLogin_Saml2_Utils.element_text(cert_node).split()))
                    if len(encryption_nodes) > 0:
                        certs['encryption'] = []
                        for cert_node in encryption_nodes:
                            certs['encryption'].append(''.join(OneLogin_Saml2_Utils.element_text(cert_node).split()))

                data['idp'] = {}

                if idp_entity_id is not None:
                    data['idp']['entityId'] = idp_entity_id

                if idp_sso_url is not None:
                    data['idp']['singleSignOnService'] = {}
                    data['idp']['singleSignOnService']['url'] = idp_sso_url
                    data['idp']['singleSignOnService']['binding'] = required_sso_binding

                if idp_slo_url is not None:
                    data['idp']['singleLogoutService'] = {}
                    data['idp']['singleLogoutService']['url'] = idp_slo_url
                    data['idp']['singleLogoutService']['binding'] = required_slo_binding

                if certs is not None:
                    if (len(certs) == 1 and
                        (('signing' in certs and len(certs['signing']) == 1) or
                         ('encryption' in certs and len(certs['encryption']) == 1))) or \
                        (('signing' in certs and len(certs['signing']) == 1) and
                         ('encryption' in certs and len(certs['encryption']) == 1 and
                         certs['signing'][0] == certs['encryption'][0])):
                        if 'signing' in certs:
                            data['idp']['x509cert'] = certs['signing'][0]
                        else:
                            data['idp']['x509cert'] = certs['encryption'][0]
                    else:
                        data['idp']['x509certMulti'] = certs

                if want_authn_requests_signed is not None:
                    data['security'] = {}
                    data['security']['authnRequestsSigned'] = want_authn_requests_signed

                if idp_name_id_format:
                    data['sp'] = {}
                    data['sp']['NameIDFormat'] = idp_name_id_format
        return data
Exemple #16
0
    def generate_name_id(value,
                         sp_nq,
                         sp_format=None,
                         cert=None,
                         debug=False,
                         nq=None):
        """
        Generates a nameID.

        :param value: fingerprint
        :type: string

        :param sp_nq: SP Name Qualifier
        :type: string

        :param sp_format: SP Format
        :type: string

        :param cert: IdP Public Cert to encrypt the nameID
        :type: string

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

        :param nq: IDP Name Qualifier
        :type: string

        :returns: DOMElement | XMLSec nameID
        :rtype: string
        """
        doc = Document()
        name_id_container = doc.createElementNS(
            OneLogin_Saml2_Constants.NS_SAML, 'container')
        name_id_container.setAttribute("xmlns:saml",
                                       OneLogin_Saml2_Constants.NS_SAML)

        name_id = doc.createElement('saml:NameID')
        if sp_nq is not None:
            name_id.setAttribute('SPNameQualifier', sp_nq)
        if nq is not None:
            name_id.setAttribute('NameQualifier', nq)
        if sp_format is not None:
            name_id.setAttribute('Format', sp_format)
        name_id.appendChild(doc.createTextNode(value))
        name_id_container.appendChild(name_id)

        if cert is not None:
            xml = name_id_container.toxml()
            elem = fromstring(xml, forbid_dtd=True)

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

            # Load the public cert
            mngr = xmlsec.KeysMngr()
            file_cert = OneLogin_Saml2_Utils.write_temp_file(cert)
            key_data = xmlsec.Key.load(file_cert.name,
                                       xmlsec.KeyDataFormatCertPem, None)
            key_data.name = basename(file_cert.name)
            mngr.addKey(key_data)
            file_cert.close()

            # Prepare for encryption
            enc_data = EncData(xmlsec.TransformAes128Cbc,
                               type=xmlsec.TypeEncElement)
            enc_data.ensureCipherValue()
            key_info = enc_data.ensureKeyInfo()
            # enc_key = key_info.addEncryptedKey(xmlsec.TransformRsaPkcs1)
            enc_key = key_info.addEncryptedKey(xmlsec.TransformRsaOaep)
            enc_key.ensureCipherValue()

            # Encrypt!
            enc_ctx = xmlsec.EncCtx(mngr)
            enc_ctx.encKey = xmlsec.Key.generate(xmlsec.KeyDataAes, 128,
                                                 xmlsec.KeyDataTypeSession)

            edata = enc_ctx.encryptXml(enc_data, elem[0])

            newdoc = parseString(tostring(edata,
                                          encoding='unicode').encode('utf-8'),
                                 forbid_dtd=True,
                                 forbid_entities=True,
                                 forbid_external=True)

            if newdoc.hasChildNodes():
                child = newdoc.firstChild
                child.removeAttribute('xmlns')
                child.removeAttribute('xmlns:saml')
                child.setAttribute('xmlns:xenc',
                                   OneLogin_Saml2_Constants.NS_XENC)
                child.setAttribute('xmlns:dsig',
                                   OneLogin_Saml2_Constants.NS_DS)

            nodes = newdoc.getElementsByTagName("*")
            for node in nodes:
                if node.tagName == 'ns0:KeyInfo':
                    node.tagName = 'dsig:KeyInfo'
                    node.removeAttribute('xmlns:ns0')
                    node.setAttribute('xmlns:dsig',
                                      OneLogin_Saml2_Constants.NS_DS)
                else:
                    node.tagName = 'xenc:' + node.tagName

            encrypted_id = newdoc.createElement('saml:EncryptedID')
            encrypted_data = newdoc.replaceChild(encrypted_id,
                                                 newdoc.firstChild)
            encrypted_id.appendChild(encrypted_data)
            return newdoc.saveXML(encrypted_id)
        else:
            return doc.saveXML(name_id)
Exemple #17
0
    def validate_metadata_sign(xml,
                               cert=None,
                               fingerprint=None,
                               fingerprintalg='sha1',
                               validatecert=False,
                               debug=False):
        """
        Validates a signature of a EntityDescriptor.

        :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 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), forbid_dtd=True)
        elif isinstance(xml, Element):
            xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_MD),
                               'xmlns:md',
                               unicode(OneLogin_Saml2_Constants.NS_MD))
            xml = xml.toxml()
            elem = fromstring(str(xml), forbid_dtd=True)
        elif isinstance(xml, basestring):
            elem = fromstring(str(xml), forbid_dtd=True)
        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"])

        signature_nodes = OneLogin_Saml2_Utils.query(
            elem, '/md:EntitiesDescriptor/ds:Signature')

        if len(signature_nodes) == 0:
            signature_nodes += OneLogin_Saml2_Utils.query(
                elem, '/md:EntityDescriptor/ds:Signature')

            if len(signature_nodes) == 0:
                signature_nodes += OneLogin_Saml2_Utils.query(
                    elem,
                    '/md:EntityDescriptor/md:SPSSODescriptor/ds:Signature')
                signature_nodes += OneLogin_Saml2_Utils.query(
                    elem,
                    '/md:EntityDescriptor/md:IDPSSODescriptor/ds:Signature')

        if len(signature_nodes) > 0:
            for signature_node in signature_nodes:
                OneLogin_Saml2_Utils.validate_node_sign(signature_node,
                                                        elem,
                                                        cert,
                                                        fingerprint,
                                                        fingerprintalg,
                                                        validatecert,
                                                        debug,
                                                        raise_exceptions=True)
            return True
        else:
            raise Exception(
                'Could not validate metadata signature: No signature nodes found.'
            )
Exemple #18
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), forbid_dtd=True)
        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), forbid_dtd=True)
        elif isinstance(xml, basestring):
            elem = fromstring(str(xml), forbid_dtd=True)
        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)