def saml_validate_post_response(response): issuer = 'urn:rea:sshephalopod' request_id_expiration_period_ms = 3600000 xml = ET.fromstring(response) path = "/*[local-name()='Response']/@InResponseTo" in_response_to = xml.xpath(path) in_response_to = in_response_to[0] if in_response_to else "" path = ".//*[local-name()='X509Certificate']/text()" cert = xml.xpath(path)[0] cert = "-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----".format(cert) if not OneLogin_Saml2_Utils.validate_sign(xml, cert, validatecert=True): raise Exception("Top level signature is not valid!") assertions = xml.xpath("/*[local-name()='Response']/*[local-name()='Assertion']") encrypted_assertions = xml.xpath("/*[local-name()='Response']/*[local-name()='EncryptedAssertion']") if len(assertions) + len(encrypted_assertions) > 1: # There's apparently no reason we want to handle multiple assertions raise Exception("Too many assertions!") if len(assertions) == 1: if not OneLogin_Saml2_Utils.validate_sign(assertions[0], cert, validatecert=True): raise Exception("Invalid signature in assertion") return process_validly_signed_assertion(ET.tostring(assertions[0])) raise NotImplementedError("Sorry, only know about if there is an assertion in the response")
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
def is_valid(self, request_data, request_id=None): """ Validates the response object. :param request_data: Request Data :type request_data: dict :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP :type request_id: string :returns: True if the SAML Response is valid, False if not :rtype: bool """ self.__error = None try: # Checks SAML version if self.document.get('Version', None) != '2.0': raise Exception('Unsupported SAML version') # Checks that ID exists if self.document.get('ID', None) is None: raise Exception('Missing ID attribute on SAML Response') # Checks that the response only has one assertion if not self.validate_num_assertions(): raise Exception('SAML Response must contain 1 assertion') # Checks that the response has the SUCCESS status self.check_status() idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data.get('entityId', '') sp_data = self.__settings.get_sp_data() sp_entity_id = sp_data.get('entityId', '') signed_elements = self.process_signed_elements() if self.__settings.is_strict(): no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' res = OneLogin_Saml2_Utils.validate_xml(etree.tostring(self.document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if not isinstance(res, Document): raise Exception(no_valid_xml_msg) # If encrypted, check also the decrypted document if self.encrypted: res = OneLogin_Saml2_Utils.validate_xml(etree.tostring(self.decrypted_document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if not isinstance(res, Document): raise Exception(no_valid_xml_msg) security = self.__settings.get_security_data() current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided in_response_to = self.document.get('InResponseTo', None) if in_response_to and request_id: if in_response_to != request_id: raise Exception('The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id)) if not self.encrypted and security.get('wantAssertionsEncrypted', False): raise Exception('The assertion of the Response is not encrypted and the SP require it') if security.get('wantNameIdEncrypted', False): encrypted_nameid_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData') if len(encrypted_nameid_nodes) == 0: raise Exception('The NameID of the Response is not encrypted and the SP require it') # Checks that there is at least one AttributeStatement if required attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement') if security.get('wantAttributeStatement', True) and not attribute_statement_nodes: raise Exception('There is no AttributeStatement on the Response') # Validates Assertion timestamps if not self.validate_timestamps(): raise Exception('Timing issues (please check your clock settings)') encrypted_attributes_nodes = self.__query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute') if encrypted_attributes_nodes: raise Exception('There is an EncryptedAttribute in the Response and this SP not support them') # Checks destination destination = self.document.get('Destination', '') if destination: if not destination.startswith(current_url): # TODO: Review if following lines are required, since we can control the # request_data # current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data) # if not destination.startswith(current_url_routed): raise Exception('The response was received at %s instead of %s' % (current_url, destination)) # Checks audience valid_audiences = self.get_audiences() if valid_audiences and sp_entity_id not in valid_audiences: raise Exception('%s is not a valid audience for this Response' % sp_entity_id) # Checks the issuers issuers = self.get_issuers() for issuer in issuers: if issuer is None or issuer != idp_entity_id: raise Exception('Invalid issuer in the Assertion/Response') # Checks the session Expiration session_expiration = self.get_session_not_on_or_after() if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now(): raise Exception('The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response') # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid any_subject_confirmation = False subject_confirmation_nodes = self.__query_assertion('/saml:Subject/saml:SubjectConfirmation') for scn in subject_confirmation_nodes: method = scn.get('Method', None) if method and method != OneLogin_Saml2_Constants.CM_BEARER: continue sc_data = scn.find('saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP) if sc_data is None: continue else: irt = sc_data.get('InResponseTo', None) if in_response_to and irt and irt != in_response_to: continue recipient = sc_data.get('Recipient', None) if recipient and current_url not in recipient: continue nooa = sc_data.get('NotOnOrAfter', None) if nooa: parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa) if parsed_nooa <= OneLogin_Saml2_Utils.now(): continue nb = sc_data.get('NotBefore', None) if nb: parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(nb) if parsed_nb > OneLogin_Saml2_Utils.now(): continue any_subject_confirmation = True break if not any_subject_confirmation: raise Exception('A valid SubjectConfirmation was not found on this Response') if security.get('wantAssertionsSigned', False) and ('{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML) not in signed_elements: raise Exception('The Assertion of the Response is not signed and the SP require it') if security.get('wantMessagesSigned', False) and ('{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP) not in signed_elements: raise Exception('The Message of the Response is not signed and the SP require it') if len(signed_elements) > 0: if len(signed_elements) > 2: raise Exception('Too many Signatures found. SAML Response rejected') certs = idp_data.get('x509certs', []) fingerprint = idp_data.get('certFingerprint', None) fingerprintalg = idp_data.get('certFingerprintAlgorithm', None) # If find a Signature on the Response, validates it checking the original response if '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements: document_to_validate = self.document # Otherwise validates the assertion (decrypted assertion if was encrypted) else: if self.encrypted: document_to_validate = self.decrypted_document else: document_to_validate = self.document if certs: validated = any(OneLogin_Saml2_Utils.validate_sign(document_to_validate, cert, fingerprint, fingerprintalg) for cert in certs) else: validated = OneLogin_Saml2_Utils.validate_sign(document_to_validate, None, fingerprint, fingerprintalg) if not validated: raise Exception('Signature validation failed. SAML Response rejected') else: raise Exception('No Signature found. SAML Response rejected') return True except Exception as err: self.__error = err.__str__() debug = self.__settings.is_debug_active() if debug: print err.__str__() return False
def testValidateSign(self): """ Tests the validate_sign method of the OneLogin_Saml2_Utils """ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) idp_data = settings.get_idp_data() cert = idp_data['x509cert'] settings_2 = OneLogin_Saml2_Settings( self.loadSettingsJSON('settings2.json')) idp_data2 = settings_2.get_idp_data() cert_2 = idp_data2['x509cert'] fingerprint_2 = OneLogin_Saml2_Utils.calculate_x509_fingerprint(cert_2) fingerprint_2_256 = OneLogin_Saml2_Utils.calculate_x509_fingerprint( cert_2, 'sha256') try: self.assertFalse(OneLogin_Saml2_Utils.validate_sign('', cert)) except Exception as e: self.assertEqual('Empty string supplied as input', str(e)) # expired cert xml_metadata_signed = self.file_contents( join(self.data_path, 'metadata', 'signed_metadata_settings1.xml')) self.assertTrue( OneLogin_Saml2_Utils.validate_metadata_sign( xml_metadata_signed, cert)) # expired cert, verified it self.assertFalse( OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed, cert, validatecert=True)) xml_metadata_signed_2 = self.file_contents( join(self.data_path, 'metadata', 'signed_metadata_settings2.xml')) self.assertTrue( OneLogin_Saml2_Utils.validate_metadata_sign( xml_metadata_signed_2, cert_2)) self.assertTrue( OneLogin_Saml2_Utils.validate_metadata_sign( xml_metadata_signed_2, None, fingerprint_2)) xml_response_msg_signed = b64decode( self.file_contents( join(self.data_path, 'responses', 'signed_message_response.xml.base64'))) # expired cert self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert)) # expired cert, verified it self.assertFalse( OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert, validatecert=True)) # modified cert other_cert_path = join(dirname(__file__), '..', '..', '..', 'certs') f = open(other_cert_path + '/certificate1', 'r') cert_x = f.read() f.close() self.assertFalse( OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert_x)) self.assertFalse( OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert_x, validatecert=True)) xml_response_msg_signed_2 = b64decode( self.file_contents( join(self.data_path, 'responses', 'signed_message_response2.xml.base64'))) self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, cert_2)) self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None, fingerprint_2)) self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None, fingerprint_2, 'sha1')) self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None, fingerprint_2_256, 'sha256')) xml_response_assert_signed = b64decode( self.file_contents( join(self.data_path, 'responses', 'signed_assertion_response.xml.base64'))) # expired cert self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed, cert)) # expired cert, verified it self.assertFalse( OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed, cert, validatecert=True)) xml_response_assert_signed_2 = b64decode( self.file_contents( join(self.data_path, 'responses', 'signed_assertion_response2.xml.base64'))) self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed_2, cert_2)) self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed_2, None, fingerprint_2)) xml_response_double_signed = b64decode( self.file_contents( join(self.data_path, 'responses', 'double_signed_response.xml.base64'))) # expired cert self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed, cert)) # expired cert, verified it self.assertFalse( OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed, cert, validatecert=True)) xml_response_double_signed_2 = b64decode( self.file_contents( join(self.data_path, 'responses', 'double_signed_response2.xml.base64'))) self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed_2, cert_2)) self.assertTrue( OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed_2, None, fingerprint_2)) dom = parseString(xml_response_msg_signed_2) self.assertTrue(OneLogin_Saml2_Utils.validate_sign( dom.toxml(), cert_2)) dom.firstChild.firstChild.firstChild.nodeValue = 'https://idp.example.com/simplesaml/saml2/idp/metadata.php' dom.firstChild.getAttributeNode( 'ID').nodeValue = u'_34fg27g212d63k1f923845324475802ac0fc24530b' # Reference validation failed self.assertFalse( OneLogin_Saml2_Utils.validate_sign(dom.toxml(), cert_2)) invalid_fingerprint = 'afe71c34ef740bc87434be13a2263d31271da1f9' # Wrong fingerprint self.assertFalse( OneLogin_Saml2_Utils.validate_metadata_sign( xml_metadata_signed_2, None, invalid_fingerprint)) dom_2 = parseString(xml_response_double_signed_2) self.assertTrue( OneLogin_Saml2_Utils.validate_sign(dom_2.toxml(), cert_2)) dom_2.firstChild.firstChild.firstChild.nodeValue = 'https://example.com/other-idp' # Modified message self.assertFalse( OneLogin_Saml2_Utils.validate_sign(dom_2.toxml(), cert_2)) # Try to validate directly the Assertion dom_3 = parseString(xml_response_double_signed_2) assert_elem_3 = dom_3.firstChild.firstChild.nextSibling.nextSibling.nextSibling assert_elem_3.setAttributeNS(OneLogin_Saml2_Constants.NS_SAML, 'xmlns:saml', OneLogin_Saml2_Constants.NS_SAML) self.assertFalse( OneLogin_Saml2_Utils.validate_sign(assert_elem_3.toxml(), cert_2)) # Wrong scheme no_signed = b64decode( self.file_contents( join(self.data_path, 'responses', 'invalids', 'no_signature.xml.base64'))) self.assertFalse(OneLogin_Saml2_Utils.validate_sign(no_signed, cert)) no_key = b64decode( self.file_contents( join(self.data_path, 'responses', 'invalids', 'no_key.xml.base64'))) self.assertFalse(OneLogin_Saml2_Utils.validate_sign(no_key, cert)) # Signature Wrapping attack wrapping_attack1 = b64decode( self.file_contents( join(self.data_path, 'responses', 'invalids', 'signature_wrapping_attack.xml.base64'))) self.assertFalse( OneLogin_Saml2_Utils.validate_sign(wrapping_attack1, cert))
def is_valid(self, request_data, request_id=None): """ Validates the response object. :param request_data: Request Data :type request_data: dict :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP :type request_id: string :returns: True if the SAML Response is valid, False if not :rtype: bool """ self.__error = None try: # Checks SAML version if self.document.get('Version', None) != '2.0': raise Exception('Unsupported SAML version') # Checks that ID exists if self.document.get('ID', None) is None: raise Exception('Missing ID attribute on SAML Response') # Checks that the response only has one assertion if not self.validate_num_assertions(): raise Exception('SAML Response must contain 1 assertion') # Checks that the response has the SUCCESS status self.check_status() idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data.get('entityId', '') sp_data = self.__settings.get_sp_data() sp_entity_id = sp_data.get('entityId', '') sign_nodes = self.__query('//ds:Signature') signed_elements = [] for sign_node in sign_nodes: signed_elements.append(sign_node.getparent().tag) if self.__settings.is_strict(): res = OneLogin_Saml2_Utils.validate_xml( etree.tostring(self.document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if not isinstance(res, Document): raise Exception( 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' ) security = self.__settings.get_security_data() current_url = OneLogin_Saml2_Utils.get_self_url_no_query( request_data) # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided in_response_to = self.document.get('InResponseTo', None) if in_response_to and request_id: if in_response_to != request_id: raise Exception( 'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id)) if not self.encrypted and security.get( 'wantAssertionsEncrypted', False): raise Exception( 'The assertion of the Response is not encrypted and the SP require it' ) if security.get('wantNameIdEncrypted', False): encrypted_nameid_nodes = self.__query_assertion( '/saml:Subject/saml:EncryptedID/xenc:EncryptedData') if len(encrypted_nameid_nodes) == 0: raise Exception( 'The NameID of the Response is not encrypted and the SP require it' ) # Checks that there is at least one AttributeStatement if required attribute_statement_nodes = self.__query_assertion( '/saml:AttributeStatement') if security.get('wantAttributeStatement', True) and not attribute_statement_nodes: raise Exception( 'There is no AttributeStatement on the Response') # Validates Assertion timestamps if not self.validate_timestamps(): raise Exception( 'Timing issues (please check your clock settings)') encrypted_attributes_nodes = self.__query_assertion( '/saml:AttributeStatement/saml:EncryptedAttribute') if encrypted_attributes_nodes: raise Exception( 'There is an EncryptedAttribute in the Response and this SP not support them' ) # Checks destination destination = self.document.get('Destination', '') if destination: if not destination.startswith(current_url): # TODO: Review if following lines are required, since we can control the # request_data # current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data) # if not destination.startswith(current_url_routed): raise Exception( 'The response was received at %s instead of %s' % (current_url, destination)) # Checks audience valid_audiences = self.get_audiences() if valid_audiences and sp_entity_id not in valid_audiences: raise Exception( '%s is not a valid audience for this Response' % sp_entity_id) # Checks the issuers issuers = self.get_issuers() for issuer in issuers: if issuer is None or issuer != idp_entity_id: raise Exception( 'Invalid issuer in the Assertion/Response') # Checks the session Expiration session_expiration = self.get_session_not_on_or_after() if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now( ): raise Exception( 'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response' ) # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid any_subject_confirmation = False subject_confirmation_nodes = self.__query_assertion( '/saml:Subject/saml:SubjectConfirmation') for scn in subject_confirmation_nodes: method = scn.get('Method', None) if method and method != OneLogin_Saml2_Constants.CM_BEARER: continue sc_data = scn.find( 'saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP) if sc_data is None: continue else: irt = sc_data.get('InResponseTo', None) if irt != in_response_to: continue recipient = sc_data.get('Recipient', None) if recipient and current_url not in recipient: continue nooa = sc_data.get('NotOnOrAfter', None) if nooa: parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time( nooa) if parsed_nooa <= OneLogin_Saml2_Utils.now(): continue nb = sc_data.get('NotBefore', None) if nb: parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time( nb) if parsed_nb > OneLogin_Saml2_Utils.now(): continue any_subject_confirmation = True break if not any_subject_confirmation: raise Exception( 'A valid SubjectConfirmation was not found on this Response' ) if security.get('wantAssertionsSigned', False) and ( '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML ) not in signed_elements: raise Exception( 'The Assertion of the Response is not signed and the SP require it' ) if security.get('wantMessagesSigned', False) and ( '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP ) not in signed_elements: raise Exception( 'The Message of the Response is not signed and the SP require it' ) if len(signed_elements) > 0: if len(signed_elements) > 2: raise Exception( 'Too many Signatures found. SAML Response rejected' ) cert = idp_data.get('x509cert', None) fingerprint = idp_data.get('certFingerprint', None) fingerprintalg = idp_data.get('certFingerprintAlgorithm', None) # If find a Signature on the Response, validates it checking the original response if '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements: document_to_validate = self.document # Otherwise validates the assertion (decrypted assertion if was encrypted) else: if self.encrypted: document_to_validate = self.decrypted_document else: document_to_validate = self.document if not OneLogin_Saml2_Utils.validate_sign( document_to_validate, cert, fingerprint, fingerprintalg): raise Exception( 'Signature validation failed. SAML Response rejected' ) else: raise Exception( 'No Signature found. SAML Response rejected') return True except Exception as err: self.__error = err.__str__() debug = self.__settings.is_debug_active() if debug: print err.__str__() return False
def testValidateSign(self): """ Tests the validate_sign method of the OneLogin_Saml2_Utils """ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) idp_data = settings.get_idp_data() cert = idp_data['x509cert'] settings_2 = OneLogin_Saml2_Settings(self.loadSettingsJSON('settings2.json')) idp_data2 = settings_2.get_idp_data() cert_2 = idp_data2['x509cert'] fingerprint_2 = OneLogin_Saml2_Utils.calculate_x509_fingerprint(cert_2) fingerprint_2_256 = OneLogin_Saml2_Utils.calculate_x509_fingerprint(cert_2, 'sha256') try: self.assertFalse(OneLogin_Saml2_Utils.validate_sign('', cert)) except Exception as e: self.assertEqual('Empty string supplied as input', e.message) try: self.assertFalse(OneLogin_Saml2_Utils.validate_sign(1, cert)) except Exception as e: self.assertEqual('Error parsing xml string', e.message) # expired cert xml_metadata_signed = self.file_contents(join(self.data_path, 'metadata', 'signed_metadata_settings1.xml')) self.assertTrue(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed, cert)) # expired cert, verified it self.assertFalse(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed, cert, validatecert=True)) xml_metadata_signed_2 = self.file_contents(join(self.data_path, 'metadata', 'signed_metadata_settings2.xml')) self.assertTrue(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed_2, cert_2)) self.assertTrue(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed_2, None, fingerprint_2)) xml_response_msg_signed = b64decode(self.file_contents(join(self.data_path, 'responses', 'signed_message_response.xml.base64'))) # expired cert self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert)) # expired cert, verified it self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert, validatecert=True)) # modified cert other_cert_path = join(dirname(__file__), '..', '..', '..', 'certs') f = open(other_cert_path + '/certificate1', 'r') cert_x = f.read() f.close() self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert_x)) self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed, cert_x, validatecert=True)) xml_response_msg_signed_2 = b64decode(self.file_contents(join(self.data_path, 'responses', 'signed_message_response2.xml.base64'))) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, cert_2)) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None, fingerprint_2)) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None, fingerprint_2, 'sha1')) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_msg_signed_2, None, fingerprint_2_256, 'sha256')) xml_response_assert_signed = b64decode(self.file_contents(join(self.data_path, 'responses', 'signed_assertion_response.xml.base64'))) # expired cert self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed, cert)) # expired cert, verified it self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed, cert, validatecert=True)) xml_response_assert_signed_2 = b64decode(self.file_contents(join(self.data_path, 'responses', 'signed_assertion_response2.xml.base64'))) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed_2, cert_2)) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_assert_signed_2, None, fingerprint_2)) xml_response_double_signed = b64decode(self.file_contents(join(self.data_path, 'responses', 'double_signed_response.xml.base64'))) # expired cert self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed, cert)) # expired cert, verified it self.assertFalse(OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed, cert, validatecert=True)) xml_response_double_signed_2 = b64decode(self.file_contents(join(self.data_path, 'responses', 'double_signed_response2.xml.base64'))) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed_2, cert_2)) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(xml_response_double_signed_2, None, fingerprint_2)) dom = parseString(xml_response_msg_signed_2) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(dom, cert_2)) dom.firstChild.firstChild.firstChild.nodeValue = 'https://idp.example.com/simplesaml/saml2/idp/metadata.php' dom.firstChild.getAttributeNode('ID').nodeValue = u'_34fg27g212d63k1f923845324475802ac0fc24530b' # Reference validation failed self.assertFalse(OneLogin_Saml2_Utils.validate_sign(dom, cert_2)) invalid_fingerprint = 'afe71c34ef740bc87434be13a2263d31271da1f9' # Wrong fingerprint self.assertFalse(OneLogin_Saml2_Utils.validate_metadata_sign(xml_metadata_signed_2, None, invalid_fingerprint)) dom_2 = parseString(xml_response_double_signed_2) self.assertTrue(OneLogin_Saml2_Utils.validate_sign(dom_2, cert_2)) dom_2.firstChild.firstChild.firstChild.nodeValue = 'https://example.com/other-idp' # Modified message self.assertFalse(OneLogin_Saml2_Utils.validate_sign(dom_2, cert_2)) # Try to validate directly the Assertion dom_3 = parseString(xml_response_double_signed_2) assert_elem_3 = dom_3.firstChild.firstChild.nextSibling.nextSibling.nextSibling self.assertFalse(OneLogin_Saml2_Utils.validate_sign(assert_elem_3, cert_2)) # Wrong scheme no_signed = b64decode(self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_signature.xml.base64'))) self.assertFalse(OneLogin_Saml2_Utils.validate_sign(no_signed, cert)) no_key = b64decode(self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_key.xml.base64'))) self.assertFalse(OneLogin_Saml2_Utils.validate_sign(no_key, cert)) # Signature Wrapping attack wrapping_attack1 = b64decode(self.file_contents(join(self.data_path, 'responses', 'invalids', 'signature_wrapping_attack.xml.base64'))) self.assertFalse(OneLogin_Saml2_Utils.validate_sign(wrapping_attack1, cert))
def is_valid(self, request_data, request_id=None, raise_exceptions=False): """ Validates the response object. :param request_data: Request Data :type request_data: dict :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP :type request_id: string :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean :returns: True if the SAML Response is valid, False if not :rtype: bool """ self.__error = None try: # Checks SAML version if self.document.get('Version', None) != '2.0': raise OneLogin_Saml2_ValidationError( 'Unsupported SAML version', OneLogin_Saml2_ValidationError.UNSUPPORTED_SAML_VERSION ) # Checks that ID exists if self.document.get('ID', None) is None: raise OneLogin_Saml2_ValidationError( 'Missing ID attribute on SAML Response', OneLogin_Saml2_ValidationError.MISSING_ID ) # Checks that the response has the SUCCESS status self.check_status() # Checks that the response only has one assertion if not self.validate_num_assertions(): raise OneLogin_Saml2_ValidationError( 'SAML Response must contain 1 assertion', OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS ) idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data.get('entityId', '') sp_data = self.__settings.get_sp_data() sp_entity_id = sp_data.get('entityId', '') signed_elements = self.process_signed_elements() has_signed_response = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements has_signed_assertion = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML in signed_elements if self.__settings.is_strict(): no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' res = OneLogin_Saml2_Utils.validate_xml( tostring(self.document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active() ) if not isinstance(res, Document): raise OneLogin_Saml2_ValidationError( no_valid_xml_msg, OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT ) # If encrypted, check also the decrypted document if self.encrypted: res = OneLogin_Saml2_Utils.validate_xml( tostring(self.decrypted_document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active() ) if not isinstance(res, Document): raise OneLogin_Saml2_ValidationError( no_valid_xml_msg, OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT ) security = self.__settings.get_security_data() current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) in_response_to = self.document.get('InResponseTo', None) if request_id is None and in_response_to is not None and security.get('rejectUnsolicitedResponsesWithInResponseTo', False): raise OneLogin_Saml2_ValidationError( 'The Response has an InResponseTo attribute: %s while no InResponseTo was expected' % in_response_to, OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO ) # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided if request_id is not None and in_response_to != request_id: raise OneLogin_Saml2_ValidationError( 'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id), OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO ) if not self.encrypted and security.get('wantAssertionsEncrypted', False): raise OneLogin_Saml2_ValidationError( 'The assertion of the Response is not encrypted and the SP require it', OneLogin_Saml2_ValidationError.NO_ENCRYPTED_ASSERTION ) if security.get('wantNameIdEncrypted', False): encrypted_nameid_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData') if len(encrypted_nameid_nodes) != 1: raise OneLogin_Saml2_ValidationError( 'The NameID of the Response is not encrypted and the SP require it', OneLogin_Saml2_ValidationError.NO_ENCRYPTED_NAMEID ) # Checks that a Conditions element exists if not self.check_one_condition(): raise OneLogin_Saml2_ValidationError( 'The Assertion must include a Conditions element', OneLogin_Saml2_ValidationError.MISSING_CONDITIONS ) # Validates Assertion timestamps self.validate_timestamps(raise_exceptions=True) # Checks that an AuthnStatement element exists and is unique if not self.check_one_authnstatement(): raise OneLogin_Saml2_ValidationError( 'The Assertion must include an AuthnStatement element', OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_AUTHSTATEMENTS ) # Checks that the response has all of the AuthnContexts that we provided in the request. # Only check if failOnAuthnContextMismatch is true and requestedAuthnContext is set to a list. requested_authn_contexts = security.get('requestedAuthnContext', True) if security.get('failOnAuthnContextMismatch', False) and requested_authn_contexts and requested_authn_contexts is not True: authn_contexts = self.get_authn_contexts() unmatched_contexts = set(requested_authn_contexts).difference(authn_contexts) if unmatched_contexts: raise OneLogin_Saml2_ValidationError( 'The AuthnContext "%s" didn\'t include requested context "%s"' % (', '.join(authn_contexts), ', '.join(unmatched_contexts)), OneLogin_Saml2_ValidationError.AUTHN_CONTEXT_MISMATCH ) # Checks that there is at least one AttributeStatement if required attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement') if security.get('wantAttributeStatement', True) and not attribute_statement_nodes: raise OneLogin_Saml2_ValidationError( 'There is no AttributeStatement on the Response', OneLogin_Saml2_ValidationError.NO_ATTRIBUTESTATEMENT ) encrypted_attributes_nodes = self.__query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute') if encrypted_attributes_nodes: raise OneLogin_Saml2_ValidationError( 'There is an EncryptedAttribute in the Response and this SP not support them', OneLogin_Saml2_ValidationError.ENCRYPTED_ATTRIBUTES ) # Checks destination destination = self.document.get('Destination', None) if destination: if not destination.startswith(current_url): # TODO: Review if following lines are required, since we can control the # request_data # current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data) # if not destination.startswith(current_url_routed): raise OneLogin_Saml2_ValidationError( 'The response was received at %s instead of %s' % (current_url, destination), OneLogin_Saml2_ValidationError.WRONG_DESTINATION ) elif destination == '': raise OneLogin_Saml2_ValidationError( 'The response has an empty Destination value', OneLogin_Saml2_ValidationError.EMPTY_DESTINATION ) # Checks audience valid_audiences = self.get_audiences() if valid_audiences and sp_entity_id not in valid_audiences: raise OneLogin_Saml2_ValidationError( '%s is not a valid audience for this Response' % sp_entity_id, OneLogin_Saml2_ValidationError.WRONG_AUDIENCE ) # Checks the issuers issuers = self.get_issuers() for issuer in issuers: if issuer is None or issuer != idp_entity_id: raise OneLogin_Saml2_ValidationError( 'Invalid issuer in the Assertion/Response (expected %(idpEntityId)s, got %(issuer)s)' % { 'idpEntityId': idp_entity_id, 'issuer': issuer }, OneLogin_Saml2_ValidationError.WRONG_ISSUER ) # Checks the session Expiration session_expiration = self.get_session_not_on_or_after() if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now(): raise OneLogin_Saml2_ValidationError( 'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response', OneLogin_Saml2_ValidationError.SESSION_EXPIRED ) # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid any_subject_confirmation = False subject_confirmation_nodes = self.__query_assertion('/saml:Subject/saml:SubjectConfirmation') for scn in subject_confirmation_nodes: method = scn.get('Method', None) if method and method != OneLogin_Saml2_Constants.CM_BEARER: continue sc_data = scn.find('saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP) if sc_data is None: continue else: irt = sc_data.get('InResponseTo', None) if (in_response_to is None and irt is not None and security.get('rejectUnsolicitedResponsesWithInResponseTo', False)) or \ in_response_to and irt and irt != in_response_to: continue recipient = sc_data.get('Recipient', None) if recipient and current_url not in recipient: continue nooa = sc_data.get('NotOnOrAfter', None) if nooa: parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa) if parsed_nooa <= OneLogin_Saml2_Utils.now(): continue nb = sc_data.get('NotBefore', None) if nb: parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(nb) if parsed_nb > OneLogin_Saml2_Utils.now(): continue if nooa: self.valid_scd_not_on_or_after = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa) any_subject_confirmation = True break if not any_subject_confirmation: raise OneLogin_Saml2_ValidationError( 'A valid SubjectConfirmation was not found on this Response', OneLogin_Saml2_ValidationError.WRONG_SUBJECTCONFIRMATION ) if security.get('wantAssertionsSigned', False) and not has_signed_assertion: raise OneLogin_Saml2_ValidationError( 'The Assertion of the Response is not signed and the SP require it', OneLogin_Saml2_ValidationError.NO_SIGNED_ASSERTION ) if security.get('wantMessagesSigned', False) and not has_signed_response: raise OneLogin_Saml2_ValidationError( 'The Message of the Response is not signed and the SP require it', OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE ) if not signed_elements or (not has_signed_response and not has_signed_assertion): raise OneLogin_Saml2_ValidationError( 'No Signature found. SAML Response rejected', OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND ) else: cert = idp_data.get('x509cert', None) fingerprint = idp_data.get('certFingerprint', None) fingerprintalg = idp_data.get('certFingerprintAlgorithm', None) multicerts = None if 'x509certMulti' in idp_data and 'signing' in idp_data['x509certMulti'] and idp_data['x509certMulti']['signing']: multicerts = idp_data['x509certMulti']['signing'] # If find a Signature on the Response, validates it checking the original response if has_signed_response and not OneLogin_Saml2_Utils.validate_sign(self.document, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False): raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE ) document_check_assertion = self.decrypted_document if self.encrypted else self.document if has_signed_assertion and not OneLogin_Saml2_Utils.validate_sign(document_check_assertion, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False): raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE ) return True except Exception as err: self.__error = err.__str__() debug = self.__settings.is_debug_active() if debug: print(err.__str__()) if raise_exceptions: raise err return False
def validateAssertion(xml, fingerprint=None, fingerprintalg=None): result = { 'schemaValidate': False, 'signCheck': False, 'certValidity': False, 'certAllowed': True, 'error': 0, 'msg': '', 'assertionName': None } assert isinstance(xml, compat.text_types) if len(xml) == 0: result['error'] = 1 result['msg'] = 'Empty string supplied as input' return result xml = xmlRemoveDeclaration(xml) parsedassertion = etree.fromstring(xml) # assertion name path assertionNameXpath = "local-name(/*)" assertionName = parsedassertion.xpath(assertionNameXpath) assertionName = str(assertionName) # find assertion schema if assertionName == 'EntityDescriptor': asscertionShema = 'saml-schema-metadata-2.0.xsd' elif assertionName == 'Response': asscertionShema = 'saml-schema-protocol-2.0.xsd' elif assertionName == 'AuthnRequest': asscertionShema = 'saml-schema-protocol-2.0.xsd' else: result['error'] = 2 result['msg'] = 'Assertion unknown' return result # siganture node path signatureNodeXpath = ".//*[local-name()='Signature']" if assertionName == 'Response': signatureNodeXpath = "*[local-name(/*)='Response']//*[local-name()='Signature']" result['assertionName'] = assertionName # get certificate signing signingcert = easyspid.lib.easyspid.get_signature_cert(xml) # validate xml against its schema schemaCheck = OneLogin_Saml2_XML.validate_xml(xml, asscertionShema, False) if isinstance(schemaCheck, str): result['msg'] = schemaCheck result['schemaValidate'] = False else: result['schemaValidate'] = True # check signature signingfingerprintalg = 'sha1' if fingerprintalg is not None: signingfingerprintalg = fingerprintalg signingfingerprint = (easyspid.lib.easyspid.calcCertFingerprint( signingcert, signingfingerprintalg))['result'] if assertionName == 'EntityDescriptor' and fingerprint is None: allowedCert = easyspid.lib.easyspid.get_metadata_allowed_cert(xml) allowedfingerprint = (easyspid.lib.easyspid.calcCertFingerprint( allowedCert, signingfingerprintalg))['result'] elif assertionName != 'EntityDescriptor' and fingerprint is None: allowedfingerprint = signingfingerprint if fingerprint is not None: allowedfingerprint = fingerprint signCheck = OneLogin_Saml2_Utils.validate_sign( xml, cert=signingcert, fingerprint=signingfingerprint, fingerprintalg=signingfingerprintalg, validatecert=False, debug=False, xpath=signatureNodeXpath, multicerts=None) if signCheck: result['signCheck'] = True # checktime validity certificate certTimeValdity = easyspid.lib.easyspid.timeValidateCert(signingcert) if certTimeValdity: result['certValidity'] = True # checktime certificate allow if allowedfingerprint != signingfingerprint: result['certAllowed'] = False return result