@@ -82,16 +82,19 @@ def is_valid(self, request_data, request_id=None):
8282 sp_data = self .__settings .get_sp_data ()
8383 sp_entity_id = sp_data ['entityId' ]
8484
85- sign_nodes = self .__query ('//ds:Signature' )
86-
87- signed_elements = []
88- for sign_node in sign_nodes :
89- signed_elements .append (sign_node .getparent ().tag )
85+ signed_elements = self .process_signed_elements ()
9086
9187 if self .__settings .is_strict ():
88+ no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd'
9289 res = OneLogin_Saml2_XML .validate_xml (self .document , 'saml-schema-protocol-2.0.xsd' , self .__settings .is_debug_active ())
9390 if isinstance (res , str ):
94- raise Exception ('Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' )
91+ raise Exception (no_valid_xml_msg )
92+
93+ # If encrypted, check also the decrypted document
94+ if self .encrypted :
95+ res = OneLogin_Saml2_XML .validate_xml (self .decrypted_document , 'saml-schema-protocol-2.0.xsd' , self .__settings .is_debug_active ())
96+ if isinstance (res , str ):
97+ raise Exception (no_valid_xml_msg )
9598
9699 security = self .__settings .get_security_data ()
97100 current_url = OneLogin_Saml2_Utils .get_self_url_no_query (request_data )
@@ -365,6 +368,53 @@ def validate_num_assertions(self):
365368 assertion_nodes = OneLogin_Saml2_XML .query (self .document , '//saml:Assertion' )
366369 return (len (encrypted_assertion_nodes ) + len (assertion_nodes )) == 1
367370
371+ def process_signed_elements (self ):
372+ """
373+ Verifies the signature nodes:
374+ - Checks that are Response or Assertion
375+ - Check that IDs and reference URI are unique and consistent.
376+
377+ :returns: The signed elements tag names
378+ :rtype: list
379+ """
380+ sign_nodes = self .__query ('//ds:Signature' )
381+
382+ signed_elements = []
383+ verified_seis = []
384+ verified_ids = []
385+ response_tag = '{%s}Response' % OneLogin_Saml2_Constants .NS_SAMLP
386+ assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants .NS_SAML
387+
388+ for sign_node in sign_nodes :
389+ signed_element = sign_node .getparent ().tag
390+ if signed_element != response_tag and signed_element != assertion_tag :
391+ raise Exception ('Invalid Signature Element %s SAML Response rejected' % signed_element )
392+
393+ if not sign_node .getparent ().get ('ID' ):
394+ raise Exception ('Signed Element must contain an ID. SAML Response rejected' )
395+
396+ id_value = sign_node .getparent ().get ('ID' )
397+ if id_value in verified_ids :
398+ raise Exception ('Duplicated ID. SAML Response rejected' )
399+ verified_ids .append (id_value )
400+
401+ # Check that reference URI matches the parent ID and no duplicate References or IDs
402+ ref = OneLogin_Saml2_XML .query (sign_node , './/ds:Reference' )
403+ if ref :
404+ ref = ref [0 ]
405+ if ref .get ('URI' ):
406+ sei = ref .get ('URI' )[1 :]
407+
408+ if sei != id_value :
409+ raise Exception ('Found an invalid Signed Element. SAML Response rejected' )
410+
411+ if sei in verified_seis :
412+ raise Exception ('Duplicated Reference URI. SAML Response rejected' )
413+ verified_seis .append (sei )
414+
415+ signed_elements .append (signed_element )
416+ return signed_elements
417+
368418 def validate_timestamps (self ):
369419 """
370420 Verifies that the document is valid according to Conditions Element
@@ -393,10 +443,8 @@ def __query_assertion(self, xpath_expr):
393443 :returns: The queried nodes
394444 :rtype: list
395445 """
396- if self .encrypted :
397- assertion_expr = '/saml:EncryptedAssertion/saml:Assertion'
398- else :
399- assertion_expr = '/saml:Assertion'
446+
447+ assertion_expr = '/saml:Assertion'
400448 signature_expr = '/ds:Signature/ds:SignedInfo/ds:Reference'
401449 signed_assertion_query = '/samlp:Response' + assertion_expr + signature_expr
402450 assertion_reference_nodes = self .__query (signed_assertion_query )
@@ -473,7 +521,8 @@ def __decrypt_assertion(self, xml):
473521 keyinfo .append (encrypted_key [0 ])
474522
475523 encrypted_data = encrypted_data_nodes [0 ]
476- OneLogin_Saml2_Utils .decrypt_element (encrypted_data , key , debug )
524+ decrypted = OneLogin_Saml2_Utils .decrypt_element (encrypted_data , key , debug )
525+ xml .replace (encrypted_assertion_nodes [0 ], decrypted )
477526 return xml
478527
479528 def get_error (self ):
0 commit comments