From 2c8591ff2c1aab5b2b4ef0ebd53916edaba28a3c 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 | 13 ++++--------- test/test.py | 4 ++-- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index d592d6b..02ea658 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..35c0dbc 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, @@ -316,7 +315,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 +335,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 +428,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