service-identity

Verifies that a pyOpenSSL or cryptography TLS certificate is genuinely valid for the hostname, IP, or SRV name you intended to connect to.

Library
PyPI
v26.1.0
106stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
64/100Good
Development Activity68
Maintenance44
Community72
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture90
Code Quality92
Innovation58
Learning Curve85

service-identity is a small, security-critical Python library maintained under the PyCA (Python Cryptographic Authority) umbrella. It solves one narrow but easy-to-get-wrong problem: given a certificate presented during a TLS handshake, is it actually valid for the service you meant to talk to? It implements RFC 6125 hostname verification in full, including DNS-ID, SRV-ID, URI-ID, and IP address identity matching, plus the specific wildcard rules that RFC 6125 imposes (single leftmost label only, minimum host component count, no wildcards inside IDNA-encoded labels).

The library exposes two thin, ecosystem-specific front doors — service_identity.pyopenssl for code using pyOpenSSL connections, and service_identity.cryptography for code holding a cryptography x509.Certificate object directly — both of which delegate to a single shared verification core in hazmat.py. That core extracts subjectAltName entries from the certificate, builds typed pattern objects (DNSPattern, IPAddressPattern, URIPattern, SRVPattern), and matches them against caller-supplied DNS_ID/IPAddress_ID/URI_ID/SRV_ID objects, raising service_identity.VerificationError with a structured list of mismatches on failure or service_identity.CertificateError if the certificate itself is malformed (e.g. no SAN entries at all).

Because it sits directly in the trust path between an application and a supposedly-authenticated TLS peer, the project is deliberately conservative: no runtime dependencies beyond attrs and cryptography, full type annotations checked with strict mypy, and a public no-AI-generated-code policy for contributions. It’s a dependency of Twisted, pyOpenSSL-based clients, and any project that needs certificate identity checks outside of what the stdlib ssl module already verifies during the handshake itself.

What You Get

  • verify_hostname() / verify_ip_address() - one-call checks against a live pyOpenSSL Connection object’s peer certificate.
  • cryptography.verify_certificate_hostname() / verify_certificate_ip_address() - the same checks for code already holding a cryptography x509.Certificate.
  • Typed pattern and ID classes - DNSPattern, IPAddressPattern, URIPattern, SRVPattern and their corresponding DNS_ID, IPAddress_ID, URI_ID, SRV_ID service identifiers, usable directly for custom verification flows via verify_service_identity().
  • Structured failure reporting - VerificationError.errors is a list of typed Mismatch subclasses (DNSMismatch, SRVMismatch, URIMismatch, IPAddressMismatch), not just a string, so callers can branch on what specifically failed.
  • Optional IDNA support - non-ASCII hostnames are handled transparently when the optional idna extra is installed.
  • Full RFC 6125 wildcard rules - single leftmost-label wildcards only, minimum host-component enforcement, and rejection of wildcards inside IDNA (xn--) labels.

Common Use Cases

  • Verifying pyOpenSSL client connections - a client using OpenSSL.SSL.Connection directly (rather than the stdlib ssl module) calls verify_hostname(connection, hostname) right after the handshake to catch a certificate presented for the wrong host.
  • Verifying certificates outside a live handshake - code that has already parsed a cryptography x509.Certificate object (from a cert file, a proxy, or a custom transport) calls verify_certificate_hostname() to run the same check without needing a socket.
  • Twisted and async network libraries - projects like Twisted use service-identity as their hostname-verification backend for TLS-secured protocol implementations.
  • Custom identity policies - code that needs to verify against an SRV-ID or URI-ID (not just a plain hostname) builds SRV_ID/URI_ID objects and calls verify_service_identity() directly with the certificate’s extracted patterns.

Under The Hood

Architecture The codebase is a small, cleanly layered library: hazmat.py holds the entire matching engine — pattern extraction types (DNSPattern, IPAddressPattern, URIPattern, SRVPattern), service-identifier types (DNS_ID, IPAddress_ID, URI_ID, SRV_ID) implementing a shared ServiceID protocol, and the verify_service_identity() function that matches obligatory and optional IDs against extracted certificate patterns and raises a structured VerificationError on mismatch. cryptography.py and pyopenssl.py are thin adapters: each implements only extract_patterns() (pulling subjectAltName values out of its respective certificate object type) plus a couple of public verify_* convenience functions that call into the shared hazmat core — so the two integrations can never drift in their actual verification logic, only in how they get certificate bytes out of their respective libraries. exceptions.py is deliberately isolated purely to keep tracebacks short. Nothing in the public API surface is more than one call deep from the core engine.

Tech Stack Pure Python with a deliberately minimal dependency footprint: attrs for the internal data classes (@attr.s(slots=True) throughout, for memory efficiency and immutability-by-convention) and cryptography for its Rust-based ASN.1 decoder, which recently replaced a pyasn1/pyasn1-modules dependency entirely. idna is an optional extra for non-ASCII hostname support. Packaging uses hatchling with hatch-vcs for git-tag-derived versioning and hatch-fancy-pypi-readme to assemble the PyPI long description from the README and changelog fragments. CI runs through GitHub Actions with a pinned, hash-locked action set, tox for cross-version testing, and a matrix driven directly from the built package’s trove classifiers rather than a hardcoded version list. Documentation is built with Sphinx and the Furo theme, hosted on Read the Docs.

Code Quality Test coverage is unusually thorough for the library’s size: over 1,100 lines of tests (test_hazmat.py, test_cryptography.py, test_pyopenssl.py) against roughly 990 lines of source, run with pytest and measured with branch-aware coverage.py. The project enables the full ruff ALL rule set with narrow, justified per-rule ignores, formats with ruff format, and type-checks with mypy --strict, including a separate tests/typing/ directory of type-only assertions checked in CI. A .pre-commit-config.yaml enforces all of this locally before commit, and interrogate enforces 100% docstring coverage outside test files. Error handling is explicit and typed throughout — verification failures produce structured Mismatch subclasses rather than bare strings or swallowed exceptions.

API Design The public surface is intentionally tiny: one or two verification functions per backend (verify_hostname/verify_ip_address for pyOpenSSL, verify_certificate_hostname/verify_certificate_ip_address for cryptography), each taking just a certificate/connection object and a string. Naming is consistent across both adapters, and docstrings follow a uniform Args/Raises/versionchanged format that Sphinx renders directly into the API reference. Getting started requires no configuration or setup step beyond calling one function inside an existing TLS code path, and the library deliberately keeps commonName-based fallback matching out of scope (removed in 23.1.0) to avoid encouraging a known-insecure verification shortcut.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search