55
66import base64
77import hashlib
8+ import itertools
89import logging
910import os
1011import ssl
@@ -591,10 +592,6 @@ def verify_redirect_signature(saml_msg, crypto, cert=None, sigkey=None):
591592 return bool (signer .verify (string , _sign , _key ))
592593
593594
594- LOG_LINE = 60 * '=' + '\n %s\n ' + 60 * '-' + '\n %s' + 60 * '='
595- LOG_LINE_2 = 60 * '=' + '\n %s\n %s\n ' + 60 * '-' + '\n %s' + 60 * '='
596-
597-
598595def make_str (txt ):
599596 if isinstance (txt , six .string_types ):
600597 return txt
@@ -682,10 +679,9 @@ def __init__(self, xmlsec_binary, **kwargs):
682679 CryptoBackend .__init__ (self , ** kwargs )
683680 assert (isinstance (xmlsec_binary , six .string_types ))
684681 self .xmlsec = xmlsec_binary
685- if os .environ .get ('PYSAML2_KEEP_XMLSEC_TMP' , None ):
686- self ._xmlsec_delete_tmpfiles = False
687- else :
688- self ._xmlsec_delete_tmpfiles = True
682+ self ._xmlsec_delete_tmpfiles = os .environ .get (
683+ 'PYSAML2_KEEP_XMLSEC_TMP' , False
684+ )
689685
690686 try :
691687 self .non_xml_crypto = RSACrypto (kwargs ['rsa_key' ])
@@ -727,11 +723,10 @@ def encrypt(self, text, recv_key, template, session_key_type, xpath=''):
727723 if xpath :
728724 com_list .extend (['--node-xpath' , xpath ])
729725
730- (_stdout , _stderr , output ) = self ._run_xmlsec (
731- com_list ,
732- [template ],
733- exception = DecryptError ,
734- validate_output = False )
726+ try :
727+ (_stdout , _stderr , output ) = self ._run_xmlsec (com_list , [template ])
728+ except XmlsecError as e :
729+ six .raise_from (EncryptError (com_list ), e )
735730
736731 return output
737732
@@ -753,8 +748,9 @@ def encrypt_assertion(self, statement, enc_key, template, key_type='des-192', no
753748 if isinstance (statement , SamlBase ):
754749 statement = pre_encrypt_assertion (statement )
755750
756- _ , fil = make_temp (_str (statement ), decode = False ,
757- delete = False )
751+ _ , fil = make_temp (
752+ _str (statement ), decode = False , delete = self ._xmlsec_delete_tmpfiles
753+ )
758754 _ , tmpl = make_temp (_str (template ), decode = False )
759755
760756 if not node_xpath :
@@ -772,15 +768,10 @@ def encrypt_assertion(self, statement, enc_key, template, key_type='des-192', no
772768 if node_id :
773769 com_list .extend (['--node-id' , node_id ])
774770
775- (_stdout , _stderr , output ) = self ._run_xmlsec (
776- com_list ,
777- [tmpl ],
778- exception = EncryptError ,
779- validate_output = False )
780-
781- os .unlink (fil )
782- if not output :
783- raise EncryptError (_stderr )
771+ try :
772+ (_stdout , _stderr , output ) = self ._run_xmlsec (com_list , [tmpl ])
773+ except XmlsecError as e :
774+ six .raise_from (EncryptError (com_list ), e )
784775
785776 return output .decode ('utf-8' )
786777
@@ -803,11 +794,11 @@ def decrypt(self, enctext, key_file, id_attr):
803794 ENC_KEY_CLASS ,
804795 ]
805796
806- ( _stdout , _stderr , output ) = self . _run_xmlsec (
807- com_list ,
808- [ fil ],
809- exception = DecryptError ,
810- validate_output = False )
797+ try :
798+ ( _stdout , _stderr , output ) = self . _run_xmlsec ( com_list , [ fil ])
799+ except XmlsecError as e :
800+ six . raise_from ( DecryptError ( com_list ), e )
801+
811802 return output .decode ('utf-8' )
812803
813804 def sign_statement (self , statement , node_name , key_file , node_id , id_attr ):
@@ -826,10 +817,11 @@ def sign_statement(self, statement, node_name, key_file, node_id, id_attr):
826817 statement = str (statement )
827818
828819 _ , fil = make_temp (
829- statement ,
830- suffix = '.xml' ,
831- decode = False ,
832- delete = self ._xmlsec_delete_tmpfiles )
820+ statement ,
821+ suffix = '.xml' ,
822+ decode = False ,
823+ delete = self ._xmlsec_delete_tmpfiles ,
824+ )
833825
834826 com_list = [
835827 self .xmlsec ,
@@ -843,20 +835,16 @@ def sign_statement(self, statement, node_name, key_file, node_id, id_attr):
843835 com_list .extend (['--node-id' , node_id ])
844836
845837 try :
846- (stdout , stderr , signed_statement ) = self ._run_xmlsec (
847- com_list ,
848- [fil ],
849- validate_output = False )
838+ (stdout , stderr , output ) = self ._run_xmlsec (com_list , [fil ])
839+ except XmlsecError as e :
840+ raise SignatureError (com_list )
850841
851- # this doesn't work if --store-signatures are used
852- if stdout == '' :
853- if signed_statement :
854- return signed_statement .decode ('utf-8' )
855-
856- logger .error ('Signing operation failed :\n stdout : %s\n stderr : %s' , stdout , stderr )
857- raise SigverError (stderr )
858- except DecryptError :
859- raise SigverError ('Signing failed' )
842+ # this does not work if --store-signatures is used
843+ if output :
844+ return output .decode ("utf-8" )
845+ if stdout :
846+ return stdout .decode ("utf-8" )
847+ raise SignatureError (stderr )
860848
861849 def validate_signature (self , signedtext , cert_file , cert_type , node_name , node_id , id_attr ):
862850 """
@@ -867,17 +855,19 @@ def validate_signature(self, signedtext, cert_file, cert_type, node_name, node_i
867855 :param cert_type: The file type of the certificate
868856 :param node_name: The name of the class that is signed
869857 :param node_id: The identifier of the node
870- :param id_attr: Should normally be one of 'id', 'Id' or 'ID'
858+ :param id_attr: The attribute name for the identifier, normally one of
859+ 'id','Id' or 'ID'
871860 :return: Boolean True if the signature was correct otherwise False.
872861 """
873862 if not isinstance (signedtext , six .binary_type ):
874863 signedtext = signedtext .encode ('utf-8' )
875864
876865 _ , fil = make_temp (
877- signedtext ,
878- suffix = '.xml' ,
879- decode = False ,
880- delete = self ._xmlsec_delete_tmpfiles )
866+ signedtext ,
867+ suffix = '.xml' ,
868+ decode = False ,
869+ delete = self ._xmlsec_delete_tmpfiles ,
870+ )
881871
882872 com_list = [
883873 self .xmlsec ,
@@ -891,21 +881,19 @@ def validate_signature(self, signedtext, cert_file, cert_type, node_name, node_i
891881 if node_id :
892882 com_list .extend (['--node-id' , node_id ])
893883
894- ( _stdout , stderr , _output ) = self . _run_xmlsec (
895- com_list ,
896- [ fil ],
897- exception = SignatureError )
884+ try :
885+ ( _stdout , stderr , _output ) = self . _run_xmlsec ( com_list , [ fil ])
886+ except XmlsecError as e :
887+ six . raise_from ( SignatureError ( com_list ), e )
898888
899889 return parse_xmlsec_output (stderr )
900890
901- def _run_xmlsec (self , com_list , extra_args , validate_output = True , exception = XmlsecError ):
891+ def _run_xmlsec (self , com_list , extra_args ):
902892 """
903893 Common code to invoke xmlsec and parse the output.
904894 :param com_list: Key-value parameter list for xmlsec
905895 :param extra_args: Positional parameters to be appended after all
906896 key-value parameters
907- :param validate_output: Parse and validate the output
908- :param exception: The exception class to raise on errors
909897 :result: Whatever xmlsec wrote to an --output temporary file
910898 """
911899 with NamedTemporaryFile (suffix = '.xml' , delete = self ._xmlsec_delete_tmpfiles ) as ntf :
@@ -919,17 +907,12 @@ def _run_xmlsec(self, com_list, extra_args, validate_output=True, exception=Xmls
919907 p_out = p_out .decode ()
920908 p_err = p_err .decode ()
921909
922- if pof .returncode is not None and pof .returncode < 0 :
923- logger .error (LOG_LINE , p_out , p_err )
924- raise XmlsecError ('{err_code}:{err_msg}' .format (
925- err_code = pof .returncode , err_msg = p_err ))
926-
927- try :
928- if validate_output :
929- parse_xmlsec_output (p_err )
930- except XmlsecError as exc :
931- logger .error (LOG_LINE_2 , p_out , p_err , exc )
932- raise
910+ if pof .returncode != 0 :
911+ errmsg = "returncode={code}\n error={err}\n output={out}" .format (
912+ code = pof .returncode , err = p_err , out = p_out
913+ )
914+ logger .error (errmsg )
915+ raise XmlsecError (errmsg )
933916
934917 ntf .seek (0 )
935918 return p_out , p_err , ntf .read ()
@@ -1366,55 +1349,60 @@ def decrypt_keys(self, enctext, keys=None, id_attr=''):
13661349 """ Decrypting an encrypted text by the use of a private key.
13671350
13681351 :param enctext: The encrypted text as a string
1352+ :param keys: Keys to try to decrypt enctext with
1353+ :param id_attr: The attribute name for the identifier, normally one of
1354+ 'id','Id' or 'ID'
13691355 :return: The decrypted text
13701356 """
1371- _enctext = None
1372-
1373- if not id_attr :
1374- id_attr = self .id_attr
1357+ key_files = []
13751358
13761359 if not isinstance (keys , list ):
13771360 keys = [keys ]
13781361
1379- if self .enc_key_files is not None :
1380- for _enc_key_file in self .enc_key_files :
1381- _enctext = self .crypto .decrypt (enctext , _enc_key_file , id_attr )
1382- if _enctext is not None and len (_enctext ) > 0 :
1383- return _enctext
1384-
1385- for _key in keys :
1386- if _key is not None and len (_key .strip ()) > 0 :
1387- if not isinstance (_key , six .binary_type ):
1388- _key = str (_key ).encode ('ascii' )
1389- _ , key_file = make_temp (_key , decode = False )
1390- _enctext = self .crypto .decrypt (enctext , key_file , id_attr )
1391- if _enctext is not None and len (_enctext ) > 0 :
1392- return _enctext
1362+ keys = [key for key in keys if key ]
1363+ for key in keys :
1364+ if not isinstance (key , six .binary_type ):
1365+ key = key .encode ("ascii" )
1366+ _ , key_file = make_temp (key , decode = False , delete = False )
1367+ key_files .append (key_file )
13931368
1394- return enctext
1369+ try :
1370+ dectext = self .decrypt (enctext , key_file = key_files , id_attr = id_attr )
1371+ except DecryptError as e :
1372+ raise
1373+ else :
1374+ return dectext
1375+ finally :
1376+ for key_file in key_files :
1377+ os .unlink (key_file )
13951378
13961379 def decrypt (self , enctext , key_file = None , id_attr = '' ):
13971380 """ Decrypting an encrypted text by the use of a private key.
13981381
13991382 :param enctext: The encrypted text as a string
14001383 :return: The decrypted text
14011384 """
1402- _enctext = None
1403-
14041385 if not id_attr :
14051386 id_attr = self .id_attr
14061387
1407- if self .enc_key_files is not None :
1408- for _enc_key_file in self .enc_key_files :
1409- _enctext = self .crypto .decrypt (enctext , _enc_key_file , id_attr )
1410- if _enctext is not None and len (_enctext ) > 0 :
1411- return _enctext
1388+ if not isinstance (key_file , list ):
1389+ key_file = [key_file ]
1390+
1391+ key_files = [
1392+ key for key in itertools .chain (key_file , self .enc_key_files ) if key
1393+ ]
1394+ for key_file in key_files :
1395+ try :
1396+ dectext = self .crypto .decrypt (enctext , key_file , id_attr )
1397+ except XmlsecError as e :
1398+ continue
1399+ else :
1400+ if dectext :
1401+ return dectext
14121402
1413- if key_file is not None and len (key_file .strip ()) > 0 :
1414- _enctext = self .crypto .decrypt (enctext , key_file , id_attr )
1415- if _enctext is not None and len (_enctext ) > 0 :
1416- return _enctext
1417- return enctext
1403+ errmsg = "No key was able to decrypt the ciphertext. Keys tried: {keys}"
1404+ errmsg = errmsg .format (keys = key_files )
1405+ raise DecryptError (errmsg )
14181406
14191407 def verify_signature (self , signedtext , cert_file = None , cert_type = 'pem' , node_name = NODE_NAME , node_id = None , id_attr = '' ):
14201408 """ Verifies the signature of a XML document.
@@ -1424,7 +1412,8 @@ def verify_signature(self, signedtext, cert_file=None, cert_type='pem', node_nam
14241412 :param cert_type: The file type of the certificate
14251413 :param node_name: The name of the class that is signed
14261414 :param node_id: The identifier of the node
1427- :param id_attr: Should normally be one of 'id', 'Id' or 'ID'
1415+ :param id_attr: The attribute name for the identifier, normally one of
1416+ 'id','Id' or 'ID'
14281417 :return: Boolean True if the signature was correct otherwise False.
14291418 """
14301419 # This is only for testing purposes, otherwise when would you receive
@@ -1466,11 +1455,14 @@ def _check_signature(self, decoded_xml, item, node_name=NODE_NAME, origdoc=None,
14661455
14671456 for cert in _certs :
14681457 if isinstance (cert , six .string_types ):
1469- certs .append (make_temp (
1470- pem_format (cert ),
1471- suffix = '.pem' ,
1472- decode = False ,
1473- delete = self ._xmlsec_delete_tmpfiles ))
1458+ certs .append (
1459+ make_temp (
1460+ pem_format (cert ),
1461+ suffix = '.pem' ,
1462+ decode = False ,
1463+ delete = self ._xmlsec_delete_tmpfiles ,
1464+ )
1465+ )
14741466 else :
14751467 certs .append (cert )
14761468 else :
@@ -1483,7 +1475,8 @@ def _check_signature(self, decoded_xml, item, node_name=NODE_NAME, origdoc=None,
14831475 pem_format (cert ),
14841476 suffix = '.pem' ,
14851477 decode = False ,
1486- delete = self ._xmlsec_delete_tmpfiles )
1478+ delete = self ._xmlsec_delete_tmpfiles ,
1479+ )
14871480 for cert in cert_from_instance (item )
14881481 ]
14891482 else :
@@ -1527,7 +1520,8 @@ def check_signature(self, item, node_name=NODE_NAME, origdoc=None, id_attr='', m
15271520 :param item: Parsed entity
15281521 :param node_name: The name of the node/class/element that is signed
15291522 :param origdoc: The original XML string
1530- :param id_attr:
1523+ :param id_attr: The attribute name for the identifier, normally one of
1524+ 'id','Id' or 'ID'
15311525 :param must:
15321526 :return:
15331527 """
0 commit comments