Follow-up to #14 (chain validator). Goal: reduce the Rust dependency tree to just pyo3 + Rust std, eliminating x509-parser in favor of a minimal, in-tree, strict-DER X.509 parser scoped to exactly what certinfo exposes to Python.
Scope (only what certinfo actually needs)
Strict DER TLV decoder, OID decoder, Name/RDN walker (CN/O/OU/C), UTCTime + GeneralizedTime, Integer/OctetString/BitString, SPKI (RSA modulus bit length, EC point bit length + curve OID), and three extensions: BasicConstraints (2.5.29.19), SKI (2.5.29.14), AKI (2.5.29.35). no_std-clean, zero external crates. Rough estimate: 1000–1500 LOC.
Explicitly not supported
BER indefinite-length, DSA/Ed25519/Ed448 public keys beyond the existing "unknown" stub, and any extension outside the three above. Parser rejects the unsupported cleanly rather than guessing.
Security posture
- Strict DER only; every length-read bounds-checks against the parent slice.
- No
unsafe; no panics on malformed input (all paths return Result).
- Dedicated
cargo fuzz target with a CT-log-sourced corpus.
- Differential fuzzing against
x509-parser as a merge gate so field extraction is byte-identical across the transition.
cargo audit continues to run.
Tradeoffs
Full control + smaller attack surface vs. owning every parser bug found later (including ones x509-parser has already fixed upstream) and paying parser-code cost for every future capability (SANs, CRL distribution points, CT, etc.).
Sequencing
Land #14 first against x509-parser, then this as its own focused PR with fuzzing + differential testing as hard merge gates.
Acceptance criteria
Follow-up to #14 (chain validator). Goal: reduce the Rust dependency tree to just
pyo3+ Rust std, eliminatingx509-parserin favor of a minimal, in-tree, strict-DER X.509 parser scoped to exactly whatcertinfoexposes to Python.Scope (only what certinfo actually needs)
Strict DER TLV decoder, OID decoder, Name/RDN walker (CN/O/OU/C), UTCTime + GeneralizedTime, Integer/OctetString/BitString, SPKI (RSA modulus bit length, EC point bit length + curve OID), and three extensions: BasicConstraints (2.5.29.19), SKI (2.5.29.14), AKI (2.5.29.35).
no_std-clean, zero external crates. Rough estimate: 1000–1500 LOC.Explicitly not supported
BER indefinite-length, DSA/Ed25519/Ed448 public keys beyond the existing
"unknown"stub, and any extension outside the three above. Parser rejects the unsupported cleanly rather than guessing.Security posture
unsafe; no panics on malformed input (all paths returnResult).cargo fuzztarget with a CT-log-sourced corpus.x509-parseras a merge gate so field extraction is byte-identical across the transition.cargo auditcontinues to run.Tradeoffs
Full control + smaller attack surface vs. owning every parser bug found later (including ones
x509-parserhas already fixed upstream) and paying parser-code cost for every future capability (SANs, CRL distribution points, CT, etc.).Sequencing
Land #14 first against
x509-parser, then this as its own focused PR with fuzzing + differential testing as hard merge gates.Acceptance criteria
Cargo.tomldependencies list contains onlypyo3(plus std)parse_public_key_info,extract_public_key_der,extract_public_key_pem, andanalyze_chainproduce byte-identical output to thex509-parserbaseline across a corpus of ≥1000 real-world certscargo fuzz runtarget clean for ≥1h on malformed-DER corpusmake testgreen including coverage ≥95%