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.
Repository Health
Technical Analysis
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 pyOpenSSLConnectionobject’s peer certificate.cryptography.verify_certificate_hostname()/verify_certificate_ip_address()- the same checks for code already holding acryptographyx509.Certificate.- Typed pattern and ID classes -
DNSPattern,IPAddressPattern,URIPattern,SRVPatternand their correspondingDNS_ID,IPAddress_ID,URI_ID,SRV_IDservice identifiers, usable directly for custom verification flows viaverify_service_identity(). - Structured failure reporting -
VerificationError.errorsis a list of typedMismatchsubclasses (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
idnaextra 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.Connectiondirectly (rather than the stdlibsslmodule) callsverify_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
cryptographyx509.Certificateobject (from a cert file, a proxy, or a custom transport) callsverify_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_IDobjects and callsverify_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.
Used by 4 apps in this directory
authentik
Authentication · Security
The self-hosted Identity Provider that replaces Okta, Auth0, and Entra ID with a unified SSO platform supporting SAML, OAuth2/OIDC, LDAP, RADIUS, and WebAuthn.
Baserow
No Code Platforms · Databases
Open-source no-code platform to build databases, apps, automations, and AI agents — self-hosted or cloud, with full data ownership.
Phase Console
Security · Devops
End-to-end encrypted secrets management for engineering teams — from local dev to Kubernetes production.
SWIRL
Search · Databases · Data Engineering
Federated AI search and RAG across 100+ enterprise sources—no data extraction, no vector database required.