httpx-pki
PKCS#12 client-certificate (mTLS) sessions for httpx2 and httpx.
httpx-pki gives you an httpx.Client (and httpx.AsyncClient) subclass with a
client certificate already mounted, so mutual-TLS endpoints "just work":
from httpx_pki import PKIClient
with PKIClient("client.p12", password="secret") as client:
resp = client.get("https://mtls.example.com/")
print(resp.status_code)
Purpose
httpx deprecated its cert= argument in 0.28 — a design httpx2 keeps — in
favor of building an ssl.SSLContext yourself, which stdlib ssl can't do
from PKCS#12 or in-memory bytes. httpx-pki is that missing piece.
Install
pip install httpx-pki
Requires Python 3.10+. httpx2 comes with it, along with cryptography,
truststore, and certifi.
Prefer the original httpx? It stays fully supported — install with --no-deps
so httpx2 isn't pulled in. See
Install and
Backends.
Whatever you were handed, there's a one-liner for it
Certificate files come with all sorts of extensions — .p12, .pfx, .pem,
.crt, .tls — but an extension is just a name. httpx-pki detects the
encoding from the bytes, so you can point it at whatever your PKI team sent
you:
from httpx_pki import PKIClient
# PKCS#12 bundle — key + cert + chain in one blob
PKIClient("client.p12", password="secret")
# PEM bundle — key + cert(s) in one file, any block order
PKIClient("client.pem")
# Raw bytes you already have in hand
PKIClient(p12_bytes, password=b"secret")
# Separate certificate and key, PEM or DER
PKIClient.from_key_pair("client.crt", "client.key")
# ...with intermediates, as PEM or PKCS#7
PKIClient.from_key_pair("client.crt", "client.key", chain="chain.p7b")
# The Windows certificate store (Windows only)
PKIClient.from_windows_cert_store(name="Acme Corp")
# The macOS keychain (macOS only)
PKIClient.from_macos_keychain(name="Acme Corp")
# Configured entirely by environment variables
PKIClient.from_env()
Async
from httpx_pki import AsyncPKIClient
async with AsyncPKIClient("client.p12", password="secret") as client:
resp = await client.get("https://mtls.example.com/")
One file, several certificates
A PKCS#12 or PEM bundle can hold more than one identity — a dual key pair from
AD key archival, or a renewed certificate kept beside the one it replaces.
cryptography can't express that: it returns the first key and leaves the other
identity's certificate looking like a chain certificate. httpx-pki reads the
structure itself, so you can inspect and select:
from httpx_pki import PKIClient, list_identities, currently_valid
list_identities("corp.p12", password="secret") # see what's in there
PKIClient("corp.p12", password="secret", key_usage="digital_signature")
PKIClient("corp.p12", password="secret", identity="Signature")
PKIClient("corp.p12", password="secret", identity=currently_valid)
Loading a multi-identity bundle without a selector raises rather than guessing.
→ Choosing the right certificate
Server trust
Your client certificate and the server's are independent. verify=True (the
default) uses the OS trust store, so corporate CAs distributed by group
policy or MDM work out of the box:
PKIClient("client.p12", password="secret", verify="/etc/ssl/internal-ca.pem")
PKIClient("client.p12", password="secret", verify="certifi")
Expiry and rotation
Certificates keep getting shorter-lived. Warn early, reload automatically, or fail loudly:
from datetime import timedelta
PKIClient(
"/etc/certs/client.pem",
auto_reload=True, # pick up cert-manager rotations
strict_validity=True, # fail clearly, not at handshake
warn_if_expires_within=timedelta(days=7),
)
Inspecting what's mounted
client.cn # 'corp-user'
client.not_valid_after # datetime (UTC)
client.is_expired # bool
client.cert_info() # CertInfo: subject, issuer, fingerprints, usages, SANs
Just the SSL context
Don't want the client wrapper? build_ssl_context() gives you the hard part,
ready for a plain httpx.Client or a custom transport:
import httpx
from httpx_pki import build_ssl_context
ctx = build_ssl_context("client.p12", password="secret")
client = httpx.Client(verify=ctx)
⚠️ Passing a custom transport= makes httpx ignore verify= — put the context
on the inner transport, not the client.
Testing helpers
httpx_pki.testing mints throwaway certificates, including multi-identity
bundles that nothing else readily produces:
from httpx_pki.testing import make_ca, make_client_cert
ca = make_ca()
bundle = make_client_cert("svc-client", ca=ca, dns_names=["svc.internal"])
expired = make_client_cert("old", ca=ca, expired=True)
⚠️ Security note on pickling
To support pickling, a client stores its certificate material and rebuilds the
SSL context on unpickle. The pickle therefore contains the decrypted private
key in cleartext — treat it as a secret. repr() never reveals key material.
Passwords are not retained, with one exception: enabling auto_reload keeps the
password on the client so unattended reloads can decrypt the rotated source.
→ Security notes · SECURITY.md
How it works
Stdlib ssl can't load PKCS#12 or in-memory key material, so httpx-pki uses
cryptography to extract the key and certificates,
stages them where OpenSSL can read them, and passes the resulting
ssl.SSLContext to httpx via verify=.
On Linux the decrypted key never touches disk — it's staged in an anonymous
memfd that OpenSSL reads through /proc/self/fd and that ceases to exist when
closed. Elsewhere it's a 0600 temp file, deleted immediately after loading.
Non-goals
Scoped to credentials whose private key can be exported into memory. Not
supported: PKCS#11 / smartcards / HSMs / TPMs (incompatible with stdlib
ssl, which needs the raw key bytes), Java keystores (convert to PKCS#12
with keytool), workload-identity protocol clients (point auto_reload at
the files they write), and OCSP / CRL revocation (nothing in stdlib ssl to
build on).
Supply chain
Released to PyPI exclusively from GitHub Actions via
Trusted Publishing (OIDC — no
long-lived tokens) with PEP 740
attestations, from a tagged commit whose version is verified against
__version__ at build time. All Actions are pinned to full commit SHAs.
Install via a lockfile that records hashes, as with any security-sensitive dependency.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file httpx_pki-0.8.0.tar.gz.
File metadata
- Download URL: httpx_pki-0.8.0.tar.gz
- Upload date:
- Size: 148.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c073c0299f74c9803f66518424fb9650f8c5f16d2a0df8a7873710eba6f5b32
|
|
| MD5 |
cd1f5b0575e55a90e05f909596e16832
|
|
| BLAKE2b-256 |
88f3358a40f1500c2c851ba9c0d5bf0ba82976d27299f62972fcacd2e8ce8138
|
Provenance
The following attestation bundles were made for httpx_pki-0.8.0.tar.gz:
Publisher:
publish.yml on ccbest/httpx-pki
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
httpx_pki-0.8.0.tar.gz -
Subject digest:
6c073c0299f74c9803f66518424fb9650f8c5f16d2a0df8a7873710eba6f5b32 - Sigstore transparency entry: 2351432316
- Sigstore integration time:
-
Permalink:
ccbest/httpx-pki@5fa8a32f5fd57e8ee74f439deb12a5b638bb106b -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/ccbest
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5fa8a32f5fd57e8ee74f439deb12a5b638bb106b -
Trigger Event:
release
-
Statement type:
File details
Details for the file httpx_pki-0.8.0-py3-none-any.whl.
File metadata
- Download URL: httpx_pki-0.8.0-py3-none-any.whl
- Upload date:
- Size: 70.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc964412cf32d36a52a0a25bc57c15706b45fc47536a1a1f7983b9cfa42aed82
|
|
| MD5 |
e546dedb5aa837f66ab6b8350c397393
|
|
| BLAKE2b-256 |
1e9fd990477625ef18907e12bd0d21cc23a102248ee0b72ff64d52e5148a7eca
|
Provenance
The following attestation bundles were made for httpx_pki-0.8.0-py3-none-any.whl:
Publisher:
publish.yml on ccbest/httpx-pki
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
httpx_pki-0.8.0-py3-none-any.whl -
Subject digest:
dc964412cf32d36a52a0a25bc57c15706b45fc47536a1a1f7983b9cfa42aed82 - Sigstore transparency entry: 2351432967
- Sigstore integration time:
-
Permalink:
ccbest/httpx-pki@5fa8a32f5fd57e8ee74f439deb12a5b638bb106b -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/ccbest
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5fa8a32f5fd57e8ee74f439deb12a5b638bb106b -
Trigger Event:
release
-
Statement type: