x509kit is a pure-Python implementation of X.509, PKCS#10, PKCS#12, CRL, and OCSP. It has zero third-party dependencies — only the Python standard library.
It is a learning project. It is not a replacement for OpenSSL, cryptography, or pyOpenSSL in any context where real security matters. The fact that it is cross-verified against OpenSSL means the code is functionally correct — not that it is safe for adversarial environments.
- ASN.1 DER parser and encoder (strict, minimal-length, depth-limited) with size limits: DER 512 KB, PEM 1 MB, depth 32
- RSA PKCS#1 v1.5 and RSASSA-PSS (with blinding and CRT)
- Ed25519 (RFC 8032)
- ECDSA (P-256, P-384, P-521, secp256k1) with RFC 6979 deterministic nonces
- AES-256-CBC and AES-256-GCM (pure Python)
- X.509 v3 certificate parsing, verification, and chain validation
- PKCS#10 CSR creation and verification
- CRL (RFC 5280) and OCSP (RFC 6960)
- PKCS#12 (RFC 7292) container format
- PKCS#8 EncryptedPrivateKeyInfo (PBES2 + PBKDF2 + AES-256-CBC/GCM)
- CMS (RFC 5652) SignedData — parsing, signing, verification
(
x509kit.cms): ContentInfo, SignerInfo, signed attributes, embedded certificates and CRLs,cms_sign()for RSA / Ed25519 / ECDSA,CMSSignedData.verify()for signature + messageDigest + optional chain to a trust anchor. Verified against OpenSSL CLI. - RFC 4518 DN canonicalization
- RFC 6125 hostname validation
Be honest with yourself about these before using x509kit anywhere non-trivial.
Python big-integer arithmetic is not constant-time. RSA uses blinding to reduce timing leakage; ECDSA uses RFC 6979 to remove nonce reuse. But pow(), scalar multiplication, and comparisons still leak timing information. If an adversary can measure wall-clock time of your private-key operations with nanosecond precision, they can potentially recover the key.
Use OpenSSL for anything exposed to a remote attacker.
fuzz_x509.py exists and runs, but is not part of CI. Parsers handle random bytes without crashing, but the corpus is not exhaustive.
The library rejects oversized DER (512 KB), PEM (1 MB), and deeply nested ASN.1 (depth 32). It does not protect against:
- CPU exhaustion: Ed25519 sign+verify in pure Python is ~30 ops/sec; an attacker can slow down a service by requesting many signatures.
- Memory exhaustion from many small objects: each parsed certificate allocates memory; parse rate is ~10,000 certs/sec on a modern CPU.
- Algorithmic complexity: DER parsing is O(n) in input size, but cryptographic verification is O(bit_length³) for RSA.
If you accept untrusted certificates, add rate limiting at the application level. The library cannot do this for you.
The code has been cross-verified against cryptography and OpenSSL 3.6.5, which means it produces compatible output. This is not the same as a security audit. No third party has reviewed the code for logic errors, side channels, or attack surface.
x509kit.cms verifies signatures, messageDigest, and (optionally) a
certificate chain to a caller-supplied trust anchor. It does not
implement: EnvelopedData / DigestedData / EncryptedData /
AuthenticatedData, CAdES-BES/LT/LTA, RFC 3161 timestamping, external
TSA, or countersignature handling. It also does not implement CRL/OCSP
checking during verification — pass trust_anchor and validate
revocation yourself.
Signing uses one signer per cms_sign() call. Multi-signer CMS is
parsed correctly but not produced by this API.
The library parses and verifies certificates. It does not implement full RFC 5280 Section 6 path validation: no policy constraints, no name-constraints intersection across the entire path in the strictest sense.
As of 0.6.0, limited network helpers exist:
Cert.fetch_crl(ca=...)— download a CRL via CRL Distribution PointsCert.fetch_ocsp(issuer=...)— POST an OCSP request to AIA respondersCert.fetch_issuer(trust_anchor=...)— AIA chasing up the chainx509kit.revocation— a small HTTP server for CRL/OCSP
These are convenience wrappers, not a hardened PKI client. They do not implement: response caching, retry policy, AIA loops detection beyond max_depth, OCSP stapling, CRL delta handling, or signature verification against non-issuer responders. Do not point them at untrusted URLs without rate limiting.
No HSM support, no OS keystore, no key attestation, no TPM. Keys live in Python memory as plain integers.
Python does not have buffer overflows, but it does have MemoryError on adversarial input, and secrets may be copied by the interpreter before your wipe() runs.
This is what OpenSSL 3.x accepts. Older implementations (Windows 7, some Java keystores) may require HMAC-SHA-1. If you need broad compatibility, generate the .p12 with OpenSSL directly.
Please do not open a public GitHub issue for security problems.
Report privately:
- Email: artemkamirn@mail.ru
- Or via GitHub Security Advisories: https://github.com/artem122py/x509kit/security/advisories/new
Include:
- What is wrong (short description)
- Minimal reproduction (code that demonstrates the problem)
- Impact — what can an attacker do
- Version (python -c "import x509kit; print(x509kit.version)")
- A reply within 7 days (usually faster)
- Coordinated disclosure after a fix is published
- Credit in the release notes (unless you prefer to stay anonymous)
- SLA on fixes
- Backports to older versions
- Responses on weekends or holidays
- Audit-level scrutiny of every commit
This is a project maintained by one person in their free time. There is no team, no company, no funding, no bug bounty.
If you decide to use this library for anything beyond learning:
-
Cross-verify with OpenSSL yourself. Do not trust our verify() method. Use openssl verify, openssl pkcs12 -info, openssl crl -text. If they disagree with us, we are wrong.
-
Bound your inputs. When accepting certificates, CSRs, or PKCS#12 files from outside, enforce size limits. The library defaults are: DER 512 KB, PEM 1 MB, nesting depth 32. Real X.509 certificates are under 50 KB, so these give 10x headroom. If your context allows stricter limits, set them — you can monkey-patch
x509kit.x509._MAX_DER_SIZEbefore parsing untrusted data. -
Do not use for long-lived private keys under load. RSA at 10,000 signatures/hour in pure Python — use OpenSSL instead.
-
Watch for updates. We track OpenSSH/OpenSSL security advisories, but we are not always fast.
Only the latest release is supported. 0.3.0 is current. Once 1.0.0 is released, the API freezes and semantic versioning applies strictly.
MIT. See LICENSE.