SevyDevy

Guide · Backend

eMudhra's October 2026 CA Compromise Explained: Weak Keys and What to Update

20 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • The eMudhra certificate compromise: eight intermediate certificate authority keys from eMudhra, an Indian certificate authority, were generated so badly that anyone could work them out from public data.
  • An intermediate is a certificate that signs other certificates on behalf of a trusted root. With one of the six website-signing keys, an attacker could make a certificate for any website that many programs would accept.
  • Firefox and Chrome pushed block lists within hours, on 17 October 2026. Programs that check certificates on their own, such as many Python, Go and Node services, needed new releases.
  • The fix for your code is to upgrade and rebuild: certifi 2026.10.19, Go 1.27.3 or 1.26.10, Node 22.23.4, 24.22.1 or 26.12.1, or later, then redeploy every image.
  • Beginners can stop after the section on why services are still at risk. Engineers then get a tested script that scans certificate files for the eight keys, and why Go blocks keys, not roots.

What goes wrong when a certificate authority's keys can be guessed?

Anyone who guesses the key can create website certificates that your software trusts. The eMudhra certificate compromise of October 2026 made that possible, and an affected app would show no warning.

A certificate authority, or CA, is a company that vouches for websites. Your browser and your code trust a short list of CA root certificates, and every certificate those roots vouch for. If a signing key leaks, that trust becomes a weapon.

Picture a common setup. A small payments team in Pune runs a Python service that calls a partner bank's API over HTTPS. It ships in a Docker image, a packaged copy of the app and its files, built in July 2026. The setup is made up, but the gap it shows is real. On 17 October their laptops' browsers were protected without anyone lifting a finger. The service in the container was not.

This guide follows that service through the incident. You will see why browsers were fixed in hours, why servers needed work, and how to check your own code.

Which words do you need before reading about this incident?

You need five terms. Each one is explained here in plain words, and the guide uses the same name for it throughout.

Five terms used in every section below
TermPlain meaningIn this incident
Root certificateA CA certificate that your browser or library trusts directly, stored in a list called a trust storeemSign ECC Root CA - G3, an eMudhra root, was in the Mozilla, Chrome, Apple and Microsoft trust stores
Intermediate certificateA certificate signed by a root that is allowed to sign website certificates itselfEight eMudhra intermediates had private keys anyone could recover
Private keyThe secret number a CA uses to sign; the matching public key is printed in the certificateEach of the eight secrets was a whole number below 256
RevocationA CA or browser saying a certificate must no longer be trusted, before it expiresBrowsers pushed their own block lists; many other programs never check revocation
Certificate Transparency (CT)Public, append-only logs of issued website certificates that browsers can requireThe tracker found no CT entries for these intermediates, which protected CT-enforcing browsers

The chain itself is simple. A website shows its certificate, the intermediate that signed it, and points to a root your machine already trusts. The guide on how TLS works covers that chain in detail.

What was the eMudhra certificate compromise of October 2026?

The eMudhra certificate compromise is the discovery, reported on 17 October 2026, that eight eMudhra intermediate CA private keys could be recovered from the certificates themselves. Mozilla's security blog says these keys could let attackers issue fraudulent HTTPS certificates for sites they do not control.

eMudhra is an Indian CA. Its root certificates name eMudhra Technologies Limited with country code IN. The eight affected intermediates were issued in the names of eMudhra BV in the Netherlands and eMudhra Inc in the United States.

Six of the eight intermediates sign website (TLS) certificates. They cover the DV, OV and EV levels, which differ in how much the CA checks about the site owner. Two sign code. All eight were created on 30 April 2025, according to the incident tracker's certificate list and Mozilla's bug report.

Timeline, all times UTC. Dates are from the sources named in each row.
WhenWhat happenedSource
30 Apr 2025A key ceremony, the formal session where a CA makes keys, creates 24 intermediates, including the eightMozilla bug 2083449, tracker list
14 May 2025Sectigo's AAA Certificate Services root signs a second copy of eachTracker certificate list
17 Oct 2026, 20:06Mozilla receives the reportMozilla Security Blog
17 Oct 2026, 22:59Firefox block list (OneCRL) reaches all standard Firefox installsMozilla Security Blog
17 Oct 2026Chrome also blocks the certificatesTracker
18 Oct 2026, 09:24 to 09:38eMudhra revokes its eight intermediates as CA compromiseTracker certificate list
19 Oct 2026Go 1.27.3 and 1.26.10 released; Apple devices block the certificatesgo.dev release history, tracker
20 Oct 2026Mozilla publishes; Firefox 158.0.1 and NSS 3.131.1; Microsoft blocksMozilla Security Blog, tracker
21 Oct 2026Node.js 22.23.4, 24.22.1 and 26.12.1 update root certificatesNode.js release notes

How weak were the eMudhra keys, and why did key size not help?

Each private key was a whole number below 256, so it could be found in at most 255 guesses. Mozilla's preliminary analysis on its bug report says so and calls the keys recoverable near instantly.

These are elliptic-curve keys on the P-384 curve. A private key there is meant to be a random number about 384 bits long. The public key comes from combining that number, d, with a fixed point on the curve, G, using curve arithmetic written d times G.

Going from d to the public key is quick. Going back is meant to be practically impossible when d is random, but trivial when d is tiny.

It shows a normal size. We saved the eMudhra root from certifi 2026.7.22 as a PEM file and inspected it. On that root, openssl prints Public-Key: (384 bit), and the tracker lists all eight weak keys as 384-bit P-384 keys too. The size describes the curve, not how random the secret was.

bash
openssl x509 -in emsign_ecc_root_g3.pem -noout -text | grep -E "Public-Key|NIST CURVE"
#                 Public-Key: (384 bit)
#                 NIST CURVE: P-384

An analogy: a huge dial with a tiny code

Think of a combination lock whose dial has a 116-digit number of positions. That is roughly how many P-384 private keys exist. Now imagine the owner set the code to a number below 256.

  • The dial's positions map to all possible private keys, about 2 to the power 384.
  • The chosen code maps to the private key, here a number below 256.
  • The lock's outside maps to the certificate: it looks just as strong either way.
  • Trying a code maps to multiplying a guess by G and comparing it with the published public key.

Where the picture breaks: nobody has to touch a real lock. The public key is printed in every copy of the certificate, so each guess is checked offline, silently, in a fraction of a second.

Scott Helme, who tested the keys, reports that each one fell in less than a second. Mozilla says multiple people recovered the keys independently. We did not recover any key and do not publish the values.

How did browsers block the certificates within hours?

Browsers have their own block lists that they push to users over the internet, so no app update was needed. Firefox uses OneCRL and Chrome uses CRLSets, and both were updated on 17 October.

Mozilla's blog says its OneCRL update reached every Firefox user with standard settings by 22:59 UTC, under three hours after the report. Firefox 158.0.1 and two Extended Support Releases (ESR) then covered users whose settings blocked that update.

A second shield was Certificate Transparency. The incident tracker says the CT logs hold no entries for these intermediates, so CT-enforcing browsers such as Firefox, Chrome, Edge and Safari could not have been attacked. A forged certificate would need log proofs to pass those browsers, and would normally then show up in the public logs. That is the tracker's assessment as of 20 October.

CT does not stop a bad certificate from being made. As Scott Helme puts it, CT detects mis-issuance; it does not prevent it. It protects only the clients that insist on seeing proof that a certificate was logged.

How the fix reached each kind of client

Notice that only the browser path ends without any action from you. Every other path ends at a box that needs a rebuild or redeploy.

Diagram connections:

  • Report to root programmes to OneCRL and CRLSets (17 Oct)
  • OneCRL and CRLSets to Browsers (pushed automatically)
  • Report to root programmes to NSS 3.131.1 (20 Oct)
  • NSS 3.131.1 to certifi 2026.10.19 (bundle update)
  • certifi 2026.10.19 to Python service image (you upgrade and rebuild)
  • Report to root programmes to Go 1.27.3 and 1.26.10 (19 Oct)
  • Go 1.27.3 and 1.26.10 to Go binary (you recompile)
  • Report to root programmes: Mozilla received it 17 Oct, 20:06 UTC
  • OneCRL and CRLSets: Browser block lists pushed over the internet
  • Browsers: Protected the same day, no user action
  • NSS 3.131.1: Mozilla removes emSign ECC Root CA - G3
  • certifi 2026.10.19: Python bundle copied from Mozilla's list
  • Python service image: Safe only after rebuild and redeploy
  • Go 1.27.3 and 1.26.10: crypto/x509 blocks eight key hashes
  • Go binary: Safe only after recompiling

Why are Python, Go and Node services still at risk?

Many programs outside the browser do not check revocation and do not enforce CT. They trust whatever roots sit in their bundle, a file or a list built into the runtime, so they keep trusting the weak intermediates until that bundle changes.

The incident tracker and Cloudflare both say this. Scott Helme tested it: a forged certificate for a real API host was accepted by curl, Python and Node on every Linux and macOS machine he tried.

Back to the payments service

The service uses the requests library, which reads certifi's bundle by default. Its July image has certifi 2026.7.22, which still trusts emSign ECC Root CA - G3. An attacker on its network path could present a forged bank certificate and the service would accept it.

Nothing in the browser story helps here. The image never downloads OneCRL. It changes only when the team installs certifi 2026.10.19 or later and redeploys.

One weak key, two chains to trust

Each weak key sits in two certificates. One copy was signed through eMudhra's own roots, and one by Sectigo's older AAA Certificate Services root. That gives an attacker two different routes to a trusted root.

Two chains lead to the same weak key

Notice that both copies carry the same public key. Removing one root closes one route; blocking the key's hash closes both.

Diagram connections:

  • Forged site certificate to Weak intermediate key (signed by)
  • Weak intermediate key to eMudhra-signed copy (published in)
  • Weak intermediate key to Sectigo-signed copy (published in)
  • eMudhra-signed copy to emSign ECC Root CA - G3 (chains up to)
  • Sectigo-signed copy to AAA Certificate Services (chains up to)
  • Forged site certificate: Signed with a recovered weak key
  • Weak intermediate key: One P-384 key, secret below 256
  • eMudhra-signed copy: Issuer: emSign Root TLS CA - G3
  • Sectigo-signed copy: Issuer: AAA Certificate Services
  • emSign ECC Root CA - G3: Cross-signer; was in all four big stores
  • AAA Certificate Services: In Apple and Microsoft stores only

The eMudhra copy was issued by a newer eMudhra root, emSign Root TLS CA - G3, which only Microsoft trusts directly. The tracker lists emSign ECC Root CA - G3 as its cross-signer: an older root that vouches for the newer one.

Cross-signing lets clients that trust only the older root accept the newer one. Before this incident, emSign ECC Root CA - G3 was in all four big trust stores. AAA Certificate Services is removed from Mozilla and Chrome, but still in Apple and Microsoft, per the tracker.

Beginners can stop here. The rest is for engineers: exact fixed versions, a tested way to check a service, how Go chose to block keys, and what is still unknown.

Which updates fix the eMudhra certificate compromise?

Upgrade the library or runtime that holds your trust list, then rebuild and redeploy. The table lists the fixes we checked ourselves on 21 October 2026.

Fixed versions checked on 21 October 2026, and how we checked them
SoftwareFixed versionWhat changedHow we checked
Firefox158.0.1, ESR 115.43.1, ESR 153.5.1Ships the block for users who miss OneCRLMozilla Security Blog
NSS3.131.1Removes emSign ECC Root CA - G3Mozilla blog and bug 2083449
Go1.27.3 and 1.26.10 (19 Oct)crypto/x509 rejects chains holding eight key hashesgo.dev release history and the Go commit
Python certifi2026.10.19emSign ECC Root CA - G3 removedInstalled it and diffed the bundle
pip26.2.2Bundles certifi 2026.10.19Downloaded the wheel and read its bundle
Node.js22.23.4, 24.22.1, 26.12.1 (21 Oct)NSS 3.131.1 roots, plus rejecting chains with blocked public keysNode.js release notes

Linux distributions ship fixes in their ca-certificates package. The incident tracker keeps a crowdsourced list of patched versions for Debian, Ubuntu, Alpine, Fedora, Rust crates, Electron and more. We did not check those versions ourselves, so use the tracker's list.

Cloudflare's advice is worth repeating: do not silence certificate errors by turning verification off. Find the real component doing the check, which may sit under your SDK, and update that.

How do you check that your service no longer trusts the weak keys?

Check the bundle your code actually uses, not your laptop. Run these checks inside the same container image or server that makes the HTTPS calls.

  1. Find the bundle

    With requests, print requests.utils.DEFAULT_CA_BUNDLE_PATH; it points into certifi. If REQUESTS_CA_BUNDLE or CURL_CA_BUNDLE is set, requests 2.32.5 uses that file instead, so check it too. Plain urllib and ssl use the operating system bundle, shown by ssl.get_default_verify_paths().

  2. Check the certifi version

    Run the first command below. Expect 2026.10.19 or later. Older versions print a zero-padded date, such as 2026.07.22.

  3. Check that the root is gone

    Count the root's name in the bundle file. Expect 0. On certifi 2026.7.22 the same command prints 3, because the name appears in three comment lines.

  4. Check Go and Node

    Run go version and node --version. Go needs 1.27.3, 1.26.10 or later, and every binary must be rebuilt with it. Node needs 22.23.4, 24.22.1, 26.12.1 or later.

  5. Test the live path

    The tracker runs test hosts such as dv-g3b-emudhra.we-have-a-problem.com. A certificate error means your client does not trust the weak intermediates. Call it from your app's own HTTP client, since curl tests only curl.

  6. Rebuild and redeploy

    Rebuild images from the fixed base, restart long-running processes, and repeat these checks in production.

bash
python -c "import certifi; print(certifi.__version__)"
# 2026.10.19   (fixed)
# 2026.07.22   (the July image: not fixed)

grep -c "emSign ECC Root CA - G3" "$(python -m certifi)"
# 0   (fixed)
# 3   (certifi 2026.7.22)

For files that may hold intermediates, such as a chain file or a pinned set, check the keys themselves. This script hashes each certificate's public key the way Go does and compares it with the eight known hashes.

bash
#!/usr/bin/env bash
# Usage: bash check_emudhra_keys.sh FILE.pem
# Prints any certificate whose public key is one of the eight eMudhra keys.
BAD="ebed6475c2c2d80fcb199f3fe8dd6e6b3ce60b77eb15d4e42c3dec0c6f5b5105
43bffa19d6ef651451f6841e019931e96f6f2eaeff28ab4b6f1ce4576de2717b
e0254c7fcdc265e2f0f8a428dabb75ab4ae039b4872d62f5ee57f0877d5a8a41
49dd353dc2b919a3770645117d0e60c16115e7d1bc259eefe734b00ea434fe5f
63ecefee431d49523bc1136c4e72d0206a477086caf606cd5cf025686c109cf8
5c0660a2b164910d8ff589c18e14552e9e0ec237dcefe2cb736d41a4811f81a0
ec4ffa1ad0ac88081314b7aaffef6a91f97cd57291bb5ba686c5415f5eb4f6b6
92c063d67e8f14db71d288e11266ffdaf69cbf115f7bab14f3bc2f68d25e0e30"
tmp=$(mktemp -d)
awk -v d="$tmp" '/BEGIN CERT/{n++; on=1} on{print > (d "/" n ".pem")} /END CERT/{on=0}' "$1"
count=0; bad=0
for f in "$tmp"/*.pem; do
  count=$((count+1))
  h=$(openssl x509 -in "$f" -noout -pubkey | openssl pkey -pubin -outform DER | openssl dgst -sha256 -r | cut -d' ' -f1)
  if grep -qx "$h" <<< "$BAD"; then
    bad=$((bad+1)); echo "WEAK KEY: $(openssl x509 -in "$f" -noout -subject)"
  fi
done
rm -rf "$tmp"
echo "checked $count certificates, $bad with a weak eMudhra key"

The script took about 4 seconds for a 120-certificate bundle on our test machine. A hit means that file still trusts or serves one of the weak keys, and it should be replaced.

Why did Go block public keys instead of removing a root?

Blocking the key closes every chain that contains it, whichever root sits on top. The Go fix, commit 7f86f95 for CVE-2026-111943, adds a list of eight SHA-256 hashes of the keys' SubjectPublicKeyInfo.

SubjectPublicKeyInfo, or SPKI, is the part of a certificate that holds the public key and its algorithm. Hash it and you get a fingerprint of the key, not of one certificate. Both copies of a weak key share one hash.

Go's Verify now filters out any chain that holds one of those hashes. The same check was added to the macOS and Windows system verifiers, where Apple's and Microsoft's stores still trust AAA Certificate Services. Node's new releases include a change titled reject chains containing blocked public keys. Their release notes do not list which keys.

Root removal works well for bundles copied from Mozilla, because Mozilla had already removed AAA Certificate Services. By the tracker's roots list, removing emSign ECC Root CA - G3 therefore closes both routes for certifi users.

A detail from diffing certifi

certifi shipped twice in two days. Version 2026.10.18, uploaded on 18 October, removed all four emSign roots: G1, G3, C1 and C3. Version 2026.10.19, uploaded on 20 October, restored G1, C1 and C3 and removed only G3.

That matches Mozilla's decision on its bug to remove only emSign ECC Root CA - G3. We found this by installing both versions and comparing them; we did not find it described in the sources above.

Version 2026.10.18 also blocks the weak keys, but sites chaining to the other three emSign roots could fail with it. Prefer 2026.10.19 or later.

Removing a root has a cost too. eMudhra wrote on Mozilla's bug that, by initial estimates, about 30,000 certificates chaining to its G3 root need migration. It plans to move those customers to chains under its G1 root.

What do we know, and not know, about the root cause?

We know the keys were weak. Why they were weak rests on eMudhra's own preliminary account, posted on Mozilla's bug, which no source we read has independently verified.

eMudhra says a flaw in the script used that day created a second, weaker key pair on its hardware security module, or HSM. It says the weaker pair was used by mistake to make the certificate request, because the key labels were similar.

It also says there were human errors in multiple steps of the process. Scott Helme relays the same account and notes he did not verify it.

  • Known: eight P-384 keys below 256, revoked on 18 and 19 October, blocked by the four big browsers and their platforms.
  • Known: the same ceremony made 24 intermediates. In its preliminary check, Mozilla could not recover the other P-384 keys and had not yet examined the RSA keys.
  • Not known: whether anyone used the keys against clients that skip CT between April 2025 and October 2026.
  • Not known: the final root cause, until eMudhra completes its incident report.

Cloudflare says its own review found no active exploitation on its services. The tracker says it does not know whether attackers targeted clients outside the browser world. Both statements are as of 20 October.

How do you explain the eMudhra incident in a system design interview?

Use it as a story about trust boundaries and patch paths. Interviewers want to hear that you know who protects each client, and how a fix reaches it.

  1. State the failure

    Eight intermediate CA keys were recoverable from public data, so anyone could sign certificates that many clients trusted.

  2. Map the trust paths

    Name each client: browsers, mobile apps, backend services, CI runners and devices. Say where each one gets its trust store.

  3. Name the shields

    Browsers had pushed block lists and CT enforcement. Many backend clients had neither, so only a bundle update helped them.

  4. Choose the fix

    Remove the root where that closes every route, and block key hashes where another root still reaches the key.

  5. Design for next time

    Keep base images rebuilt on a schedule, track trust-store versions like any dependency, and test the live TLS path, not a laptop.

Questions people ask

Is my browser safe from the eMudhra certificate compromise?
Yes, if it is up to date. Firefox and Chrome blocked the certificates on 17 October 2026, Apple devices on 19 October and Microsoft on 20 October, according to the incident tracker. Firefox's block arrived without a restart. If your settings stop Firefox's block list, install Firefox 158.0.1 or the matching ESR release.
Was anyone attacked with these keys?
No source we read reports an attack, as of 21 October 2026, but nobody can rule one out for every client. The tracker says CT-enforcing browsers could not have been attacked. Cloudflare found no active exploitation on its services. For clients that skip CT, the tracker says it does not know.
Can I just turn off certificate verification to stop the new errors?
No. Turning verification off removes the very check that protects you, and Cloudflare's post says this directly. A new error after updating can mean a server still sends a chain through the distrusted G3 root. Ask that server's owner to move to a certificate under a different chain.
Why was revoking the certificates not enough?
Revocation only helps clients that check it. eMudhra revoked its eight intermediates on 18 October, and browsers act on their own block lists. Many backend libraries, command-line tools and devices never fetch revocation data. They keep trusting a revoked intermediate until their trust bundle is updated.
Does a 384-bit key size mean a key is safe?
No. The size only says which curve the key uses. The eMudhra keys were genuine P-384 keys, and tools show them as 384-bit, yet each secret was below 256. Safety depends on the private key being drawn from a good random source, which you cannot see from outside.
What is an SPKI hash?
It is a SHA-256 fingerprint of the certificate part that holds the public key and its algorithm. Two certificates with the same key share one SPKI hash. That is why Go blocks these eight hashes: one rule catches every certificate that carries a weak key.
Does this affect other eMudhra certificates?
The sources name only these eight intermediates, six for websites and two for code signing. Mozilla's preliminary check could not recover the other P-384 keys from the same ceremony and had not yet looked at the RSA keys. For other eMudhra products, follow eMudhra's own notices and Mozilla's bug.

Sources and further reading

Practise this with Lala

Practise