Skip to content

key_info reports all EC certificates as is_valid: false (curve OID vs name mismatch) #48

@bradh11

Description

@bradh11

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#L99curve_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

  • A live P-256 / P-384 cert validates as is_valid: true.
  • An unknown/weak curve (e.g. secp192r1, secp256k1) still validates as is_valid: false.
  • parse_public_key_info returns curve as a name for known curves, OID string for unknown.
  • Regression test exercises real parser output, not a hand-built dict.
  • make ci clean; coverage >= 95%.
  • CHANGELOG entry under Fixed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions