{"id":2692,"library":"pyhanko-certvalidator","title":"pyhanko-certvalidator","description":"pyhanko-certvalidator is a Python library designed for robust validation of X.509 certificates and certificate paths. Originally forked from wbond/certvalidator, it has since diverged significantly, incorporating features and architectural changes tailored for the pyHanko ecosystem, particularly for PDF digital signature validation. It supports advanced features such as revocation checks (CRLs and OCSP), point-in-time validation, policy constraints, and various signature algorithms. The current version is 0.30.2, and it follows a regular release cadence as part of the broader pyHanko project.","status":"active","version":"0.30.2","language":"en","source_language":"en","source_url":"https://github.com/MatthiasValvekens/pyHanko/tree/master/pkgs/pyhanko-certvalidator","tags":["x.509","certificate","validation","cryptography","PKI","asynchronous"],"install":[{"cmd":"pip install pyhanko-certvalidator","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"Core cryptographic operations for ASN.1 parsing and serialization.","package":"asn1crypto","optional":false},{"reason":"Fundamental cryptographic primitives and algorithms.","package":"cryptography","optional":false},{"reason":"RFC 3986 compliant URI parsing.","package":"uritools","optional":false},{"reason":"Access to system trust stores and some cryptographic operations.","package":"oscrypto","optional":false},{"reason":"Default HTTP client for fetching CRLs/OCSP responses.","package":"requests","optional":false},{"reason":"Alternative, more performant asynchronous HTTP client for fetching CRLs/OCSP responses. Requires explicit configuration.","package":"aiohttp","optional":true}],"imports":[{"symbol":"ValidationContext","correct":"from pyhanko_certvalidator import ValidationContext"},{"symbol":"CertificateValidator","correct":"from pyhanko_certvalidator import CertificateValidator"},{"symbol":"errors","correct":"from pyhanko_certvalidator import errors"},{"note":"The package was renamed from 'certvalidator' to 'pyhanko_certvalidator' to avoid namespace conflicts.","wrong":"from certvalidator import ValidationContext","symbol":"ValidationContext"}],"quickstart":{"code":"import asyncio\nfrom datetime import datetime, timedelta\n\nfrom asn1crypto import x509, pem\nfrom cryptography.hazmat.primitives import hashes, serialization\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.hazmat.backends import default_backend\nfrom pyhanko_certvalidator import ValidationContext, CertificateValidator, errors\n\nasync def run_validation_example():\n    # 1. Generate a self-signed root CA certificate\n    root_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())\n    root_subject = x509.Name([\n        x509.NameAttribute('2.5.4.6', 'US'),\n        x509.NameAttribute('2.5.4.10', 'Root CA Inc.'),\n        x509.NameAttribute('2.5.4.3', 'Example Root CA')\n    ])\n    root_cert = x509.CertificateBuilder().\n        subject_name(root_subject).\n        issuer_name(root_subject).\n        public_key(root_key.public_key()).\n        serial_number(x509.random_serial_number()).\n        not_valid_before(datetime.utcnow() - timedelta(days=1)).\n        not_valid_after(datetime.utcnow() + timedelta(days=3650)).\n        add_extension(x509.BasicConstraints(ca=True, path_length=1), critical=True).\n        add_extension(x509.KeyUsage(key_cert_sign=True, crl_sign=True), critical=True).\n        sign(root_key, hashes.SHA256(), default_backend())\n\n    # 2. Generate an intermediate CA certificate signed by the root CA\n    intermediate_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())\n    intermediate_subject = x509.Name([\n        x509.NameAttribute('2.5.4.6', 'US'),\n        x509.NameAttribute('2.5.4.10', 'Intermediate CA Corp.'),\n        x509.NameAttribute('2.5.4.3', 'Example Intermediate CA')\n    ])\n    intermediate_cert = x509.CertificateBuilder().\n        subject_name(intermediate_subject).\n        issuer_name(root_subject).\n        public_key(intermediate_key.public_key()).\n        serial_number(x509.random_serial_number()).\n        not_valid_before(datetime.utcnow() - timedelta(days=1)).\n        not_valid_after(datetime.utcnow() + timedelta(days=1825)).\n        add_extension(x509.BasicConstraints(ca=True, path_length=0), critical=True).\n        add_extension(x509.KeyUsage(key_cert_sign=True, crl_sign=True), critical=True).\n        sign(root_key, hashes.SHA256(), default_backend())\n\n    # 3. Generate an end-entity certificate signed by the intermediate CA\n    ee_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())\n    ee_subject = x509.Name([\n        x509.NameAttribute('2.5.4.6', 'US'),\n        x509.NameAttribute('2.5.4.10', 'End-Entity Dept.'),\n        x509.NameAttribute('2.5.4.3', 'example.com')\n    ])\n    ee_cert = x509.CertificateBuilder().\n        subject_name(ee_subject).\n        issuer_name(intermediate_subject).\n        public_key(ee_key.public_key()).\n        serial_number(x509.random_serial_number()).\n        not_valid_before(datetime.utcnow() - timedelta(days=1)).\n        not_valid_after(datetime.utcnow() + timedelta(days=365)).\n        add_extension(x509.BasicConstraints(ca=False), critical=True).\n        add_extension(x509.KeyUsage(digital_signature=True, key_encipherment=True), critical=True).\n        add_extension(x509.ExtendedKeyUsage([x509.ExtendedKeyUsageOID.SERVER_AUTH]), critical=False).\n        sign(intermediate_key, hashes.SHA256(), default_backend())\n\n    # Prepare certificates for validation\n    root_cert_asn1 = x509.Certificate.load(root_cert.public_bytes(encoding=serialization.Encoding.DER))\n    intermediate_cert_asn1 = x509.Certificate.load(intermediate_cert.public_bytes(encoding=serialization.Encoding.DER))\n    ee_cert_asn1 = x509.Certificate.load(ee_cert.public_bytes(encoding=serialization.Encoding.DER))\n\n    # 4. Create a ValidationContext with the root CA as trust anchor\n    validation_context = ValidationContext(\n        trust_roots=[root_cert_asn1],\n        # For more complex scenarios, you might add 'intermediate_certs' or enable fetching:\n        intermediate_certs=[intermediate_cert_asn1],\n        allow_fetching=False # Set to True to allow HTTP fetching of CRLs/OCSP\n    )\n\n    # 5. Validate the end-entity certificate path\n    try:\n        validator = CertificateValidator(ee_cert_asn1, [], validation_context)\n        valid_paths = await validator.async_validate_tls()\n        print(f\"Certificate for {ee_cert_asn1.subject.human_friendly} is VALID.\")\n        print(\"Validated paths found:\")\n        for path in valid_paths:\n            print(f\" - {len(path.certs)} certificates in path\")\n    except errors.PathValidationError as e:\n        print(f\"Certificate validation FAILED: {e}\")\n    except Exception as e:\n        print(f\"An unexpected error occurred: {e}\")\n\nif __name__ == '__main__':\n    asyncio.run(run_validation_example())\n","lang":"python","description":"This example demonstrates how to perform a basic certificate path validation using `pyhanko-certvalidator`. It generates a synthetic certificate chain (Root CA -> Intermediate CA -> End-Entity) and then uses `ValidationContext` and `CertificateValidator` to verify the end-entity certificate against the trusted root. It showcases the asynchronous `async_validate_tls` method, which is the recommended approach for modern usage."},"warnings":[{"fix":"Migrate your code to use the `asyncio` variants of API calls (e.g., `async_validate_tls()` instead of `validate_tls()`). If using custom fetchers, consider implementing `aiohttp`-based fetchers for better performance in async contexts, though `requests`-based ones remain the default.","message":"Starting with version 0.17.0, the library underwent significant refactoring to favour asynchronous I/O. While most high-level API entrypoints can still be used synchronously, their `asyncio` equivalents are now preferred and many synchronous methods have been deprecated.","severity":"breaking","affected_versions":">=0.17.0"},{"fix":"Update all `import certvalidator` statements to `import pyhanko_certvalidator` or `from pyhanko_certvalidator import ...`.","message":"The package was renamed from `certvalidator` to `pyhanko_certvalidator` to prevent namespace collisions, as the library significantly diverged from the original `wbond/certvalidator` project.","severity":"breaking","affected_versions":"<=0.12.0 (when the fork occurred, though actual package rename for public consumption happened later for older versions)"},{"fix":"Thoroughly review the `pyhanko-certvalidator` documentation, especially if migrating from `wbond/certvalidator`, to understand any API or behaviour differences.","message":"This library is a fork of `wbond/certvalidator` and has diverged considerably. While basic usage might be similar, specific features, internal workings, and advanced configurations may differ. Directly swapping between the two without review may lead to unexpected behaviour.","severity":"gotcha","affected_versions":"All versions"},{"fix":"For support or bug reporting, refer to the main pyHanko project's GitHub issues page: `https://github.com/MatthiasValvekens/pyHanko/issues`.","message":"GitHub issues are disabled on the `pyhanko-certvalidator` repository. Bug reports and usage questions should be submitted to the main `pyHanko` issue tracker or discussion forum.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure your Python environment is running Python 3.10 or newer.","message":"While `pyhanko-certvalidator` previously supported Python 3.7+, the broader `pyHanko` project and its latest releases now require Python 3.10 or later for full compatibility and intended functionality.","severity":"gotcha","affected_versions":"0.30.0 onwards (recommendation for ecosystem)"}],"env_vars":null,"last_verified":"2026-04-10T00:00:00.000Z","next_check":"2026-07-09T00:00:00.000Z"}