@@ -86,16 +86,19 @@ def is_valid(self, request_data, request_id=None):
8686 sp_data = self .__settings .get_sp_data ()
8787 sp_entity_id = sp_data .get ('entityId' , '' )
8888
89- sign_nodes = self .__query ('//ds:Signature' )
90-
91- signed_elements = []
92- for sign_node in sign_nodes :
93- signed_elements .append (sign_node .getparent ().tag )
89+ signed_elements = self .process_signed_elements ()
9490
9591 if self .__settings .is_strict ():
92+ no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd'
9693 res = OneLogin_Saml2_Utils .validate_xml (etree .tostring (self .document ), 'saml-schema-protocol-2.0.xsd' , self .__settings .is_debug_active ())
9794 if not isinstance (res , Document ):
98- raise Exception ('Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' )
95+ raise Exception (no_valid_xml_msg )
96+
97+ # If encrypted, check also the decrypted document
98+ if self .encrypted :
99+ res = OneLogin_Saml2_Utils .validate_xml (etree .tostring (self .decrypted_document ), 'saml-schema-protocol-2.0.xsd' , self .__settings .is_debug_active ())
100+ if not isinstance (res , Document ):
101+ raise Exception (no_valid_xml_msg )
99102
100103 security = self .__settings .get_security_data ()
101104 current_url = OneLogin_Saml2_Utils .get_self_url_no_query (request_data )
@@ -385,6 +388,53 @@ def validate_num_assertions(self):
385388 assertion_nodes = OneLogin_Saml2_Utils .query (self .document , '//saml:Assertion' )
386389 return (len (encrypted_assertion_nodes ) + len (assertion_nodes )) == 1
387390
391+ def process_signed_elements (self ):
392+ """
393+ Verifies the signature nodes:
394+ - Checks that are Response or Assertion
395+ - Check that IDs and reference URI are unique and consistent.
396+
397+ :returns: The signed elements tag names
398+ :rtype: list
399+ """
400+ sign_nodes = self .__query ('//ds:Signature' )
401+
402+ signed_elements = []
403+ verified_seis = []
404+ verified_ids = []
405+ response_tag = '{%s}Response' % OneLogin_Saml2_Constants .NS_SAMLP
406+ assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants .NS_SAML
407+
408+ for sign_node in sign_nodes :
409+ signed_element = sign_node .getparent ().tag
410+ if signed_element != response_tag and signed_element != assertion_tag :
411+ raise Exception ('Invalid Signature Element %s SAML Response rejected' % signed_element )
412+
413+ if not sign_node .getparent ().get ('ID' ):
414+ raise Exception ('Signed Element must contain an ID. SAML Response rejected' )
415+
416+ id_value = sign_node .getparent ().get ('ID' )
417+ if id_value in verified_ids :
418+ raise Exception ('Duplicated ID. SAML Response rejected' )
419+ verified_ids .append (id_value )
420+
421+ # Check that reference URI matches the parent ID and no duplicate References or IDs
422+ ref = OneLogin_Saml2_Utils .query (sign_node , './/ds:Reference' )
423+ if ref :
424+ ref = ref [0 ]
425+ if ref .get ('URI' ):
426+ sei = ref .get ('URI' )[1 :]
427+
428+ if sei != id_value :
429+ raise Exception ('Found an invalid Signed Element. SAML Response rejected' )
430+
431+ if sei in verified_seis :
432+ raise Exception ('Duplicated Reference URI. SAML Response rejected' )
433+ verified_seis .append (sei )
434+
435+ signed_elements .append (signed_element )
436+ return signed_elements
437+
388438 def validate_timestamps (self ):
389439 """
390440 Verifies that the document is valid according to Conditions Element
@@ -413,10 +463,7 @@ def __query_assertion(self, xpath_expr):
413463 :returns: The queried nodes
414464 :rtype: list
415465 """
416- if self .encrypted :
417- assertion_expr = '/saml:EncryptedAssertion/saml:Assertion'
418- else :
419- assertion_expr = '/saml:Assertion'
466+ assertion_expr = '/saml:Assertion'
420467 signature_expr = '/ds:Signature/ds:SignedInfo/ds:Reference'
421468 signed_assertion_query = '/samlp:Response' + assertion_expr + signature_expr
422469 assertion_reference_nodes = self .__query (signed_assertion_query )
@@ -493,7 +540,8 @@ def __decrypt_assertion(self, dom):
493540 keyinfo .append (encrypted_key [0 ])
494541
495542 encrypted_data = encrypted_data_nodes [0 ]
496- OneLogin_Saml2_Utils .decrypt_element (encrypted_data , key , debug )
543+ decrypted = OneLogin_Saml2_Utils .decrypt_element (encrypted_data , key , debug )
544+ dom .replace (encrypted_assertion_nodes [0 ], decrypted )
497545 return dom
498546
499547 def get_error (self ):
0 commit comments