From 7e7f50494184404e4a95e10b1c29a54e09a53352 Mon Sep 17 00:00:00 2001 From: Andrey Kislyuk Date: Fri, 20 Sep 2024 09:17:55 -0700 Subject: [PATCH] Fully remove the ca_path parameter; add docs for signature location pinning --- README.rst | 12 +++++++++--- signxml/util/__init__.py | 6 +----- signxml/verifier.py | 25 ++++++++++++++----------- test/test.py | 4 ++-- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/README.rst b/README.rst index d592d6b..6ca451d 100644 --- a/README.rst +++ b/README.rst @@ -90,8 +90,14 @@ Assuming ``metadata.xml`` contains SAML metadata for the assertion source: `SAML signature wrapping `_. In SignXML, you can ensure that the information signed is what you expect to be signed by only trusting the - data returned by the ``verify()`` method. The ``signed_xml`` attribute of the return value is the XML node or string that - was signed. + data returned by ``XMLVerifier.verify()``. The ``signed_xml`` attribute of the return value is the XML node or string + that was signed. We also recommend that you assert the expected location for the signature within the document: + + .. code-block:: python + + from signxml import XMLVerifier, SignatureConfiguration + config = SignatureConfiguration(location="./{urn:oasis:names:tc:SAML:2.0:assertion}Assertion") + XMLVerifier(...).verify(..., expect_config=config) **Recommended reading:** `W3C XML Signature Best Practices for Applications `_, `On Breaking SAML: Be Whoever You Want to Be @@ -106,7 +112,7 @@ Assuming ``metadata.xml`` contains SAML metadata for the assertion source: ``x509_cert`` argument to specify a certificate that was pre-shared out-of-band (e.g. via SAML metadata, as shown in *Verifying SAML assertions*), or ``cert_subject_name`` to specify a subject name that must be in the signing X.509 certificate given by the signature (verified as if it were a - domain name), or ``ca_pem_file``/``ca_path`` to give a custom CA. + domain name), or ``ca_pem_file`` to give a custom CA. XML signature construction methods: enveloped, detached, enveloping ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/signxml/util/__init__.py b/signxml/util/__init__.py index b073b69..6c4483a 100644 --- a/signxml/util/__init__.py +++ b/signxml/util/__init__.py @@ -225,14 +225,10 @@ class X509CertChainVerifier: contact SignXML maintainers. """ - def __init__(self, ca_pem_file=None, ca_path=None, verification_time=None): + def __init__(self, ca_pem_file=None, verification_time=None): if ca_pem_file is None: ca_pem_file = certifi.where() self.ca_pem_file = ca_pem_file - if ca_path is not None: - msg = "CApath is not supported. If you need this feature, please contact SignXML maintainers." - raise NotImplementedError(msg) - self.verification_time = verification_time @property diff --git a/signxml/verifier.py b/signxml/verifier.py index 71143a4..4b90480 100644 --- a/signxml/verifier.py +++ b/signxml/verifier.py @@ -233,8 +233,8 @@ def _apply_transforms(self, payload, *, transforms_node: etree._Element, signatu return payload - def get_cert_chain_verifier(self, ca_pem_file, ca_path): - return X509CertChainVerifier(ca_pem_file=ca_pem_file, ca_path=ca_path) + def get_cert_chain_verifier(self, ca_pem_file): + return X509CertChainVerifier(ca_pem_file=ca_pem_file) def _match_key_values(self, key_value, der_encoded_key_value, signing_cert, signature_alg): if self.config.ignore_ambiguous_key_info is False: @@ -279,7 +279,6 @@ def verify( cert_subject_name: Optional[str] = None, cert_resolver: Optional[Callable] = None, ca_pem_file: Optional[Union[str, bytes]] = None, - ca_path: Optional[str] = None, hmac_key: Optional[str] = None, validate_schema: bool = True, parser=None, @@ -302,8 +301,16 @@ def verify( signed by that signature. In SignXML, you can ensure that the information signed is what you expect to be signed by only trusting the - data returned by the ``verify()`` method. The return value is the XML node or string that was signed. Also, - depending on the canonicalization method used by the signature, comments in the XML data may not be subject to + data returned by ``XMLVerifier.verify()``. The ``signed_xml`` attribute of the return value is the XML node or string + that was signed. We also recommend that you assert the expected location for the signature within the document: + + .. code-block:: python + + from signxml import XMLVerifier, SignatureConfiguration + config = SignatureConfiguration(location="./{urn:oasis:names:tc:SAML:2.0:assertion}Assertion") + XMLVerifier(...).verify(..., expect_config=config) + + Depending on the canonicalization method used by the signature, comments in the XML data may not be subject to signing, so may need to be untrusted. If so, they are excised from the return value of ``verify()``. **Recommended reading:** http://www.w3.org/TR/xmldsig-bestpractices/#practices-applications @@ -316,7 +323,7 @@ def verify( ``x509_cert`` argument to specify a certificate that was pre-shared out-of-band (e.g. via SAML metadata, as shown in :ref:`Verifying SAML assertions `), or ``cert_subject_name`` to specify a subject name that must be in the signing X.509 certificate given by the signature (verified as if it were a - domain name), or ``ca_pem_file``/``ca_path`` to give a custom CA. + domain name), or ``ca_pem_file`` to give a custom CA. :param data: Signature data to verify :type data: String, file-like object, or XML ElementTree Element API compatible object @@ -336,10 +343,6 @@ def verify( :param ca_pem_file: Filename of a PEM file containing certificate authority information to use when verifying certificate-based signatures. - :param ca_path: - Path to a directory containing PEM-formatted certificate authority files to use when verifying - certificate-based signatures. If neither **ca_pem_file** nor **ca_path** is given, the Mozilla CA bundle - provided by :py:mod:`certifi` will be loaded. :param hmac_key: If using HMAC, a string containing the shared secret. :param validate_schema: Whether to validate **data** against the XML Signature schema. :param parser: @@ -433,7 +436,7 @@ def verify( else: cert_chain = [x509.load_pem_x509_certificate(add_pem_header(cert)) for cert in certs] - cert_verifier = self.get_cert_chain_verifier(ca_pem_file=ca_pem_file, ca_path=ca_path) + cert_verifier = self.get_cert_chain_verifier(ca_pem_file=ca_pem_file) signing_cert = cert_verifier.verify(cert_chain) elif isinstance(self.x509_cert, x509.Certificate): diff --git a/test/test.py b/test/test.py index 63ce129..bf83fb2 100755 --- a/test/test.py +++ b/test/test.py @@ -68,8 +68,8 @@ def reset_tree(t, method): def get_verifier_for_year(year: int): class _Verifier(XMLVerifier): - def get_cert_chain_verifier(self, ca_pem_file, ca_path): - verifier = super().get_cert_chain_verifier(ca_pem_file, ca_path) + def get_cert_chain_verifier(self, ca_pem_file): + verifier = super().get_cert_chain_verifier(ca_pem_file) verifier.verification_time = datetime(year, 1, 1) return verifier