1616from isodate import parse_duration as duration_parser
1717import re
1818from textwrap import wrap
19+ from functools import wraps
1920from uuid import uuid4
2021
2122import zlib
3334 from urllib import quote_plus # py2
3435
3536
37+ def return_false_on_exception (func ):
38+ """
39+ Decorator. When applied to a function, it will, by default, suppress any exceptions
40+ raised by that function and return False. It may be overridden by passing a
41+ "raise_exceptions" keyword argument when calling the wrapped function.
42+ """
43+ @wraps (func )
44+ def exceptfalse (* args , ** kwargs ):
45+ if not kwargs .pop ('raise_exceptions' , False ):
46+ try :
47+ return func (* args , ** kwargs )
48+ except Exception :
49+ return False
50+ else :
51+ return func (* args , ** kwargs )
52+ return exceptfalse
53+
54+
3655class OneLogin_Saml2_Utils (object ):
3756 """
3857
@@ -719,6 +738,7 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant
719738 return OneLogin_Saml2_XML .to_string (elem )
720739
721740 @staticmethod
741+ @return_false_on_exception
722742 def validate_sign (xml , cert = None , fingerprint = None , fingerprintalg = 'sha1' , validatecert = False , debug = False , xpath = None ):
723743 """
724744 Validates a signature (Message or Assertion).
@@ -743,36 +763,34 @@ def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', valid
743763
744764 :param xpath: The xpath of the signed element
745765 :type: string
746- """
747- try :
748- if xml is None or xml == '' :
749- raise Exception ('Empty string supplied as input' )
750-
751- elem = OneLogin_Saml2_XML .to_etree (xml )
752- xmlsec .enable_debug_trace (debug )
753- xmlsec .tree .add_ids (elem , ["ID" ])
754766
755- if xpath :
756- signature_nodes = OneLogin_Saml2_XML .query (elem , xpath )
757- else :
758- signature_nodes = OneLogin_Saml2_XML .query (elem , OneLogin_Saml2_Utils .RESPONSE_SIGNATURE_XPATH )
767+ :param raise_exceptions: Whether to return false on failure or raise an exception
768+ :type raise_exceptions: Boolean
769+ """
770+ if xml is None or xml == '' :
771+ raise Exception ('Empty string supplied as input' )
759772
760- if len (signature_nodes ) == 0 :
761- signature_nodes = OneLogin_Saml2_XML .query (elem , OneLogin_Saml2_Utils .ASSERTION_SIGNATURE_XPATH )
773+ elem = OneLogin_Saml2_XML .to_etree (xml )
774+ xmlsec .enable_debug_trace (debug )
775+ xmlsec .tree .add_ids (elem , ["ID" ])
762776
763- if len (signature_nodes ) == 1 :
764- signature_node = signature_nodes [0 ]
777+ if xpath :
778+ signature_nodes = OneLogin_Saml2_XML .query (elem , xpath )
779+ else :
780+ signature_nodes = OneLogin_Saml2_XML .query (elem , OneLogin_Saml2_Utils .RESPONSE_SIGNATURE_XPATH )
765781
766- return OneLogin_Saml2_Utils .validate_node_sign (signature_node , elem , cert , fingerprint , fingerprintalg , validatecert , debug )
767- else :
768- return False
769- except xmlsec .Error as e :
770- if debug :
771- print (e )
782+ if len (signature_nodes ) == 0 :
783+ signature_nodes = OneLogin_Saml2_XML .query (elem , OneLogin_Saml2_Utils .ASSERTION_SIGNATURE_XPATH )
772784
773- return False
785+ if len (signature_nodes ) == 1 :
786+ signature_node = signature_nodes [0 ]
787+ # Raises expection if invalid
788+ return OneLogin_Saml2_Utils .validate_node_sign (signature_node , elem , cert , fingerprint , fingerprintalg , validatecert , debug , raise_exceptions = True )
789+ else :
790+ raise Exception ('Expected exactly one signature node; got {}.' .format (len (signature_nodes )))
774791
775792 @staticmethod
793+ @return_false_on_exception
776794 def validate_metadata_sign (xml , cert = None , fingerprint = None , fingerprintalg = 'sha1' , validatecert = False , debug = False ):
777795 """
778796 Validates a signature of a EntityDescriptor.
@@ -794,35 +812,36 @@ def validate_metadata_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha
794812
795813 :param debug: Activate the xmlsec debug
796814 :type: bool
815+
816+ :param raise_exceptions: Whether to return false on failure or raise an exception
817+ :type raise_exceptions: Boolean
797818 """
798- try :
799- if xml is None or xml == '' :
800- raise Exception ('Empty string supplied as input' )
819+ if xml is None or xml == '' :
820+ raise Exception ('Empty string supplied as input' )
801821
802- elem = OneLogin_Saml2_XML .to_etree (xml )
803- xmlsec .enable_debug_trace (debug )
804- xmlsec .tree .add_ids (elem , ["ID" ])
822+ elem = OneLogin_Saml2_XML .to_etree (xml )
823+ xmlsec .enable_debug_trace (debug )
824+ xmlsec .tree .add_ids (elem , ["ID" ])
805825
806- signature_nodes = OneLogin_Saml2_XML .query (elem , '/md:EntitiesDescriptor/ds:Signature' )
826+ signature_nodes = OneLogin_Saml2_XML .query (elem , '/md:EntitiesDescriptor/ds:Signature' )
807827
808- if len (signature_nodes ) == 0 :
809- signature_nodes += OneLogin_Saml2_XML .query (elem , '/md:EntityDescriptor/ds:Signature' )
828+ if len (signature_nodes ) == 0 :
829+ signature_nodes += OneLogin_Saml2_XML .query (elem , '/md:EntityDescriptor/ds:Signature' )
810830
811- if len (signature_nodes ) == 0 :
812- signature_nodes += OneLogin_Saml2_XML .query (elem , '/md:EntityDescriptor/md:SPSSODescriptor/ds:Signature' )
813- signature_nodes += OneLogin_Saml2_XML .query (elem , '/md:EntityDescriptor/md:IDPSSODescriptor/ds:Signature' )
831+ if len (signature_nodes ) == 0 :
832+ signature_nodes += OneLogin_Saml2_XML .query (elem , '/md:EntityDescriptor/md:SPSSODescriptor/ds:Signature' )
833+ signature_nodes += OneLogin_Saml2_XML .query (elem , '/md:EntityDescriptor/md:IDPSSODescriptor/ds:Signature' )
814834
815- if len (signature_nodes ) > 0 :
816- for signature_node in signature_nodes :
817- if not OneLogin_Saml2_Utils .validate_node_sign (signature_node , elem , cert , fingerprint , fingerprintalg , validatecert , debug ):
818- return False
819- return True
820- else :
821- return False
822- except Exception :
823- return False
835+ if len (signature_nodes ) > 0 :
836+ for signature_node in signature_nodes :
837+ # Raises expection if invalid
838+ OneLogin_Saml2_Utils .validate_node_sign (signature_node , elem , cert , fingerprint , fingerprintalg , validatecert , debug , raise_exceptions = True )
839+ return True
840+ else :
841+ raise Exception ('Could not validate metadata signature: No signature nodes found.' )
824842
825843 @staticmethod
844+ @return_false_on_exception
826845 def validate_node_sign (signature_node , elem , cert = None , fingerprint = None , fingerprintalg = 'sha1' , validatecert = False , debug = False ):
827846 """
828847 Validates a signature node.
@@ -847,40 +866,42 @@ def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, finger
847866
848867 :param debug: Activate the xmlsec debug
849868 :type: bool
869+
870+ :param raise_exceptions: Whether to return false on failure or raise an exception
871+ :type raise_exceptions: Boolean
850872 """
851- try :
852- if (cert is None or cert == '' ) and fingerprint :
853- x509_certificate_nodes = OneLogin_Saml2_XML .query (signature_node , '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate' )
854- if len (x509_certificate_nodes ) > 0 :
855- x509_certificate_node = x509_certificate_nodes [0 ]
856- x509_cert_value = x509_certificate_node .text
857- x509_fingerprint_value = OneLogin_Saml2_Utils .calculate_x509_fingerprint (x509_cert_value , fingerprintalg )
858- if fingerprint == x509_fingerprint_value :
859- cert = OneLogin_Saml2_Utils .format_cert (x509_cert_value )
860-
861- if cert is None or cert == '' :
862- return False
873+ if (cert is None or cert == '' ) and fingerprint :
874+ x509_certificate_nodes = OneLogin_Saml2_XML .query (signature_node , '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate' )
875+ if len (x509_certificate_nodes ) > 0 :
876+ x509_certificate_node = x509_certificate_nodes [0 ]
877+ x509_cert_value = x509_certificate_node .text
878+ x509_fingerprint_value = OneLogin_Saml2_Utils .calculate_x509_fingerprint (x509_cert_value , fingerprintalg )
879+ if fingerprint == x509_fingerprint_value :
880+ cert = OneLogin_Saml2_Utils .format_cert (x509_cert_value )
863881
864- # Check if Reference URI is empty
865- reference_elem = OneLogin_Saml2_XML .query (signature_node , '//ds:Reference' )
866- if len (reference_elem ) > 0 :
867- if reference_elem [0 ].get ('URI' ) == '' :
868- reference_elem [0 ].set ('URI' , '#%s' % signature_node .getparent ().get ('ID' ))
882+ if cert is None or cert == '' :
883+ raise Exception ('Could not validate node signature: No certificate provided.' )
869884
870- if validatecert :
871- manager = xmlsec .KeysManager ()
872- manager .load_cert_from_memory (cert , xmlsec .KeyFormat .CERT_PEM , xmlsec .KeyDataType .TRUSTED )
873- dsig_ctx = xmlsec .SignatureContext (manager )
874- else :
875- dsig_ctx = xmlsec .SignatureContext ()
876- dsig_ctx .key = xmlsec .Key .from_memory (cert , xmlsec .KeyFormat .CERT_PEM , None )
885+ # Check if Reference URI is empty
886+ reference_elem = OneLogin_Saml2_XML .query (signature_node , '//ds:Reference' )
887+ if len (reference_elem ) > 0 :
888+ if reference_elem [0 ].get ('URI' ) == '' :
889+ reference_elem [0 ].set ('URI' , '#%s' % signature_node .getparent ().get ('ID' ))
890+
891+ if validatecert :
892+ manager = xmlsec .KeysManager ()
893+ manager .load_cert_from_memory (cert , xmlsec .KeyFormat .CERT_PEM , xmlsec .KeyDataType .TRUSTED )
894+ dsig_ctx = xmlsec .SignatureContext (manager )
895+ else :
896+ dsig_ctx = xmlsec .SignatureContext ()
897+ dsig_ctx .key = xmlsec .Key .from_memory (cert , xmlsec .KeyFormat .CERT_PEM , None )
877898
878- dsig_ctx .set_enabled_key_data ([xmlsec .KeyData .X509 ])
899+ dsig_ctx .set_enabled_key_data ([xmlsec .KeyData .X509 ])
900+ try :
879901 dsig_ctx .verify (signature_node )
880- return True
881- except xmlsec .Error as e :
882- if debug :
883- print (e )
902+ except Exception :
903+ raise Exception ('Signature validation failed. SAML Response rejected' )
904+ return True
884905
885906 @staticmethod
886907 def sign_binary (msg , key , algorithm = xmlsec .Transform .RSA_SHA1 , debug = False ):
0 commit comments