Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-signxml for openSUSE:Factory 
checked in at 2026-07-06 12:35:08
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-signxml (Old)
 and      /work/SRC/openSUSE:Factory/.python-signxml.new.1982 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-signxml"

Mon Jul  6 12:35:08 2026 rev:4 rq:1363955 version:5.1.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-signxml/python-signxml.changes    
2026-03-04 21:09:02.709938301 +0100
+++ /work/SRC/openSUSE:Factory/.python-signxml.new.1982/python-signxml.changes  
2026-07-06 12:37:43.338644063 +0200
@@ -1,0 +2,14 @@
+Mon Jul  6 06:03:21 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 5.1.0:
+  * Implement point-in-time verification
+  * xades: fix CertDigest handling
+  * Confirm certs passed to validate are X.509 formatted
+  * Clarify location parameter syntax
+  * Increase minimum cryptography version to 45
+  * X.509 certificate valitation now requires the provided
+    certificate to list digital signature as a key usage.
+  * fix rst formatting
+  * Support setting certificate validation policy
+
+-------------------------------------------------------------------

Old:
----
  signxml-4.4.0.tar.gz

New:
----
  signxml-5.1.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-signxml.spec ++++++
--- /var/tmp/diff_new_pack.2lNZpC/_old  2026-07-06 12:37:43.882662966 +0200
+++ /var/tmp/diff_new_pack.2lNZpC/_new  2026-07-06 12:37:43.886663104 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-signxml
-Version:        4.4.0
+Version:        5.1.0
 Release:        0
 Summary:        Python XML Signature and XAdES library
 License:        Apache-2.0

++++++ signxml-4.4.0.tar.gz -> signxml-5.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/.github/workflows/ci.yml 
new/signxml-5.1.0/.github/workflows/ci.yml
--- old/signxml-4.4.0/.github/workflows/ci.yml  2020-02-02 01:00:00.000000000 
+0100
+++ new/signxml-5.1.0/.github/workflows/ci.yml  2020-02-02 01:00:00.000000000 
+0100
@@ -8,11 +8,11 @@
     strategy:
       max-parallel: 8
       matrix:
-        os: [ubuntu-22.04, ubuntu-latest, macos-14, macos-latest]
+        os: [ubuntu-22.04, ubuntu-latest, macos-15, macos-latest]
         python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
     steps:
-    - uses: actions/checkout@v4
-    - uses: actions/setup-python@v5
+    - uses: actions/checkout@v6
+    - uses: actions/setup-python@v6
       with:
         python-version: ${{matrix.python-version}}
     - run: |
@@ -26,13 +26,13 @@
   isort:
     runs-on: ubuntu-22.04
     steps:
-    - uses: actions/checkout@v4
-    - uses: isort/[email protected]
+    - uses: actions/checkout@v6
+    - uses: isort/[email protected]
   ruff:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
-      - uses: astral-sh/ruff-action@v1
-      - uses: astral-sh/ruff-action@v1
+      - uses: actions/checkout@v6
+      - uses: astral-sh/[email protected]
+      - uses: astral-sh/[email protected]
         with:
           args: "format --check"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/Changes.rst 
new/signxml-5.1.0/Changes.rst
--- old/signxml-4.4.0/Changes.rst       2020-02-02 01:00:00.000000000 +0100
+++ new/signxml-5.1.0/Changes.rst       2020-02-02 01:00:00.000000000 +0100
@@ -1,3 +1,32 @@
+Changes for v5.1.0 (2026-07-04)
+===============================
+
+-  Implement point-in-time verification
+-  xades: fix CertDigest handling
+-  Confirm certs passed to validate are X.509 formatted
+-  Clarify location parameter syntax
+
+Changes for v5.0.1 (2026-06-23)
+===============================
+
+-  Increase minimum cryptography version to 45 (#295)
+
+Changes for v5.0.0 (2026-06-20)
+===============================
+
+-  X.509 certificate valitation now requires the provided certificate to
+   list digital signature as a key usage.
+
+Changes for v4.5.1 (2026-06-13)
+===============================
+
+- fix rst formatting
+
+Changes for v4.5.0 (2026-06-13)
+===============================
+
+- Support setting certificate validation policy
+
 Changes for v4.4.0 (2026-03-01)
 ===============================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/PKG-INFO new/signxml-5.1.0/PKG-INFO
--- old/signxml-4.4.0/PKG-INFO  2020-02-02 01:00:00.000000000 +0100
+++ new/signxml-5.1.0/PKG-INFO  2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: signxml
-Version: 4.4.0
+Version: 5.1.0
 Summary: Python XML Signature and XAdES library
 Project-URL: Homepage, https://github.com/XML-Security/signxml
 Project-URL: Documentation, https://xml-security.github.io/signxml/
@@ -32,7 +32,7 @@
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Requires-Python: >=3.9
 Requires-Dist: certifi>=2023.11.17
-Requires-Dist: cryptography>=43
+Requires-Dist: cryptography>=45
 Requires-Dist: lxml<7,>=5.2.1
 Provides-Extra: test
 Requires-Dist: build; extra == 'test'
@@ -95,7 +95,7 @@
 To make this example self-sufficient for test purposes:
 
 - Generate a test certificate and key using
-  ``openssl req -x509 -nodes -subj "/CN=test" -days 1 -newkey rsa -keyout 
privkey.pem -out cert.pem``
+  ``openssl req -x509 -nodes -subj "/CN=test" -days 1 -newkey rsa -keyout 
privkey.pem -out cert.pem -addext "keyUsage = digitalSignature"``
   (run ``apt-get install openssl``, ``yum install openssl``, or ``brew install 
openssl`` if the ``openssl`` executable
   is not found).
 - Pass the ``x509_cert=cert`` keyword argument to ``XMLVerifier.verify()``. 
(In production, ensure this is replaced with
@@ -145,6 +145,17 @@
     config = SignatureConfiguration(location="./")
     XMLVerifier(...).verify(..., expect_config=config)
 
+ The ``location`` value describes the expected parent path for the 
``ds:Signature`` element. SignXML
+ appends ``ds:Signature`` to this path internally. For example, if a SAML 
``Response`` root contains a
+ signed ``Assertion`` child, configure verification like this:
+
+ .. code-block:: python
+
+    config = SignatureConfiguration(
+        location="./{urn:oasis:names:tc:SAML:2.0:assertion}Assertion/"
+    )
+    XMLVerifier().verify(response_xml, x509_cert=cert, expect_config=config)
+
  **Recommended reading:** `W3C XML Signature Best Practices for Applications
  <http://www.w3.org/TR/xmldsig-bestpractices/#practices-applications>`_, `On 
Breaking SAML: Be Whoever You Want to Be
  
<https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final91.pdf>`_,
 `Duo Finds SAML Vulnerabilities
@@ -162,6 +173,33 @@
  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`` to give a custom CA.
 
+ To verify a signature at a specific point in time, such as an archived 
signature whose certificate has since
+ expired, configure ``verification_time``. The configured time is used for 
X.509 certificate validity checks,
+ including certificate chain validation and certificates supplied with 
``x509_cert``:
+
+ .. code-block:: python
+
+    from datetime import datetime, timezone
+    from signxml import SignatureConfiguration, XMLVerifier
+
+    config = SignatureConfiguration(
+        verification_time=datetime(2018, 5, 28, 17, 0, tzinfo=timezone.utc)
+    )
+    verified_data = XMLVerifier().verify(data, expect_config=config).signed_xml
+
+Relaxing digital signature key usage extension validation for certificates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+When verifying X.509 certificate chains, SignXML's default end-entity 
certificate policy requires the
+``KeyUsage`` extension to be present and to allow ``digitalSignature``. To 
accept certificates without that
+key usage, pass a custom end-entity certificate policy that does not require 
it:
+
+.. code-block:: python
+
+    ee_policy = x509.verification.ExtensionPolicy.permit_all()
+
+    XMLVerifier(...).verify(..., ee_policy=ee_policy)
+
+
 XML signature construction methods: enveloped, detached, enveloping
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 The XML Signature specification defines three ways to compose a signature with 
the data being signed: enveloped,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/README.rst new/signxml-5.1.0/README.rst
--- old/signxml-4.4.0/README.rst        2020-02-02 01:00:00.000000000 +0100
+++ new/signxml-5.1.0/README.rst        2020-02-02 01:00:00.000000000 +0100
@@ -50,7 +50,7 @@
 To make this example self-sufficient for test purposes:
 
 - Generate a test certificate and key using
-  ``openssl req -x509 -nodes -subj "/CN=test" -days 1 -newkey rsa -keyout 
privkey.pem -out cert.pem``
+  ``openssl req -x509 -nodes -subj "/CN=test" -days 1 -newkey rsa -keyout 
privkey.pem -out cert.pem -addext "keyUsage = digitalSignature"``
   (run ``apt-get install openssl``, ``yum install openssl``, or ``brew install 
openssl`` if the ``openssl`` executable
   is not found).
 - Pass the ``x509_cert=cert`` keyword argument to ``XMLVerifier.verify()``. 
(In production, ensure this is replaced with
@@ -100,6 +100,17 @@
     config = SignatureConfiguration(location="./")
     XMLVerifier(...).verify(..., expect_config=config)
 
+ The ``location`` value describes the expected parent path for the 
``ds:Signature`` element. SignXML
+ appends ``ds:Signature`` to this path internally. For example, if a SAML 
``Response`` root contains a
+ signed ``Assertion`` child, configure verification like this:
+
+ .. code-block:: python
+
+    config = SignatureConfiguration(
+        location="./{urn:oasis:names:tc:SAML:2.0:assertion}Assertion/"
+    )
+    XMLVerifier().verify(response_xml, x509_cert=cert, expect_config=config)
+
  **Recommended reading:** `W3C XML Signature Best Practices for Applications
  <http://www.w3.org/TR/xmldsig-bestpractices/#practices-applications>`_, `On 
Breaking SAML: Be Whoever You Want to Be
  
<https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final91.pdf>`_,
 `Duo Finds SAML Vulnerabilities
@@ -117,6 +128,33 @@
  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`` to give a custom CA.
 
+ To verify a signature at a specific point in time, such as an archived 
signature whose certificate has since
+ expired, configure ``verification_time``. The configured time is used for 
X.509 certificate validity checks,
+ including certificate chain validation and certificates supplied with 
``x509_cert``:
+
+ .. code-block:: python
+
+    from datetime import datetime, timezone
+    from signxml import SignatureConfiguration, XMLVerifier
+
+    config = SignatureConfiguration(
+        verification_time=datetime(2018, 5, 28, 17, 0, tzinfo=timezone.utc)
+    )
+    verified_data = XMLVerifier().verify(data, expect_config=config).signed_xml
+
+Relaxing digital signature key usage extension validation for certificates
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+When verifying X.509 certificate chains, SignXML's default end-entity 
certificate policy requires the
+``KeyUsage`` extension to be present and to allow ``digitalSignature``. To 
accept certificates without that
+key usage, pass a custom end-entity certificate policy that does not require 
it:
+
+.. code-block:: python
+
+    ee_policy = x509.verification.ExtensionPolicy.permit_all()
+
+    XMLVerifier(...).verify(..., ee_policy=ee_policy)
+
+
 XML signature construction methods: enveloped, detached, enveloping
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 The XML Signature specification defines three ways to compose a signature with 
the data being signed: enveloped,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/pyproject.toml 
new/signxml-5.1.0/pyproject.toml
--- old/signxml-4.4.0/pyproject.toml    2020-02-02 01:00:00.000000000 +0100
+++ new/signxml-5.1.0/pyproject.toml    2020-02-02 01:00:00.000000000 +0100
@@ -27,7 +27,7 @@
 ]
 dependencies = [
     "lxml >= 5.2.1, < 7",  # Ubuntu 24.04 LTS
-    "cryptography >= 43",  # Required to support client certificate validation
+    "cryptography >= 45",  # Required to support Extension Policy
     "certifi >= 2023.11.17",  # Ubuntu 24.04 LTS
     # "tsp-client >= 0.1.3",
 ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/signxml/__pyinstaller/hook-signxml.py 
new/signxml-5.1.0/signxml/__pyinstaller/hook-signxml.py
--- old/signxml-4.4.0/signxml/__pyinstaller/hook-signxml.py     2020-02-02 
01:00:00.000000000 +0100
+++ new/signxml-5.1.0/signxml/__pyinstaller/hook-signxml.py     2020-02-02 
01:00:00.000000000 +0100
@@ -1,5 +1,5 @@
 """Hook for pyinstaller to include the files are signxml/schemas/* into the 
final build."""
 
-from PyInstaller.utils.hooks import collect_data_files  # type: ignore
+from PyInstaller.utils.hooks import collect_data_files
 
 datas = collect_data_files("signxml", excludes=["__pyinstaller"])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/signxml/algorithms.py 
new/signxml-5.1.0/signxml/algorithms.py
--- old/signxml-4.4.0/signxml/algorithms.py     2020-02-02 01:00:00.000000000 
+0100
+++ new/signxml-5.1.0/signxml/algorithms.py     2020-02-02 01:00:00.000000000 
+0100
@@ -1,5 +1,5 @@
 from enum import Enum
-from typing import Callable, Dict, Type, Union
+from typing import Callable, Dict, Iterable, Type, Union, cast
 
 from cryptography.hazmat.primitives import hashes
 
@@ -37,7 +37,7 @@
 class FragmentLookupMixin:
     @classmethod
     def from_fragment(cls, fragment):
-        for i in cls:  # type: ignore[attr-defined]
+        for i in cast(Iterable[Enum], cls):
             if i.value.endswith("#" + fragment):
                 return i
         else:
@@ -50,7 +50,8 @@
         raise InvalidInput(f"Unrecognized {cls.__name__}: {value}")
 
     def __repr__(self):
-        return f"{self.__class__.__name__}.{self.name}"  # type: 
ignore[attr-defined]
+        enum_self = cast(Enum, self)
+        return f"{self.__class__.__name__}.{enum_self.name}"
 
 
 class DigestAlgorithm(FragmentLookupMixin, InvalidInputErrorMixin, Enum):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/signxml/processor.py 
new/signxml-5.1.0/signxml/processor.py
--- old/signxml-4.4.0/signxml/processor.py      2020-02-02 01:00:00.000000000 
+0100
+++ new/signxml-5.1.0/signxml/processor.py      2020-02-02 01:00:00.000000000 
+0100
@@ -2,7 +2,7 @@
 import logging
 import threading
 from functools import lru_cache
-from typing import Any, List, Tuple
+from typing import Any, Dict, List, Tuple
 from xml.etree import ElementTree as stdlibElementTree
 
 from cryptography.hazmat.primitives.asymmetric import ec
@@ -27,15 +27,15 @@
 
 class XMLProcessor:
     _schemas: List[Any] = []
+    _schemas_lock = threading.Lock()
     schema_files: List[Any] = []
     _default_parser, _parser = None, None
 
     @classmethod
     def schemas(cls):
-        with threading.Lock():
-            if len(cls._schemas) == 0:
-                for schema_file in cls.schema_files:
-                    cls._schemas.append(get_schema(schema_file))
+        with cls._schemas_lock:
+            if "_schemas" not in cls.__dict__:
+                cls._schemas = [get_schema(schema_file) for schema_file in 
cls.schema_files]
         return cls._schemas
 
     @property
@@ -85,14 +85,14 @@
     # "urn:oid:1.3.132.0.36": ec.SECT409K1,
     # "urn:oid:1.3.132.0.37": ec.SECT409R1,
     # "urn:oid:1.3.132.0.38": ec.SECT571K1
-    known_ecdsa_curves = {
-        "urn:oid:1.2.840.10045.3.1.7": ec.SECP256R1,
-        "urn:oid:1.3.132.0.34": ec.SECP384R1,
-        "urn:oid:1.3.132.0.35": ec.SECP521R1,
-        "urn:oid:1.2.840.10045.3.1.1": ec.SECP192R1,
-        "urn:oid:1.3.132.0.33": ec.SECP224R1,
+    known_ecdsa_curves: Dict[str, ec.EllipticCurve] = {
+        "urn:oid:1.2.840.10045.3.1.7": ec.SECP256R1(),
+        "urn:oid:1.3.132.0.34": ec.SECP384R1(),
+        "urn:oid:1.3.132.0.35": ec.SECP521R1(),
+        "urn:oid:1.2.840.10045.3.1.1": ec.SECP192R1(),
+        "urn:oid:1.3.132.0.33": ec.SECP224R1(),
     }
-    known_ecdsa_curve_oids = {ec().name: oid for oid, ec in 
known_ecdsa_curves.items()}  # type: ignore[abstract]
+    known_ecdsa_curve_oids = {curve.name: oid for oid, curve in 
known_ecdsa_curves.items()}
 
     excise_empty_xmlns_declarations = False
 
@@ -108,10 +108,11 @@
         namespace = "ds"
         if ":" in query:
             namespace, _, query = query.partition(":")
-        result = element.find(f"{xpath}{namespace}:{query}", 
namespaces=namespaces)
+        expression = f"{xpath}{namespace}:{query}"
+        result = element.find(expression, namespaces=namespaces)
 
         if require and result is None:
-            raise InvalidInput(f"Expected to find XML element {query} in 
{element.tag}")
+            raise InvalidInput(f"Expected to find XML element {query} in 
{element.tag} using XPath {expression}")
         return result
 
     def _findall(self, element, query, xpath=""):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/signxml/signer.py 
new/signxml-5.1.0/signxml/signer.py
--- old/signxml-4.4.0/signxml/signer.py 2020-02-02 01:00:00.000000000 +0100
+++ new/signxml-5.1.0/signxml/signer.py 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 from base64 import b64encode
 from dataclasses import dataclass, replace
-from typing import List, Optional, Union
+from typing import Dict, List, Optional, Union, cast
 
 from cryptography import x509
 from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa, utils
@@ -21,6 +21,7 @@
 from .util import (
     SigningSettings,
     _remove_sig,
+    add_pem_header,
     bits_to_bytes_unit,
     ds_tag,
     dsig11_tag,
@@ -113,7 +114,7 @@
             self.digest_alg = DigestAlgorithm(digest_algorithm)
         self.check_deprecated_methods()
         self.c14n_alg = CanonicalizationMethod(c14n_algorithm)
-        self.namespaces = dict(ds=namespaces.ds)
+        self.namespaces: Dict[str, str] = {"ds": namespaces.ds}
         self._parser = None
         self.signature_annotators = [self._add_key_info]
 
@@ -127,7 +128,8 @@
 
         See https://github.com/XML-Security/signxml/issues/275
         """
-        if None in self.namespaces and self.namespaces[None] == namespaces.ds: 
 # type:ignore[index]
+        namespaces_map = cast(Dict[Optional[str], str], self.namespaces)
+        if namespaces_map.get(None) == namespaces.ds:
             return QName(None, tag)
         return ds_tag(tag)
 
@@ -216,12 +218,7 @@
         if id_attribute is not None:
             self.id_attributes = (id_attribute,)
 
-        if isinstance(cert, (str, bytes)):
-            cert_chain = list(iterate_pem(cert))
-            if len(cert_chain) == 0:
-                raise InvalidInput("No PEM-encoded certificates found in 
string cert input data")
-        else:
-            cert_chain = cert  # type:ignore[assignment]
+        cert_chain = self._get_cert_chain(cert)
 
         input_references = self._preprocess_reference_uri(reference_uri)
 
@@ -265,7 +262,11 @@
             signed_info_node, algorithm=self.c14n_alg, 
inclusive_ns_prefixes=inclusive_ns_prefixes
         )
         if self.sign_alg.name.startswith("HMAC_"):
-            signer = HMAC(key=key, 
algorithm=digest_algorithm_implementations[self.sign_alg]())  # 
type:ignore[arg-type]
+            if isinstance(key, str):
+                key = key.encode()
+            elif not isinstance(key, bytes):
+                raise InvalidInput('Parameter "key" must be bytes when signing 
with HMAC')
+            signer = HMAC(key=key, 
algorithm=digest_algorithm_implementations[self.sign_alg]())
             signer.update(signed_info_c14n)
             signature_value_node.text = b64encode(signer.finalize()).decode()
             sig_root.append(signature_value_node)
@@ -304,6 +305,30 @@
 
         return doc_root if self.construction_method == 
SignatureConstructionMethod.enveloped else sig_root
 
+    def _get_cert_chain(self, cert):
+        if isinstance(cert, (str, bytes)):
+            cert_chain = list(iterate_pem(cert))
+            if len(cert_chain) == 0:
+                raise InvalidInput("No PEM-encoded certificates found in 
string cert input data")
+        elif cert is None:
+            return None
+        else:
+            cert_chain = list(cert)
+            if len(cert_chain) == 0:
+                raise InvalidInput("No certificates found in cert input data")
+
+        for certificate in cert_chain:
+            if isinstance(certificate, x509.Certificate):
+                continue
+            if not isinstance(certificate, (str, bytes)):
+                raise InvalidInput("Expected cert input data to be a X.509 
certificate or PEM-encoded certificate")
+            try:
+                x509.load_pem_x509_certificate(add_pem_header(certificate))
+            except ValueError as exc:
+                raise InvalidInput("Invalid X.509 certificate supplied in cert 
input data") from exc
+
+        return cert_chain
+
     def _preprocess_reference_uri(self, reference_uris):
         if reference_uris is None:
             return None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/signxml/util/__init__.py 
new/signxml-5.1.0/signxml/util/__init__.py
--- old/signxml-4.4.0/signxml/util/__init__.py  2020-02-02 01:00:00.000000000 
+0100
+++ new/signxml-5.1.0/signxml/util/__init__.py  2020-02-02 01:00:00.000000000 
+0100
@@ -228,12 +228,40 @@
     contact SignXML maintainers.
     """
 
-    def __init__(self, ca_pem_file=None, verification_time=None):
+    def __init__(self, ca_pem_file=None, verification_time=None, 
ee_policy=None, ca_policy=None):
         if ca_pem_file is None:
             ca_pem_file = certifi.where()
         self.ca_pem_file = ca_pem_file
         self.verification_time = verification_time
 
+        if ee_policy is not None:
+            if not isinstance(ee_policy, x509.verification.ExtensionPolicy):
+                raise ValueError("ee_policy must be an instance of 
x509.verification.ExtensionPolicy")
+            self.ee_policy = ee_policy
+        else:
+            self.ee_policy = self.get_default_ee_policy()
+
+        if ca_policy is not None:
+            if not isinstance(ca_policy, x509.verification.ExtensionPolicy):
+                raise ValueError("ca_policy must be an instance of 
x509.verification.ExtensionPolicy")
+            self.ca_policy = ca_policy
+        else:
+            self.ca_policy = self.get_default_ca_policy()
+
+    def validate_key_usage(self, policy, cert, key_usage):
+        if not key_usage.digital_signature:
+            raise ValueError("certificate KeyUsage does not allow 
digitalSignature")
+
+    def get_default_ee_policy(self):
+        return x509.verification.ExtensionPolicy.permit_all().require_present(
+            x509.KeyUsage,
+            x509.verification.Criticality.AGNOSTIC,
+            self.validate_key_usage,
+        )
+
+    def get_default_ca_policy(self):
+        return x509.verification.ExtensionPolicy.webpki_defaults_ca()
+
     @property
     def store(self):
         with open(self.ca_pem_file, "rb") as pems:
@@ -244,6 +272,10 @@
     def builder(self):
         builder = x509.verification.PolicyBuilder()
         builder = builder.store(self.store)
+        builder = builder.extension_policies(
+            ee_policy=self.ee_policy,
+            ca_policy=self.ca_policy,
+        )
         if self.verification_time is not None:
             builder = builder.time(self.verification_time)
         return builder
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/signxml/verifier.py 
new/signxml-5.1.0/signxml/verifier.py
--- old/signxml-4.4.0/signxml/verifier.py       2020-02-02 01:00:00.000000000 
+0100
+++ new/signxml-5.1.0/signxml/verifier.py       2020-02-02 01:00:00.000000000 
+0100
@@ -1,6 +1,7 @@
 from base64 import b64decode
 from dataclasses import dataclass, replace
-from typing import Callable, FrozenSet, List, Optional, Tuple, Union
+from datetime import datetime, timezone
+from typing import Any, Callable, FrozenSet, List, Optional, Tuple, Union, cast
 from warnings import warn
 
 import cryptography.exceptions
@@ -87,6 +88,12 @@
     ``True`` to bypass the check and validate the signature using X509Data 
only.
     """
 
+    verification_time: Optional[datetime] = None
+    """
+    The time to use when validating X.509 certificate validity periods. If 
unset, the current time is used.
+    Use this to verify signatures with certificates that are expired or not 
yet valid at the current time.
+    """
+
     default_reference_c14n_method: CanonicalizationMethod = 
CanonicalizationMethod.CANONICAL_XML_1_1
     """
     The default canonicalization method to use for referenced data structures, 
if there is no canonicalization
@@ -158,8 +165,11 @@
                 key_data = b64decode(public_key.text)[1:]
                 x = bytes_to_long(key_data[: len(key_data) // 2])
                 y = bytes_to_long(key_data[len(key_data) // 2 :])
-                curve_class = self.known_ecdsa_curves[named_curve.get("URI")]
-                ecpn = ec.EllipticCurvePublicNumbers(x=x, y=y, 
curve=curve_class())  # type: ignore[abstract]
+                curve_uri = named_curve.get("URI")
+                if curve_uri is None:
+                    raise InvalidInput("NamedCurve element is missing a URI 
attribute")
+                curve = self.known_ecdsa_curves[curve_uri]
+                ecpn = ec.EllipticCurvePublicNumbers(x=x, y=y, curve=curve)
                 key = ecpn.public_key()
             elif not isinstance(key, ec.EllipticCurvePublicKey):
                 raise InvalidInput("DER encoded key value does not match 
specified signature algorithm")
@@ -257,8 +267,50 @@
             payload = self._c14n(payload, 
algorithm=self.config.default_reference_c14n_method)
         return payload
 
-    def get_cert_chain_verifier(self, ca_pem_file):
-        return X509CertChainVerifier(ca_pem_file=ca_pem_file)
+    def get_cert_chain_verifier(self, ca_pem_file, ee_policy, ca_policy):
+        return X509CertChainVerifier(
+            ca_pem_file=ca_pem_file,
+            verification_time=self._get_cert_verification_time(),
+            ee_policy=ee_policy,
+            ca_policy=ca_policy,
+        )
+
+    def _get_cert_verification_time(self):
+        verification_time = self.config.verification_time or 
datetime.now(tz=timezone.utc)
+        if verification_time.tzinfo is None:
+            return verification_time.replace(tzinfo=timezone.utc)
+        return verification_time.astimezone(timezone.utc)
+
+    def _get_cert_description(self, cert, cert_source):
+        try:
+            subject = cert.subject.rfc4514_string()
+        except ValueError:
+            subject = "<invalid subject>"
+        return f"{cert_source} (subject={subject}, 
serial={cert.serial_number})"
+
+    def _get_cert_validity_error(self, cert, cert_source):
+        cert_description = self._get_cert_description(cert, cert_source)
+        verification_time = self._get_cert_verification_time()
+        if verification_time < cert.not_valid_before_utc:
+            return (
+                f"{cert_description}: certificate is not yet valid at 
validation time "
+                f"{verification_time.isoformat()}; certificate is not valid 
before "
+                f"{cert.not_valid_before_utc.isoformat()}. Use 
SignatureConfiguration(verification_time=...) "
+                "to verify at a different point in time."
+            )
+        if verification_time > cert.not_valid_after_utc:
+            return (
+                f"{cert_description}: certificate has expired at validation 
time "
+                f"{verification_time.isoformat()}; certificate is not valid 
after "
+                f"{cert.not_valid_after_utc.isoformat()}. Use 
SignatureConfiguration(verification_time=...) "
+                "if you must verify with an expired cert."
+            )
+        return None
+
+    def _check_cert_validity(self, cert, cert_source):
+        msg = self._get_cert_validity_error(cert, cert_source)
+        if msg is not None:
+            raise InvalidCertificate(msg)
 
     def _match_key_values(self, key_value, der_encoded_key_value, 
signing_cert, signature_alg):
         if self.config.ignore_ambiguous_key_info is True:
@@ -309,6 +361,8 @@
         uri_resolver: Optional[Callable] = None,
         id_attribute: Optional[str] = None,
         expect_config: SignatureConfiguration = SignatureConfiguration(),
+        ee_policy: Optional[x509.verification.ExtensionPolicy] = None,
+        ca_policy: Optional[x509.verification.ExtensionPolicy] = None,
         **deprecated_kwargs,
     ) -> Union[VerifyResult, List[VerifyResult]]:
         """
@@ -334,6 +388,17 @@
              config = SignatureConfiguration(location="./")
              XMLVerifier(...).verify(..., expect_config=config)
 
+         The ``location`` value describes the expected parent path for the 
``ds:Signature`` element. SignXML
+         appends ``ds:Signature`` to this path internally. For example, if a 
SAML ``Response`` root contains a
+         signed ``Assertion`` child, configure verification like this:
+
+         .. code-block:: python
+
+             config = SignatureConfiguration(
+                 location="./{urn:oasis:names:tc:SAML:2.0:assertion}Assertion/"
+             )
+             XMLVerifier().verify(response_xml, x509_cert=cert, 
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()``.
 
@@ -347,7 +412,8 @@
          ``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 
<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`` to give a custom CA.
+         domain name), or ``ca_pem_file`` to give a custom CA. If you need to 
verify a signature at the time it was
+         created rather than the current time, pass 
``expect_config=SignatureConfiguration(verification_time=...)``.
 
         :param data: Signature data to verify
         :type data: String, file-like object, or XML ElementTree Element API 
compatible object
@@ -383,6 +449,10 @@
         :param expect_config:
             Expected signature configuration. Pass a 
:class:`SignatureConfiguration` object to describe expected
             properties of the verified signature. Signatures with unexpected 
configurations will fail validation.
+        :param ee_policy:
+            Custom x509.verification.ExtensionPolicy to use for validating the 
end-entity certificate.
+        :param ca_policy:
+            Custom x509.verification.ExtensionPolicy to use for validating the 
CA certificate.
         :param deprecated_kwargs:
             Direct application of the parameters **require_x509**, 
**expect_references**, and
             **ignore_ambiguous_key_info** is deprecated. Use **expect_config** 
instead.
@@ -412,7 +482,7 @@
                 "SignXML received a PyOpenSSL object as x509_cert input. 
Please pass a Cryptography.X509 object instead.",
                 DeprecationWarning,
             )
-            x509_cert = x509_cert.to_cryptography()  # type: ignore[union-attr]
+            x509_cert = cast(Any, x509_cert).to_cryptography()
 
         self.x509_cert = x509_cert
 
@@ -449,6 +519,7 @@
                 if x509_data is None:
                     raise InvalidInput("Expected a X.509 certificate based 
signature")
                 certs = [cert.text for cert in self._findall(x509_data, 
"X509Certificate")]
+                cert_chain_source = "X509Data/X509Certificate"
                 if len(certs) == 0:
                     x509_iss = 
x509_data.find("ds:X509IssuerSerial/ds:X509IssuerName", namespaces=namespaces)
                     x509_sn = 
x509_data.find("ds:X509IssuerSerial/ds:X509SerialNumber", namespaces=namespaces)
@@ -461,6 +532,7 @@
                         )
                         if len(cert_chain) == 0:
                             raise InvalidCertificate("No certificate found for 
given X509 data")
+                        cert_chain_source = "cert_resolver result"
                         if not all(isinstance(c, x509.Certificate) for c in 
cert_chain):
                             cert_chain = 
[x509.load_pem_x509_certificate(add_pem_header(cert)) for cert in cert_chain]
                     else:
@@ -470,13 +542,26 @@
                 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)
+                cert_verifier = self.get_cert_chain_verifier(
+                    ca_pem_file=ca_pem_file, ee_policy=ee_policy, 
ca_policy=ca_policy
+                )
 
-                signing_cert = cert_verifier.verify(cert_chain)
+                try:
+                    signing_cert = cert_verifier.verify(cert_chain)
+                except InvalidCertificate as exc:
+                    if "valid at validation time" in str(exc):
+                        for index, cert in enumerate(cert_chain):
+                            self._check_cert_validity(cert, 
f"{cert_chain_source} certificate at index {index}")
+                    raise
+                signing_cert_source = f"verified {cert_chain_source} signing 
certificate"
             elif isinstance(self.x509_cert, x509.Certificate):
                 signing_cert = self.x509_cert
+                signing_cert_source = "provided x509_cert"
             else:
                 signing_cert = 
x509.load_pem_x509_certificate(add_pem_header(self.x509_cert))
+                signing_cert_source = "provided x509_cert"
+
+            self._check_cert_validity(signing_cert, signing_cert_source)
 
             if cert_subject_name is not None:
                 cn_oid = x509.oid.NameOID.COMMON_NAME
@@ -602,13 +687,16 @@
             key_data = b64decode(pub_key.text)[1:]
             x = bytes_to_long(key_data[: len(key_data) // 2])
             y = bytes_to_long(key_data[len(key_data) // 2 :])
-            curve_class = self.known_ecdsa_curves[named_curve.get("URI")]
+            curve_uri = named_curve.get("URI")
+            if curve_uri is None:
+                return False
+            curve = self.known_ecdsa_curves[curve_uri]
 
             pubk_curve = public_key.public_numbers().curve
             pubk_x = public_key.public_numbers().x
             pubk_y = public_key.public_numbers().y
 
-            return isinstance(pubk_curve, curve_class) and x == pubk_x and y 
== pubk_y
+            return pubk_curve.name == curve.name and x == pubk_x and y == 
pubk_y
 
         elif signature_alg.name.startswith("DSA_") and isinstance(public_key, 
dsa.DSAPublicKey):
             dsa_key_value = self._find(key_value, "DSAKeyValue")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/signxml/xades/xades.py 
new/signxml-5.1.0/signxml/xades/xades.py
--- old/signxml-4.4.0/signxml/xades/xades.py    2020-02-02 01:00:00.000000000 
+0100
+++ new/signxml-5.1.0/signxml/xades/xades.py    2020-02-02 01:00:00.000000000 
+0100
@@ -19,22 +19,38 @@
 The main difference with plain XML Signature is that HMAC algorithms are not 
supported, and SHA1 is deprecated.
 """
 
+from __future__ import annotations
+
 import datetime
 import os
 import secrets
 from base64 import b64decode, b64encode
-from dataclasses import astuple, dataclass
+from dataclasses import dataclass
 from functools import wraps
-from typing import Dict, List, Optional, Union
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
 
 from cryptography import x509
-from cryptography.hazmat.primitives.serialization import Encoding
+from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
 from lxml.etree import SubElement, _Element
 
 from .. import SignatureConfiguration, VerifyResult, XMLSignatureProcessor, 
XMLSigner, XMLVerifier
 from ..algorithms import DigestAlgorithm, digest_algorithm_implementations
 from ..exceptions import InvalidDigest, InvalidInput
-from ..util import SigningSettings, add_pem_header, ds_tag, namespaces, 
xades_tag
+from ..util import SigningSettings, add_pem_header, ds_tag, ensure_str, 
namespaces, xades_tag
+
+if TYPE_CHECKING:
+    from typing import Callable, Concatenate, ParamSpec
+
+    _P = ParamSpec("_P")
+
+
+def _with_parent_signature(
+    wrapped: Callable[Concatenate[XMLSigner, _P], _Element],
+) -> Callable[[Callable[..., _Element]], Callable[Concatenate[XMLSigner, _P], 
_Element]]:
+    def decorator(wrapper: Callable[..., _Element]) -> 
Callable[Concatenate[XMLSigner, _P], _Element]:
+        return cast(Any, wraps(wrapped)(wrapper))
+
+    return decorator
 
 
 @dataclass(frozen=True)
@@ -129,8 +145,8 @@
         self.data_object_format = data_object_format
         self.namespaces.update(xades=namespaces.xades)
 
-    @wraps(XMLSigner.sign)
-    def sign(self, data, always_add_key_value: bool = True, **kwargs) -> 
_Element:  # type: ignore[override]
+    @_with_parent_signature(XMLSigner.sign)
+    def sign(self, data, always_add_key_value: bool = True, **kwargs: Any) -> 
_Element:
         return super().sign(data=data, 
always_add_key_value=always_add_key_value, **kwargs)
 
     def _get_token(self, length=4):
@@ -221,11 +237,15 @@
                 digest_value_node.text = 
b64encode(cert_digest_sha1_bytes).decode()
                 issuer_serial = SubElement(cert_node_legacy, 
xades_tag("IssuerSerial"), nsmap=self.namespaces)
                 issuer_name = SubElement(issuer_serial, 
ds_tag("X509IssuerName"), nsmap=self.namespaces)
-                issuer_name.text = "C={C},O={O},OU={OU},CN={CN}".format(  # 
type:ignore[str-bytes-safe]
-                    
C=loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.COUNTRY_NAME)[0].value,
-                    
O=loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.ORGANIZATION_NAME)[0].value,
-                    
OU=loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.ORGANIZATIONAL_UNIT_NAME)[0].value,
-                    
CN=loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0].value,
+                issuer_name.text = "C={C},O={O},OU={OU},CN={CN}".format(
+                    
C=ensure_str(loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.COUNTRY_NAME)[0].value),
+                    O=ensure_str(
+                        
loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.ORGANIZATION_NAME)[0].value
+                    ),
+                    OU=ensure_str(
+                        
loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.ORGANIZATIONAL_UNIT_NAME)[0].value
+                    ),
+                    
CN=ensure_str(loaded_cert.issuer.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)[0].value),
                 )
                 serial_number = SubElement(issuer_serial, 
ds_tag("X509SerialNumber"), nsmap=self.namespaces)
                 serial_number.text = str(loaded_cert.serial_number)
@@ -297,37 +317,80 @@
     Create a new XAdES Signature Verifier object, which can be used to verify 
multiple pieces of data.
     """
 
+    expect_signature_policy: Optional[XAdESSignaturePolicy]
+
     # TODO: document/support SignatureTimeStamp / timestamp attestation
     # SignatureTimeStamp is required by certain profiles but is an unsigned 
property
     def _verify_signing_time(self, verify_result: VerifyResult):
         pass
 
-    def _verify_cert_digest(self, signing_cert_node, expect_cert):
-        for cert in self._findall(signing_cert_node, "xades:Cert"):
+    def _get_cert_public_key(self, cert: x509.Certificate) -> bytes:
+        return cert.public_key().public_bytes(Encoding.PEM, 
PublicFormat.SubjectPublicKeyInfo)
+
+    def _add_cert_if_missing(self, certs: List[x509.Certificate], cert: 
x509.Certificate) -> None:
+        cert_der = cert.public_bytes(Encoding.DER)
+        if not any(candidate.public_bytes(Encoding.DER) == cert_der for 
candidate in certs):
+            certs.append(cert)
+
+    def _find_signing_cert(
+        self, verify_result: VerifyResult, certs: List[x509.Certificate]
+    ) -> Optional[x509.Certificate]:
+        if self.x509_cert is not None:
+            if isinstance(self.x509_cert, x509.Certificate):
+                return self.x509_cert
+            return 
x509.load_pem_x509_certificate(add_pem_header(self.x509_cert))
+
+        for cert in certs:
+            if self._get_cert_public_key(cert) == verify_result.signature_key:
+                return cert
+        return None
+
+    def _verify_cert_digest(
+        self,
+        signing_cert_node,
+        certs: List[x509.Certificate],
+        signing_cert: Optional[x509.Certificate],
+    ):
+        if len(certs) == 0:
+            raise InvalidInput("Expected to find an X509Certificate element in 
the signature")
+
+        for cert_idx, cert in enumerate(self._findall(signing_cert_node, 
"xades:Cert")):
             cert_digest = self._find(cert, "xades:CertDigest")
             digest_alg = DigestAlgorithm(self._find(cert_digest, 
"DigestMethod").get("Algorithm"))
             digest_value = self._find(cert_digest, "DigestValue")
             # check spec for specific method of retrieving cert
             digest_alg_impl = digest_algorithm_implementations[digest_alg]()
-            if b64decode(digest_value.text) != 
expect_cert.fingerprint(digest_alg_impl):
+            digest_value_bytes = b64decode(digest_value.text)
+            if not any(digest_value_bytes == 
candidate.fingerprint(digest_alg_impl) for candidate in certs):
                 raise InvalidDigest("Digest mismatch for certificate digest")
 
+            if (
+                cert_idx == 0
+                and signing_cert is not None
+                and digest_value_bytes != 
signing_cert.fingerprint(digest_alg_impl)
+            ):
+                raise InvalidDigest("Digest mismatch for signing certificate 
digest")
+
     def _verify_cert_digests(self, verify_result: VerifyResult):
-        x509_data = verify_result.signature_xml.find("ds:KeyInfo/ds:X509Data", 
namespaces=namespaces)
-        cert_from_key_info = x509.load_pem_x509_certificate(
-            add_pem_header(self._find(x509_data, "X509Certificate").text)
-        )
+        certs = []
+        for x509_data in self._findall(verify_result.signature_xml, 
"ds:KeyInfo/ds:X509Data"):
+            for cert in self._findall(x509_data, "X509Certificate"):
+                
certs.append(x509.load_pem_x509_certificate(add_pem_header(cert.text)))
+        signing_cert = self._find_signing_cert(verify_result, certs)
+        if signing_cert is not None:
+            self._add_cert_if_missing(certs, signing_cert)
+
         signed_signature_props = self._find(verify_result.signed_xml, 
"xades:SignedSignatureProperties")
-        signing_cert = self._find(signed_signature_props, 
"xades:SigningCertificate", require=False)
-        signing_cert_v2 = self._find(signed_signature_props, 
"xades:SigningCertificateV2", require=False)
-        if signing_cert is None and signing_cert_v2 is None:
+        signing_cert_node = self._find(signed_signature_props, 
"xades:SigningCertificate", require=False)
+        signing_cert_v2_node = self._find(signed_signature_props, 
"xades:SigningCertificateV2", require=False)
+        if signing_cert_node is None and signing_cert_v2_node is None:
             raise InvalidInput("Expected to find XML element 
xades:SigningCertificate or xades:SigningCertificateV2")
-        if signing_cert is not None and signing_cert_v2 is not None:
+        if signing_cert_node is not None and signing_cert_v2_node is not None:
             raise InvalidInput("Expected to find exactly one of 
xades:SigningCertificate or xades:SigningCertificateV2")
-        if signing_cert is not None:
-            self._verify_cert_digest(signing_cert, 
expect_cert=cert_from_key_info)
-        elif signing_cert_v2 is not None:
-            self._verify_cert_digest(signing_cert_v2, 
expect_cert=cert_from_key_info)
+        if signing_cert_node is not None:
+            self._verify_cert_digest(signing_cert_node, certs=certs, 
signing_cert=signing_cert)
+        elif signing_cert_v2_node is not None:
+            self._verify_cert_digest(signing_cert_v2_node, certs=certs, 
signing_cert=signing_cert)
 
     def _verify_signature_policy(self, verify_result: VerifyResult, 
expect_signature_policy: XAdESSignaturePolicy):
         signed_signature_props = self._find(verify_result.signed_xml, 
"xades:SignedSignatureProperties")
@@ -362,14 +425,14 @@
             )
         return self._find(verify_result.signed_xml, 
"xades:SignedSignatureProperties")
 
-    def verify(  # type: ignore[override]
+    def verify(
         self,
         data,
         *,
         expect_signature_policy: Optional[XAdESSignaturePolicy] = None,
-        expect_config: XAdESSignatureConfiguration = 
XAdESSignatureConfiguration(),
-        **xml_verifier_args,
-    ) -> List[XAdESVerifyResult]:
+        expect_config: SignatureConfiguration = XAdESSignatureConfiguration(),
+        **xml_verifier_args: Any,
+    ) -> List[VerifyResult]:
         """
         Verify the XAdES signature supplied in the data and return a list of 
:class:`XAdESVerifyResult` data structures
         representing the data signed by the signature, or raise an exception 
if the signature is not valid. This method
@@ -396,12 +459,16 @@
             if verify_result.signed_xml is None:
                 continue
             if verify_result.signed_xml.tag == xades_tag("SignedProperties"):
-                verify_results[i] = XAdESVerifyResult(  # type: ignore[misc]
-                    *astuple(verify_result), 
signed_properties=self._verify_signed_properties(verify_result)
+                verify_results[i] = XAdESVerifyResult(
+                    signed_data=verify_result.signed_data,
+                    signed_xml=verify_result.signed_xml,
+                    signature_xml=verify_result.signature_xml,
+                    signature_key=verify_result.signature_key,
+                    
signed_properties=self._verify_signed_properties(verify_result),
                 )
                 break
         else:
             raise InvalidInput("Expected to find a xades:SignedProperties 
element")
 
         # TODO: assert all mandatory signed properties are set
-        return verify_results  # type: ignore[return-value]
+        return verify_results
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/test/issues/issue_284_expired_cert.xml 
new/signxml-5.1.0/test/issues/issue_284_expired_cert.xml
--- old/signxml-4.4.0/test/issues/issue_284_expired_cert.xml    1970-01-01 
01:00:00.000000000 +0100
+++ new/signxml-5.1.0/test/issues/issue_284_expired_cert.xml    2020-02-02 
01:00:00.000000000 +0100
@@ -0,0 +1 @@
+<NFe xmlns="http://www.portalfiscal.inf.br/nfe";><infNFe 
Id="NFe41180508187168000160550010020000001020000009" 
versao="4.00"><ide><cUF>41</cUF><cNF>02000000</cNF><natOp>VENDA DE MERCADORIA 
ADQ. DE TERCEIRO - PF E PJ NAO 
CONTRIBUI</natOp><mod>55</mod><serie>1</serie><nNF>2000000</nNF><dhEmi>2018-05-28T17:00:00-03:00</dhEmi><dhSaiEnt>2018-05-28T17:00:00-03:00</dhSaiEnt><tpNF>1</tpNF><idDest>1</idDest><cMunFG>4115200</cMunFG><tpImp>1</tpImp><tpEmis>1</tpEmis><cDV>9</cDV><tpAmb>2</tpAmb><finNFe>1</finNFe><indFinal>1</indFinal><indPres>1</indPres><procEmi>0</procEmi><verProc>TecnoERP
 - 1.2.3</verProc></ide><emit><CNPJ>08187168000160</CNPJ><xNome>TECNOSPEED 
&amp; TECNOLOGIA</xNome><xFant>TECNOSPEED &amp; 
TECNOLOGIA</xFant><enderEmit><xLgr>AVENIDA DUQUE DE 
CAXIAS</xLgr><nro>882</nro><xBairro>ZONA 
01</xBairro><cMun>4115200</cMun><xMun>MARINGA</xMun><UF>PR</UF><CEP>87020025</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>4430379500</fone></enderEmit><IE>9044016688</IE><CRT>3</CRT></emit><de
 st><CNPJ>08187168000160</CNPJ><xNome>NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - 
SEM VALOR FISCAL</xNome><enderDest><xLgr>AVENIDA DUQUE DE 
CAXIAS</xLgr><nro>882</nro><xBairro>ZONA 
01</xBairro><cMun>4115200</cMun><xMun>MARINGA</xMun><UF>PR</UF><CEP>87020025</CEP><cPais>1058</cPais><xPais>BRASIL</xPais><fone>4430379500</fone></enderDest><indIEDest>1</indIEDest><IE>9044016688</IE></dest><det
 nItem="1"><prod><cProd>0999</cProd><cEAN/><xProd>AMIDO DE 
MILHO</xProd><NCM>11081200</NCM><CEST>0123456</CEST><indEscala>S</indEscala><CFOP>5102</CFOP><uCom>CX</uCom><qCom>1</qCom><vUnCom>0.0100</vUnCom><vProd>0.01</vProd><cEANTrib/><uTrib>CX</uTrib><qTrib>1</qTrib><vUnTrib>0.0100</vUnTrib><indTot>1</indTot></prod><imposto><ICMS><ICMS00><orig>0</orig><CST>00</CST><modBC>0</modBC><vBC>0.01</vBC><pICMS>12.00</pICMS><vICMS>0.01</vICMS></ICMS00></ICMS><PIS><PISAliq><CST>01</CST><vBC>0.01</vBC><pPIS>1.65</pPIS><vPIS>0.00</vPIS></PISAliq></PIS><COFINS><COFINSAliq><CST>01</CST><vBC>0.01</vBC><pCOFINS>7.60</
 
pCOFINS><vCOFINS>0.01</vCOFINS></COFINSAliq></COFINS></imposto></det><total><ICMSTot><vBC>0.01</vBC><vICMS>0.01</vICMS><vICMSDeson>0.00</vICMSDeson><vFCP>0.00</vFCP><vBCST>0.00</vBCST><vST>0.00</vST><vFCPST>0.00</vFCPST><vFCPSTRet>0.00</vFCPSTRet><vProd>0.01</vProd><vFrete>0.00</vFrete><vSeg>0.00</vSeg><vDesc>0.00</vDesc><vII>0.00</vII><vIPI>0.00</vIPI><vIPIDevol>0.00</vIPIDevol><vPIS>0.00</vPIS><vCOFINS>0.01</vCOFINS><vOutro>0.00</vOutro><vNF>0.01</vNF></ICMSTot></total><transp><modFrete>0</modFrete></transp><cobr><fat><nFat>123</nFat><vOrig>0.01</vOrig><vLiq>0.01</vLiq></fat></cobr><pag><detPag><tPag>15</tPag><vPag>0.01</vPag></detPag></pag></infNFe><Signature
 xmlns="http://www.w3.org/2000/09/xmldsig#";><SignedInfo><CanonicalizationMethod 
Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod 
Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference 
URI="#NFe41180508187168000160550010020000001020000009"><Transforms><Transform 
Algorithm="http://www.w3
 .org/2000/09/xmldsig#enveloped-signature"/><Transform 
Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod
 
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>QptSZtOaO3h0wlX5oRCZwDraOCo=</DigestValue></Reference></SignedInfo><SignatureValue>gMr87sT9Ru5MLGTYex6Bzqa1DDW1lwghCJQ440adN0BLGl/eY823QG0GWzHc8j4dyBmtYBt1Dt4HKCj1BFn9NPE0zWh4Jd8Nvo2WrVR2dq0BOct9FjkZuRK42kgZbFl5Oz1a0ROb52nhxIAwkJyRwkXzo/XmdleRML2x6X+PPCf3oZVo0GSr54jSU7M+yHQb0CESalpqCgzPw6bLYscD5gdy7dPyZmIxykOCvud3ChkuB7glwLcpq7cT7r8jb7i8WVz098TWyoMUQfapXRu8/TGmkHjV0JBWfZbzSgVYe5al8/LLUu2J35nggcFMhKbhHMq5jstTCKhq9RgjMGCv4Q==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIHsTCCBZmgAwIBAgIIJkEYBQJA1oowDQYJKoZIhvcNAQELBQAwgYkxCzAJBgNVBAYTAkJSMRMwEQYDVQQKEwpJQ1AtQnJhc2lsMTQwMgYDVQQLEytBdXRvcmlkYWRlIENlcnRpZmljYWRvcmEgUmFpeiBCcmFzaWxlaXJhIHYyMRIwEAYDVQQLEwlBQyBTT0xVVEkxGzAZBgNVBAMTEkFDIFNPTFVUSSBNdWx0aXBsYTAeFw0xODA1MDMyMDQ5MTBaFw0xOTA1MDMxNjM0MDBaMIHNMQswCQYDVQQGEwJCUjETMBEGA1
 
UEChMKSUNQLUJyYXNpbDE0MDIGA1UECxMrQXV0b3JpZGFkZSBDZXJ0aWZpY2Fkb3JhIFJhaXogQnJhc2lsZWlyYSB2MjESMBAGA1UECxMJQUMgU09MVVRJMRswGQYDVQQLExJBQyBTT0xVVEkgTXVsdGlwbGExGjAYBgNVBAsTEUNlcnRpZmljYWRvIFBKIEExMSYwJAYDVQQDEx1URUNOT1NQRUVEIFMgQTowODE4NzE2ODAwMDE2MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJGqmKAfyc2BSRm0KslakFBx3qe/tthZavpKdbFF6lsKlKDNgiQ6W3wsyzPVUC1D4/xvlhTl1oSJIbiNtfmBQOqt68vm7r3lpXKLE4v1Q5VWTpAOndXoe6C4bpePr4MQxDy8XjCeVav2tU6Ejoj5PbYeKD88eucxKURoQVeaHIiw9aZ/cNF561M7+7Z4OQLdAfDgsQxj/R0PrvXKq4Ag0aE8aeR3J7IKF+ah2bEl7GvGBFrJziFg33H4ZcMEpPxhxr5uJfF4GrY65G5/NxCuQkOsR+h8DcQomNjooJH++M6gbSj2chqPtkVV7DamFkaU1tzdERw8N5YZBXh4Ft2h0/0CAwEAAaOCAtUwggLRMFQGCCsGAQUFBwEBBEgwRjBEBggrBgEFBQcwAoY4aHR0cDovL2NjZC5hY3NvbHV0aS5jb20uYnIvbGNyL2FjLXNvbHV0aS1tdWx0aXBsYS12MS5wN2IwHQYDVR0OBBYEFMVOO7pkK0WOMqZR+r3FAAO/aqRoMAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUNa4xFPZe0npPWP40qBpnlwrEmwcwXgYDVR0gBFcwVTBTBgZgTAECASYwSTBHBggrBgEFBQcCARY7aHR0cHM6Ly9jY2QuYWNzb2x1dGkuY29tLmJyL2RvY3MvZHBjLWFjLXNvbHV0aS1tdWx0aXBsYS5wZGYwgd4
 
GA1UdHwSB1jCB0zA+oDygOoY4aHR0cDovL2NjZC5hY3NvbHV0aS5jb20uYnIvbGNyL2FjLXNvbHV0aS1tdWx0aXBsYS12MS5jcmwwP6A9oDuGOWh0dHA6Ly9jY2QyLmFjc29sdXRpLmNvbS5ici9sY3IvYWMtc29sdXRpLW11bHRpcGxhLXYxLmNybDBQoE6gTIZKaHR0cDovL3JlcG9zaXRvcmlvLmljcGJyYXNpbC5nb3YuYnIvbGNyL0FDU09MVVRJL2FjLXNvbHV0aS1tdWx0aXBsYS12MS5jcmwwDgYDVR0PAQH/BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDCBvQYDVR0RBIG1MIGygR9lcmlrZS5hbG1laWRhQHRlY25vc3BlZWQuY29tLmJyoCEGBWBMAQMCoBgTFkVSSUtFIExFSVRFIERFIEFMTUVJREGgGQYFYEwBAwOgEBMOMDgxODcxNjgwMDAxNjCgOAYFYEwBAwSgLxMtMTQxMDE5Nzg5OTA0OTMyNTkwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwoBcGBWBMAQMHoA4TDDAwMDAwMDAwMDAwMDANBgkqhkiG9w0BAQsFAAOCAgEARGADzr06vz0jDtcj6SETQUWv5WNnBqjf1paGAghO0dxDWdhh02KnQdg152BdyjBhb/3vMYjkqOiv8Sg6oVqqGHIxzD7mOj0ffLZPE4GHNvd5UgMecAkdnarPZ7QP/VmEcYstyaO/6hjrON1XWFZR/VnLJGijDl1PekYyRYJO8WUo6F++VEU8Vi1/ef0X5Y6kO5C20eI+o4hRQPDai13HvlHvrlpSZTUYpydqn07WYZ587qkNhRcggMRQ+QWPVTieW8YDpAPuKhrcwN3mphePVCnRlEyX2eG2IiBRCFl1cqVZ2RJSS0DCZMR1pZ8FZiRJH5M8FAmQPhAFXLX30BlRXtA+2MUGL+t56QNn
 
Rs+LJW5ViMo6DaNt/jL5mW92gHNB7CatixAcmop1OhN8t9/Krzv6x4JbpAJQ4e2zjWVpWoB8Nm6WrsC6RBUsBdELhWu5zRB2RTnrJpf5SiY6CcHyOV5haZVypQ0wjq0pIex9GpGWT2wapH+/orz+Jc2G42nJuxvvz1Sq7o6Ws2wILKWppZ0vzqTVQQYmWnNnrzu+Vmsp5QTE1eyI3ffaCmRHaBs9zXdSNkuzAfBcKzCH0J8iyfhkZ1EaGEfVe/fxPqK2QUMcuESdybMc3zwJ5K4CA0dyDB9hi8xkXzg/xhb+RkaEqQoyO6Y5EJYoKKWvxl2oXw8=</X509Certificate></X509Data></KeyInfo></Signature></NFe>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/signxml-4.4.0/test/test.py 
new/signxml-5.1.0/test/test.py
--- old/signxml-4.4.0/test/test.py      2020-02-02 01:00:00.000000000 +0100
+++ new/signxml-5.1.0/test/test.py      2020-02-02 01:00:00.000000000 +0100
@@ -4,20 +4,24 @@
 import os
 import re
 import sys
+import tempfile
+import time
 import unittest
 from base64 import b64decode, b64encode
 from concurrent.futures import ThreadPoolExecutor
 from dataclasses import replace
-from datetime import datetime
+from datetime import datetime, timezone
 from glob import glob
 from xml.etree import ElementTree as stdlibElementTree
 
 import cryptography.exceptions
+from cryptography import x509
+from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa
-from cryptography.x509 import load_der_x509_certificate, 
load_pem_x509_certificate
 from lxml import etree
 
 sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 
"..")))
+import signxml.processor as processor  # noqa:E402
 from signxml import (  # noqa:E402
     CanonicalizationMethod,
     DigestAlgorithm,
@@ -59,6 +63,18 @@
 xades_sha1_ok = XAdESSignatureConfiguration(
     signature_methods=list(SignatureMethod), 
digest_algorithms=list(DigestAlgorithm)
 )
+example_cert_verification_time = datetime(2015, 1, 1, tzinfo=timezone.utc)
+example_cert_config = 
SignatureConfiguration(verification_time=example_cert_verification_time)
+sha1_ok_example_cert = replace(sha1_ok, 
verification_time=example_cert_verification_time)
+xades_sha1_ok_example_cert = replace(xades_sha1_ok, 
verification_time=example_cert_verification_time)
+
+
+def get_cert_midpoint_verification_time(cert_text):
+    try:
+        cert = 
x509.load_der_x509_certificate(b64decode("".join(cert_text.split())))
+    except ValueError:
+        return None
+    return cert.not_valid_before_utc + (cert.not_valid_after_utc - 
cert.not_valid_before_utc) / 2
 
 
 def reset_tree(t, method):
@@ -69,12 +85,33 @@
             s.getparent().remove(s)
 
 
+def get_no_dsig_key_usage_ee_policy_verifier_at_time(verification_time):
+    class _Verifier(XMLVerifier):
+        def verify(self, data, **kwargs):
+            expect_config = kwargs.pop("expect_config", 
SignatureConfiguration())
+            expect_config = replace(expect_config, 
verification_time=verification_time)
+            return super().verify(data, expect_config=expect_config, **kwargs)
+
+        def get_cert_chain_verifier(self, ca_pem_file, ee_policy, ca_policy):
+            ee_policy = x509.verification.ExtensionPolicy.webpki_defaults_ee()
+            return super().get_cert_chain_verifier(ca_pem_file, ee_policy, 
ca_policy)
+
+    return _Verifier()
+
+
+def get_no_dsig_key_usage_ee_policy_verifier_for_year(year: int):
+    verification_time = datetime(year, 1, 1, tzinfo=timezone.utc)
+    return get_no_dsig_key_usage_ee_policy_verifier_at_time(verification_time)
+
+
 def get_verifier_for_year(year: int):
+    verification_time = datetime(year, 1, 1, tzinfo=timezone.utc)
+
     class _Verifier(XMLVerifier):
-        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
+        def verify(self, data, **kwargs):
+            expect_config = kwargs.pop("expect_config", 
SignatureConfiguration())
+            expect_config = replace(expect_config, 
verification_time=verification_time)
+            return super().verify(data, expect_config=expect_config, **kwargs)
 
     return _Verifier()
 
@@ -117,6 +154,7 @@
             data=etree.parse(example_file),
             x509_cert=cert,
             expect_references=2,
+            expect_config=example_cert_config,
         )
 
     def test_example_multi_unspecified_reference_count(self):
@@ -128,11 +166,52 @@
             data=etree.parse(example_file),
             x509_cert=cert,
             expect_references=True,
+            expect_config=example_cert_config,
         )
 
         self.assertIsInstance(res, list)
         self.assertEqual(2, len(res))
 
+    def test_schema_cache_is_scoped_to_processor_subclasses(self):
+        for first, second in ((XMLVerifier, XAdESVerifier), (XAdESVerifier, 
XMLVerifier)):
+            for cls in (XMLVerifier, XAdESVerifier):
+                if "_schemas" in cls.__dict__:
+                    delattr(cls, "_schemas")
+
+            first_schemas = first.schemas()
+            second_schemas = second.schemas()
+
+            self.assertIsNot(first_schemas, second_schemas)
+            self.assertEqual(len(first_schemas), len(first.schema_files))
+            self.assertEqual(len(second_schemas), len(second.schema_files))
+
+    def test_schema_cache_is_thread_safe_on_first_access(self):
+        class ThreadedProcessor(processor.XMLProcessor):
+            schema_files = ["schema-a.xsd", "schema-b.xsd"]
+
+        original_get_schema = processor.get_schema
+        original_schemas = processor.XMLProcessor._schemas
+        calls = []
+
+        def fake_get_schema(schema_file):
+            time.sleep(0.01)
+            calls.append(schema_file)
+            return schema_file
+
+        processor.XMLProcessor._schemas = []
+        processor.get_schema = fake_get_schema
+        try:
+            with ThreadPoolExecutor(max_workers=8) as executor:
+                results = list(executor.map(lambda _: 
ThreadedProcessor.schemas(), range(8)))
+        finally:
+            processor.get_schema = original_get_schema
+            processor.XMLProcessor._schemas = original_schemas
+            if "_schemas" in ThreadedProcessor.__dict__:
+                delattr(ThreadedProcessor, "_schemas")
+
+        self.assertEqual(ThreadedProcessor.schema_files, calls)
+        self.assertTrue(all(result is results[0] for result in results))
+
 
 class TestSignXML(unittest.TestCase, LoadExampleKeys):
     def setUp(self):
@@ -260,7 +339,7 @@
             pass
 
     def test_x509_certs(self):
-        verifier = get_verifier_for_year(2015)
+        verifier = get_no_dsig_key_usage_ee_policy_verifier_for_year(2015)
         tree = etree.parse(self.example_xml_files[0])
         ca_pem_file = os.path.join(os.path.dirname(__file__), 
"example-ca.pem").encode("utf-8")
         crt, key = self.load_example_keys()
@@ -270,8 +349,8 @@
             signer = XMLSigner(method=method, 
signature_algorithm=SignatureMethod.RSA_SHA256)
             signed = signer.sign(data, key=key, cert=crt)
             signed_data = etree.tostring(signed)
-            verifier.verify(signed_data, x509_cert=crt)
-            verifier.verify(signed_data, 
x509_cert=load_pem_x509_certificate(crt))
+            verifier.verify(signed_data, x509_cert=crt, 
expect_config=example_cert_config)
+            verifier.verify(signed_data, 
x509_cert=x509.load_pem_x509_certificate(crt))
             verifier.verify(signed_data, x509_cert=crt, 
cert_subject_name="*.example.com")
 
             with self.assertRaises(ValueError):
@@ -289,10 +368,218 @@
 
             # TODO: negative: verify with wrong cert, wrong CA
 
+    def test_x509_cert_chain_verification_time(self):
+        def make_key_usage(digital_signature=False, key_cert_sign=False, 
crl_sign=False):
+            return x509.KeyUsage(
+                digital_signature=digital_signature,
+                content_commitment=False,
+                key_encipherment=False,
+                data_encipherment=False,
+                key_agreement=False,
+                key_cert_sign=key_cert_sign,
+                crl_sign=crl_sign,
+                encipher_only=False,
+                decipher_only=False,
+            )
+
+        tree = etree.parse(self.example_xml_files[0])
+        ca_key = ec.generate_private_key(ec.SECP384R1())
+        ca_name = x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, 
"SignXML Test CA")])
+        ca_cert = (
+            x509.CertificateBuilder()
+            .subject_name(ca_name)
+            .issuer_name(ca_name)
+            .public_key(ca_key.public_key())
+            .serial_number(x509.random_serial_number())
+            .not_valid_before(datetime(2020, 1, 1, tzinfo=timezone.utc))
+            .not_valid_after(datetime(2030, 1, 1, tzinfo=timezone.utc))
+            .add_extension(x509.BasicConstraints(ca=True, path_length=None), 
critical=True)
+            .add_extension(make_key_usage(key_cert_sign=True, crl_sign=True), 
critical=True)
+            
.add_extension(x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()), 
critical=False)
+            .sign(ca_key, hashes.SHA384())
+        )
+
+        leaf_key = ec.generate_private_key(ec.SECP384R1())
+        leaf_cert = (
+            x509.CertificateBuilder()
+            
.subject_name(x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "XML 
Signature Test")]))
+            .issuer_name(ca_cert.subject)
+            .public_key(leaf_key.public_key())
+            .serial_number(x509.random_serial_number())
+            .not_valid_before(datetime(2020, 1, 1, tzinfo=timezone.utc))
+            .not_valid_after(datetime(2021, 1, 1, tzinfo=timezone.utc))
+            .add_extension(x509.BasicConstraints(ca=False, path_length=None), 
critical=True)
+            .add_extension(make_key_usage(digital_signature=True), 
critical=True)
+            
.add_extension(x509.SubjectKeyIdentifier.from_public_key(leaf_key.public_key()),
 critical=False)
+            
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
 critical=False)
+            .sign(ca_key, hashes.SHA384())
+        )
+
+        signer = XMLSigner(method=methods.enveloped, 
signature_algorithm=SignatureMethod.ECDSA_SHA384)
+        signed = signer.sign(tree.getroot(), key=leaf_key, cert=[leaf_cert])
+        signed_data = etree.tostring(signed)
+
+        XMLVerifier().verify(
+            signed_data,
+            x509_cert=leaf_cert,
+            
expect_config=SignatureConfiguration(verification_time=datetime(2020, 6, 1, 
tzinfo=timezone.utc)),
+        )
+        with self.assertRaisesRegex(
+            InvalidCertificate, "provided x509_cert.*certificate is not yet 
valid.*SignatureConfiguration"
+        ):
+            XMLVerifier().verify(
+                signed_data,
+                x509_cert=leaf_cert,
+                
expect_config=SignatureConfiguration(verification_time=datetime(2019, 1, 1, 
tzinfo=timezone.utc)),
+            )
+        with self.assertRaisesRegex(
+            InvalidCertificate, "provided x509_cert.*certificate has 
expired.*SignatureConfiguration"
+        ):
+            XMLVerifier().verify(
+                signed_data,
+                x509_cert=leaf_cert,
+                
expect_config=SignatureConfiguration(verification_time=datetime(2025, 1, 1, 
tzinfo=timezone.utc)),
+            )
+
+        with tempfile.TemporaryDirectory() as temp_dir:
+            ca_pem_file = os.path.join(temp_dir, "ca.pem")
+            with open(ca_pem_file, "wb") as fh:
+                fh.write(ca_cert.public_bytes(serialization.Encoding.PEM))
+
+            XMLVerifier().verify(
+                signed_data,
+                ca_pem_file=ca_pem_file,
+                
expect_config=SignatureConfiguration(verification_time=datetime(2020, 6, 1, 
tzinfo=timezone.utc)),
+            )
+            with self.assertRaisesRegex(
+                InvalidCertificate,
+                "X509Data/X509Certificate certificate at index 0.*certificate 
has expired.*SignatureConfiguration",
+            ):
+                XMLVerifier().verify(
+                    signed_data,
+                    ca_pem_file=ca_pem_file,
+                    
expect_config=SignatureConfiguration(verification_time=datetime(2025, 1, 1, 
tzinfo=timezone.utc)),
+                )
+
+    def test_issue_284_expired_certificate_can_verify_at_signing_time(self):
+        issue_file = os.path.join(os.path.dirname(__file__), "issues", 
"issue_284_expired_cert.xml")
+        with open(issue_file, "rb") as fh:
+            signed_xml = fh.read()
+        doc = etree.fromstring(signed_xml)
+        cert_text = doc.find(".//ds:X509Certificate", 
namespaces=namespaces).text
+        signing_cert = x509.load_der_x509_certificate(b64decode(cert_text))
+        config = SignatureConfiguration(
+            signature_methods=frozenset({SignatureMethod.RSA_SHA1}),
+            digest_algorithms=frozenset({DigestAlgorithm.SHA1}),
+            
default_reference_c14n_method=CanonicalizationMethod.CANONICAL_XML_1_0,
+        )
+
+        verifier = XMLVerifier()
+        verifier.excise_empty_xmlns_declarations = True
+        with self.assertRaisesRegex(
+            InvalidCertificate, "provided x509_cert.*certificate has 
expired.*SignatureConfiguration"
+        ):
+            verifier.verify(signed_xml, x509_cert=signing_cert, 
expect_config=config)
+
+        verifier = XMLVerifier()
+        verifier.excise_empty_xmlns_declarations = True
+        result = verifier.verify(
+            signed_xml,
+            x509_cert=signing_cert,
+            expect_config=replace(config, verification_time=datetime(2018, 5, 
28, 17, 0, tzinfo=timezone.utc)),
+        )
+        self.assertIsInstance(result, VerifyResult)
+
+    def test_sign_rejects_invalid_x509_cert_input(self):
+        _, key = self.load_example_keys()
+        data = etree.parse(self.example_xml_files[0]).getroot()
+        invalid_pem = "-----BEGIN CERTIFICATE-----\nnot a 
certificate\n-----END CERTIFICATE-----"
+
+        invalid_cert_inputs = [
+            ("", "No PEM-encoded certificates found"),
+            ([], "No certificates found"),
+            ([""], "Invalid X.509 certificate"),
+            (invalid_pem, "Invalid X.509 certificate"),
+            ([invalid_pem], "Invalid X.509 certificate"),
+        ]
+        for cert, message in invalid_cert_inputs:
+            with self.subTest(cert=repr(cert)):
+                with self.assertRaisesRegex(InvalidInput, message):
+                    XMLSigner().sign(data, key=key, cert=cert)
+
+    def test_x509_cert_chain_requires_digital_signature_key_usage(self):
+        def make_key_usage(digital_signature=False, key_cert_sign=False, 
crl_sign=False):
+            return x509.KeyUsage(
+                digital_signature=digital_signature,
+                content_commitment=False,
+                key_encipherment=False,
+                data_encipherment=False,
+                key_agreement=False,
+                key_cert_sign=key_cert_sign,
+                crl_sign=crl_sign,
+                encipher_only=False,
+                decipher_only=False,
+            )
+
+        verifier = get_verifier_for_year(2027)
+        tree = etree.parse(self.example_xml_files[0])
+        ca_key = ec.generate_private_key(ec.SECP384R1())
+        ca_name = x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, 
"SignXML Test CA")])
+        ca_cert = (
+            x509.CertificateBuilder()
+            .subject_name(ca_name)
+            .issuer_name(ca_name)
+            .public_key(ca_key.public_key())
+            .serial_number(x509.random_serial_number())
+            .not_valid_before(datetime(2026, 1, 1))
+            .not_valid_after(datetime(2030, 1, 1))
+            .add_extension(x509.BasicConstraints(ca=True, path_length=None), 
critical=True)
+            .add_extension(make_key_usage(key_cert_sign=True, crl_sign=True), 
critical=True)
+            
.add_extension(x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()), 
critical=False)
+            .sign(ca_key, hashes.SHA384())
+        )
+
+        def make_leaf_cert(digital_signature):
+            leaf_key = ec.generate_private_key(ec.SECP384R1())
+            leaf_cert = (
+                x509.CertificateBuilder()
+                
.subject_name(x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "XML 
Signature Test")]))
+                .issuer_name(ca_cert.subject)
+                .public_key(leaf_key.public_key())
+                .serial_number(x509.random_serial_number())
+                .not_valid_before(datetime(2026, 1, 1))
+                .not_valid_after(datetime(2030, 1, 1))
+                .add_extension(x509.BasicConstraints(ca=False, 
path_length=None), critical=True)
+                
.add_extension(make_key_usage(digital_signature=digital_signature), 
critical=True)
+                
.add_extension(x509.SubjectKeyIdentifier.from_public_key(leaf_key.public_key()),
 critical=False)
+                
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(ca_key.public_key()),
 critical=False)
+                .sign(ca_key, hashes.SHA384())
+            )
+            return leaf_key, leaf_cert
+
+        signer = XMLSigner(method=methods.enveloped, 
signature_algorithm=SignatureMethod.ECDSA_SHA384)
+        with tempfile.TemporaryDirectory() as temp_dir:
+            ca_pem_file = os.path.join(temp_dir, "ca.pem")
+            with open(ca_pem_file, "wb") as fh:
+                fh.write(ca_cert.public_bytes(serialization.Encoding.PEM))
+
+            leaf_key, leaf_cert = make_leaf_cert(digital_signature=True)
+            key_usage = 
leaf_cert.extensions.get_extension_for_class(x509.KeyUsage).value
+            self.assertTrue(key_usage.digital_signature)
+            signed = signer.sign(tree.getroot(), key=leaf_key, 
cert=[leaf_cert])
+            verifier.verify(etree.tostring(signed), ca_pem_file=ca_pem_file)
+
+            leaf_key, leaf_cert = make_leaf_cert(digital_signature=False)
+            key_usage = 
leaf_cert.extensions.get_extension_for_class(x509.KeyUsage).value
+            self.assertFalse(key_usage.digital_signature)
+            signed = 
signer.sign(etree.parse(self.example_xml_files[0]).getroot(), key=leaf_key, 
cert=[leaf_cert])
+            with self.assertRaisesRegex(InvalidCertificate, "certificate 
KeyUsage does not allow digitalSignature"):
+                verifier.verify(etree.tostring(signed), 
ca_pem_file=ca_pem_file)
+
     def test_xmldsig_interop_examples(self):
         ca_pem_file = os.path.join(os.path.dirname(__file__), "interop", 
"cacert.pem").encode("utf-8")
 
-        verifier = get_verifier_for_year(2015)
+        verifier = get_no_dsig_key_usage_ee_policy_verifier_for_year(2015)
         for signature_file in glob(os.path.join(os.path.dirname(__file__), 
"interop", "*.xml")):
             print("Verifying", signature_file)
             with open(signature_file, "rb") as fh:
@@ -303,7 +590,7 @@
     def test_xmldsig_interop_TR2012(self):
         def get_x509_cert(**kwargs):
             with open(os.path.join(interop_dir, "TR2012", "rsa-cert.der"), 
"rb") as fh:
-                return [load_der_x509_certificate(fh.read())]
+                return [x509.load_der_x509_certificate(fh.read())]
 
         signature_files = glob(os.path.join(interop_dir, "TR2012", 
"signature*.xml"))
         for signature_file in signature_files:
@@ -381,7 +668,10 @@
             with open(signature_file, "rb") as fh:
                 try:
                     sig = fh.read()
-                    verifier = get_verifier_for_year(2010 if "phaos" in 
signature_file else 2014)
+                    verification_time = datetime(2010 if "phaos" in 
signature_file else 2014, 1, 1, tzinfo=timezone.utc)
+                    if "pyXMLSecurity" in signature_file:
+                        verification_time = datetime(2009, 10, 15, 
tzinfo=timezone.utc)
+                    verifier = 
get_no_dsig_key_usage_ee_policy_verifier_at_time(verification_time)
                     verifier.excise_empty_xmlns_declarations = True
                     verifier.verify(
                         sig,
@@ -422,7 +712,10 @@
                     if signature_file.endswith("expired-cert.xml") or 
signature_file.endswith(
                         "wsfederation_metadata.xml"
                     ):  # noqa
-                        with self.assertRaisesRegex(InvalidCertificate, "cert 
is not valid at validation time"):
+                        with self.assertRaisesRegex(
+                            InvalidCertificate,
+                            "certificate (has expired|is not yet 
valid).*SignatureConfiguration",
+                        ):
                             raise
                     elif 
signature_file.endswith("invalid_enveloped_transform.xml"):
                         self.assertIsInstance(e, InvalidSignature)
@@ -507,7 +800,7 @@
         signed_data = etree.tostring(signed)
         verifier = XMLVerifier()
         verifier.excise_empty_xmlns_declarations = True
-        verifier.verify(signed_data, x509_cert=crt)
+        verifier.verify(signed_data, x509_cert=crt, 
expect_config=example_cert_config)
 
     def test_elementtree_compat(self):
         data = stdlibElementTree.parse(self.example_xml_files[0]).getroot()
@@ -539,7 +832,9 @@
         data = etree.fromstring(self.saml_test_vectors[0])
         reference_uri = "assertionId"
         signed_root = XMLSigner().sign(data, reference_uri=reference_uri, 
key=key, cert=crt)
-        res = XMLVerifier().verify(etree.tostring(signed_root), x509_cert=crt, 
expect_references=True)
+        res = XMLVerifier().verify(
+            etree.tostring(signed_root), x509_cert=crt, 
expect_references=True, expect_config=example_cert_config
+        )
 
         self.assertIsInstance(res, list)
         self.assertEqual(1, len(res))
@@ -552,7 +847,9 @@
             data = etree.fromstring(d)
             reference_uri = ["assertionId", "assertion2"] if "assertion2" in d 
else "assertionId"
             signed_root = XMLSigner().sign(data, reference_uri=reference_uri, 
key=key, cert=crt)
-            res = XMLVerifier().verify(etree.tostring(signed_root), 
x509_cert=crt, expect_references=True)
+            res = XMLVerifier().verify(
+                etree.tostring(signed_root), x509_cert=crt, 
expect_references=True, expect_config=example_cert_config
+            )
             signed_data_root = res[0].signed_xml
             ref = signed_root.xpath(
                 
"/samlp:Response/saml:Assertion/ds:Signature/ds:SignedInfo/ds:Reference",
@@ -594,11 +891,17 @@
             # Test setting both X509Data and KeyInfo
             s4 = XMLSigner().sign(data, reference_uri=reference_uri, key=key, 
cert=crt, always_add_key_value=True)
             try:
-                XMLVerifier().verify(s4, x509_cert=crt)
+                XMLVerifier().verify(s4, x509_cert=crt, 
expect_config=example_cert_config)
             except InvalidSignature as e:
                 self.assertIn("Expected to find 1 references, but found 2", 
str(e))
             expect_refs = etree.tostring(s4).decode().count("<ds:Reference")
-            XMLVerifier().verify(s4, x509_cert=crt, 
ignore_ambiguous_key_info=True, expect_references=expect_refs)
+            XMLVerifier().verify(
+                s4,
+                x509_cert=crt,
+                ignore_ambiguous_key_info=True,
+                expect_references=expect_refs,
+                expect_config=example_cert_config,
+            )
 
     def test_inclusive_namespaces_signing(self):
         # Test exclusive canonicalization with InclusiveNamespace PrefixList
@@ -696,9 +999,11 @@
             InvalidInput,
             "Both X509Data and KeyValue found and they represent different 
public keys",
         ):
-            XMLVerifier().verify(signed_xml, x509_cert=crt)
+            XMLVerifier().verify(signed_xml, x509_cert=crt, 
expect_config=example_cert_config)
 
-        XMLVerifier().verify(signed_xml, x509_cert=crt, 
ignore_ambiguous_key_info=True)
+        XMLVerifier().verify(
+            signed_xml, x509_cert=crt, ignore_ambiguous_key_info=True, 
expect_config=example_cert_config
+        )
 
     def test_mismatched_ecdsa_key_value_with_x509_data(self):
         crt, key = self.load_example_ecdsa_keys()
@@ -773,7 +1078,9 @@
             doc, cert=cert, key=key, reference_uri="#mytest", 
signature_properties=sigprop
         )
         fulldoc = b"<root>" + etree.tostring(signature) + etree.tostring(doc) 
+ b"</root>"
-        XMLVerifier().verify(etree.fromstring(fulldoc), x509_cert=cert, 
expect_references=2)
+        XMLVerifier().verify(
+            etree.fromstring(fulldoc), x509_cert=cert, expect_references=2, 
expect_config=example_cert_config
+        )
 
     def test_signature_properties_with_detached_method_re_enveloping(self):
         doc = etree.Element("{http://somenamespace}Test";, attrib={"Id": 
"mytest"})
@@ -789,7 +1096,9 @@
             + etree.tostring(doc)
             + b"</ns0:root>"
         )
-        XMLVerifier().verify(etree.fromstring(fulldoc), x509_cert=cert, 
expect_references=2)
+        XMLVerifier().verify(
+            etree.fromstring(fulldoc), x509_cert=cert, expect_references=2, 
expect_config=example_cert_config
+        )
 
     def test_payload_c14n(self):
         doc = etree.fromstring('<abc xmlns="http://example.com";><foo 
xmlns="">bar</foo></abc>')
@@ -806,7 +1115,7 @@
             "</rDE>"
         )
         root = XMLSigner().sign(doc, cert=cert, key=key, 
reference_uri="#target")
-        XMLVerifier().verify(root, x509_cert=cert)
+        XMLVerifier().verify(root, x509_cert=cert, 
expect_config=example_cert_config)
 
     def test_include_c14n_transform_element_by_default(self):
         cert, key = self.load_example_keys()
@@ -816,7 +1125,7 @@
             "</rDE>"
         )
         root = XMLSigner().sign(doc, cert=cert, key=key, 
reference_uri="#target")
-        XMLVerifier().verify(root, x509_cert=cert)
+        XMLVerifier().verify(root, x509_cert=cert, 
expect_config=example_cert_config)
         transform_elements = root.findall(
             
"ds:Signature/ds:SignedInfo/ds:Reference/ds:Transforms/ds:Transform", 
namespaces=namespaces
         )
@@ -843,11 +1152,13 @@
             XMLVerifier().verify,
             root,
             x509_cert=cert,
+            expect_config=example_cert_config,
         )
 
         # However, if we use the right configuration, it should verify 
correctly
         config = SignatureConfiguration(
-            
default_reference_c14n_method=CanonicalizationMethod.CANONICAL_XML_1_0_WITH_COMMENTS
+            
default_reference_c14n_method=CanonicalizationMethod.CANONICAL_XML_1_0_WITH_COMMENTS,
+            verification_time=example_cert_verification_time,
         )
         XMLVerifier().verify(root, x509_cert=cert, expect_config=config)
         transform_elements = root.findall(
@@ -864,17 +1175,21 @@
         signer = XMLSigner()
         signed = signer.sign(data, cert=cert, key=key)
         verifier = XMLVerifier()
-        verifier.verify(signed, x509_cert=cert)
+        verifier.verify(signed, x509_cert=cert, 
expect_config=example_cert_config)
         config = SignatureConfiguration(location="./foo/bar/")
-        with self.assertRaisesRegex(InvalidInput, "Expected to find XML 
element Signature in data"):
+        with self.assertRaisesRegex(
+            InvalidInput, "Expected to find XML element Signature in data 
using XPath ./foo/bar/ds:Signature"
+        ):
             verifier.verify(signed, x509_cert=cert, expect_config=config)
         config = SignatureConfiguration(signature_methods=[])
         with self.assertRaisesRegex(InvalidInput, "Signature method RSA_SHA256 
forbidden by configuration"):
             verifier.verify(signed, x509_cert=cert, expect_config=config)
-        config = 
SignatureConfiguration(digest_algorithms=[DigestAlgorithm.SHA3_512])
+        config = SignatureConfiguration(
+            digest_algorithms=[DigestAlgorithm.SHA3_512], 
verification_time=example_cert_verification_time
+        )
         with self.assertRaisesRegex(InvalidInput, "Digest algorithm SHA256 
forbidden by configuration"):
             verifier.verify(signed, x509_cert=cert, expect_config=config)
-        config = SignatureConfiguration(digest_algorithms=[])
+        config = SignatureConfiguration(digest_algorithms=[], 
verification_time=example_cert_verification_time)
         with self.assertRaisesRegex(InvalidInput, "Digest algorithm SHA256 
forbidden by configuration"):
             verifier.verify(signed, x509_cert=cert, expect_config=config)
 
@@ -888,7 +1203,7 @@
         verifier = XMLVerifier()
         with self.assertRaisesRegex(InvalidInput, "Signature method RSA_SHA1 
forbidden by configuration"):
             verifier.verify(signed, x509_cert=cert)
-        verifier.verify(signed, x509_cert=cert, expect_config=sha1_ok)
+        verifier.verify(signed, x509_cert=cert, 
expect_config=sha1_ok_example_cert)
 
 
 class TestXAdES(unittest.TestCase, LoadExampleKeys):
@@ -935,7 +1250,41 @@
 
         verifier = XAdESVerifier()
         verify_results = verifier.verify(
-            signed_doc, x509_cert=cert, expect_references=3, 
expect_signature_policy=self.signature_policy
+            signed_doc,
+            x509_cert=cert,
+            expect_references=3,
+            expect_signature_policy=self.signature_policy,
+            expect_config=xades_sha1_ok_example_cert,
+        )
+        self.assertIsInstance(verify_results[1], XAdESVerifyResult)
+        self.assertTrue(hasattr(verify_results[1], "signed_properties"))
+
+    def test_xades_roundtrip_with_cert_chain(self):
+        cert, key = self.load_example_keys()
+        with open(os.path.join(os.path.dirname(__file__), "example-ca.pem"), 
"rb") as fh:
+            ca_cert = fh.read()
+        with open(os.path.join(os.path.dirname(__file__), "example.xml"), 
"rb") as fh:
+            doc = etree.parse(fh)
+        signer = XAdESSigner(
+            signature_policy=self.signature_policy,
+            claimed_roles=self.claimed_roles,
+            data_object_format=self.data_object_format,
+        )
+        signed_doc = signer.sign(doc, key=key, cert=[cert, ca_cert])
+
+        self.assertEqual(2, len(signed_doc.findall(".//ds:X509Certificate", 
namespaces=namespaces)))
+        self.assertEqual(
+            2,
+            len(signed_doc.findall(".//xades:SigningCertificateV2/xades:Cert", 
namespaces=namespaces)),
+        )
+
+        verifier = XAdESVerifier()
+        verify_results = verifier.verify(
+            signed_doc,
+            x509_cert=cert,
+            expect_references=3,
+            expect_signature_policy=self.signature_policy,
+            expect_config=xades_sha1_ok_example_cert,
         )
         self.assertIsInstance(verify_results[1], XAdESVerifyResult)
         self.assertTrue(hasattr(verify_results[1], "signed_properties"))
@@ -948,7 +1297,6 @@
             "corrupted-cert": etree.DocumentInvalid,  # FIXME - flaky 
validation
             "cert-v2-wrong-digest": InvalidDigest,
             "wrong-sign-cert-digest": InvalidDigest,
-            "nonconformant-X_BE_CONN_10": InvalidDigest,
             "sigPolStore-noDigest": InvalidInput,
         }
         for sig_file in glob(os.path.join(os.path.dirname(__file__), "xades", 
"*.xml")):
@@ -956,10 +1304,14 @@
             with open(sig_file, "rb") as fh:
                 doc = etree.parse(fh)
             cert = 
doc.find(".//{http://www.w3.org/2000/09/xmldsig#}X509Certificate";).text
+            expect_config = xades_sha1_ok
+            verification_time = get_cert_midpoint_verification_time(cert)
+            if verification_time is not None:
+                expect_config = replace(expect_config, 
verification_time=verification_time)
             kwargs = dict(
                 x509_cert=cert,
                 
expect_references=self.expect_references.get(os.path.basename(sig_file), 2),
-                expect_config=xades_sha1_ok,
+                expect_config=expect_config,
             )
             if "nonconformant" in sig_file:
                 kwargs.update(validate_schema=False)

Reply via email to