For seventeen months between April 2025 and October 2026, the security of eight intermediate certificate authorities in the trusted stores of Mozilla, Chrome, Apple, Microsoft, and half the internet rested on eight bits. Not on a stolen key, not on a curve vulnerability, and not on a cryptographic flaw — but on the fact that eight numbers that were supposed to be random 116-digit monsters turned out to be integers smaller than 256. One of them was 4.
eMudhra is an Indian certificate authority traded on the Bombay Stock Exchange and the National Stock Exchange. Its public TLS brand is called emSign, its roots sit in the trust stores of every major platform, and the company itself reports issuing more than 60 million certificates. On April 30, 2025, eMudhra held a key ceremony at which 24 intermediate CAs were meant to be born: three regional series (A for India, B for the Dutch eMudhra BV, C for the American eMudhra Inc), two key types (RSA-4096 and P-384 elliptic curve), and four purposes (DV, OV, EV, plus code signing).
Eight of the twenty-four broke — all the P-384 keys in series B and C. Six of them were authorized to issue TLS certificates for websites; two could sign code. Trust in them travelled by two paths: through the root emSign ECC Root CA - G3 and through a Sectigo cross-signature backed by its ancient root AAA Certificate Services — meaning they were trusted even by stores that knew only Sectigo.
On October 17, 2026, at 16:27 UTC, a researcher using the pseudonym Wayne ran his own key-checking tool against certificates from CCADB — the shared certificate authority database maintained by browser trust programs. Eight keys fell over. By 18:05, he had signed the same phrase with each of them — “We have a problem here - Wayne” — and sent the proofs, along with the certificates, to Apple, Chrome, Microsoft, Mozilla and, to their credit, eMudhra itself.
What followed moved at a speed that would make the authors of any formal vulnerability-disclosure procedure weep with joy. Chrome blocked the six TLS intermediates through its CRLSet at 19:45 — one hundred minutes after receiving the email. At 21:28, eMudhra acknowledged receipt. At 21:48, Mozilla, without knowing the method, independently recovered all eight keys. At 22:59, the block reached Firefox users through OneCRL. The next day, eMudhra revoked all eight intermediates with the reason cACompromise. On October 20, everything became public: Mozilla published a bug report, removed the emSign ECC Root CA - G3 root from its store entirely, and released Firefox 158.0.1; Go shipped a patch with eight hardcoded public-key hashes; Microsoft blocked the certificates; Apple did so on the 22nd.
Mozilla and Cloudflare separately checked Certificate Transparency logs and attacks on their networks: not one issued certificate, not one trace of exploitation through their infrastructure. For browsers, that is good news — they require CT proofs, and a forged certificate could not have passed in them at any point during all seventeen months. The bad news is that browsers are not the greater part of the internet.
Elliptic-curve cryptography on the P-384 curve looks formidable, but it is built almost poetically. A private key is just a number, d. A public key is a point Q produced by adding a fixed curve point G to itself d times: Q = d·G. The forward problem — multiplication — is instantaneous. The reverse one — finding d from a known point Q — is so hard that the entire secure web rests on it. A random d in P-384 means roughly 10¹¹⁵ possibilities, a number that does not exist in any physical form anywhere in the world.
The eMudhra private keys looked like this: 47 bytes of zeros and one significant byte. In hex, a mile-long string of zeroes and “04” at the end. The attack called “count up” does not even require expertise: calculate 1·G, compare it with Q; calculate 2·G, compare. At most 255 multiplications — and you are inside someone else’s key. On a laptop, that takes milliseconds.
The public key, meanwhile, looks perfectly normal. The point 4·G is every bit as legitimate a point on the curve as the result of an honest throw of 384 dice. Every structural certificate check passes; the file size is standard, the encoding impeccable, the HSM reports a successful operation. The only way to detect a weak key is to try to recover it. That is the quiet horror of this story: the certificate does not lie, it simply honestly displays a curve point that says nothing about how many steps it took to get there.
eMudhra’s explanation, posted in Mozilla bug 2083449, will make anyone who has ever written production scripts smile nervously. The keys were intended for export to data centers in Europe and the United States — a matter of data sovereignty — so an approved deviation from the standard scenario was added to the ceremony: a test of the key wrap/unwrap procedure. Alongside the correct pair, the ceremony script created a second pair on the HSM — a weak one. The two pairs ended up with similar labels. The wrong one was selected for certificate requests. The CSRs went to the parent root and to Sectigo for cross-signing, and the crooked keys froze forever inside public certificates.
The status of the hardware deserves its own paragraph. The FIPS 140-2 Level 3-certified Utimaco HSMs worked exactly as intended — device validation says nothing about the script controlling them. An HSM will faithfully carry out any request: generate a key, wrap it, unwrap it, sign. If the script asks it to import a key with a value chosen somewhere else, it will import it and report that everything succeeded. Certification of the module is not certification of the ceremony. A key label is a name printed by a human; the only thing that truly identifies a key is its public half, and that is what must be compared.
Mozilla added that it had tried to crack the remaining P-384 keys in series A — the Indian series, generated on Entrust modules — “considerably more aggressively,” and failed. The RSA keys from the same ceremony went through a different code path and are intact too. So it was not the company, not the platform, and not the cryptography that broke — it was one script plus a human procedure that compared names instead of values.
The hardest part of the story happened seventeen months before the public disclosure. In May and June 2025, Sectigo and outside observers noticed that eMudhra was distributing CRLs — revocation lists, signed documents clients download to verify the current status of certificates — for exactly those six TLS intermediates, and the signatures on those lists did not verify. eMudhra closed the bug with the wording “incorrect CA key configuration” and a manual process, re-signed the lists, and everyone moved on.
Now that the mechanics are known, the picture comes alive in an unpleasant way. First eMudhra signed the CRLs with the correct keys — and the signatures did not match the certificates, because the certificates contained the public halves of the weak keys. Then the CRLs were re-signed with the weak keys — and everything lined up. A certificate authority in possession of a key pair received from its own cryptography the loudest message it is capable of sending: “a signature made with your real key does not match what is written in your own certificate.” The response was not an investigation, but switching to the key for which the error disappeared. Against the hierarchy both keys protected — public to everyone, private to the owner — the cryptographic message had no chance.
A demanding reader will ask: how can the rest of eMudhra’s checks be trusted after that? The browser trust programs’ answer sounds roughly like this: by October 2026, around ten incident reports had accumulated against eMudhra — key sizes prohibited by its own policy, bad OCSP certificates, validity one second over the limit, seven undisclosed subordinate CAs. And in that same window, ten new eMudhra roots were moving through Mozilla’s inclusion pipeline, with notice of the intent to approve them issued on October 6, 2026, eleven days before the explosion. Chrome is now “carefully evaluating” whether the eMudhra root should remain in the store. Mozilla’s wording — “we are not removing it at this time” — is useful precisely because you can hear the “for now.”
Browsers got away with a scare, and those scares have technical surnames: CRLSet in Chrome, OneCRL and CRLite in Mozilla, rapid trust-store updates, mandatory Certificate Transparency. A browser has one application and a push-update channel — it can be fixed with a switch in hours.
The rest of the world works differently, and this is where what Scott Helme called “eight bits of trust” begins. The overwhelming majority of non-browser TLS clients with default settings do three things: trust a roots file sitting on disk, do not check revocation, and do not check CT. eMudhra could update every revocation list; Sectigo could update every revocation list years into the future — these clients would not care.
Helme tested it himself: he used the forgery to sign a certificate for api.stripe.com — a name expensive enough to require no explanation — and offered it to default curl, Python, and Node on Linux and macOS. All accepted it without a single warning. Windows CryptoAPI accepted it as long as revocation checking was enabled; even then, only one of the two paths came back as revoked, because the Sectigo cross-signature lived separately in the Apple and Microsoft stores.
The risk categories arrange themselves around those who automate the internet: system scripts, curl|sh installers, binaries crawling from site to site, CI runners with cloud keys, payment-system SDKs, TLS-inspecting middleboxes at the edge of corporate networks, embedded firmware in hardware that has not been updated since installation. Helme cites frozen firmware with validity through 2038; fenko.nz gives the practical limit — July 2039, when the compromised intermediate certificates themselves expire. This entire ecosystem trusts the cracked key until the file is updated or the certificate expires — whichever comes first.
It is also curious who took longest to fix things. Patches came in a wave: Go 1.27.3 and 1.26.10 with hashes hardcoded directly into crypto/x509; NSS 3.131.1; Python certifi 2026.10.19 — which candidly admitted that it initially killed one too many roots from the store, giving uninvolved parties their roots back a day later; curl CA bundle 2026-10-20; Node.js 26.12.1, 24.22.1, 22.23.4; Bun 1.4.4; Deno 2.9.8; Electron across three branches; OpenJDK throughout the line from 27 down to 8; Python 3.15.1, 3.14.9, 3.13.17; pip, uv, rustup, Docker Desktop, ArgoCD, Conda, pixi — all the way to Claude Code 2.1.303, which is itself a signal of how many necessary things in the modern stack travel over HTTPS with their own root lists. One separate trap is worth stating plainly: a Go binary is fixed only by rebuilding it with a patched toolchain — updating Go on the build host does not rewrite what is already deployed.
I can write this section with a clear conscience, because everything described below was done by hand on my machine on the night this article was written. I downloaded proof.zip from the disclosure site — it contains eight certificates, eight public keys, eight detached Wayne signatures, and the message itself. I extracted the public point from each certificate with openssl. I wrote P-384 point multiplication in plain Python — forty lines using the high-school tangent formula — and ran a brute-force search from 1 to 255 across all eight certificates.
The result: 0.55 seconds for all eight keys, including point extraction. The table matched exactly, down to the unit, the independently published table by Scott Helme and the reproduction by fenko.nz:
| Intermediate | Purpose | d |
|---|---|---|
| EM DV TLS CA - G3B | TLS | 156 |
| EM DV TLS CA - G3C | TLS | 230 |
| EM EV TLS CA - G3B | TLS | 115 |
| EM EV TLS CA - G3C | TLS | 56 |
| EM OV CS CA - G3B | Code signing | 66 |
| EM OV CS CA - G3C | Code signing | 237 |
| EM OV TLS CA - G3B | TLS | 155 |
| EM OV TLS CA - G3C | TLS | 4 |
Then came reconstruction in both directions. From the recovered scalar, I built an RFC5915 DER private-key structure, wrote out the PEM, fed it to openssl — the public half of each matched the public half in the original certificate, eight out of eight. Then I verified Wayne’s signature: the message “We have a problem here - Wayne” — exactly 30 bytes, no trailing newline — with a detached SHA-384 signature — Verified OK on all eight certificates.
The final chord was the full-length attack. Using my recovered key — the one with d=4 — I signed a freshly issued certificate with CN=api.stripe.com and SAN=DNS:api.stripe.com, assembled the chain through the Sectigo cross-signed intermediate, started a local TLS server, and connected to it with curl using the --resolve trick. The log said:
* subject: CN=api.stripe.com
* issuer: C=US; O=eMudhra Inc; CN=EM OV TLS CA - G3C
* SSL certificate verify ok.
A forged certificate for someone else’s domain name, issued with a key recovered from public data in a fraction of a second, — accepted by my curl silently, without warnings, with “verify ok.” There is no broken cryptography anywhere in this chain, at any step. There is only a small number where a large one should have been.
The most sobering discovery of the evening did not come from key generation, but from checking the disclosure site’s test hosts. The magic of this site is that it measures itself: behind each host is a server counting successful connections by user-agent, and its statistics are a live metric of how many clients in the world still trust the compromised CA. Here are three results from my own rig:
$ curl https://dv-g3b-emudhra.we-have-a-problem.com/
Your client successfully connected to the site. That's bad!
$ curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
Your client successfully connected to the site. That's bad!
$ curl https://dv-g3b-noct-emudhra.we-have-a-problem.com/
Your client successfully connected to the site. That's bad!
Three out of three. Including the NoCT host — a leaf that never made it into Certificate Transparency, meaning my client trusts the compromised intermediate, does not check CT, and does not check revocation. That is precisely the class of clients the disclosure site describes: “these are the clients that can be attacked without anyone noticing”.
The reason is elegant and sitting in plain sight. Debian 13 (trixie), /etc/os-release: ca-certificates 20250419, update candidate in the mirror — the same 20250419. According to the disclosure site’s table, the fix comes in package 20250419+deb13u1, and it has not reached my machine yet. Meanwhile, the bundle of 150 roots contains both AAA Certificate Services and emSign ECC Root CA - G3 — the two “correct” trust anchors through which a chain to the compromised keys can be built. The patch exists, the releases are marked October 20, my mirrors simply have not distributed it yet.
The moral of this episode matters to me more than all the mathematics: “an unpatched Linux system” in this story is not an IoT joke or a box in a server room abandoned in 2019. It is a production machine running fresh Debian 13 and curl 8.14.1, applying the latest updates four days after the disclosure of a “critical” incident. The gap between “the patch was released” and “the patch arrived” is the window in which all compromise-time estimates operate, and that window is not measured in hours.
Checking your own machine takes a minute:
curl https://dv-g3b-emudhra.we-have-a-problem.com/
curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
curl https://dv-g3b-noct-emudhra.we-have-a-problem.com/
A certificate error is good; a page is bad: update ca-certificates, certifi, rebuild your containers. If you work with Go — do not forget to rebuild. If you own an emSign certificate, check which root is in your chain: everything going through emSign ECC Root CA - G3 will stop validating in Firefox immediately and in Chrome Root Store version 43 around October 27. eMudhra is moving around 30,000 certificates to the RSA hierarchy emSign Root CA - G1 — with ACME issuance, this means a forced renewal, but it is worth manually checking that the renewal happened. For owners of infrastructure with TLS inspection at the edge — middleboxes, UTMs, proxies with re-termination — a separate note: they validate against the vendor’s trust store, are fixed on their vendor’s own delivery schedule, and fenko.nz recommends selectively blocking the eight public keys by SPKI hash wherever the product supports it.
What remains when all the secondary pain has passed? Three things.
First. Checking a label instead of checking a value is the kind of mistake that does not register as a mistake. A person selected “the wrong key” because the names looked alike; everything else — signatures, CSRs, Sectigo cross-signing, root verification, TLS handshakes, curl, Python, Node — worked perfectly, validating someone else’s error at every step. A confident chain of tools, every link reporting “everything is valid,” cannot replace one simple equality: the public key in the CSR must be the public key recorded in the ceremony log, not a file with a similar name. fenko.nz ends its analysis with a sentence worth printing and hanging above every HSM: “a signature made with your own key that fails verification against your own certificate is not a configuration error that can be re-signed. It is the certificate telling you which key is actually in it.”
Second. Certified hardware does not certify a procedure. FIPS 140-2 Level 3 on the Utimaco did not violate its promises: it did what it was told. The validity of a cryptographic module does not cover the script driving it, and cannot distinguish between “a key born from an honest RNG” and “a key arriving through wrap/unwrap with a stupid scalar.” All hope rests on tests layered on top: check scalar length where it is accessible; run a recovery cycle against the public half where it is not; compare the CSR with the log. A ceremony containing none of these checks is not a ceremony, but a ritual with expensive hardware.
Third. The tools of attack have become free, and that changes the obligations of defense. Scott Helme recovered the keys by every available method, including an agent with a single prompt; eMudhra checked its remaining hierarchies with “mathematical models” using AI; my plain Python managed it in half a second without any libraries at all. When brute-forcing 1..255 costs zero, a CA’s duty is not to “store keys well,” but to “find the weak key itself before anyone else checks.” In a world where auditing one certificate costs a fraction of a CPU-second, the absence of a continuously running check is not an oversight, but a choice.
And somewhere at the periphery remains a small aesthetic note, the kind that makes a story not only alarming but human. Someone, likely unknown forever, named their key tool and their message so that they sound the same from anywhere in the world: “We have a problem here.” Behind that phrase are eight numbers smaller than 256, more than seventy zero bytes in binary representation, seventeen months of silence, and the entire cryptographic infrastructure of the internet, which worked exactly as it should have: it found the problem once it was shown it.