Testing helpers¶
Testing mTLS code means having certificates, and checking key material into a
repository is a bad habit. httpx_pki.testing mints throwaway certificates so
your suite does not have to re-derive the cryptography boilerplate.
from httpx_pki import PKIClient
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"])
with PKIClient(bundle.pkcs12(), password=b"") as client:
assert client.cn == "svc-client"
The module is not imported by httpx_pki itself — import it explicitly. It is
for tests, and it is not a CA.
What you get back¶
Both make_ca() and make_client_cert() return a CertBundle, which can hand
you the material in whatever shape the code under test wants:
Accessor |
|
|---|---|
|
A PKCS#12 blob |
|
Key and certificate concatenated, ready for |
|
The certificate alone |
|
The unencrypted private key alone |
|
The issuing CA’s certificate — useful as |
|
The subject common name |
|
The issuing |
PKIClient(bundle.pkcs12(), password=b"") # PKCS#12
PKIClient(bundle.pem) # PEM bundle
PKIClient.from_key_pair(bundle.cert_pem, bundle.key_pem)
Realistic extensions by default¶
Minted certificates carry what a real CA would issue — a
digital_signature / key_encipherment KeyUsage and a client_auth
ExtendedKeyUsage — so servers that enforce EKU accept them:
info = client.cert_info()
info.key_usage # frozenset({'digital_signature', 'key_encipherment'})
info.extended_key_usage # ['client_auth']
Override either to test your own selection logic:
make_client_cert("me", ca=ca, key_usage=["digital_signature"])
make_client_cert("me", ca=ca, extended_key_usage=["email_protection"])
Exercising the validity paths¶
expired = make_client_cert("old", ca=ca, expired=True)
future = make_client_cert(
"new",
ca=ca,
not_valid_before=datetime.now(timezone.utc) + timedelta(days=5),
not_valid_after=datetime.now(timezone.utc) + timedelta(days=50),
)
PKIClient(expired.pem).is_expired # True
PKIClient(future.pem).is_not_yet_valid # True
Both emit a CertificateValidityWarning on load, so a test that builds one
deliberately will want to filter it — see
Filtering warnings.
Multi-identity bundles¶
make_pkcs12 writes several identities into one bundle, which nothing else
readily does — cryptography and the openssl command line both keep a single
key. That makes it the only convenient way to test how your code handles a dual
key pair:
from httpx_pki.testing import make_ca, make_client_cert, make_pkcs12
ca = make_ca()
signing = make_client_cert("me", ca=ca, key_usage=["digital_signature"])
encryption = make_client_cert("me", ca=ca, key_usage=["key_encipherment"])
blob = make_pkcs12(
[(signing, "Signature"), (encryption, "Encryption")],
password="secret",
)
Each entry is a CertBundle, or a (bundle, friendly_name) tuple when you want
the identity labelled. The result behaves exactly like a real dual key pair:
PKIClient(blob, password="secret") # AmbiguousCertificateError
PKIClient(blob, password="secret", key_usage="digital_signature") # picks Signature
Bundle layout¶
By default make_pkcs12 lays the file out the way OpenSSL and Windows write
one: certificates in a PBES2-encrypted block, each key individually shrouded,
and an HMAC over the whole file. Three flags produce the plainer variants, for
testing a parser against the shapes it will meet in the wild:
make_pkcs12([bundle], password="pw", encrypt_certs=False)
make_pkcs12([bundle], password="pw", mac=False)
make_pkcs12([bundle], password="pw", keys_in_encrypted_safe=True)
make_pkcs12([bundle]) # no password at all
A pytest fixture¶
import pytest
from httpx_pki import PKIClient
from httpx_pki.testing import make_ca, make_client_cert
@pytest.fixture(scope="session")
def ca():
return make_ca()
@pytest.fixture
def client(ca):
bundle = make_client_cert("test-client", ca=ca)
with PKIClient(bundle.pem, verify=False) as session:
yield session
For a full round trip, point a local TLS server at ca.cert_pem as its client
CA and pass verify=bundle.ca_pem to the client — the two halves of the same
CA.
Next steps¶
Choosing the right certificate — what multi-identity bundles are for
Expiry and rotation — what the expired-certificate paths do
Testing helpers — the generated API reference