October 21, 2026
Scott Helme published an analysis of a problem with eMudhra’s intermediate CAs. I have not verified any of it myself, so everything below is his account and the one given by eMudhra on the Mozilla ticket, which he quotes.
What was found
On 17 October a researcher reported to Apple, Google, Microsoft, Mozilla and eMudhra that the private keys of eight eMudhra intermediate certificates were trivially recoverable. The intermediates were created in April 2025: six issue TLS certificates (DV, OV and EV, two generations each) and two are for code signing. In May 2025 all eight were cross-signed by Sectigo’s AAA Certificate Services root, so each key appears in two certificates, sixteen in total.
All the keys are P-384. A P-384 private key should be a random number of 384 bits. These ones are 47 bytes of zeros followed by a single non-zero byte, which means there are at most 255 candidates. Recovering a key is a loop: compute 1×G, 2×G, 3×G and so on, until the result equals the public key in the certificate. The published values are tiny, for example 4, 56, 66, 115, 156, 230, 237. Helme says he recovered all of them in under a second on a laptop.
The public keys are valid points on the curve and pass every structural check, so nothing in the certificate looks wrong. The only way to notice is to try to recover the key.
Why it cannot be contained
A leaked key can at least be assumed to be in the hands of whoever leaked it. Here the key is derivable from public data, so anyone who saw these certificates in the past 17 months could have recovered it, and there is no way to know whether anyone did. Revocation and removal fix the future, they say nothing about the past.
How it happened
According to eMudhra’s explanation on the Mozilla ticket (Bugzilla 2083449), a bug in the script used during the key ceremony created a second, weak key pair on the HSM. The two pairs had similar names, and a person used the wrong one when generating the certificate signing request.
That is two failures stacked: a script that generated a bad key, and a manual step where two similarly named objects were confused. I think it is worth separating the two, because they call for different fixes. Human attention will always fail at some rate, so a procedure that depends on a person picking the right one out of two similar names is fragile. What is missing is a check that does not depend on attention. A test that rejects a key whose value is smaller than, say, 2^128, or an independent review of the generated public key before it is certified, would have caught this in seconds. The check costs almost nothing compared to the ceremony around it.
The same applies on the other side. Sectigo cross-signed these intermediates a month later, and nobody in the chain noticed that the public keys belonged to private keys that were trivial to find. Linting tools exist for many certificate properties, but a weak key of this kind is not something the usual structural checks can see.
What the browsers did
Chrome pushed a CRLSet update on the evening of the 17th and Mozilla blocked the intermediates through OneCRL a few hours later. On the 20th Mozilla made the issue public and shipped an NSS update that removes the eMudhra root, and Chrome confirmed the root is being removed in Chrome Root Store v43.
Chrome, Firefox and Safari require Certificate Transparency, and no TLS certificate chaining to these CAs showed up in the logs, so Chrome’s position is that no forged certificate would have been trusted by default in Chrome. Helme points out that CT detects misissuance, it does not prevent it, and that a large part of the TLS clients in the world do not check it.
Where the real exposure is
Most software outside the browsers trusts a CA bundle stored on disk, does not check revocation by default and does not check CT. For those clients, revocation by eMudhra and Sectigo changes nothing. Helme set up a local server with a certificate for api.stripe.com issued under one of the weak intermediates, and reports that:
- curl with default settings printed “SSL certificate verify ok”
- Python completed the handshake
- Node reported
authorized: true
This was true on every Linux and macOS machine he tested. On Windows the chain was accepted unless revocation checking was enabled, and even then only one of the two revocation routes flagged it.
The places where this matters are the ones he lists: curl | sh installers and self-updaters, CI/CD pipelines that hold cloud and registry credentials, server-to-server calls carrying payments, webhooks and OAuth tokens, and Windows code signing, since Windows trusts the eMudhra code-signing root directly.
The devices that cannot be updated are the worst case. Routers, printers, TVs and appliances with frozen firmware keep the old trust bundle, and the certificates are valid until July 2039. For those, removing the root from the browsers does nothing.
One more detail
In an update to his article, Helme writes that a simple prompt to an AI agent was enough to recover the keys, and that Claude did it with a single prompt. I mention it because it describes the cost of the attack well: what used to require someone who knew elliptic curve arithmetic is now a short request. It also means that a flaw of this type, once someone looks at it, is found quickly. The window in which it stayed unnoticed was 17 months, and it ended when somebody decided to look.
What I take from it
- Update the components that ship a CA bundle: the OS
ca-certificatespackage, certifi, Node, Homebrew, and rebuild container images once the base images are updated. - A trust store is a list of organizations whose procedures I cannot inspect. When one of them fails, my protection is the number of independent layers that can notice: revocation, CT, key checks. Clients that skip all of them have only the first line.
- Sanity checks on generated keys are cheap and should be part of any key ceremony, whatever the script says it did.
Source: Scott Helme, “Eight bits of trust: eMudhra’s CA keys cracked in milliseconds”.