-
Notifications
You must be signed in to change notification settings - Fork 8
Description
Summary
The library verifies cryptographic signatures but does not validate that signing certificates chain to a trusted root CA. A signature from an untrusted or self-signed certificate will pass verification.
Current Verification Flow
When verifying a signature, the flow is:
Container.verify_signatures() (container.py:249-256)
→ XmlSignature.verify() (xmlsig.py:265-271)
→ signature_verifier.verify() (signature_verifier.py:35-76)
signature_verifier.verify() only performs cryptographic verification:
# signature_verifier.py:35-76
def verify(certificate, signature, data, hash_algo="SHA256", prehashed=False):
"""Verify RSA and EC signatures with the cryptography library"""
if not isinstance(certificate, Certificate):
certificate = load_der_x509_certificate(certificate, default_backend())
# ...
public_key = certificate.public_key()
# Only verifies the signature is mathematically valid
public_key.verify(signature, data, padding.PKCS1v15(), chosen_hash)What's Missing
1. No chain building or path validation
The signing certificate is extracted and used directly. There is no validation that:
- The certificate was issued by a trusted CA
- Intermediate certificates form a valid chain to a root
- Each issuer certificate has
CA=Truein BasicConstraints
2. No certificate validity period check
certificate.not_valid_before and certificate.not_valid_after are never checked against the signing time.
3. No key usage validation
The signing certificate should have digitalSignature in its key usage extension. This is not verified.
4. Root CA stored but never validated against
XmlSignature.set_root_ca_cert() at xmlsig.py:328-334 stores a root CA certificate, but it's never used to validate the chain:
# xmlsig.py:328-334
def set_root_ca_cert(self, root_ca_cert: bytes):
"""Set the root CA cert that issued the OCSP responder's certificate"""
el = self._find_element(f".//{XmlSignature.XADES_NS}CertificateValues")
SubElement(el, f"{XmlSignature.XADES_NS}EncapsulatedX509Certificate").text = (
b64encode(root_ca_cert).decode("ascii")
)
return selfAcknowledged Gap in OCSP
The codebase explicitly acknowledges this issue in ocsp.py:134-137:
"""
Verify the OCSP response signature.
Ideally this should also verify the signer certificate, as does openssl with:
openssl ocsp -respin ocsp.der
"""The OCSP responder certificate is extracted and used to verify the response signature (ocsp.py:163-182), but the responder certificate itself is never validated for chain, expiration, or EKU.
Impact
- Signatures from self-signed certificates pass verification
- Signatures from expired certificates pass verification
- Signatures from certificates issued by untrusted CAs pass verification
- Cannot distinguish qualified signatures (from QTSPs) from unqualified ones
References
- ETSI EN 319 102-1 - Signature Validation Procedures
- RFC 5280 Section 6 - Certificate Path Validation