Beispiel #1
0
    def extract_tag_text(xml, tagname):
        open_tag = compat.to_bytes("<%s" % tagname)
        close_tag = compat.to_bytes("</%s>" % tagname)

        xml = OneLogin_Saml2_XML.to_string(xml)
        start = xml.find(open_tag)
        assert start != -1

        end = xml.find(close_tag, start) + len(close_tag)
        assert end != -1
        return compat.to_string(xml[start:end])
Beispiel #2
0
    def extract_tag_text(xml, tagname):
        open_tag = compat.to_bytes("<%s" % tagname)
        close_tag = compat.to_bytes("</%s>" % tagname)

        xml = OneLogin_Saml2_XML.to_string(xml)
        start = xml.find(open_tag)
        assert start != -1

        end = xml.find(close_tag, start) + len(close_tag)
        assert end != -1
        return compat.to_string(xml[start:end])
Beispiel #3
0
    def validate_binary_sign(signed_query,
                             signature,
                             cert=None,
                             algorithm=OneLogin_Saml2_Constants.RSA_SHA1,
                             debug=False):
        """
        Validates signed binary data (Used to validate GET Signature).

        :param signed_query: The element we should validate
        :type: string


        :param signature: The signature that will be validate
        :type: string

        :param cert: The public cert
        :type: string

        :param algorithm: Signature algorithm
        :type: string

        :param debug: Activate the xmlsec debug
        :type: bool
        """
        try:
            xmlsec.enable_debug_trace(debug)

            dsig_ctx = xmlsec.SignatureContext()
            dsig_ctx.key = xmlsec.Key.from_memory(cert,
                                                  xmlsec.KeyFormat.CERT_PEM,
                                                  None)

            sign_algorithm_transform_map = {
                OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.Transform.DSA_SHA1,
                OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.Transform.RSA_SHA1,
                OneLogin_Saml2_Constants.RSA_SHA256:
                xmlsec.Transform.RSA_SHA256,
                OneLogin_Saml2_Constants.RSA_SHA384:
                xmlsec.Transform.RSA_SHA384,
                OneLogin_Saml2_Constants.RSA_SHA512:
                xmlsec.Transform.RSA_SHA512
            }
            sign_algorithm_transform = sign_algorithm_transform_map.get(
                algorithm, xmlsec.Transform.RSA_SHA1)

            dsig_ctx.verify_binary(compat.to_bytes(signed_query),
                                   sign_algorithm_transform,
                                   compat.to_bytes(signature))
            return True
        except xmlsec.Error as e:
            if debug:
                print(e)
            return False
Beispiel #4
0
    def testProcessResponseInvalidRequestId(self):
        """
        Tests the process_response method of the OneLogin_Saml2_Auth class
        Case Invalid Response, Invalid requestID
        """
        request_data = self.get_request()
        message = self.file_contents(join(self.data_path, 'responses', 'unsigned_response.xml.base64'))
        plain_message = compat.to_string(b64decode(message))
        current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
        plain_message = plain_message.replace('http://stuff.com/endpoints/endpoints/acs.php', current_url)
        del request_data['get_data']
        request_data['post_data'] = {
            'SAMLResponse': compat.to_string(b64encode(compat.to_bytes(plain_message)))
        }
        auth = OneLogin_Saml2_Auth(request_data, old_settings=self.loadSettingsJSON())
        request_id = 'invalid'
        auth.process_response(request_id)
        self.assertEqual('No Signature found. SAML Response rejected', auth.get_last_error_reason())

        auth.set_strict(True)
        auth.process_response(request_id)
        self.assertEqual(auth.get_errors(), ['invalid_response'])
        self.assertEqual('The InResponseTo of the Response: _57bcbf70-7b1f-012e-c821-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid', auth.get_last_error_reason())

        valid_request_id = '_57bcbf70-7b1f-012e-c821-782bcb13bb38'
        auth.process_response(valid_request_id)
        self.assertEqual('No Signature found. SAML Response rejected', auth.get_last_error_reason())
Beispiel #5
0
    def sign_binary(msg,
                    key,
                    algorithm=xmlsec.Transform.RSA_SHA1,
                    debug=False):
        """
        Sign binary message

        :param msg: The element we should validate
        :type: bytes

        :param key: The private key
        :type: string

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

        :return signed message
        :rtype str
        """

        if isinstance(msg, str):
            msg = msg.encode('utf8')

        xmlsec.enable_debug_trace(debug)
        dsig_ctx = xmlsec.SignatureContext()
        dsig_ctx.key = xmlsec.Key.from_memory(key, xmlsec.KeyFormat.PEM, None)
        return dsig_ctx.sign_binary(compat.to_bytes(msg), algorithm)
Beispiel #6
0
    def generate_unique_id():
        """
        Generates an unique string (used for example as ID for assertions).

        :return: A unique string
        :rtype: string
        """
        return 'ONELOGIN_%s' % sha1(compat.to_bytes(uuid4().hex)).hexdigest()
Beispiel #7
0
    def generate_unique_id():
        """
        Generates an unique string (used for example as ID for assertions).

        :return: A unique string
        :rtype: string
        """
        return 'ONELOGIN_%s' % sha1(compat.to_bytes(uuid4().hex)).hexdigest()
Beispiel #8
0
 def deflate_and_base64_encode(value):
     """
     Deflates and the base64 encodes a string
     :param value: The string to deflate and encode
     :type value: string
     :returns: The deflated and encoded string
     :rtype: string
     """
     return OneLogin_Saml2_Utils.b64encode(zlib.compress(compat.to_bytes(value))[2:-4])
Beispiel #9
0
 def deflate_and_base64_encode(value):
     """
     Deflates and then base64 encodes a string
     :param value: The string to deflate and encode
     :type value: string
     :returns: The deflated and encoded string
     :rtype: string
     """
     return OneLogin_Saml2_Utils.b64encode(zlib.compress(compat.to_bytes(value))[2:-4])
Beispiel #10
0
    def validate_binary_sign(signed_query, signature, cert=None, algorithm=OneLogin_Saml2_Constants.RSA_SHA1, debug=False):
        """
        Validates signed binary data (Used to validate GET Signature).

        :param signed_query: The element we should validate
        :type: string


        :param signature: The signature that will be validate
        :type: string

        :param cert: The public cert
        :type: string

        :param algorithm: Signature algorithm
        :type: string

        :param debug: Activate the xmlsec debug
        :type: bool
        """
        try:
            xmlsec.enable_debug_trace(debug)
            dsig_ctx = xmlsec.SignatureContext()
            dsig_ctx.key = xmlsec.Key.from_memory(cert, xmlsec.KeyFormat.CERT_PEM, None)

            sign_algorithm_transform_map = {
                OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.Transform.DSA_SHA1,
                OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.Transform.RSA_SHA1,
                OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.Transform.RSA_SHA256,
                OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.Transform.RSA_SHA384,
                OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.Transform.RSA_SHA512
            }
            sign_algorithm_transform = sign_algorithm_transform_map.get(algorithm, xmlsec.Transform.RSA_SHA1)

            dsig_ctx.verify_binary(compat.to_bytes(signed_query),
                                   sign_algorithm_transform,
                                   compat.to_bytes(signature))
            return True
        except xmlsec.Error as e:
            if debug:
                print(e)
            return False
Beispiel #11
0
    def calculate_x509_fingerprint(x509_cert, alg='sha1'):
        """
        Calculates the fingerprint of a formatted x509cert.

        :param x509_cert: x509 cert formatted
        :type: string

        :param alg: The algorithm to build the fingerprint
        :type: string

        :returns: fingerprint
        :rtype: string
        """
        assert isinstance(x509_cert, compat.str_type)

        lines = x509_cert.split('\n')
        data = ''
        inData = False

        for line in lines:
            # Remove '\r' from end of line if present.
            line = line.rstrip()
            if not inData:
                if line == '-----BEGIN CERTIFICATE-----':
                    inData = True
                elif line == '-----BEGIN PUBLIC KEY-----' or line == '-----BEGIN RSA PRIVATE KEY-----':
                    # This isn't an X509 certificate.
                    return None
            else:
                if line == '-----END CERTIFICATE-----':
                    break

                # Append the current line to the certificate data.
                data += line

        if not data:
            return None

        decoded_data = base64.b64decode(compat.to_bytes(data))

        if alg == 'sha512':
            fingerprint = sha512(decoded_data)
        elif alg == 'sha384':
            fingerprint = sha384(decoded_data)
        elif alg == 'sha256':
            fingerprint = sha256(decoded_data)
        else:
            fingerprint = sha1(decoded_data)

        return fingerprint.hexdigest().lower()
Beispiel #12
0
    def to_etree(xml):
        """
        Parses an XML document or fragment from a string.
        :param xml: the string to parse
        :type xml: str|bytes|xml.dom.minidom.Document|etree.Element
        :returns: the root node
        :rtype: OneLogin_Saml2_XML._element_class
        """
        if isinstance(xml, OneLogin_Saml2_XML._element_class):
            return xml
        if isinstance(xml, OneLogin_Saml2_XML._bytes_class):
            return OneLogin_Saml2_XML._parse_etree(xml, forbid_dtd=True)
        if isinstance(xml, OneLogin_Saml2_XML._text_class):
            return OneLogin_Saml2_XML._parse_etree(compat.to_bytes(xml), forbid_dtd=True)

        raise ValueError('unsupported type %r' % type(xml))
Beispiel #13
0
    def calculate_x509_fingerprint(x509_cert, alg='sha1'):
        """
        Calculates the fingerprint of a x509cert.

        :param x509_cert: x509 cert
        :type: string

        :param alg: The algorithm to build the fingerprint
        :type: string

        :returns: fingerprint
        :rtype: string
        """
        assert isinstance(x509_cert, compat.str_type)

        lines = x509_cert.split('\n')
        data = ''

        for line in lines:
            # Remove '\r' from end of line if present.
            line = line.rstrip()
            if line == '-----BEGIN CERTIFICATE-----':
                # Delete junk from before the certificate.
                data = ''
            elif line == '-----END CERTIFICATE-----':
                # Ignore data after the certificate.
                break
            elif line == '-----BEGIN PUBLIC KEY-----' or line == '-----BEGIN RSA PRIVATE KEY-----':
                # This isn't an X509 certificate.
                return None
            else:
                # Append the current line to the certificate data.
                data += line
        decoded_data = base64.b64decode(compat.to_bytes(data))

        if alg == 'sha512':
            fingerprint = sha512(decoded_data)
        elif alg == 'sha384':
            fingerprint = sha384(decoded_data)
        elif alg == 'sha256':
            fingerprint = sha256(decoded_data)
        else:
            fingerprint = sha1(decoded_data)

        return fingerprint.hexdigest().lower()
    def __init__(self, settings, response=None):
        """
        Constructs a Logout Response object (Initialize params from settings
        and if provided load the Logout Response.

        Arguments are:
            * (OneLogin_Saml2_Settings)   settings. Setting data
            * (string)                    response. An UUEncoded SAML Logout
                                                    response from the IdP.
        """
        self.__settings = settings
        self.__error = None
        self.id = None

        if response is not None:
            self.__logout_response = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(response, ignore_zip=True))
            self.document = OneLogin_Saml2_XML.to_etree(compat.to_bytes(self.__logout_response))
            self.id = self.document.get('ID', None)
Beispiel #15
0
    def to_etree(cls, xml):
        """
        Parses an XML document or fragment from a string.

        Args:
            xml (`str` or `bytes` or `xml.dom.minidom.Document` or `etree.Element`): the string to parse

        Returns:
            (OneLogin_Saml2_XML._element_class) the root node
        """
        # This is nearly verbatim the method from the super method,
        # except this method calls other methods from this class using cls.
        # The super class explicitly calls these methods by explicitly using
        # its name instead of using cls, which is why modifying the parser
        # and parsing with the inherited method doesn't work.

        if isinstance(xml, cls._element_class):
            return xml
        if isinstance(xml, cls._bytes_class):
            return cls._parse_etree(xml, forbid_dtd=True)
        if isinstance(xml, cls._text_class):
            return cls._parse_etree(compat.to_bytes(xml), forbid_dtd=True)
Beispiel #16
0
    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 = OneLogin_Saml2_XML.to_etree(compat.to_bytes(xml))
                idp_descriptor_nodes = OneLogin_Saml2_XML.query(
                    dom, '//md:IDPSSODescriptor')
                if idp_descriptor_nodes:
                    valid = True
            except:
                pass

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

        return xml
Beispiel #17
0
    def sign_binary(msg, key, algorithm=xmlsec.Transform.RSA_SHA1, debug=False):
        """
        Sign binary message

        :param msg: The element we should validate
        :type: bytes

        :param key: The private key
        :type: string

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

        :return signed message
        :rtype str
        """

        if isinstance(msg, str):
            msg = msg.encode('utf8')

        xmlsec.enable_debug_trace(debug)
        dsig_ctx = xmlsec.SignatureContext()
        dsig_ctx.key = xmlsec.Key.from_memory(key, xmlsec.KeyFormat.PEM, None)
        return dsig_ctx.sign_binary(compat.to_bytes(msg), algorithm)
Beispiel #18
0
 def b64encode(data):
     """base64 encode"""
     return compat.to_string(base64.b64encode(compat.to_bytes(data)))
Beispiel #19
0
    def __init__(self,
                 settings,
                 request=None,
                 name_id=None,
                 session_index=None,
                 nq=None,
                 name_id_format=None):
        """
        Constructs the Logout Request object.

        :param settings: Setting data
        :type settings: OneLogin_Saml2_Settings

        :param request: Optional. A LogoutRequest to be loaded instead build one.
        :type request: string

        :param name_id: The NameID that will be set in the LogoutRequest.
        :type name_id: string

        :param session_index: SessionIndex that identifies the session of the user.
        :type session_index: string

        :param nq: IDP Name Qualifier
        :type: string

        :param name_id_format: The NameID Format that will be set in the LogoutRequest.
        :type: string
        """
        self.__settings = settings
        self.__error = None
        self.id = None

        if request is None:
            sp_data = self.__settings.get_sp_data()
            idp_data = self.__settings.get_idp_data()
            security = self.__settings.get_security_data()

            uid = OneLogin_Saml2_Utils.generate_unique_id()
            self.id = uid

            issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(
                OneLogin_Saml2_Utils.now())

            cert = None
            if security['nameIdEncrypted']:
                exists_multix509enc = 'x509certMulti' in idp_data and \
                    'encryption' in idp_data['x509certMulti'] and \
                    idp_data['x509certMulti']['encryption']
                if exists_multix509enc:
                    cert = idp_data['x509certMulti']['encryption'][0]
                else:
                    cert = idp_data['x509cert']

            if name_id is not None:
                if not name_id_format and sp_data[
                        'NameIDFormat'] != OneLogin_Saml2_Constants.NAMEID_UNSPECIFIED:
                    name_id_format = sp_data['NameIDFormat']
            else:
                name_id_format = OneLogin_Saml2_Constants.NAMEID_ENTITY

            sp_name_qualifier = None
            if name_id_format == OneLogin_Saml2_Constants.NAMEID_ENTITY:
                name_id = idp_data['entityId']
                nq = None
            elif nq is not None:
                # We only gonna include SPNameQualifier if NameQualifier is provided
                sp_name_qualifier = sp_data['entityId']

            name_id_obj = OneLogin_Saml2_Utils.generate_name_id(
                name_id, sp_name_qualifier, name_id_format, cert, False, nq)

            if session_index:
                session_index_str = '<samlp:SessionIndex>%s</samlp:SessionIndex>' % session_index
            else:
                session_index_str = ''

            logout_request = OneLogin_Saml2_Templates.LOGOUT_REQUEST % \
                {
                    'id': uid,
                    'issue_instant': issue_instant,
                    'single_logout_url': idp_data['singleLogoutService']['url'],
                    'entity_id': sp_data['entityId'],
                    'name_id': name_id_obj,
                    'session_index': session_index_str,
                }
        else:
            logout_request = OneLogin_Saml2_Utils.decode_base64_and_inflate(
                request, ignore_zip=True)
            self.id = self.get_id(logout_request)

        self.__logout_request = compat.to_bytes(logout_request)
Beispiel #20
0
    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):
        """
        Parses the Identity Provider metadata and return a dict with extracted data.

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

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

        Parses 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 = OneLogin_Saml2_XML.to_etree(compat.to_bytes(idp_metadata))
        idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None

        entity_desc_path = '//md:EntityDescriptor'
        if entity_id:
            entity_desc_path += "[@entityID='%s']" % entity_id
        entity_descriptor_nodes = OneLogin_Saml2_XML.query(
            dom, entity_desc_path)

        if len(entity_descriptor_nodes) > 0:
            entity_descriptor_node = entity_descriptor_nodes[0]
            idp_descriptor_nodes = OneLogin_Saml2_XML.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_XML.query(
                    idp_descriptor_node, './md:NameIDFormat')
                if len(name_id_format_nodes) > 0:
                    idp_name_id_format = name_id_format_nodes[0].text

                sso_nodes = OneLogin_Saml2_XML.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_XML.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_XML.query(
                    idp_descriptor_node,
                    "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
                )
                encryption_nodes = OneLogin_Saml2_XML.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(
                                cert_node.text.split()))
                    if len(encryption_nodes) > 0:
                        certs['encryption'] = []
                        for cert_node in encryption_nodes:
                            certs['encryption'].append(''.join(
                                cert_node.text.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 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

                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
        return data
Beispiel #21
0
 def b64encode(data):
     """base64 encode"""
     return compat.to_string(base64.b64encode(compat.to_bytes(data)))