Skip to main content

Zero-dependency TLS/SSL certificate monitoring and validation, including post-quantum (ML-KEM / ML-DSA) readiness checks.

Project description

CertMonitor Logo

CertMonitor

Zero-dependency certificate monitoring and validation for Python. Native, portable, extensible, and secure.
All orchestration and logic are pure Python standard library. Public key parsing and elliptic curve support are powered by Rust. No third-party Python dependencies - ever.

Test Status PyPI version Supported Python versions ReadTheDocs


⚡️ Why CertMonitor?

CertMonitor was born out of real-world frustration: outages and security incidents caused by expired certificates, missing Subject Alternative Names, or incomplete certificate chains. This tool is a labor of love, built to solve those pain points with a zero-dependency, native Python approach. All orchestration and logic are pure Python stdlib, but advanced public key parsing and elliptic curve support are powered by Rust for speed, safety, and correctness. CertMonitor is always improving, and your feedback is welcome!


✨ Features

  • 🔒 Zero Dependencies: 100% standard library. No third-party Python packages required. Ever.
  • 🛡️ Certificate Validators: Modular checks for expiration, hostname, SANs, key strength, protocol, ciphers, and more.
  • High Performance: Async- and batch-friendly. Designed for speed and concurrency.
  • 🧩 Extensible: Add your own custom validators for organization-specific checks.
  • 🔮 Post-Quantum Readiness: Opt-in validators detect post-quantum (hybrid/pure ML-KEM) TLS key exchange and post-quantum certificate keys/signatures (ML-DSA, SLH-DSA, composite), so you can track quantum-safe migration and harvest-now-decrypt-later exposure. See below.
  • 🐍 Native Python First: Works out-of-the-box in any Python 3.8+ environment.
  • 🦀 Rust-Powered Parsing: Certificate parsing and public key extraction are handled by a Rust extension for speed, safety, and correctness. This is required for advanced public key and elliptic curve features, but all orchestration and logic are pure Python stdlib.
  • 📦 Portable: No system dependencies. Drop it into any project or CI pipeline.
  • 📝 Comprehensive Docs: ReadTheDocs with usage, API, and advanced guides.

🔍 Validators: The Heart of CertMonitor

CertMonitor uses a powerful system of validators, modular checks that automatically assess certificate health, security, and compliance. Validators can:

  • Detect expired or soon-to-expire certificates
  • Ensure hostnames and SANs match
  • Enforce strong key types and lengths
  • Require modern TLS versions and strong cipher suites
  • Allow you to add custom organization-specific checks

You can enable, disable, or extend validators to fit your needs, making CertMonitor ideal for continuous monitoring, compliance automation, and proactive security.

Available Validators

  • expiration: Validates that the certificate is not expired.
  • hostname: Validates that the hostname matches the certificate's subject alternative names (SANs).
  • subject_alt_names: Validates the presence and content of the SANs in the certificate.
  • root_certificate: Validates if the certificate is issued by a trusted root CA.
  • key_info: Validates the public key type and strength.
  • tls_version: Validates the negotiated TLS version.
  • weak_cipher: Validates that the negotiated cipher suite is in the allowed list.
  • sensitive_date: Validates that the certificate doesn't expire on built-in or user specified sensitive dates.
  • chain: Validates the full TLS certificate chain for structural problems (missing intermediates, out-of-order, expired members, weak signatures).
  • pq_key_exchange: Reports whether the TLS 1.3 key exchange is post-quantum (hybrid or pure ML-KEM). This is the harvest-now-decrypt-later question. Opt-in.
  • pq_signature: Reports the leaf certificate's post-quantum posture (the key and signature algorithm: ML-DSA / SLH-DSA / composite). Opt-in.
  • pq_chain: Reports the post-quantum posture of every certificate in the presented chain. Opt-in.

The pq_* validators are opt-in (not enabled by default). See Post-Quantum Readiness below.


🔮 Post-Quantum Readiness

CertMonitor helps you measure your migration to post-quantum cryptography (PQC) across both surfaces that matter, using NIST's finalized standards (FIPS 203 ML-KEM, FIPS 204 ML-DSA, FIPS 205 SLH-DSA):

  • Key exchange (the urgent one). TLS 1.3 hybrid key exchange (e.g. X25519MLKEM768) is what defends today's traffic against harvest-now-decrypt-later (HNDL), where an attacker records encrypted traffic now to decrypt once a quantum computer exists. The pq_key_exchange validator reads the negotiated group directly off the wire (the Python ssl module doesn't expose it) and tells you whether the session is quantum-safe.
  • Certificate keys & signatures. As CAs and operators roll out ML-DSA / SLH-DSA and composite (hybrid) certificates, pq_signature and pq_chain report the post-quantum posture of the leaf and the full chain.

"PQ" includes hybrid algorithms (classical + post-quantum), which is what real-world deployments use today. Requiring pure PQ would fail every server currently in production.

from certmonitor import CertMonitor

with CertMonitor("cloudflare.com", enabled_validators=["pq_key_exchange", "pq_signature", "pq_chain"]) as monitor:
    results = monitor.validate()
    print(results["pq_key_exchange"])
    # {'kem_id': 4588, 'kem_name': 'X25519MLKEM768', 'kem_kind': 'hybrid_pq',
    #  'is_pq': True, 'is_valid': True}

These validators are opt-in (not in the default set) while PQC adoption is still ramping. Full details: PqKeyExchange · PqSignature · PqChain.


📚 Learn How It Works

New to TLS, certificates, or the post-quantum transition? The docs include vendor-neutral explainers with diagrams:


📦 Installation & Quickstart

Install CertMonitor from PyPI using your preferred package manager:

Using pip:

pip install certmonitor

Using uv:

uv add certmonitor

For instructions on installing from source for development, please see the Development Guide.

Once installed, here's the pattern you'll use most often. Connect to a host, pull the certificate details, and run the validators:

from certmonitor import CertMonitor

with CertMonitor("example.com") as monitor:
    cert_data = monitor.get_cert_info()
    validation_results = monitor.validate()
    print(cert_data)
    print(validation_results)

Two calls do the work. get_cert_info() gives you the parsed certificate, and validate() runs the checks against it.

What get_cert_info() returns

A structured dictionary describing the certificate:

{
  "subject": {
    "countryName": "US",
    "stateOrProvinceName": "California",
    "localityName": "Los Angeles",
    "organizationName": "Internet Corporation for Assigned Names and Numbers",
    "commonName": "www.example.com"
  },
  "issuer": {
    "countryName": "US",
    "organizationName": "DigiCert Inc",
    "commonName": "DigiCert Global G2 TLS RSA SHA256 2020 CA1"
  },
  "version": 3,
  "serialNumber": "075BCEF30689C8ADDF13E51AF4AFE187",
  "notBefore": "2024-01-30T00:00:00",
  "notAfter": "2025-03-01T23:59:59",
  "subjectAltName": {
    "DNS": ["www.example.com", "example.com"],
    "IP Address": []
  },
  "OCSP": ["http://ocsp.digicert.com"],
  "caIssuers": ["http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt"],
  "crlDistributionPoints": [
    "http://crl3.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crl",
    "http://crl4.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crl"
  ]
}

It's all there: who the certificate is for (subject), who issued it (issuer), how long it's valid (notBefore and notAfter), the alternate names it covers, and the revocation endpoints.

What validate() returns

A dictionary keyed by validator name, with a structured result under each one:

{
  "expiration": {
    "is_valid": true,
    "days_to_expiry": 120,
    "expires_on": "2025-03-01T23:59:59",
    "warnings": []
  },
  "subject_alt_names": {
    "is_valid": true,
    "sans": {"DNS": ["www.example.com", "example.com"], "IP Address": []},
    "count": 2,
    "contains_host": {"name": "www.example.com", "is_valid": true, "reason": "Exact match for www.example.com found in DNS SANs"},
    "contains_alternate": {"example.com": {"name": "example.com", "is_valid": true, "reason": "Exact match for example.com found in DNS SANs"}},
    "warnings": []
  }
}

Each validator reports its own is_valid flag plus the details behind its decision. That structure is consistent across every validator, so once you can read one result you can read them all.


⚙️ Configuration

You can configure CertMonitor by specifying which validators to enable in the enabled_validators parameter. If not specified, it will use the default validators defined in the configuration.

Default Validators

By default, the following validators are enabled:

  • expiration
  • hostname
  • root_certificate

Environment Variables

CertMonitor can also read the list of enabled validators from an environment variable ENABLED_VALIDATORS. This is useful for configuring the validators without modifying the code.

Example:

export ENABLED_VALIDATORS="expiration,hostname,subject_alt_names,root_certificate,key_info,tls_version,weak_cipher"

🔎 Protocol Detection

CertMonitor automatically detects the protocol (SSL/TLS or SSH) for the target host. Most features are focused on SSL/TLS. SSH support is limited.


🚨 Error Handling

If an error occurs (e.g., connection failure, invalid certificate), CertMonitor methods will return a dictionary with an error key and details. Always check for errors in returned data:

cert = monitor.get_cert_info()
if isinstance(cert, dict) and "error" in cert:
    print("Error:", cert["message"])

🔐 Why Trust CertMonitor

CertMonitor's certificate parser handles untrusted bytes from every TLS handshake it monitors. We take that seriously:

  • Zero runtime dependencies. The Python layer uses only the standard library. The Rust extension's X.509 / DER parser is written in-house against the Rust standard library, with no third-party parsing crates in the runtime dependency tree.
  • #![forbid(unsafe_code)] at the Rust crate root. No unsafe blocks anywhere in the parser. Memory safety is enforced by the Rust compiler, not by manual auditing.
  • Every parser path returns Result. Malformed input produces a structured error, never a crash. No .unwrap() on user-input-derived data.
  • 2.8 billion fuzz iterations, zero crashes. The parser is continuously fuzz-tested with cargo-fuzz (libFuzzer) against adversarial byte sequences. A 1-hour soak run explores 310 code-coverage points and 505 libfuzzer features with zero panics. Run it yourself: make fuzz.
  • 130-cert real-world corpus on every CI run. Every commit is tested against captured certificates from 101 production hosts spanning Google Trust Services, DigiCert, Let's Encrypt, Sectigo, Cloudflare, and more, covering both RSA and ECDSA key types.
  • 540+ Python tests at 99% line coverage, plus 99 Rust unit tests. The full test suite runs across Python 3.8 to 3.13 and Rust stable on macOS, Ubuntu, and Windows.
  • cargo audit on every PR. CertMonitor declares a single direct Rust dependency, pyo3 (the Python bridge). The whole compiled tree is 15 crates, all pyo3 and its helpers, with no third-party parsing crates, scanned for known vulnerabilities on every pull request.

📄 License

This project is licensed under the MIT License. See the LICENSE file for details.


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

certmonitor-0.4.0-cp38-abi3-win_amd64.whl (198.4 kB view details)

Uploaded CPython 3.8+Windows x86-64

certmonitor-0.4.0-cp38-abi3-manylinux_2_34_x86_64.whl (339.2 kB view details)

Uploaded CPython 3.8+manylinux: glibc 2.34+ x86-64

certmonitor-0.4.0-cp38-abi3-macosx_11_0_arm64.whl (304.0 kB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

File details

Details for the file certmonitor-0.4.0-cp38-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for certmonitor-0.4.0-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 9546cb9e38289a135948a88dfb15dca0920e4e197dc13404445ceab4c8aac920
MD5 51a1a47e6ff241b2b1598729a518c2bd
BLAKE2b-256 5f8459e7906b31f153e88e928b1dfbc02c32f6efafdbe99a632054f503798256

See more details on using hashes here.

File details

Details for the file certmonitor-0.4.0-cp38-abi3-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for certmonitor-0.4.0-cp38-abi3-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 9765c1b39b6356e7c9d1e8d52fb70b43901c61b2b5a14b997def931a18ef374d
MD5 1879edf61548584ee1a383073c7fdec1
BLAKE2b-256 fda93cd2d6ed6d255b1df76f79a293070e06251f12bea5f31d4f36280815b919

See more details on using hashes here.

File details

Details for the file certmonitor-0.4.0-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for certmonitor-0.4.0-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1feb93b9441ce84900127505a7e80218210a0e75bec02c1a4da0b1db3fd40e8b
MD5 e6e70c7752b9df669c9301f28aed2d9c
BLAKE2b-256 dab6fc05f8c915f0e7383b214f9aded05f3307a9c7ea241aae920e107fc736bf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page