40
40
from saml2 .time_util import utc_now
41
41
from saml2 .time_util import str_to_time
42
42
43
- from tempfile import NamedTemporaryFile
43
+ from tempfile import NamedTemporaryFile , mkdtemp
44
44
from subprocess import Popen , PIPE
45
45
46
46
from xmldsig import SIG_RSA_SHA1
@@ -563,6 +563,24 @@ def parse_xmlsec_output(output):
563
563
def sha1_digest (msg ):
564
564
return hashlib .sha1 (msg ).digest ()
565
565
566
+ # --------------------------------------------------------------------------
567
+
568
+ class NamedPipe (object ):
569
+ def __init__ (self ):
570
+ self ._tempdir = mkdtemp ()
571
+ self .name = os .path .join (self ._tempdir , 'fifo' )
572
+
573
+ try :
574
+ os .mkfifo (self .name )
575
+ except :
576
+ os .rmdir (self ._tempdir )
577
+
578
+ def close (self ):
579
+ os .remove (self .name )
580
+ os .rmdir (self ._tempdir )
581
+
582
+ # --------------------------------------------------------------------------
583
+
566
584
567
585
class Signer (object ):
568
586
"""Abstract base class for signing algorithms."""
@@ -699,7 +717,7 @@ def encrypt_assertion(self, statement, enc_key, template, key_type,
699
717
node_xpath ):
700
718
raise NotImplementedError ()
701
719
702
- def decrypt (self , enctext , key_file ):
720
+ def decrypt (self , enctext , key_file , passphrase = None ):
703
721
raise NotImplementedError ()
704
722
705
723
def sign_statement (self , statement , node_name , key_file , node_id ,
@@ -799,7 +817,7 @@ def encrypt_assertion(self, statement, enc_key, template,
799
817
800
818
return output
801
819
802
- def decrypt (self , enctext , key_file ):
820
+ def decrypt (self , enctext , key_file , passphrase = None ):
803
821
"""
804
822
805
823
:param enctext: XML document containing an encrypted part
@@ -810,16 +828,19 @@ def decrypt(self, enctext, key_file):
810
828
logger .debug ("Decrypt input len: %d" % len (enctext ))
811
829
_ , fil = make_temp ("%s" % enctext , decode = False )
812
830
813
- com_list = [self .xmlsec , "--decrypt" , "--privkey-pem" ,
814
- key_file , "--id-attr:%s" % ID_ATTR , ENC_KEY_CLASS ]
831
+ com_list = [self .xmlsec , "--decrypt" , "--id-attr:%s" % ID_ATTR ,
832
+ ENC_KEY_CLASS ]
815
833
816
834
(_stdout , _stderr , output ) = self ._run_xmlsec (com_list , [fil ],
817
835
exception = DecryptError ,
818
- validate_output = False )
836
+ validate_output = False ,
837
+ key_file = key_file ,
838
+ passphrase = passphrase )
839
+
819
840
return output
820
841
821
842
def sign_statement (self , statement , node_name , key_file , node_id ,
822
- id_attr ):
843
+ id_attr , passphrase = None ):
823
844
"""
824
845
Sign an XML statement.
825
846
@@ -832,18 +853,18 @@ def sign_statement(self, statement, node_name, key_file, node_id,
832
853
:return: The signed statement
833
854
"""
834
855
835
- _ , fil = make_temp ("%s" % statement , suffix = ".xml" , decode = False ,
856
+ _ , fil = make_temp ("%s" % statement , suffix = ".xml" , decode = False ,
836
857
delete = self ._xmlsec_delete_tmpfiles )
837
858
838
859
com_list = [self .xmlsec , "--sign" ,
839
- "--privkey-pem" , key_file ,
840
860
"--id-attr:%s" % id_attr , node_name ]
841
861
if node_id :
842
862
com_list .extend (["--node-id" , node_id ])
843
863
844
864
try :
845
865
(stdout , stderr , signed_statement ) = self ._run_xmlsec (
846
- com_list , [fil ], validate_output = False )
866
+ com_list , [fil ], validate_output = False , key_file = key_file ,
867
+ passphrase = passphrase )
847
868
# this doesn't work if --store-signatures are used
848
869
if stdout == "" :
849
870
if signed_statement :
@@ -898,7 +919,9 @@ def validate_signature(self, signedtext, cert_file, cert_type, node_name,
898
919
return parse_xmlsec_output (stderr )
899
920
900
921
def _run_xmlsec (self , com_list , extra_args , validate_output = True ,
901
- exception = XmlsecError ):
922
+ exception = XmlsecError ,
923
+ key_file = None ,
924
+ passphrase = None ):
902
925
"""
903
926
Common code to invoke xmlsec and parse the output.
904
927
:param com_list: Key-value parameter list for xmlsec
@@ -910,13 +933,40 @@ def _run_xmlsec(self, com_list, extra_args, validate_output=True,
910
933
"""
911
934
ntf = NamedTemporaryFile (suffix = ".xml" ,
912
935
delete = self ._xmlsec_delete_tmpfiles )
936
+
913
937
com_list .extend (["--output" , ntf .name ])
938
+
939
+ # Unfortunately there's no safe way to pass a password to xmlsec1.
940
+ # Instead, we'll decrypt the certificate and write it into a named pipe,
941
+ # which we'll pass to xmlsec1.
942
+ named_pipe = None
943
+ if key_file is not None :
944
+ if passphrase is not None :
945
+ named_pipe = NamedPipe ()
946
+
947
+ # Decrypt the certificate, but don't write it into the FIFO
948
+ # until after we've started xmlsec1.
949
+ with open (key_file ) as f :
950
+ key = importKey (f .read (), passphrase = passphrase )
951
+
952
+ key_file = named_pipe .name
953
+
954
+ com_list .extend (["--privkey-pem" , key_file ])
955
+
914
956
com_list += extra_args
915
957
916
958
logger .debug ("xmlsec command: %s" % " " .join (com_list ))
917
959
918
960
pof = Popen (com_list , stderr = PIPE , stdout = PIPE )
919
961
962
+ if named_pipe is not None :
963
+ # Finally, write the key into our named pipe.
964
+ try :
965
+ with open (named_pipe .name , 'wb' ) as f :
966
+ f .write (key .exportKey ())
967
+ finally :
968
+ named_pipe .close ()
969
+
920
970
p_out = pof .stdout .read ()
921
971
p_err = pof .stderr .read ()
922
972
@@ -958,7 +1008,7 @@ def version(self):
958
1008
return "XMLSecurity 0.0"
959
1009
960
1010
def sign_statement (self , statement , node_name , key_file , node_id ,
961
- _id_attr ):
1011
+ _id_attr , passphrase = None ):
962
1012
"""
963
1013
Sign an XML statement.
964
1014
@@ -974,6 +1024,8 @@ def sign_statement(self, statement, node_name, key_file, node_id,
974
1024
import xmlsec
975
1025
import lxml .etree
976
1026
1027
+ assert passphrase is None , "Encrypted key files is not supported"
1028
+
977
1029
xml = xmlsec .parse_xml (statement )
978
1030
signed = xmlsec .sign (xml , key_file )
979
1031
return lxml .etree .tostring (signed , xml_declaration = True )
@@ -1055,7 +1107,8 @@ def security_context(conf, debug=None):
1055
1107
generate_cert_info = conf .generate_cert_info ,
1056
1108
tmp_cert_file = conf .tmp_cert_file ,
1057
1109
tmp_key_file = conf .tmp_key_file ,
1058
- validate_certificate = conf .validate_certificate )
1110
+ validate_certificate = conf .validate_certificate ,
1111
+ key_file_passphrase = conf .key_file_passphrase )
1059
1112
1060
1113
1061
1114
def encrypt_cert_from_item (item ):
@@ -1218,13 +1271,15 @@ def __init__(self, crypto, key_file="", key_type="pem",
1218
1271
debug = False , template = "" , encrypt_key_type = "des-192" ,
1219
1272
only_use_keys_in_metadata = False , cert_handler_extra_class = None ,
1220
1273
generate_cert_info = None , tmp_cert_file = None ,
1221
- tmp_key_file = None , validate_certificate = None ):
1274
+ tmp_key_file = None , validate_certificate = None ,
1275
+ key_file_passphrase = None ):
1222
1276
1223
1277
self .crypto = crypto
1224
1278
assert (isinstance (self .crypto , CryptoBackend ))
1225
1279
1226
1280
# Your private key
1227
1281
self .key_file = key_file
1282
+ self .key_file_passphrase = key_file_passphrase
1228
1283
self .key_type = key_type
1229
1284
1230
1285
# Your public key
@@ -1293,15 +1348,19 @@ def encrypt_assertion(self, statement, enc_key, template,
1293
1348
"""
1294
1349
raise NotImplemented ()
1295
1350
1296
- def decrypt (self , enctext , key_file = None ):
1351
+ def decrypt (self , enctext , key_file = None , passphrase = None ):
1297
1352
""" Decrypting an encrypted text by the use of a private key.
1298
1353
1299
1354
:param enctext: The encrypted text as a string
1300
1355
:return: The decrypted text
1301
1356
"""
1302
- if key_file is not None and len (key_file .strip ()) > 0 :
1303
- return self .crypto .decrypt (enctext , key_file )
1304
- return self .crypto .decrypt (enctext , self .key_file )
1357
+ if key_file is None or len (key_file .strip ()) == 0 :
1358
+ key_file = self .key_file
1359
+
1360
+ if passphrase is None :
1361
+ passphrase = self .key_file_passphrase
1362
+
1363
+ return self .crypto .decrypt (enctext , key_file , passphrase )
1305
1364
1306
1365
def verify_signature (self , signedtext , cert_file = None , cert_type = "pem" ,
1307
1366
node_name = NODE_NAME , node_id = None , id_attr = "" ):
@@ -1619,7 +1678,8 @@ def sign_statement_using_xmlsec(self, statement, **kwargs):
1619
1678
return self .sign_statement (statement , ** kwargs )
1620
1679
1621
1680
def sign_statement (self , statement , node_name , key = None ,
1622
- key_file = None , node_id = None , id_attr = "" ):
1681
+ key_file = None , node_id = None , id_attr = "" ,
1682
+ passphrase = None ):
1623
1683
"""Sign a SAML statement.
1624
1684
1625
1685
:param statement: The statement to be signed
@@ -1640,8 +1700,12 @@ def sign_statement(self, statement, node_name, key=None,
1640
1700
if not key and not key_file :
1641
1701
key_file = self .key_file
1642
1702
1703
+ if not passphrase :
1704
+ passphrase = self .key_file_passphrase
1705
+
1643
1706
return self .crypto .sign_statement (statement , node_name , key_file ,
1644
- node_id , id_attr )
1707
+ node_id , id_attr ,
1708
+ passphrase = passphrase )
1645
1709
1646
1710
def sign_assertion_using_xmlsec (self , statement , ** kwargs ):
1647
1711
""" Deprecated function. See sign_assertion(). """
@@ -1674,7 +1738,8 @@ def sign_attribute_query(self, statement, **kwargs):
1674
1738
return self .sign_statement (statement , class_name (
1675
1739
samlp .AttributeQuery ()), ** kwargs )
1676
1740
1677
- def multiple_signatures (self , statement , to_sign , key = None , key_file = None ):
1741
+ def multiple_signatures (self , statement , to_sign , key = None , key_file = None ,
1742
+ passphrase = None ):
1678
1743
"""
1679
1744
Sign multiple parts of a statement
1680
1745
@@ -1697,7 +1762,8 @@ def multiple_signatures(self, statement, to_sign, key=None, key_file=None):
1697
1762
1698
1763
statement = self .sign_statement (statement , class_name (item ),
1699
1764
key = key , key_file = key_file ,
1700
- node_id = sid , id_attr = id_attr )
1765
+ node_id = sid , id_attr = id_attr ,
1766
+ passphrase = passphrase )
1701
1767
return statement
1702
1768
1703
1769
0 commit comments