The private key of one eMudhra intermediate CA was the number 4. Another was 56. All eight compromised keys were integers below 256, on a curve where a real key is a random 384-bit number. Firefox and Chrome are now removing the emSign ECC Root CA - G3 root, and about 30,000 certificates that chain to it have to be replaced. If you have an emSign or eMudhra certificate anywhere in your stack, you have a deadline, and the first warning may be a bug report from a user whose curl stopped working.
I'll go through the timeline, what Firefox, Chrome, Apple and Microsoft have done so far, and a one-line openssl check for your own chain. Then I spend more time than most write-ups on backend clients, because yours probably still trust the broken keys.
What happened to eMudhra's CA keys
On Saturday 17 October 2026 a researcher reported to the root programs that the private keys for eight eMudhra intermediate CAs could be recovered from the certificates themselves (incident page). Six are TLS issuing CAs (DV, OV and EV, generations G3B and G3C) and two are code-signing CAs. All were created in April 2025 and cross-signed by Sectigo's AAA Certificate Services root in May 2025.
Scott Helme's write-up explains it. The keys are P-384 elliptic-curve keys, so the private key d should be a random 384-bit number. These were "47 bytes of zeros and then one byte, and all eight keys are like that." To recover one you compute 1 × G, 2 × G, 3 × G and so on until the result matches the public key. That's at most 255 tries. He recovered all eight in "less than a second."
| Intermediate | Type | 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 TLS CA - G3B | TLS | 155 |
| EM OV TLS CA - G3C | TLS | 4 |
| EM OV CS CA - G3B | Code signing | 66 |
| EM OV CS CA - G3C | Code signing | 237 |
eMudhra's explanation in Mozilla bug 2083449 is that a flaw in the key generation ceremony script on 30 April 2025 created a second, weaker key pair on the HSM. Because the key labels were similar, someone used the wrong key for the CSR, and that CSR also went to Sectigo for cross-signing. The company says "there were human errors in multiple steps of the process." All of this happened during a formal key generation ceremony. One HN commenter called most IT audits "performative box checking activities", and this week I can't argue.
What Firefox, Chrome, Apple and Microsoft have done
This is from the incident page, Mozilla's security blog and Helme's post, and only covers what has actually been announced.
- Firefox: Mozilla got the report at 20:06 UTC on 17 October and had the intermediates blocked through OneCRL by 22:59 UTC, with no restart needed. Firefox 158.0.1 and ESR 115.43.1 / 153.5.1 shipped for users whose settings blocked OneCRL. On 20 October Mozilla shipped NSS 3.131.1, which removes the emSign ECC Root CA - G3 root entirely. Mozilla is not removing emSign Root CA - G1 for now.
- Chrome blocked the intermediates the same day. According to Helme, Chrome confirmed the root is going in Chrome Root Store v43, and on the bug Chrome says it has "committed to a similar path" as Mozilla.
- Apple blocked the certificates on 19 October. I haven't seen any announcement from Apple about removing the root.
- Microsoft blocked the certificates on 20 October.
There are two separate actions in that list, and they hit different people.
- The compromised intermediates are blocked. According to eMudhra (comment 10 on the bug), no end-entity certificates were issued from them; only EM DV TLS CA G3C was ever put into production. If you're a site operator, that isn't your main problem.
- The G3 root is being removed. That is the part that breaks sites. Mozilla asked eMudhra to stop issuing from every intermediate that chains to G3 and to migrate its customers. eMudhra's initial estimate is "about 30,000 certs that require migration and about 97% came via ACME." Mozilla expects some servers to become unreachable for clients using its trust list until they move to new chains. Your certificate can be correctly issued, with a perfectly good key, and still start failing because of where it chains.
Check if your site chains to eMudhra in one command
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
| grep -E '^ *[0-9]+ s:|^ *i:' | grep -iE 'emsign|emudhra|EM (DV|OV|EV) TLS'
If you get no output, nothing in your served chain mentions eMudhra. If you see emSign ECC Root CA - G3 or emSign Root TLS CA - G3 in any subject or issuer line, you're affected. Your server usually doesn't send the root, so the issuer line of the top intermediate is where it appears.
Run it against every hostname you terminate TLS on, including the ones behind load balancers and CDNs. The certificate in your repo isn't necessarily the one your edge serves. Then run it against the third-party APIs you call. If a payment provider or a webhook target is on eMudhra, it's your outage too.
Check whether your clients still trust the broken keys
Firefox, Chrome and Safari are covered because they block the intermediates and enforce Certificate Transparency. Your Python worker, Go service, Node script and CI runner mostly aren't. Helme ran a local server with a forged api.stripe.com certificate: curl reported "SSL certificate verify ok", Python completed the handshake and Node gave authorized: true, on every Linux and macOS machine he tried with default settings. These clients read a CA bundle from disk and skip both revocation and CT.
The incident page provides test hosts that chain through EM DV TLS CA - G3B. Both of these should fail with a certificate error:
curl -sS https://dv-g3b-emudhra.we-have-a-problem.com/ -o /dev/null && echo "STILL TRUSTED"
python3 -c "import requests; requests.get('https://dv-g3b-emudhra.we-have-a-problem.com/'); print('STILL TRUSTED')"
On the Linux box I'm writing this on, both printed STILL TRUSTED on 22 October, five days after the report. Run them inside the actual container image or runtime, not on your laptop. Fixed versions are listed on the incident page, including NSS 3.131.1, certifi 2026.10.19, Go 1.27.3 and 1.26.10, Node.js 22.23.4, 24.22.1 and 26.12.1, Deno 2.9.8, rustls-webpki 0.103.16 and webpki-roots 1.0.10. When that list was written, Bun, Erlang certifi and Java were still pending. Upgrade, rebuild your images and redeploy. An updated base image does nothing for a container built last month.
On macOS the system /etc/ssl/cert.pem used by the built-in curl hasn't been updated, so follow the page's macOS workaround.
Move off eMudhra today
eMudhra says it will migrate subscribers to intermediates under emSign Root CA - G1, its RSA root and "the only alternative available". I wouldn't take that offer. Mozilla says it isn't removing G1 for now, and this is the same CA whose ceremony controls just failed at several steps. If the root programs revisit G1 after the investigation, you'll do this migration twice.
Since 97% of these certificates came through ACME, switching is mostly a matter of changing the ACME directory URL:
# if you have CAA records, add the new CA first or it will refuse to issue
# example.com. CAA 0 issue "letsencrypt.org"
certbot certonly --server https://acme-v02.api.letsencrypt.org/directory \
-d example.com -d www.example.com --force-renewal
Do it in this order: add CAA for the new CA, issue the new certificate, deploy it with the full chain, rerun the openssl check above, and only then delete the old certificate. If you need OV/EV or contractual support, any other CA with an ACME endpoint works the same way.
mTLS and pinned clients are where this gets painful
Neither Mozilla nor the incident page says much about these, so here is my own checklist.
- mTLS servers: look at the CA bundle you use to verify client certificates (
ssl_client_certificatein nginx,ClientCAsin Go). If it contains any emSign G3 roots or the eight intermediates, remove them. An attacker with key4can mint a client certificate your server will accept, and no browser CT check helps you there. - mTLS clients with eMudhra certs: reissue them from your new CA, add the new CA to the server's trust bundle before rotating, and remove eMudhra only after the last client has switched.
- Pinned mobile apps and IoT: if you pin an eMudhra intermediate or root SPKI, ship an app update that pins the new CA's key alongside the old one, wait for adoption, and then switch the server certificate. Switch first and every pinned client loses its connection. Helme points out that routers, printers and TVs with frozen firmware will trust these certificates until they expire in July 2039.
- Go services: as jtdowney pointed out on Lobsters, the OS can distrust a root but can't distrust an intermediate in a Go program. That's why Go 1.27.3 and 1.26.10 hardcode the rejection. Upgrade the toolchain and rebuild.
And don't turn off verification to make the errors go away. With verification off, anyone can impersonate you without even needing key 4.
Why backend services were the weak spot for 17 months
From April 2025 until this month, anyone who looked could have recovered these keys in under a second. Browsers had CT to fall back on. Our backend services had a stale CA bundle and no revocation checks. On HN, Helme put the count at almost 350 roots trusted by at least one major operator, and more than 3,600 once you include intermediates. Most of us have never checked which of them our own services rely on.
When did you last check which CA actually issues the certificates your services depend on, including the third-party APIs you call? And if you run mTLS, does your client-CA bundle contain anything you couldn't explain to an auditor? Tell me in the comments.
Top comments (0)