@@ -88,6 +88,9 @@ def is_valid(self, request_data, request_id=None):
8888
8989 signed_elements = self .process_signed_elements ()
9090
91+ has_signed_response = '{%s}Response' % OneLogin_Saml2_Constants .NS_SAMLP in signed_elements
92+ has_signed_assertion = '{%s}Assertion' % OneLogin_Saml2_Constants .NS_SAML in signed_elements
93+
9194 if self .__settings .is_strict ():
9295 no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd'
9396 res = OneLogin_Saml2_Utils .validate_xml (etree .tostring (self .document ), 'saml-schema-protocol-2.0.xsd' , self .__settings .is_debug_active ())
@@ -200,32 +203,26 @@ def is_valid(self, request_data, request_id=None):
200203 if not any_subject_confirmation :
201204 raise Exception ('A valid SubjectConfirmation was not found on this Response' )
202205
203- if security .get ('wantAssertionsSigned' , False ) and ( '{%s}Assertion' % OneLogin_Saml2_Constants . NS_SAML ) not in signed_elements :
206+ if security .get ('wantAssertionsSigned' , False ) and not has_signed_assertion :
204207 raise Exception ('The Assertion of the Response is not signed and the SP require it' )
205208
206- if security .get ('wantMessagesSigned' , False ) and ( '{%s}Response' % OneLogin_Saml2_Constants . NS_SAMLP ) not in signed_elements :
209+ if security .get ('wantMessagesSigned' , False ) and not has_signed_response :
207210 raise Exception ('The Message of the Response is not signed and the SP require it' )
208211
209- if len ( signed_elements ) > 0 :
210- if len ( signed_elements ) > 2 :
211- raise Exception ( 'Too many Signatures found. SAML Response rejected' )
212+ if not signed_elements or ( not has_signed_response and not has_signed_assertion ) :
213+ raise Exception ( 'No Signature found. SAML Response rejected' )
214+ else :
212215 cert = idp_data .get ('x509cert' , None )
213216 fingerprint = idp_data .get ('certFingerprint' , None )
214217 fingerprintalg = idp_data .get ('certFingerprintAlgorithm' , None )
215218
216219 # If find a Signature on the Response, validates it checking the original response
217- if '{%s}Response' % OneLogin_Saml2_Constants .NS_SAMLP in signed_elements :
218- document_to_validate = self .document
219- # Otherwise validates the assertion (decrypted assertion if was encrypted)
220- else :
221- if self .encrypted :
222- document_to_validate = self .decrypted_document
223- else :
224- document_to_validate = self .document
225- if not OneLogin_Saml2_Utils .validate_sign (document_to_validate , cert , fingerprint , fingerprintalg ):
220+ if has_signed_response and not OneLogin_Saml2_Utils .validate_sign (self .document , cert , fingerprint , fingerprintalg , xpath = OneLogin_Saml2_Utils .RESPONSE_SIGNATURE_XPATH ):
221+ raise Exception ('Signature validation failed. SAML Response rejected' )
222+
223+ document_check_assertion = self .decrypted_document if self .encrypted else self .document
224+ if has_signed_assertion and not OneLogin_Saml2_Utils .validate_sign (document_check_assertion , cert , fingerprint , fingerprintalg , xpath = OneLogin_Saml2_Utils .ASSERTION_SIGNATURE_XPATH ):
226225 raise Exception ('Signature validation failed. SAML Response rejected' )
227- else :
228- raise Exception ('No Signature found. SAML Response rejected' )
229226
230227 return True
231228 except Exception as err :
@@ -291,7 +288,7 @@ def get_issuers(self):
291288 """
292289 issuers = []
293290
294- message_issuer_nodes = self . __query ( '/samlp:Response/saml:Issuer' )
291+ message_issuer_nodes = OneLogin_Saml2_Utils . query ( self . document , '/samlp:Response/saml:Issuer' )
295292 if len (message_issuer_nodes ) == 1 :
296293 issuers .append (message_issuer_nodes [0 ].text )
297294 else :
@@ -486,8 +483,41 @@ def process_signed_elements(self):
486483 verified_seis .append (sei )
487484
488485 signed_elements .append (signed_element )
486+
487+ if signed_elements :
488+ if not self .validate_signed_elements (signed_elements ):
489+ raise Exception ('Found an unexpected Signature Element. SAML Response rejected' )
489490 return signed_elements
490491
492+ def validate_signed_elements (self , signed_elements ):
493+ """
494+ Verifies that the document has the expected signed nodes.
495+ """
496+ if len (signed_elements ) > 2 :
497+ return False
498+
499+ response_tag = '{%s}Response' % OneLogin_Saml2_Constants .NS_SAMLP
500+ assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants .NS_SAML
501+
502+ if (response_tag in signed_elements and signed_elements .count (response_tag ) > 1 ) or \
503+ (assertion_tag in signed_elements and signed_elements .count (assertion_tag ) > 1 ) or \
504+ (response_tag not in signed_elements and assertion_tag not in signed_elements ):
505+ return False
506+
507+ # Check that the signed elements found here, are the ones that will be verified
508+ # by OneLogin_Saml2_Utils.validate_sign
509+ if response_tag in signed_elements :
510+ expected_signature_nodes = OneLogin_Saml2_Utils .query (self .document , OneLogin_Saml2_Utils .RESPONSE_SIGNATURE_XPATH )
511+ if len (expected_signature_nodes ) != 1 :
512+ raise Exception ('Unexpected number of Response signatures found. SAML Response rejected.' )
513+
514+ if assertion_tag in signed_elements :
515+ expected_signature_nodes = self .__query (OneLogin_Saml2_Utils .ASSERTION_SIGNATURE_XPATH )
516+ if len (expected_signature_nodes ) != 1 :
517+ raise Exception ('Unexpected number of Assertion signatures found. SAML Response rejected.' )
518+
519+ return True
520+
491521 def validate_timestamps (self ):
492522 """
493523 Verifies that the document is valid according to Conditions Element
0 commit comments