trustme: #1 quality TLS certs while you wait¶
You wrote a cool network client or server. It encrypts connections using TLS. Your test suite needs to make TLS connections to itself.
Uh oh. Your test suite probably doesn’t have a valid TLS certificate. Now what?
trustme
is a tiny Python package that does one thing: it gives you
a fake
certificate authority (CA) that you can use to generate fake TLS certs
to use in your tests. Well, technically they’re real certs, they’re
just signed by your CA, which nobody trusts. But you can trust
it. Trust me.
Vital statistics¶
Install: pip install -U trustme
Documentation: https://trustme.readthedocs.io
Bug tracker and source code: https://github.com/python-trio/trustme
Tested on: Python 3.8+, CPython and PyPy
License: MIT or Apache 2, your choice.
Code of conduct: Contributors are requested to follow our code of conduct in all project spaces.
Cheat sheet¶
Programmatic usage:
import trustme
# ----- Creating certs -----
# Look, you just created your certificate authority!
ca = trustme.CA()
# And now you issued a cert signed by this fake CA
# https://en.wikipedia.org/wiki/Example.org
server_cert = ca.issue_cert("test-host.example.org")
# That's it!
# ----- Using your shiny new certs -----
# You can configure SSL context objects to trust this CA:
ca.configure_trust(ssl_context)
# Or configure them to present the server certificate
server_cert.configure_cert(ssl_context)
# You can use standard library or PyOpenSSL context objects here,
# trustme is happy either way.
# ----- or -----
# Save the PEM-encoded data to a file to use in non-Python test
# suites:
ca.cert_pem.write_to_path("ca.pem")
server_cert.private_key_and_cert_chain_pem.write_to_path("server.pem")
# ----- or -----
# Put the PEM-encoded data in a temporary file, for libraries that
# insist on that:
with ca.cert_pem.tempfile() as ca_temp_path:
requests.get("https://...", verify=ca_temp_path)
Command line usage:
$ # Certs may be generated from anywhere. Here's where we are:
$ pwd
/tmp
$ # ----- Creating certs -----
$ python -m trustme
Generated a certificate for 'localhost', '127.0.0.1', '::1'
Configure your server to use the following files:
cert=/tmp/server.pem
key=/tmp/server.key
Configure your client to use the following files:
cert=/tmp/client.pem
$ # ----- Using certs -----
$ gunicorn --keyfile server.key --certfile server.pem app:app
$ curl --cacert client.pem https://localhost:8000/
Hello, world!
FAQ¶
Should I use these certs for anything real? Certainly not.
Why not just use self-signed certificates? These are more realistic. You don’t have to disable your certificate validation code in your test suite, which is good because you want to test what you run in production, and you would never disable your certificate validation code in production, right? Plus, they’re just as easy to work with. Actually easier, in many cases.
What if I want to test how my code handles some bizarre TLS configuration? We think trustme hits a sweet spot of ease-of-use and generality as it is. The defaults are carefully chosen to work on all major operating systems and be as fast as possible. We don’t want to turn trustme into a second-rate re-export of everything in cryptography. If you have more complex needs, consider using them directly, possibly starting from the trustme code.
Will you automate installing CA cert into system trust store? No. mkcert already does this well, and we would not have anything to add.
Full working example¶
Here’s a fully working example you can run to see how trustme
works. It demonstrates a simple TLS server and client that connect to
each other using trustme
-generated certs.
This example requires Trio (pip
install -U trio
) and Python 3.8+. Note that while trustme
is
maintained by the Trio project, trustme
is happy to work with
any networking library.
The key lines are the calls to configure_trust()
,
configure_cert()
– try commenting them out one at a
time to see what happens! Also notice that the hostname
test-host.example.org
appears twice – try changing one of the
strings so that the two copies no longer match, and see what happens
then!
# trustme-trio-example.py
import trustme
import trio
import ssl
# Create our fake certificates
ca = trustme.CA()
server_cert = ca.issue_cert("test-host.example.org")
client_cert = ca.issue_cert("client@example.org")
async def demo_server(server_raw_stream):
server_ssl_context = ssl.create_default_context(
ssl.Purpose.CLIENT_AUTH)
# Set up the server's SSLContext to use our fake server cert
server_cert.configure_cert(server_ssl_context)
# Set up the server's SSLContext to trust our fake CA, that signed
# our client cert, so that it can validate client's cert.
ca.configure_trust(server_ssl_context)
# Verify that client sent us their TLS cert signed by a trusted CA
server_ssl_context.verify_mode = ssl.CERT_REQUIRED
server_ssl_stream = trio.SSLStream(
server_raw_stream,
server_ssl_context,
server_side=True,
)
# Send some data to check that the connection is really working
await server_ssl_stream.send_all(b"x")
print("Server successfully sent data over the encrypted channel!")
print("Client cert looks like:", server_ssl_stream.getpeercert())
async def demo_client(client_raw_stream):
client_ssl_context = ssl.create_default_context()
# Set up the client's SSLContext to trust our fake CA, that signed
# our server cert, so that it can validate server's cert.
ca.configure_trust(client_ssl_context)
# Set up the client's SSLContext to use our fake client cert
client_cert.configure_cert(client_ssl_context)
client_ssl_stream = trio.SSLStream(
client_raw_stream,
client_ssl_context,
# Tell the client that it's looking for a trusted cert for this
# particular hostname (must match what we passed to issue_cert)
server_hostname="test-host.example.org",
)
assert await client_ssl_stream.receive_some(1) == b"x"
print("Client successfully received data over the encrypted channel!")
print("Server cert looks like:", client_ssl_stream.getpeercert())
async def main():
from trio.testing import memory_stream_pair
server_raw_stream, client_raw_stream = memory_stream_pair()
async with trio.open_nursery() as nursery:
nursery.start_soon(demo_server, server_raw_stream)
nursery.start_soon(demo_client, client_raw_stream)
trio.run(main)
CLI reference¶
All options:
$ python -m trustme --help
usage: trustme [-h] [-d DIR] [-i [IDENTITIES [IDENTITIES ...]]]
[--common-name COMMON_NAME] [-q]
optional arguments:
-h, --help Show this help message and exit.
-d DIR, --dir DIR Directory where certificates and keys are written to.
Defaults to cwd.
-i [IDENTITIES [IDENTITIES ...]], --identities [IDENTITIES [IDENTITIES ...]]
Identities for the certificate. Defaults to 'localhost
127.0.0.1 ::1'.
--common-name COMMON_NAME
Also sets the deprecated 'commonName' field.
-q, --quiet Doesn't print out helpful information for humans.
Default configuration:
$ cd /tmp/
$ python -m trustme
Generated a certificate for 'localhost', '127.0.0.1', '::1'
Configure your server to use the following files:
cert=/tmp/server.pem
key=/tmp/server.key
Configure your client to use the following files:
cert=/tmp/client.pem
Designate different identities:
$ python -m trustme -i www.example.org example.org
Generated a certificate for 'www.example.org', 'example.org'
Configure your server to use the following files:
cert=/tmp/server.pem
key=/tmp/server.key
Configure your client to use the following files:
cert=/tmp/client.pem
Generate files into a directory:
$ mkdir /tmp/a
$ python -m trustme -d /tmp/a
Generated a certificate for 'localhost', '127.0.0.1', '::1'
Configure your server to use the following files:
cert=/tmp/a/server.pem
key=/tmp/a/server.key
Configure your client to use the following files:
cert=/tmp/a/client.pem
Configure certs for server/client:
$ gunicorn --keyfile /tmp/a/server.key --certfile /tmp/a/server.pem app:app
$ curl --cacert /tmp/a/client.pem https://localhost:8000
Hello, world!
API reference¶
- class trustme.CA(parent_cert: CA | None = None, path_length: int = 9, organization_name: str | None = None, organization_unit_name: str | None = None, key_type: KeyType = KeyType.ECDSA)¶
A certificate authority.
- property cert_pem: Blob¶
The PEM-encoded certificate for this CA. Add this to your trust store to trust this CA.
- Type:
- property private_key_pem: Blob¶
The PEM-encoded private key for this CA. Use this to sign other certificates from this CA.
- Type:
- create_child_ca(key_type: KeyType = KeyType.ECDSA) CA ¶
Creates a child certificate authority
- Returns:
the newly-generated certificate authority
- Return type:
- Raises:
ValueError – if the CA path length is 0
- issue_cert(*identities: str, common_name: str | None = None, organization_name: str | None = None, organization_unit_name: str | None = None, not_before: datetime | None = None, not_after: datetime | None = None, key_type: KeyType = KeyType.ECDSA) LeafCert ¶
Issues a certificate. The certificate can be used for either servers or clients.
- Parameters:
identities (str) –
The identities that this certificate will be valid for. Most commonly, these are just hostnames, but we accept any of the following forms:
Regular hostname:
example.com
Wildcard hostname:
*.example.com
International Domain Name (IDN):
café.example.com
IDN in A-label form:
xn--caf-dma.example.com
IPv4 address:
127.0.0.1
IPv6 address:
::1
IPv4 network:
10.0.0.0/8
IPv6 network:
2001::/16
Email address:
example@example.com
These ultimately end up as “Subject Alternative Names”, which are what modern programs are supposed to use when checking identity.
common_name (str | None) – Sets the “Common Name” of the certificate. This is a legacy field that used to be used to check identity. It’s an arbitrary string with poorly-defined semantics, so modern programs are supposed to ignore it. But it might be useful if you need to test how your software handles legacy or buggy certificates.
organization_name (str | None) – Sets the “Organization Name” (O) attribute on the certificate. By default, it will be “trustme” suffixed with a version number.
organization_unit_name (str | None) – Sets the “Organization Unit Name” (OU) attribute on the certificate. By default, a random one will be generated.
not_before (datetime | None) – Set the validity start date (notBefore) of the certificate. This argument type is
datetime.datetime
.not_after (datetime | None) – Set the expiry date (notAfter) of the certificate. This argument type is
datetime.datetime
.key_type (KeyType) – Set the type of key that is used for the certificate. By default this is an ECDSA based key.
- Returns:
the newly-generated certificate.
- Return type:
- configure_trust(ctx: ssl.SSLContext | OpenSSL.SSL.Context) None ¶
Configure the given context object to trust certificates signed by this CA.
- Parameters:
ctx (Union[ssl.SSLContext, OpenSSL.SSL.Context]) – The SSL context to be modified.
- class trustme.LeafCert¶
A server or client certificate.
This type has no public constructor; you get one by calling
CA.issue_cert
or similar.- cert_chain_pems¶
The zeroth entry in this list is the actual PEM-encoded certificate, and any entries after that are the rest of the certificate chain needed to reach the root CA.
- Type:
list of
Blob
objects
- private_key_and_cert_chain_pem¶
A single
Blob
containing the concatenation of the PEM-encoded private key and the PEM-encoded cert chain.- Type:
- configure_cert(ctx: ssl.SSLContext | OpenSSL.SSL.Context) None ¶
Configure the given context object to present this certificate.
- Parameters:
ctx (Union[ssl.SSLContext, OpenSSL.SSL.Context]) – The SSL context to be modified.
- class trustme.Blob¶
A convenience wrapper for a blob of bytes.
This type has no public constructor. They’re used to provide a handy interface to the PEM-encoded data generated by
trustme
. For example, seeCA.cert_pem
orLeafCert.private_key_and_cert_chain_pem
.- with tempfile(dir: str | None = None) Generator[str, None, None] as path¶
Context manager for writing data to a temporary file.
The file is created when you enter the context manager, and automatically deleted when the context manager exits.
Many libraries have annoying APIs which require that certificates be specified as filesystem paths, so even if you have already the data in memory, you have to write it out to disk and then let them read it back in again. If you encounter such a library, you should probably file a bug. But in the mean time, this context manager makes it easy to give them what they want.
Example
Here’s how to get requests to use a trustme CA (see also):
ca = trustme.CA() with ca.cert_pem.tempfile() as ca_cert_path: requests.get("https://localhost/...", verify=ca_cert_path)
- Parameters:
dir (str | None) – Passed to
tempfile.NamedTemporaryFile
.
Change history¶
Trustme 1.2.0 (2024-10-07)¶
Features¶
Bugfixes¶
Add the Authority Key Identifier extension to child CA certificates. (#642)
Deprecations and Removals¶
Remove support for Python 3.8 and PyPy 3.9. (#664)
Trustme 1.1.0 (2023-07-10)¶
Features¶
Allow
os.PathLike
in typing ofBlob.write_to_path
. (#606)Add support for PyPy 3.10 and Python 3.12. (#609)
Deprecations and Removals¶
Remove support for Python 3.7. (#609)
Trustme 1.0.0 (2023-04-24)¶
Features¶
Support for ECDSA keys in certificates and use them by default. The type of key used for certificates can be controlled by the
key_type
parameter on the multiple methods that generate certificates. ECDSA certificates as they can be generated significantly faster. (#559)
Deprecations and Removals¶
Remove support for Python 2. trustme now requires Python>=3.7 (CPython or PyPy). (#346)
Trustme 0.9.0 (2021-08-12)¶
Features¶
The package is now type annotated. If you use mypy on code which uses
trustme
, you should be able to remove any exclusions. (#339)
Trustme 0.8.0 (2021-06-08)¶
Features¶
It’s now possible to set an expiry date on server certificates, either with
--expires-on
in the CLI or withnot_after
intrustme.CA.issue_cert
. (#293)Support Python 3.10 (#327)
Set correct KeyUsage and ExtendedKeyUsage extensions, per CA/B Forum baseline requirements. (#328)
Trustme 0.7.0 (2021-02-10)¶
Features¶
trustme can now be used a command line interface with
python -m trustme
. Get the help withpython -m trustme --help
. (#265)
Trustme 0.6.0 (2019-12-19)¶
Features¶
Allow specifying organization and organization unit in CA and issued certs. (#126)
Trustme 0.5.3 (2019-10-31)¶
Features¶
Added
CA.from_pem
to import an existing certificate authority; this allows migrating to trustme step-by-step. (#107)
Trustme 0.5.2 (2019-06-03)¶
Bugfixes¶
Update to avoid a deprecation warning on cryptography 2.7. (#47)
Trustme 0.5.1 (2019-04-15)¶
Bugfixes¶
Update key size to 2048 bits, as required by recent Debian. (#45)
Trustme 0.5.0 (2019-01-21)¶
Features¶
Added
CA.create_child_ca()
to allow for certificate chains (#3)Added
CA.private_key_pem
to export CA private keys; this allows signing other certs with the same CA outside of trustme. (#27)CAs now include the KeyUsage and ExtendedKeyUsage extensions configured for SSL certificates. (#30)
CA.issue_cert
now accepts email addresses as a valid form of identity. (#33)It’s now possible to set the “common name” of generated certs; see
CA.issue_cert
for details. (#34)CA.issue_server_cert
has been renamed toCA.issue_cert
, since it supports both server and client certs. To preserve backwards compatibility, the old name is retained as an undocumented alias. (#35)
Bugfixes¶
Make sure cert expiration dates don’t exceed 2038-01-01, to avoid issues on some 32-bit platforms that suffer from the Y2038 problem. (#41)
Trustme 0.4.0 (2017-08-06)¶
Features¶
CA.issue_cert()
now accepts IP addresses and IP networks. (#19)
Bugfixes¶
Start doing our own handling of Unicode hostname (IDNs), instead of relying on cryptography to do it; this allows us to correctly handle a broader range of cases, and avoids relying on soon-to-be-deprecated behavior (#17)
Generated certs no longer contain a subject:commonName field, to better match CABF guidelines (#18)
Trustme 0.3.0 (2017-08-03)¶
Bugfixes¶
Don’t crash on Windows (#10)
Misc¶
Trustme 0.2.0 (2017-08-02)¶
Broke and re-did almost the entire public API. Sorry! Let’s just pretend v0.1.0 never happened.
Hey there are docs now though, that should be worth something right?
Trustme 0.1.0 (2017-07-18)¶
Initial release
Acknowledgements¶
This is basically just a trivial wrapper around the awesome Python cryptography library. Also, Glyph originally wrote most of the tricky bits. I got tired of never being able to remember how this works or find the magic snippets to copy/paste, so I stole the code out of Twisted and wrapped it in a bow.