Skip to content

Commit ddb78de

Browse files
Merge pull request #583 from c00kiemon5ter/fix-check-xmlsec-returncode
Check the xmlsec returncode
2 parents 435ae01 + 9ce6dfd commit ddb78de

File tree

4 files changed

+210
-219
lines changed

4 files changed

+210
-219
lines changed

src/saml2/sigver.py

Lines changed: 102 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import base64
77
import hashlib
8+
import itertools
89
import logging
910
import os
1011
import 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-
598595
def 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 :\nstdout : %s\nstderr : %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}\nerror={err}\noutput={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

Comments
 (0)