Skip to content

Security: artem122py/x509kit

Security

SECURITY.md

Security Policy

What x509kit is

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.

What is covered

  • 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

What is NOT covered

Be honest with yourself about these before using x509kit anywhere non-trivial.

Not constant-time

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.

Not fuzzed continuously

fuzz_x509.py exists and runs, but is not part of CI. Parsers handle random bytes without crashing, but the corpus is not exhaustive.

Limited DoS protection

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.

Not independently audited

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.

CMS limitations

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.

No formal PKI validation

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 Points
  • Cert.fetch_ocsp(issuer=...) — POST an OCSP request to AIA responders
  • Cert.fetch_issuer(trust_anchor=...) — AIA chasing up the chain
  • x509kit.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 platform security integration

No HSM support, no OS keystore, no key attestation, no TPM. Keys live in Python memory as plain integers.

No memory safety guarantees

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.

PKCS#12 MAC uses HMAC-SHA-256

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.

If you find a vulnerability

Please do not open a public GitHub issue for security problems.

Report privately:

Include:

  1. What is wrong (short description)
  2. Minimal reproduction (code that demonstrates the problem)
  3. Impact — what can an attacker do
  4. Version (python -c "import x509kit; print(x509kit.version)")

What I promise

  • 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)

What I do NOT promise

  • 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.

Recommendations if you use x509kit

If you decide to use this library for anything beyond learning:

  1. 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.

  2. 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_SIZE before parsing untrusted data.

  3. Do not use for long-lived private keys under load. RSA at 10,000 signatures/hour in pure Python — use OpenSSL instead.

  4. Watch for updates. We track OpenSSH/OpenSSL security advisories, but we are not always fast.

Supported versions

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.

License

MIT. See LICENSE.

There aren't any published security advisories