
TL;DR
- Eight eMudhra intermediate CA private keys turned out to be numbers smaller than 256. One was 4. We searched about 1.17 million unexpired certificates, out of 2.5 million collected, for the same mistake and found no new cases.
- Four root CAs in Microsoft’s root program use RSA exponent 3. With those roots set as local trust anchors, node-forge 1.4.0 accepted forged certificates that Node and OpenSSL rejected, and no root private key was involved. The bug is known (CVE-2026-85393) and fixed in 1.4.1, so upgrade if you use node-forge. We found no live application exposed this way.
- We reproduced two published AWS-LC bugs (CVE-2026-3336, CVE-2026-3338): 1.68.0 accepts signed PKCS#7 containers it should reject, and 1.69.0 does not. One case exists only in memory, so a regression test built from a saved file passes while the bug is still there.
Eight private keys, each smaller than 256. That was the starting point for this investigation.
On 17 October 2026, a researcher identified as Wayne reported an extraordinary failure at eMudhra, now documented in Mozilla’s public incident record. Eight P-384 intermediate CA keys generated in April 2025 had private scalars that could be found by counting through a single byte. The eight keys appeared in 16 certificates once cross-signs were included.
The discovery raised a question we could test from the outside: had the same kind of mistake appeared elsewhere?
We collected public certificates, built a set of bounded weak-key searches, and checked the results against known examples. We found no additional matches in the scope we tested. Following the unusual keys in that collection led to a different question: what happens when the key is legitimate, but the software checking a signature gets the decision wrong?
Eight small numbers
The published scalars were 4, 56, 66, 115, 155, 156, 230 and 237. P-384 is designed around a private-key space of approximately 3.94 × 10115 values. Every one of these keys sat below 256.

In its incident response, eMudhra described a wrap-and-unwrap test path that produced a weaker set of keys. The company said similar labels in the hardware security module contributed to the wrong keys being selected for certificate signing requests. That is the company’s reported explanation. eMudhra was still investigating the precise script defect when we collected our data.
Those published keys also gave us a positive control. Our tool derived each public key from the disclosed scalar, signed a fixed message, and verified it against the corresponding certificate. All 16 certificates verified. The control established that the tool could recognize this specific, already documented failure before we used it to search for more.
A large sample with explicit boundaries
We built keycheck to import CA certificates from CCADB public reports and collect certificates and precertificates from Chrome-listed Certificate Transparency logs. It supports both the RFC 6962 and Static CT interfaces and retains a cursor for each log so collection can resume.
Across three passes on 23 and 24 October 2026, the collection reached 2,510,850 certificate records carrying 2,037,410 distinct public keys. Seventy CT endpoints contributed, alongside 2,698 CCADB CA records.

The collection covered bounded windows of up to the latest 50,000 entries per log. Some endpoints rate-limited requests, while others could not serve the requested window. These results describe that sample. They do not establish the prevalence of a problem across the entire WebPKI.
We also collected over HTTPS without verifying signed tree heads or Merkle inclusion proofs. CT-derived records are therefore candidate evidence in this investigation, rather than independently attested log entries.
No additional matches in the tested scope
The candidate models included small elliptic-curve scalars, values near the group order, and a serialization hypothesis in which a little-endian buffer is interpreted as a big-endian scalar. For supported models, the tool derives a candidate public key and looks for its exact encoded public-key representation in the collected corpus.
The main sweep covered approximately 1.17 million certificates that were time-valid, absent from our local incident list, and issued with validity periods longer than 90 days. Those conditions define the scope. They do not establish that a certificate was trusted or unrevoked.
The one-byte candidate range produced no additional matches. Expanding to the two-byte range also produced none. The eMudhra positive control remained separate from those results.
A negative result is useful when its boundaries are visible. Here, it means we found no additional instances of the tested patterns in this selected corpus. It does not prove that every key was generated correctly, or rule out weaknesses outside the candidate models.
The verifiers disagreed
Inspecting the collected keys surfaced 18 RSA keys with public exponent 3. Four belonged to root certificates. That property alone does not show compromise. It does make those keys relevant to a known class of signature-verification defects.
We configured each of the four roots as an explicit trust anchor in a local test process. For each root, we constructed a leaf certificate for offline.keycheck.invalid and a malformed PKCS#1 v1.5 signature using the technique documented in node-forge issue 1149. We did not possess or use the root private keys.
Node v26.9.0 rejected all four leaf signatures. OpenSSL 3.6.4 rejected all four chains. The tested node-forge 1.4.0 verification path accepted all four. Valid and invalid control cases produced the expected outcomes.

The defect was already reported. Our contribution was to test its applicability using real root certificates in the collected material, under an explicitly configured local trust policy. The maintainers fixed it in node-forge 1.4.1 (CVE-2026-85393). We tested 1.4.0 in October. If you use node-forge to verify RSA signatures, upgrade.
The four roots were Starfield Class 2 Certification Authority, GLOBALTRUST 2015, OATI WebCARES Root CA, and Go Daddy Class 2 Certification Authority. All four are included in Microsoft’s root program, including for server authentication. That listing does not establish that any application trusts them for a particular purpose.
We did not identify a live application that combined the affected verification path with the necessary trust configuration. Chrome’s root-store snapshot did not list these certificates, and none appeared in the default Node trust material on our test host. The demonstrated result is a local verification failure with stated prerequisites.
A regression test can miss the input that matters
We also reproduced two published AWS-LC PKCS#7 verification issues using versions 1.68.0 and 1.69.0. One concerned a container with mixed trusted and untrusted signers. The other involved a tampered container holding an authenticated attribute that could not be encoded as valid DER.
Version 1.68.0 accepted the problematic cases in our local tests. Version 1.69.0 rejected them, consistent with the published signer-handling advisory and authenticated-attribute advisory.

In our local reproduction, the second case had an important testing constraint. The malformed state we constructed exists in memory and cannot be serialized as valid DER. A test that mutates a saved file and reparses it will not construct the same input. To exercise the defect, the test needs to reach the relevant in-memory state at the verification boundary.
Test the decision your application makes
The investigation began with key generation. Its most concrete application lesson concerns verification: the security decision depends on the code path, input representation, library version and trust configuration that actually run.
Start with a workflow where acceptance has consequences. That might be a certificate, signed document, authentication token or software update. Identify the components that parse it, the component that verifies it, and the policy that decides whether to trust the signer.
Then test rejection as deliberately as acceptance. Include a valid control, an invalid control and the malformed condition relevant to the implementation. Where multiple components process the same input, check that they agree. Where objects are modified after parsing, test those in-memory states too.
Every verification experiment described here ran locally. We did not probe live applications or browsers, perform live revocation checks, or establish production exploitability. The result is a set of bounded observations about what the tested implementations accepted.
Review a critical verification path
If a signature or trust decision protects customer data or funds in your application, Octane can help scope a review of the code, configuration and runtime behavior behind it. Bring the workflow and the consequence of a wrong decision. We will start there.
SOURCES & RELATED ADVISORIES
Subscribe to our newsletter












