x509kit
Pure-Python X.509 / PKCS#10 / CRL / OCSP / PKCS#12 toolkit. Standard library only.
No third-party packages. No C extensions. No vendored code.
Zero third-party dependencies
x509kit uses only the Python standard library. No cryptography,
no pyOpenSSL, no asn1crypto, no pycryptodome, no C extensions.
$ pip show x509kit | grep "^Requires"
Requires:
$ grep -rhE "^\s*(import|from) " src/x509kit/ \
| awk '{print $2}' | cut -d. -f1 | sort -u
base64
datetime
hashlib
hmac
math
os
re
secrets
socket
unicodedata
warnings
All of these are in the Python standard library. Nothing else.
Why stdlib only
- No transitive CVEs. Fewer packages — fewer places for something to go wrong.
- Embedded / restricted environments where you cannot install third-party packages.
- Air-gapped systems — drop a single package and run.
- Readable. The whole implementation is in one place.
- Auditable. The attack surface is exactly one package.
What is implemented from scratch
Every protocol and primitive is our own Python code:
- ASN.1 DER parser and encoder (strict, minimal-length)
- RSA PKCS#1 v1.5 and RSASSA-PSS (with blinding and CRT)
- Ed25519 (RFC 8032)
- ECDSA (RFC 6979 deterministic nonces)
- AES-256 block cipher (FIPS 197) and GCM mode (NIST SP 800-38D)
- PBKDF2 and PKCS#12 KDF
- Every X.509 v3, PKCS#10, PKCS#12, CRL, OCSP structure
- RFC 4518 DN canonicalization
Hashing (hashlib), MAC (hmac), and randomness (secrets) come from
the standard library — writing a custom SHA-256 in Python would be
slower and less tested than what the interpreter already ships.
cryptography and the openssl binary appear only in tests, as
independent verifiers of our output.
Verify it is really stdlib-only
Do not grep for the word "cryptography" — it appears in documentation comments saying "we do not use it". Instead, run this:
python check_no_hidden.py
This script uses Python's ast module to extract every import statement
from every .py file under src/. It compares the result against
sys.stdlib_module_names and a small blacklist of crypto libraries.
Expected output ends with:
RESULT: OK - stdlib only
If it prints anything else, that is a bug — please open an issue.
Verify in 30 seconds
Not asking you to trust me — asking you to run this:
git clone https://github.com/artem122py/x509kit
cd x509kit
pip install -e .
python flask/test_live_ca.py
In one go, this script:
- Generates Root CA + Intermediate in memory (nothing on disk)
- Issues a server leaf certificate for a Flask server
- Starts Flask on HTTPS with the leaf + intermediate chain
- Calls the real OpenSSL CLI to generate a key and a CSR
- POSTs the CSR to the server, gets a leaf back
- Runs
openssl verify -CAfile root -untrusted inter leaf.pem→ expected output:leaf.pem: OK - Revokes the leaf via
/revoke - Runs
openssl verify -CRLfile crl.pem -crl_check→ expected output:error 23 at 0 depth lookup: certificate revoked
The verification step is done by OpenSSL 3.x, called via subprocess. It is not our own verify() method. If our DER encoding were wrong, OpenSSL would reject it — and it doesn't.
Cross-verify matrix
The library is not self-tested. It is tested against external implementations, in both directions:
| Component | Independent verifier | Result |
|---|---|---|
| RSA (PKCS#1 v1.5, PSS) | cryptography + openssl pkeyutl | both ways |
| Ed25519 | RFC 8032 test vectors (byte-exact), cryptography | both ways |
| ECDSA (P-256/384/521/secp256k1) | RFC 6979 test vectors (byte-exact), cryptography | both ways |
| AES-256-CBC | FIPS 197 test vector | byte-exact |
| AES-256-GCM | NIST SP 800-38D test vectors | byte-exact |
| X.509 v3 certificates | openssl x509 -text, openssl verify | OK |
| Chains (root → intermediate → leaf) | Python ssl TLS handshake | OK |
| CRL | openssl crl -text, openssl verify -crl_check | error 23 |
| PKCS#12 (.p12) | openssl pkcs12 -info -nodes | 3 blocks extracted |
| PKCS#8 EncryptedPrivateKeyInfo | openssl pkey | OK |
| mTLS | openssl s_client -cert -key | handshake OK |
Run it yourself:
python test_all.py
Compatibility with cryptography
Run this yourself — it verifies that x509kit and cryptography agree
in both directions, for every primitive:
python compat_test.py
Expected output:
============================================================
x509kit ↔ cryptography
============================================================
1. RSA PKCS#1 v1.5
✓ наш RSA sign → их verify
✓ их RSA sign → наш verify
2. Ed25519 (RFC 8032)
✓ seed → тот же pubkey
✓ наш Ed sign → их verify
✓ их Ed sign → наш verify
3. ECDSA P-256
✓ наш EC sign → их verify
✓ их EC sign → наш verify
4. X.509 сертификат
✓ наш PEM → их x509 парсер
✓ серийник совпадает
✓ их PEM → наш Cert парсер
5. Полная цепочка
✓ их SAN читает наш leaf
✓ их CA парсится нашим CACert
============================================================
12/12 passed
============================================================
This is not a self-test. Every check calls into cryptography (which
links against OpenSSL) and asks it to verify our output, or vice versa.
If any row fails, our implementation is wrong.
Requires: pip install cryptography
Examples
See examples/ — 12 runnable scripts covering the whole
library:
01_root_ca.py— self-signed CA02_issue_from_csr.py— issue leaf from CSR03_self_signed_server.py— HTTPS with self-signed cert04_mtls_server.py— mutual TLS05_crl_revocation.py— revoke and generate CRL06_ocsp_responder.py— OCSP response07_pkcs12_export.py— export to.p1208_parse_real_cert.py— fetch and parse a live HTTPS cert09_encrypted_key.py— encrypted PKCS#8 keys10_verify_chain.py— chain verification scenarios11_name_canonicalization.py— RFC 451812_extensions.py— all extensions in one cert
Each is self-contained, prints what it does, and shows the OpenSSL command to verify the result.
What this is
A learning project that turned into a working implementation. It exists to answer the question: what actually happens inside an X.509 certificate — by writing it from scratch, on the standard library, and checking the result against OpenSSL until they agree.
What this is not
Not a replacement for OpenSSL or cryptography in production. See SECURITY.md for the full list of what is covered and what is not. Short version: no constant-time guarantees, no formal audit, no production hardening.
If you need production-grade crypto: use cryptography. If you want to understand X.509 by reading Python that OpenSSL agrees with: this is for you.
Install
pip install x509kit
Quick example
from x509kit import CertificateBuilder, Ed25519Keys, RSAKeys
ca_key = Ed25519Keys.generate()
ca = (CertificateBuilder(is_ca=True)
.add_subject({"C": "XX", "O": "MyOrg", "CN": "MyOrg Root CA"})
.add_public_key(ca_key)
.add_serial_random().add_validity(days=3650)
.add_basic_constraints(path_len=0)
.add_key_usage("keyCertSign", "cRLSign")
.self_signed().sign(ca_key))
leaf_key = RSAKeys.generate(2048)
leaf = (CertificateBuilder()
.add_subject({"C": "XX", "O": "MyOrg", "CN": "example.com"})
.add_issuer(ca).add_public_key(leaf_key)
.add_serial_random().add_validity(days=365)
.add_san_dns("example.com", "www.example.com")
.add_extended_key_usage("serverAuth")
.sign(ca_key))
assert ca.verify_chain([leaf])
Features
- ASN.1 DER parser and encoder (strict, minimal-length)
- PEM codec
- RSA PKCS#1 v1.5 + RSASSA-PSS (blinding + CRT)
- Ed25519 (RFC 8032), ECDSA (P-256/384/521/secp256k1, RFC 6979)
- X.509 v3: SAN, BasicConstraints, KeyUsage, Extended Key Usage, Name Constraints (with intersection), AKI/SKI, CRL Distribution Points, AIA
- PKCS#10 CSR, CRL (RFC 5280), OCSP (RFC 6960), PKCS#12 (RFC 7292)
- PKCS#8 EncryptedPrivateKeyInfo (PBES2 + PBKDF2 + AES-256-CBC/GCM)
- RFC 4518 DN canonicalization
- Fluent builders: CertificateBuilder, CSRBuilder, CRLBuilder, OCSPResponseBuilder, PKCS12Builder
- X509Error with fix hints, X509Warning for non-fatal issues
Status
Alpha (0.3.0). Educational. Not for production. See SECURITY.md.
License
MIT
Metadata
Release files for x509kit 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| x509kit-0.4.0.tar.gz | 119.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| x509kit-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 194.7 kB
Release files / x509kit-0.4.0.tar.gz
| Download URL | x509kit-0.4.0.tar.gz |
|---|---|
| Size | 119.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0b3319a891a84fcf18c989ede86edf595a698975d5ec9e607d187caa25e39cbd
|
|
BLAKE2b-256 checksum How to use checksums |
f6920397bdc0f1f1fd9c05656a5c55e932cb678380646aa3e00ac15e755c022b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / x509kit-0.4.0-py3-none-any.whl
| Download URL | x509kit-0.4.0-py3-none-any.whl |
|---|---|
| Size | 75.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ccb7c29f358816434394328a6bae042727ca01ce4a9bbcd9ddb3ded9161c5f8f
|
|
BLAKE2b-256 checksum How to use checksums |
d9277b0b2385f1ccbc650bf8172fc596a9f7f989182640841eaaf06e86c59d9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log