Skip to content

Commit c1942f4

Browse files
committed
Add tests and fix trust issue for X509IssuerSerial/X509Digest
Fixup for 18efa28 Fixes #152
1 parent ba4245e commit c1942f4

File tree

3 files changed

+41
-25
lines changed

3 files changed

+41
-25
lines changed

signxml/__init__.py

+26-20
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ def _verify_signature_with_pubkey(self, signed_info_c14n, raw_signature, key_val
566566
if der_encoded_key_value is not None:
567567
key = load_der_public_key(b64decode(der_encoded_key_value.text), backend=default_backend())
568568
if "ecdsa-" in signature_alg:
569-
if key_value:
569+
if key_value is not None:
570570
ec_key_value = self._find(key_value, "ECKeyValue", namespace="dsig11")
571571
named_curve = self._find(ec_key_value, "NamedCurve", namespace="dsig11")
572572
public_key = self._find(ec_key_value, "PublicKey", namespace="dsig11")
@@ -586,7 +586,7 @@ def _verify_signature_with_pubkey(self, signed_info_c14n, raw_signature, key_val
586586
),
587587
)
588588
elif "dsa-" in signature_alg:
589-
if key_value:
589+
if key_value is not None:
590590
dsa_key_value = self._find(key_value, "DSAKeyValue")
591591
p = self._get_long(dsa_key_value, "P")
592592
q = self._get_long(dsa_key_value, "Q")
@@ -602,7 +602,7 @@ def _verify_signature_with_pubkey(self, signed_info_c14n, raw_signature, key_val
602602
data=signed_info_c14n,
603603
algorithm=self._get_signature_digest_method(signature_alg))
604604
elif "rsa-" in signature_alg:
605-
if key_value:
605+
if key_value is not None:
606606
rsa_key_value = self._find(key_value, "RSAKeyValue")
607607
modulus = self._get_long(rsa_key_value, "Modulus")
608608
exponent = self._get_long(rsa_key_value, "Exponent")
@@ -660,8 +660,8 @@ def _apply_transforms(self, payload, transforms_node, signature, c14n_algorithm)
660660

661661
return payload
662662

663-
def verify(self, data, require_x509=True, x509_cert=None, cert_subject_name=None, ca_pem_file=None, ca_path=None,
664-
hmac_key=None, validate_schema=True, parser=None, uri_resolver=None, cert_resolver=None,
663+
def verify(self, data, require_x509=True, x509_cert=None, cert_subject_name=None, cert_resolver=None,
664+
ca_pem_file=None, ca_path=None, hmac_key=None, validate_schema=True, parser=None, uri_resolver=None,
665665
id_attribute=None, expect_references=1, ignore_ambiguous_key_info=False):
666666
"""
667667
Verify the XML signature supplied in the data and return the XML node signed by the signature, or raise an
@@ -704,6 +704,16 @@ def verify(self, data, require_x509=True, x509_cert=None, cert_subject_name=None
704704
``None``, requires that the signature supply a valid X.509 certificate chain that validates against the
705705
known certificate authorities. Implies **require_x509=True**.
706706
:type x509_cert: string or OpenSSL.crypto.X509
707+
:param cert_subject_name:
708+
Subject Common Name to check the signing X.509 certificate against. Implies **require_x509=True**.
709+
:type cert_subject_name: string
710+
:param cert_resolver:
711+
Function to use to resolve trusted X.509 certificates when X509IssuerSerial and X509Digest references are
712+
found in the signature. The function is called with the keyword arguments ``x509_issuer_name``,
713+
``x509_serial_number`` and ``x509_digest``, and is expected to return an iterable of one or more
714+
strings containing a PEM-formatted certificate and a chain of intermediate certificates, if needed.
715+
Implies **require_x509=True**.
716+
:type cert_resolver: callable
707717
:param ca_pem_file:
708718
Filename of a PEM file containing certificate authority information to use when verifying certificate-based
709719
signatures.
@@ -713,9 +723,6 @@ def verify(self, data, require_x509=True, x509_cert=None, cert_subject_name=None
713723
certificate-based signatures. If neither **ca_pem_file** nor **ca_path** is given, the Mozilla CA bundle
714724
provided by :py:mod:`certifi` will be loaded.
715725
:type ca_path: string
716-
:param cert_subject_name:
717-
Subject Common Name to check the signing X.509 certificate against. Implies **require_x509=True**.
718-
:type cert_subject_name: string
719726
:param hmac_key: If using HMAC, a string containing the shared secret.
720727
:type hmac_key: string
721728
:param validate_schema: Whether to validate **data** against the XML Signature schema.
@@ -728,12 +735,6 @@ def verify(self, data, require_x509=True, x509_cert=None, cert_subject_name=None
728735
Function to use to resolve reference URIs that don't start with "#". The function is called with a single
729736
string argument containing the URI to be resolved, and is expected to return a lxml.etree node or string.
730737
:type uri_resolver: callable
731-
:param cert_resolver:
732-
Function to use to resolve X.509 certificates when X509IssuerSerial and X509Digest references are found in
733-
the signature. The function is called with the keyword arguments ``x509_issuer_name``,
734-
``x509_serial_number`` and ``x509_digest``, and is expected to return an iterable of one or more
735-
strings containing PEM-formatted certificates.
736-
:type cert_resolver: callable
737738
:param id_attribute:
738739
Name of the attribute whose value ``URI`` refers to. By default, SignXML will search for "Id", then "ID".
739740
:type id_attribute: string
@@ -761,7 +762,7 @@ def verify(self, data, require_x509=True, x509_cert=None, cert_subject_name=None
761762
self.x509_cert = x509_cert
762763
self._parser = parser
763764

764-
if x509_cert:
765+
if x509_cert or cert_resolver:
765766
self.require_x509 = True
766767

767768
if id_attribute is not None:
@@ -803,15 +804,20 @@ def verify(self, data, require_x509=True, x509_cert=None, cert_subject_name=None
803804
x509_iss = x509_data.find("ds:X509IssuerSerial/ds:X509IssuerName", namespaces=namespaces)
804805
x509_sn = x509_data.find("ds:X509IssuerSerial/ds:X509SerialNumber", namespaces=namespaces)
805806
x509_digest = x509_data.find("dsig11:X509Digest", namespaces=namespaces)
806-
if cert_resolver is not None and (x509_iss or x509_sn or x509_digest):
807-
certs = cert_resolver(x509_issuer_name=x509_iss.text if x509_iss is not None else None,
808-
x509_serial_number=x509_sn.text if x509_sn is not None else None,
809-
x509_digest=x509_digest.text if x509_digest is not None else None)
807+
if cert_resolver and any(i is not None for i in (x509_iss, x509_sn, x509_digest)):
808+
cert_chain = cert_resolver(x509_issuer_name=x509_iss.text if x509_iss is not None else None,
809+
x509_serial_number=x509_sn.text if x509_sn is not None else None,
810+
x509_digest=x509_digest.text if x509_digest is not None else None)
811+
if len(cert_chain) == 0:
812+
raise InvalidCertificate("No certificate found for given X509 data")
813+
if not all(isinstance(c, X509) for c in cert_chain):
814+
cert_chain = [load_certificate(FILETYPE_PEM, add_pem_header(cert)) for cert in cert_chain]
810815
else:
811816
msg = "Expected to find an X509Certificate element in the signature"
812817
msg += " (X509SubjectName, X509SKI are not supported)"
813818
raise InvalidInput(msg)
814-
cert_chain = [load_certificate(FILETYPE_PEM, add_pem_header(cert)) for cert in certs]
819+
else:
820+
cert_chain = [load_certificate(FILETYPE_PEM, add_pem_header(cert)) for cert in certs]
815821
signing_cert = verify_x509_cert_chain(cert_chain, ca_pem_file=ca_pem_file, ca_path=ca_path)
816822
elif isinstance(self.x509_cert, X509):
817823
signing_cert = self.x509_cert

test/interop/TR2012/rsa-cert.der

724 Bytes
Binary file not shown.

test/test.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from cryptography.hazmat.primitives.asymmetric import rsa, dsa, ec
1515
from eight import str, open
1616

17-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # noqa
17+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
1818
from signxml import (XMLSigner, XMLVerifier, XMLSignatureProcessor, methods, namespaces, InvalidInput, InvalidSignature,
1919
InvalidCertificate, InvalidDigest)
2020

@@ -186,21 +186,26 @@ def test_xmldsig_interop_examples(self):
186186
XMLVerifier().verify(fh.read(), ca_pem_file=ca_pem_file)
187187

188188
def test_xmldsig_interop_TR2012(self):
189-
def get_x509_cert(signature_file):
190-
with open(os.path.join(os.path.dirname(__file__), "keys", "p256-cert.der"), "rb") as fh:
191-
return fh.read()
189+
def get_x509_cert(**kwargs):
190+
from cryptography.x509 import load_der_x509_certificate
191+
from OpenSSL.crypto import X509
192+
with open(os.path.join(interop_dir, "TR2012", "rsa-cert.der"), "rb") as fh:
193+
return [X509.from_cryptography(load_der_x509_certificate(fh.read(), backend=default_backend()))]
192194

193195
signature_files = glob(os.path.join(interop_dir, "TR2012", "signature*.xml"))
194196
for signature_file in signature_files:
195197
print("Verifying", signature_file)
196198
with open(signature_file, "rb") as fh:
197199
try:
198200
sig = fh.read()
199-
XMLVerifier().verify(sig, require_x509=False, hmac_key="testkey", validate_schema=True)
201+
XMLVerifier().verify(sig, require_x509=False, hmac_key="testkey", validate_schema=True,
202+
cert_resolver=get_x509_cert if "x509digest" in signature_file else None)
200203
decoded_sig = sig.decode("utf-8")
201204
except Exception as e:
202205
if "keyinforeference" in signature_file:
203206
print("Unsupported test case:", type(e), e)
207+
elif "x509digest" in signature_file:
208+
assert isinstance(e, InvalidCertificate)
204209
else:
205210
raise
206211

@@ -239,6 +244,10 @@ def get_ca_pem_file(signature_file):
239244
return None
240245
return ca_pem_file.encode("utf-8")
241246

247+
def cert_resolver(x509_issuer_name, x509_serial_number, x509_digest):
248+
with open(os.path.join(interop_dir, "phaos-xmldsig-three", "certs", "rsa-cert.pem")) as fh:
249+
return [fh.read()]
250+
242251
signature_files = glob(os.path.join(interop_dir, "*", "signature*.xml"))
243252
signature_files += glob(os.path.join(interop_dir, "aleksey*", "*.xml"))
244253
signature_files += glob(os.path.join(interop_dir, "xml-crypto", "*.xml"))
@@ -254,6 +263,7 @@ def get_ca_pem_file(signature_file):
254263
validate_schema=True,
255264
uri_resolver=resolver,
256265
x509_cert=get_x509_cert(signature_file),
266+
cert_resolver=cert_resolver if "issuer-serial" in signature_file else None,
257267
ca_pem_file=get_ca_pem_file(signature_file))
258268
decoded_sig = sig.decode("utf-8")
259269
if "HMACOutputLength" in decoded_sig or "bad" in signature_file or "expired" in signature_file:

0 commit comments

Comments
 (0)