Skip to main content

CertMonitor Logo

CertMonitor

CertMonitor is a Python library for SSL/TLS certificate monitoring, expiration checks, hostname/SAN validation, X.509 chain analysis, and post-quantum readiness.

Zero-dependency certificate monitoring and validation for Python, with no third-party Python runtime dependencies.
All orchestration and logic are pure Python standard library. Public key parsing and elliptic curve support are powered by a compiled Rust extension that ships inside the wheel.

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: a standard-library orchestration layer and a native Rust parser. 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!


📦 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)

get_cert_info() gives you the parsed certificate. validate() runs the checks and can collect it automatically if you have not fetched it yet.

What get_cert_info() returns

An illustrative certificate dictionary (your host and observation date will differ):

{
  "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": "Jan 30 00:00:00 2024 GMT",
  "notAfter": "Mar  1 23:59:59 2025 GMT",
  "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"
  ]  ],
  "fingerprint_sha256": "63ee284705b0cc2a22b72e4a71e60a2516811717f5302b819451852f203b1a7b"
}

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. The dates use the SSL date format shown above. Those endpoint URLs are metadata; CertMonitor does not fetch them to check revocation.

What validate() returns

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

This is the full result of a scan against example.com at the time of writing. Certificate details and dates depend on the endpoint.

{
  "expiration": {
    "is_valid": true,
    "days_to_expiry": 51,
    "expires_on": "2026-10-27T22:17:21+00:00",
    "warnings": [],
    "lifetime_days": 90,
    "lifetime_limit_days": 200,
    "status": "pass",
    "code": "expiration.pass"
  },
  "hostname": {
    "is_valid": true,
    "alt_names": [
      "example.com",
      "*.example.com"
    ],
    "identity_source": "subjectAltName",
    "common_name": "example.com",
    "common_name_matches": true,
    "matched_name": "example.com",
    "status": "pass",
    "code": "hostname.pass"
  },
  "root_certificate": {
    "is_valid": true,
    "status": "pass",
    "trust_verified": true,
    "revocation_status": "not_checked",
    "warnings": [],
    "issuer": {
      "countryName": "US",
      "organizationName": "SSL Corporation",
      "commonName": "Cloudflare TLS Issuing ECC CA 3"
    },
    "code": "root_certificate.pass"
  },
  "weak_keys": {
    "is_valid": true,
    "certs_checked": 4,
    "findings": [],
    "status": "pass",
    "code": "weak_keys.pass"
  }
}

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.


✨ Features

  • 🔒 Zero Dependencies: no third-party Python runtime dependencies, ever. The required native extension is distributed in platform wheels.
  • 📄 Files too: CertMonitor.from_file() runs the certificate checks on a PEM, DER, or PKCS#7 file with no connection at all. See Certificates from Files.
  • 📮 STARTTLS too: mail, directory, and database ports (SMTP, IMAP, POP3, FTP, PostgreSQL, LDAP) are discovered on any port and get the right preamble before the handshake, so their certificates get the same checks; starttls="smtp" pins it when you already know. See STARTTLS Services.
  • 🔁 Change detection: compare_snapshots() and certmonitor diff tell a routine renewal from a removed SAN, a new issuer, a weaker key, or a check that started failing. See Detect Changes Between Scans.
  • 🛡️ Proxies: proxy="http://..." or "socks5://...", with authentication, routes every connection through an HTTP CONNECT or SOCKS5 tunnel. See Proxies.
  • 🚫 Revocation: the opt-in revocation validator asks the certificate's OCSP responder and CRL distribution points and verifies both answers, both with in-house RSA (PKCS#1 v1.5 and PSS), ECDSA (P-256, P-384, P-521), and EdDSA (Ed25519, Ed448) signature checks against an issuer that provably signed the certificate. See Revocation.
  • ⌨️ Command line: certmonitor check example.com runs the same validators from the shell, with --json output and exit codes for cron and CI. See Command Line.
  • 🛡️ Certificate Validators: Modular checks for expiration, hostname, SANs, key strength, protocol, ciphers, and more.
  • ⚡ Batch Monitoring: overlap network waits with independent monitors and bounded workers.
  • 🧩 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.
  • 🐍 Python API: package metadata accepts Python 3.10 to 3.15; use a matching native wheel or build from source.
  • 🦀 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.
  • 📦 Distribution: install a compatible native wheel, or build from source with Rust and a platform C toolchain.
  • 📝 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
  • Catch keys, including a CA's, whose private half anyone can recover
  • 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: Checks certificate validity dates, renewal thresholds, and optional lifetime policy.
  • hostname: Validates that the hostname matches the certificate's subject alternative names (SANs).
  • subject_alt_names: Checks requested alternate DNS/IP identities against the SANs, or the primary host when none are given.
  • root_certificate: Validates if the certificate is issued by a trusted root CA.
  • key_info: Validates the public key type and strength.
  • weak_keys: Checks the key of the leaf and every chain certificate for a private key anyone can recover (a tiny EC private key, RSA primes that are too close, ROCA). On by default.
  • 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 PQ capability observed under a separate, unauthenticated TLS 1.3 probe offer. 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 observes the group selected or requested on a separate, unauthenticated probe. It reports capability under that offer; it does not verify protection of the original session or application traffic.
  • 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. Both hybrid and pure PQ groups are recognized by the capability check.

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"])
    # Illustrative abbreviated probe result (not a live guarantee):
    # {'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:


⚙️ 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
  • weak_keys

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,root_certificate,key_info,tls_version,weak_cipher"

🔎 Protocol Detection

CertMonitor automatically detects the protocol (SSL/TLS or SSH) for the target host. Services that greet in plaintext and upgrade with STARTTLS (SMTP, IMAP, POP3, FTP, PostgreSQL, LDAP) are discovered on any port and get the right preamble; pass starttls= to pin one. See STARTTLS Services. Most features are focused on SSL/TLS. SSH support is limited.


🚨 Error Handling

Ordinary retrieval failures return a dictionary with error and message. Validator failures return is_valid: false with a reason and dispatcher status. 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. No third-party Python packages; 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.
  • Error-aware parsing. Parser entry points return Result for malformed input. Regression tests and fuzzing exercise these paths; this is not proof that every possible input is handled correctly.
  • Manual fuzzing before release. The repository includes a cargo-fuzz target for adversarial byte sequences. Run a smoke check with make fuzz or a longer soak with make fuzz-long; this is separate from normal CI.
  • 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.
  • Python and Rust regression tests. Local checks require at least 95% Python coverage; CI covers its configured Python and platform matrix. See the workflow and release results for the versions actually exercised.
  • cargo audit on every PR. CertMonitor declares a single direct Rust dependency, pyo3 (the Python bridge). Its transitive dependencies are recorded in Cargo.lock and scanned for known vulnerabilities on pull requests; no third-party X.509 parsing crate is used.

📄 License

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


Validation snapshots and trust

validate() collects certificate data automatically. Certificate metadata is cached as a snapshot with snapshot_at in UTC; call refresh() to reconnect and collect a renewal. close() clears the connection while retaining the last snapshot for inspection. A new connection or refresh() clears the old snapshot before collecting another certificate.

The default root_certificate check performs a separate verified handshake using the system trust store (or cafile / capath). Its leaf must match the collected snapshot. A changed leaf produces SnapshotMismatch, requiring refresh. Trust verification does not check revocation; revocation_status is not_checked. The default hostname check uses DNS/IP SANs for identity validation and reports common_name / common_name_matches as informational fields. CN matching never overrides the SAN-based result. The opt-in subject_alt_names validator checks the alternate_names you request, or the primary host when none are given.

from certmonitor import CertMonitor, scan_hosts

with CertMonitor(
    "service.example.com",
    connection_host="192.0.2.10",
    cafile="/path/to/private-ca.pem",
    timeout=5,
) as monitor:
    results = monitor.validate({
        "expiration": {"warning_days": 30, "critical_days": 3,
                       "max_lifetime_days": 200},
    })
    print(results)
    monitor.refresh()

for result in scan_hosts(["example.com", "example.org"], max_workers=4, timeout=5):
    print(result)

Client authentication accepts client_cert and optional client_key paths. Expiration checks both validity boundaries and warns when the total lifetime exceeds the public TLS limit that applied on the issue date (200 days from March 2026, 100 from March 2027, 47 from March 2029). Set max_lifetime_days to a number for a private PKI policy, or None to disable the check. Results from validate() retain is_valid and add status (pass, warn, fail, error, unsupported) and a stable <validator>.<status> code.

chain reports structural policy separately from trust. Non-CA issuers and weak signatures fail by default; reject_weak_signatures=False permits weak signatures with warnings. Subject/issuer name equality does not verify a signature, including for certificates labeled self-signed.

The timeout bounds each network operation, including each connection attempt made while collecting the certificate, not the entire scan. OS DNS resolution cannot be interrupted by these socket timeouts. Concurrency and queued endpoints are bounded by max_workers. Cipher/TLS checks describe the negotiated connection, not an exhaustive inventory of server-supported protocols or ciphers. The native PQ probe connects to connection_host and offers server_hostname as SNI, so split-address configurations are probed.

Release CI builds Linux x64/ARM64 wheels targeting glibc 2.28+, macOS Intel/ARM64 wheels, Windows x64 wheels, and a source distribution. These targets require CI verification before release.

Metadata

Release files for certmonitor 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for certmonitor 0.7.0
File Size Uploaded Provenance
certmonitor-0.7.0.tar.gz 1.5 MB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for certmonitor 0.7.0
File
certmonitor-0.7.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
certmonitor-0.7.0-cp310-abi3-manylinux_2_28_x86_64.whl CPython 3.10 abi3 Linux glibc 2.28+ x86-64 Details
certmonitor-0.7.0-cp310-abi3-manylinux_2_28_aarch64.whl CPython 3.10 abi3 Linux glibc 2.28+ ARM64 Details
certmonitor-0.7.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
certmonitor-0.7.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 4.3 MB

Release files / certmonitor-0.7.0.tar.gz

Download URL certmonitor-0.7.0.tar.gz
Size 1.5 MB
Tags Source
SHA-256 checksum
How to use checksums
746d28c402ccb8aa7fcf2fb7df130611813aac3c97a553b87959b896027bf07e
BLAKE2b-256 checksum
How to use checksums
24eb4ed1c967e6d4e5ef40a5edde6793a38c6c62e37aa44da7e70f7be1226658
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / certmonitor-0.7.0-cp310-abi3-win_amd64.whl

Download URL certmonitor-0.7.0-cp310-abi3-win_amd64.whl
Size 474.8 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
9575c7e94ea5302b392324c549068e7338cdda361be62d0c655a205706fb7e04
BLAKE2b-256 checksum
How to use checksums
7e2c7180ca54d449522384d5adfcc0b622aac41b2e0dae76664b2b4a2c488599
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / certmonitor-0.7.0-cp310-abi3-manylinux_2_28_x86_64.whl

Download URL certmonitor-0.7.0-cp310-abi3-manylinux_2_28_x86_64.whl
Size 609.7 kB
Tags CPython 3.10 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
f1b32f017fced399d99ffac15831598f0108153247f655af02bab133cccb8333
BLAKE2b-256 checksum
How to use checksums
c7982f65443efa8b2e7d5f8ad5ae687f7aab6bae892d305d0f8dbb7e2a3e6f68
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / certmonitor-0.7.0-cp310-abi3-manylinux_2_28_aarch64.whl

Download URL certmonitor-0.7.0-cp310-abi3-manylinux_2_28_aarch64.whl
Size 599.9 kB
Tags CPython 3.10 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
81e4f146133d68f927965a7bf95400c759cd407dd730b8a02b65bdd2e887d9d2
BLAKE2b-256 checksum
How to use checksums
80fc33499bde0f6df14feae7f9b5e1e63b583f6bb8405482e8d51eba12c0a8b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / certmonitor-0.7.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL certmonitor-0.7.0-cp310-abi3-macosx_11_0_arm64.whl
Size 564.7 kB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
8b6d704c6803164dc05c37e16800a49f6f443b014cf71b9c2abba624ed22a303
BLAKE2b-256 checksum
How to use checksums
548b45b6d5e45078071a5327a77eb9748a1dc7ef73b88659ac9fcc107544c00b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / certmonitor-0.7.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL certmonitor-0.7.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 569.4 kB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
03b043ae3707e75a0f4112ce3d49e6aefd575a793d80f7408dbce9b26179c035
BLAKE2b-256 checksum
How to use checksums
26895e5050af0a1335fa55abbb8265792df73d29838352a51b21b020e7613ee4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for certmonitor 0.7.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page