def validate(self, federation, metadata): """Verify the validity of the SAML federated metadata's signature. :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 verifying the validity of the metadata's signature belonging to {0}" .format(federation)) try: OneLogin_Saml2_Utils.validate_metadata_sign(metadata, federation.certificate, raise_exceptions=True) except Exception as exception: raise SAMLFederatedMetadataValidationError( six.ensure_text(str(exception)), exception) self._logger.info( "Finished verifying the validity of the metadata's signature belonging to {0}" .format(federation))
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 testSignMetadata(self): """ Tests the signMetadata method of the OneLogin_Saml2_Metadata """ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) sp_data = settings.get_sp_data() security = settings.get_security_data() metadata = OneLogin_Saml2_Metadata.builder( sp_data, security['authnRequestsSigned'], security['wantAssertionsSigned'] ) self.assertIsNotNone(metadata) cert_path = settings.get_cert_path() key = self.file_contents(join(cert_path, 'sp.key')) cert = self.file_contents(join(cert_path, 'sp.crt')) signed_metadata = OneLogin_Saml2_Metadata.sign_metadata(metadata, key, cert) self.assertTrue(OneLogin_Saml2_Utils.validate_metadata_sign(signed_metadata, cert)) self.assertIn('<md:SPSSODescriptor', signed_metadata) self.assertIn('entityID="http://stuff.com/endpoints/metadata.php"', signed_metadata) self.assertIn('ID="ONELOGIN_', signed_metadata) self.assertIn('AuthnRequestsSigned="false"', signed_metadata) self.assertIn('WantAssertionsSigned="false"', signed_metadata) self.assertIn('<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"', signed_metadata) self.assertIn('Location="http://stuff.com/endpoints/endpoints/acs.php"', signed_metadata) self.assertIn('<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"', signed_metadata) self.assertIn(' Location="http://stuff.com/endpoints/endpoints/sls.php"/>', signed_metadata) self.assertIn('<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>', signed_metadata) self.assertIn('<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>', signed_metadata) self.assertIn('<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>', signed_metadata) self.assertIn('<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>', signed_metadata) self.assertIn('<ds:Reference', signed_metadata) self.assertIn('<ds:KeyInfo><ds:X509Data>\n<ds:X509Certificate>', signed_metadata) with self.assertRaisesRegexp(Exception, 'Empty string supplied as input'): OneLogin_Saml2_Metadata.sign_metadata('', key, cert) signed_metadata_2 = OneLogin_Saml2_Metadata.sign_metadata(metadata, key, cert, OneLogin_Saml2_Constants.RSA_SHA256, OneLogin_Saml2_Constants.SHA384) self.assertTrue(OneLogin_Saml2_Utils.validate_metadata_sign(signed_metadata_2, cert)) self.assertIn('<md:SPSSODescriptor', signed_metadata_2) self.assertIn('entityID="http://stuff.com/endpoints/metadata.php"', signed_metadata_2) self.assertIn('ID="ONELOGIN_', signed_metadata_2) self.assertIn('AuthnRequestsSigned="false"', signed_metadata_2) self.assertIn('WantAssertionsSigned="false"', signed_metadata_2) self.assertIn('<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"', signed_metadata_2) self.assertIn('Location="http://stuff.com/endpoints/endpoints/acs.php"', signed_metadata_2) self.assertIn('<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"', signed_metadata_2) self.assertIn(' Location="http://stuff.com/endpoints/endpoints/sls.php"/>', signed_metadata_2) self.assertIn('<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>', signed_metadata_2) self.assertIn('<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>', signed_metadata_2) self.assertIn('<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#sha384"/>', signed_metadata_2) self.assertIn('<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>', signed_metadata_2) self.assertIn('<ds:Reference', signed_metadata_2) self.assertIn('<ds:KeyInfo><ds:X509Data>\n<ds:X509Certificate>', signed_metadata_2)
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 validate_metadata(self, xml, fingerprint=None, fingerprintalg='sha1', validatecert=False): """ Validates an XML SP Metadata. :param xml: Metadata's XML that will be validate :type xml: 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 :returns: a dictionary with the list of found validation errors and signature check :rtype: dict """ result = { 'schemaValidate': True, 'signCheck': False, 'error': 0, 'msg': '' } assert isinstance(xml, compat.text_types) if len(xml) == 0: raise Exception('Empty string supplied as input') #errors = {'validate':[], 'signCheck':0} root = OneLogin_Saml2_XML.validate_xml( xml, 'saml-schema-metadata-2.0.xsd', self._OneLogin_Saml2_Settings__debug) if isinstance(root, str): result['msg'] = root result['schemaValidate'] = False else: if root.tag != '{%s}EntityDescriptor' % OneLogin_Saml2_Constants.NS_MD: result['msg'] = 'noEntityDescriptor_xml' result['error'] = 1 result['schemaValidate'] = False #errors.append('noEntityDescriptor_xml') else: if (len( root.findall( './/md:SPSSODescriptor', namespaces=OneLogin_Saml2_Constants.NSMAP))) != 1: #errors.append('onlySPSSODescriptor_allowed_xml') result['msg'] = 'onlySPSSODescriptor_allowed_xml' result['error'] = 2 result['schemaValidate'] = False else: valid_until, cache_duration = root.get( 'validUntil'), root.get('cacheDuration') if valid_until: valid_until = OneLogin_Saml2_Utils.parse_SAML_to_time( valid_until) expire_time = OneLogin_Saml2_Utils.get_expire_time( cache_duration, valid_until) if expire_time is not None and int( time()) > int(expire_time): #errors.append('expired_xml') result['msg'] = 'expired_xml' result['error'] = 3 result['schemaValidate'] = False # Validate Sign signCheck = OneLogin_Saml2_Utils.validate_metadata_sign( xml, fingerprint=fingerprint, fingerprintalg=fingerprintalg, validatecert=validatecert) if signCheck: result['signCheck'] = True return result