Repository navigation
feat(keys): detect near-zero EC private scalars (eMudhra P-384 compromise) - #35
Merged
Merged
Conversation
…mise)
The --keys pass was RSA-only: PublicKey discarded EC points, and every
weakness check was gated on algo=="RSA". An EC key whose private scalar is
tiny (recoverable by d*G==Q search) slipped through. The October 2026
eMudhra CA compromise shipped eight publicly-trusted P-384 intermediates
whose private scalars were 4..237, so this class is not hypothetical.
Add a self-contained EC module (src/ecc.{hpp,cpp}): short-Weierstrass curves
(P-256/384/521, secp256k1), Jacobian point ops with no per-step inverse, and
Barrett reduction so a bounded small-scalar search is fast without touching
the shared BigUint. parse_spki and parse_ssh_blob now keep the EC point, and
keys.cpp walks d=1..limit for d*G==Q under a per-file step budget, emitting
ec-small-scalar (high, validated) with the recovered d and curve.
Vendor the eight real eMudhra intermediates (public certs only, SHA-256
verified against Mozilla bug 2083449) into the key corpus and fingerprint
them. Unit KATs (incl. the real d=4 point, off-curve and below-bound
negatives) and the Python suite (synthetic d*G on four curves, all eight real
eMudhra recoveries, and a healthy-key false-positive guard) cover it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The
--keyspass could not see a whole class of broken key: EC keys whoseprivate scalar is tiny enough to recover by brute force.
PublicKeydiscardedthe EC point during parsing, and every weakness check was gated on
algo == "RSA", so an EC key walked straight through.The October 2026 eMudhra CA compromise made this concrete: eight
publicly-trusted P-384 intermediate CAs were issued with private scalars in the
range 4 to 237 (3 to 8 bits), trivially recoverable by searching
d*G == Q.mithril would have reported nothing on them.
How
src/ecc.{hpp,cpp}): short-Weierstrass curves(P-256, P-384, P-521, secp256k1), Jacobian point arithmetic (no per-step
modular inverse), and Barrett reduction so the bounded small-scalar search is
fast without modifying the shared
BigUint.parse_spkiandparse_ssh_blobnow retain the uncompressed EC point.keys.cppwalksd = 1..limitford*G == Qunder a per-file step budgetand emits
ec-small-scalar(high confidence, validated tier) carrying therecovered scalar and curve name.
corpus (public certificates only, no private keys; SHA-256 verified against
the upstream incident bug) and fingerprinted.
Testing
below-bound negative cases. 1438 unit checks, 0 failures.
d*Gacross all four curves, all eight real eMudhraP-384 scalars recovered end to end, and a healthy-key false-positive guard.
check adds negligible time to a scan.
Notes
Recovery is bounded by a small scalar limit and a per-file step budget, so the
search stays cheap and cannot hang on a malformed oversized point. Only public
key material is stored; nothing recovers or emits a private key beyond the
already-public scalar value itself.