Severity
High — every EC certificate currently validates as weak. Most of the modern web uses EC leaf certs (P-256 / P-384), so key_info returns is_valid: false for them in production.
Symptom
from certmonitor import CertMonitor
with CertMonitor("cloudflare.com", 443, enabled_validators=["key_info"]) as m:
m.get_cert_info()
print(m.validate()["key_info"])
# {'key_type': 'ecPublicKey', 'key_size': 256, 'is_valid': False, 'curve': '1.2.840.10045.3.1.7'}
# ^^^^^ WRONG — P-256 is a strong curve
Root cause
The Rust parser emits the EC curve as an OID dotted string (rust_certinfo/src/pyobj.rs#L99 — curve_oid.to_id_string()), but the key_info validator compares against curve names (certmonitor/validators/key_info.py#L151-L154):
strong_curves = ["secp256r1", "secp384r1", "secp521r1"]
return curve in strong_curves # "1.2.840.10045.3.1.7" is never in this list -> always False
So the membership test never matches and all EC keys fall through to False.
Why tests didn't catch it
tests/test_validators/test_key_info.py feeds the validator hand-built dicts with "curve": "secp256r1" (a name) — a value the real parser no longer emits. The tests assert a contract the parser stopped honoring (mock divergence): green tests, broken reality.
Scope / origin
Not caused by the post-quantum work. The PQ change only added the _PQ_ALGORITHM_NAMES short-circuit at the top of _is_key_strong_enough; the EC branch is untouched. The OID-vs-name mismatch dates to the in-tree DER/X.509 parser rewrite (v0.3.0, #22), which changed the curve field from the documented name format to an OID. It has been latent and shipping since then; the PQ smoke test is just how it surfaced.
key_info is the only Python consumer of the curve field (verified by grep), so the blast radius is contained to this validator.
Fix
Emit the curve name from Rust (single source of truth — the curve OID constants and their names already live in rust_certinfo/src/der/oid.rs), falling back to the OID dotted string for unrecognized curves. This:
- Restores the documented contract — the
key_info docstring examples and the existing tests both expect "curve": "secp256r1".
- Fixes validation automatically (the name comparison then matches).
- Keeps the curve identity table in Rust rather than duplicating it in Python (consistent with how
pq_algorithms() and the TLS group registry are handled).
Then update tests/test_validators/test_key_info.py to exercise the real parser output (parse an actual EC cert DER) so the mock can't drift from reality again, and add a regression test asserting a P-256 cert validates as is_valid: true.
Acceptance criteria
Severity
High — every EC certificate currently validates as weak. Most of the modern web uses EC leaf certs (P-256 / P-384), so
key_inforeturnsis_valid: falsefor them in production.Symptom
Root cause
The Rust parser emits the EC curve as an OID dotted string (rust_certinfo/src/pyobj.rs#L99 —
curve_oid.to_id_string()), but thekey_infovalidator compares against curve names (certmonitor/validators/key_info.py#L151-L154):So the membership test never matches and all EC keys fall through to
False.Why tests didn't catch it
tests/test_validators/test_key_info.py feeds the validator hand-built dicts with
"curve": "secp256r1"(a name) — a value the real parser no longer emits. The tests assert a contract the parser stopped honoring (mock divergence): green tests, broken reality.Scope / origin
Not caused by the post-quantum work. The PQ change only added the
_PQ_ALGORITHM_NAMESshort-circuit at the top of_is_key_strong_enough; the EC branch is untouched. The OID-vs-name mismatch dates to the in-tree DER/X.509 parser rewrite (v0.3.0, #22), which changed thecurvefield from the documented name format to an OID. It has been latent and shipping since then; the PQ smoke test is just how it surfaced.key_infois the only Python consumer of thecurvefield (verified by grep), so the blast radius is contained to this validator.Fix
Emit the curve name from Rust (single source of truth — the curve OID constants and their names already live in rust_certinfo/src/der/oid.rs), falling back to the OID dotted string for unrecognized curves. This:
key_infodocstring examples and the existing tests both expect"curve": "secp256r1".pq_algorithms()and the TLS group registry are handled).Then update tests/test_validators/test_key_info.py to exercise the real parser output (parse an actual EC cert DER) so the mock can't drift from reality again, and add a regression test asserting a P-256 cert validates as
is_valid: true.Acceptance criteria
is_valid: true.is_valid: false.parse_public_key_inforeturnscurveas a name for known curves, OID string for unknown.make ciclean; coverage >= 95%.