{"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":"python","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,"search_vec":"'0.30.2':72 'advanc':48 'algorithm':67 'architectur':33 'asynchron':92 'broader':84 'cadenc':79 'certif':17,19,88 'certvalid':3,6 'chang':34 'check':53 'constraint':63 'crls':54 'cryptographi':90 'current':69 'design':11 'digit':43 'diverg':28 'ecosystem':39 'featur':31,49 'follow':75 'fork':22 'incorpor':30 'librari':10 'ocsp':56 'origin':21 'part':81 'particular':40 'path':20 'pdf':42 'pki':91 'point':58 'point-in-tim':57 'polici':62 'project':86 'pyhanko':2,5,38,85 'pyhanko-certvalid':1,4 'python':9 'regular':77 'releas':78 'revoc':52 'robust':13 'signatur':44,66 'signific':29 'sinc':27 'support':47 'tailor':35 'time':60 'valid':14,45,61,89 'various':65 'version':70 'wbond/certvalidator':24 'x.509':16,87","created_at":"2026-04-11T01:38:47.675892+00:00","updated_at":"2026-04-16T18:29:11.073910+00:00","problems":[{"fix":"Update import statements to use `pyhanko_certvalidator` instead of `certvalidator`, for example, `from pyhanko_certvalidator import ValidationContext`.","cause":"The `pyhanko-certvalidator` library is a fork of `certvalidator`, and its modules are installed under the `pyhanko_certvalidator` namespace, meaning direct imports from `certvalidator` will fail.","error":"ModuleNotFoundError: No module named 'certvalidator'"},{"fix":"Ensure that all necessary intermediate certificates are supplied to the `ValidationContext`, that the root certificate is explicitly designated as a trust anchor, and that valid and accessible revocation information (CRLs or OCSP responses) is available.","cause":"This generic error, often stemming from `pyhanko_certvalidator.errors.PathValidationError` or `pyhanko_certvalidator.errors.RevokedError`, indicates that a complete and trusted path from the signer's certificate to a configured trust anchor could not be built, or the certificate was found to be revoked.","error":"The signer's certificate could not be validated."},{"fix":"Verify the encoding of the certificate data being provided. If fetching from a URL, ensure the server correctly provides PEM-encoded data or manually convert the data to PEM format before passing it to the library.","cause":"The certificate fetching or parsing mechanism received data that was expected to be PEM-encoded (Privacy-Enhanced Mail) but was in a different format (e.g., DER - Distinguished Encoding Rules) or the `Content-Type` header from the server was misleading.","error":"ValueError: Expected PEM data when extracting certs from application/x-x509-ca-cert payload."},{"fix":"This issue typically requires the remote server to be configured to send the `Content-Type` header. If server-side changes are not possible, consider manually fetching the resource and providing its content to the validator, or implement a custom fetcher that can handle missing `Content-Type` headers.","cause":"During an attempt to fetch a certificate, CRL, or OCSP response from a remote URL, the server's HTTP response did not include a 'Content-Type' header, which the `pyhanko-certvalidator` fetcher expects for proper processing.","error":"KeyError: 'content-type'"}],"ecosystem":"pypi","meta_description":null,"install_score":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":"0.31.4","cli_name":"","cli_version":null,"type":"library","homepage":null,"github":"https://github.com/MatthiasValvekens/pyHanko","docs":null,"changelog":null,"pypi":"https://pypi.org/project/pyhanko-certvalidator/","npm":null,"openapi_spec":null,"status_page":null,"smithery":null,"categories":["auth-security"],"base_url":null,"auth_type":null,"provenance":{"verified_status":"passing","verified_at":"2026-06-28","last_verified":"2026-08-28","next_check":"2026-07-28","install_tag":null}}