Dev.to Security 🔐 Cybersecurity 👁 0 📖 12 min read

The eMudhra CA compromise: what actually went wrong, and why you have to update everything

Introduction In late October 2026, most of the security news was about PHP2Shell, the critical PHP vulnerability. But in the background, there was another problem that kept a lot of engineers busy. eMudhra, one of the

The eMudhra CA compromise: what actually went wrong, and why you have to update everything

Introduction

In late October 2026, most of the security news was about PHP2Shell, the critical PHP vulnerability. But in the background, there was another problem that kept a lot of engineers busy.

eMudhra, one of the companies that issue HTTPS certificates, had a serious problem. The secret keys it used to issue certificates could be calculated by anyone.

At work, I was busy updating OS packages, runtimes, and libraries. But honestly, I kept thinking:

  • "If a key that issues certificates leaks, what exactly is the risk? I don't really get it."
  • "We don't even use eMudhra certificates. Why do we need to do anything?"
  • "Why do we have to update not only the OS, but also Python, Node.js, Java, and Go?"

So I looked into it, partly to sort things out in my own head. Here is what I learned.

Note: This article is based on public information as of October 25, 2026. Things may change, so please check the primary sources at the end for the latest information.

Who this is for

  • You are patching and updating things at work (or you are about to)
  • You only have a rough idea of how HTTPS and certificates work
  • You did what you were told, but you still don't really understand why

(If you already know TLS and certificates well, this will probably feel too basic.)

TL;DR

  1. At eMudhra, the secret keys used to issue certificates could be calculated by anyone. With those keys, you can create a fake certificate for any website that looks completely real.
  2. If you leave it alone, two things can go wrong: your software may trust a fake website, and a website with a perfectly valid certificate may stop working.
  3. The list of "which certificates to trust" is kept separately by the OS, browsers, Python, Node.js, and so on. That's why you have to update all of them.

Background: how certificates work (the short version)

I'll only cover what you need for this article.

Certificates prove "you are talking to the real site"

HTTPS does two main things:

  • It encrypts the connection, so nobody in the middle can read it
  • It checks that the site you connected to is the real one

This incident is about the second one. No matter how strong the encryption is, if the other side is fake, you are just having a very private conversation with an attacker.

To check that the other side is real, we use a certificate. The site shows a certificate that says "I am example.com." Your browser or program (I'll call both "the client" from here) looks at it to decide whether to trust the site.

Only trust certificates from a trusted issuer

Of course, anyone can write "I am example.com." So a certificate also has a signature from an issuer called a Certificate Authority (CA). The signature says, "We checked that this site really owns example.com."

Think of a driver's license. You trust it not because someone printed a card, but because a government office you trust issued it.

The list of trusted issuers (trust store)

Each client keeps a list of "which CAs to trust." This list is called a trust store.

  • Certificate signed by a CA on the list → trust it
  • Certificate signed by a CA not on the list → show an error like "this certificate is not trusted"

The trust store is the key to understanding the rest of this article.

Root CAs and intermediate CAs are like "headquarters and branch offices"

In practice, CAs have two or more levels. Here is a company analogy:

Certificate term Analogy What it does
Root CA Headquarters It's in the trust store. It's too important to use every day, so it usually doesn't issue site certificates directly
Intermediate CA Branch office It gets permission from headquarters and actually issues site certificates
Server certificate A document issued by the branch The certificate each website uses
Root CA (headquarters, in the trust store)
  └ Intermediate CA (branch office, signed by HQ: "you can trust this branch")
      └ Server certificate (for example.com, signed by the branch)

The client follows the signatures, starting from the server certificate. If it reaches a root CA (headquarters) in its trust store, it trusts the certificate. This chain of signatures is called a certificate chain.

The private key is the branch office's official seal

The signature is made with a private key. Think of it as the branch office's official company seal.

Normally, the branch checks that the person asking really owns the domain. Only then does it stamp the seal and issue the certificate.

But what if someone gets a copy of the seal? They can skip the check and create a certificate for any website. And because the stamp comes from the real seal, the client can't tell it apart from a real certificate.

There is also a public key, which anyone can see and which is used to check that a signature is real. Think of it as the stamp's imprint. The problem this time was that the private key could be calculated from the public key. In other words, anyone could look at the imprint and make a copy of the seal.

What happened

  • In April 2025, while creating keys for its intermediate CAs, eMudhra created very weak private keys because of problems in its process and code
  • Intermediate CAs with those weak keys were created as trusted certificates under the root CA "emSign ECC Root CA - G3"
  • On October 17, 2026, a researcher reported it to browser vendors and eMudhra. Mozilla confirmed that it could actually recover the private keys from the public keys
  • Six intermediate CAs for TLS and two for code signing were affected

Chrome and Firefox blocked the intermediate CAs on the same day. Later, it was decided that the root CA "emSign ECC Root CA - G3" itself would also be removed from the trusted lists.

Risk 1: trusting a fake certificate (client side)

With the keys, an attacker can create a certificate for github.com or api.example.com. If your client still trusts these intermediate CAs, the fake certificate is accepted as valid.

Then an attacker in the middle of your connection (public Wi-Fi, a hacked router, tampered DNS, and so on) can pretend to be the real server. They can read and change your traffic. This is called a man-in-the-middle (MITM) attack.

The key point is step 4. The stamp comes from the real seal, so even when the client follows the chain and checks it against its trust store, it can't tell the certificate is fake.

On the other hand, if the client's trust store is updated, the same attack stops at step 4.

All the patching and updating we are doing is about getting our clients into this "after the update" state.

If the code signing intermediate CAs are abused, attackers can also sign malware so it looks like legitimate software.

Browsers are relatively safe; everything else is the problem

Chrome requires Certificate Transparency (CT) for public certificates. CT is a system that records every issued certificate in public logs. Certificates that are not in the logs are not trusted. According to Google, no certificates chaining to these intermediate CAs had been logged to CT before they were blocked. So Google says fake certificates could not have been used against clients that enforce CT.

However, curl, Python's requests, Go's net/http, Java's HttpClient, and similar tools usually don't check CT. They also don't have a fast blocking system like Chrome's CRLSet.

So the remaining risk is mostly in "non-browser clients": server-to-server traffic, batch jobs, CLIs, and internal tools. Think of backends that call external APIs, or integrations for payments and authentication.

Cloudflare's blog puts it this way:

A successful browser test does not prove that a background service is protected

Everything may look fine in your browser, but that doesn't mean the services running in the background are protected.

Risk 2: your certificate stops working (server side)

This time, the whole root CA "emSign ECC Root CA - G3" is losing trust. So even valid certificates issued by intermediate CAs whose keys did NOT leak will stop working if they chain to this root.

According to Google, about 30,000 certificates are affected. Most of them were InCommon certificates, which are used by US universities and research institutions. Chrome's removal of the root is planned to reach devices that use the Chrome Root Store on October 27, 2026 (US Pacific Time).

If your site, or an external API you call, uses a certificate under this root, you will get connection errors from that day on. It's not a security problem, but it is an outage (an availability problem).

Why do we have to update everything?

This was the part I didn't understand at all.

The answer: the list of "which CAs to trust" (the trust store) is not in one place. It's in many places.

Where it lives Examples What changed this time
OS ca-certificates on Ubuntu / Debian, RHEL Package update removes the root
Browsers Chrome Root Store, Firefox (NSS) Intermediate CAs blocked with CRLSet / OneCRL, root removed
Go crypto/x509 Rejects chains that include the affected intermediate CAs
Node.js Its own built-in CA list Updated in a security release
Java The JDK's cacerts Oracle shipped an update that distrusts the intermediate CAs
Python certifi (used by requests and others) Package update
PHP Composer's ca-bundle Distrusts the intermediate CAs
Rust webpki-roots Root removed
Electron apps Bundle both Chromium and Node.js You need the fixed version

This leads to situations like these:

  • Even if you update the OS ca-certificates, Python's requests uses the certifi list, so it is not protected
  • Even if you update certifi, pip has its own built-in copy of certifi, so you also need to update pip itself
  • By default, Node.js uses its own built-in CA list. So updating the OS is not enough; you need to update Node.js too
  • Containers keep the old trust store until you rebuild the base image and redeploy
  • Long-running processes may keep using the trust store they loaded at startup. You need to restart them after updating

This line from Cloudflare's blog sums it up well:

Updating the SDK alone may not change the underlying policy.

Even if you update an SDK, the thing that actually checks the certificate may be the HTTP library, the runtime, or the OS underneath. That's why the answer is "check all the way down your dependencies and update everything."

Checking your environment

Check whether you are on or above the fixed version listed in each vendor's advisory.

First, check versions

Target Fixed version Where to check
Ubuntu ca-certificates 22.04: 20260816ubuntu0.22.04.1
24.04: 20260816ubuntu0.24.04.1
26.04: 20260816ubuntu0.26.04.1
USN-8944-1
Red Hat (RHEL) See the advisory RHSB-2026-012
Node.js v22.23.4 / v24.22.1 / v26.12.1 October 21, 2026 Security Releases
Python certifi 2026.10.19 or later Python Discourse
pip 26.2.2 or later Same as above
uv 0.13.4 or later Same as above, uv advisory
Go 1.26.10 / 1.27.3 Release History, golang/go#82345
Java (Oracle) See the advisory CVE-2026-91403
Firefox See the advisory MFSA 2026-111
Electron See the advisory GHSA-jr5w-w6x2-84p9
PHP (Composer ca-bundle) See the release notes composer/ca-bundle
Rust (webpki-roots) See the release notes rustls/webpki-roots

Server side: is your certificate affected?

# Change this to your site, or an external API you call
HOST=api.example.com

# Show the certificate chain
openssl s_client -connect "$HOST:443" -servername "$HOST" -showcerts </dev/null 2>/dev/null \
  | grep -E "^ *[0-9]+ s:|^ +i:"

For example.com, you get something like this:

 0 s:/CN=example.com
   i:/C=US/O=SSL Corporation/CN=Cloudflare TLS Issuing ECC CA 3
 1 s:/C=US/O=SSL Corporation/CN=Cloudflare TLS Issuing ECC CA 3
   i:/C=US/O=SSL Corporation/CN=SSL.com TLS Transit ECC CA R2
 ...
  • s: (subject) is whose certificate it is
  • i: (issuer) is who stamped it

In terms of the analogy, this is the "certificate → branch → headquarters" chain. If emSign is involved, "emSign ECC Root CA - G3" will appear somewhere. Some certificates are also signed by another headquarters (this is called cross-signing), so check every line, not just the last one.

Result Meaning
The chain is shown and "emSign ECC Root CA - G3" is not in it Not affected
"emSign ECC Root CA - G3" appears somewhere Affected. You need to replace the certificate
Nothing is shown You couldn't connect. Check the host name, port, and network

If your service runs on a port other than 443, change the port in $HOST:443. It's also a good idea to check the external APIs you call, not just your own sites.

A test page

we-have-a-problem.com runs test servers that use certificates chaining to the affected intermediate CAs. The page itself uses a normal certificate, so it opens as usual. When you open it, your browser automatically tries to connect to the test servers.

  • ✅: did not connect (correctly rejected). Note that a network problem also shows ✅
  • Connected: your client still trusts the affected intermediate CAs

This page only tells you about the browser you opened it in. For servers and batch jobs, use the curl commands on the page.

Result in a browser (Chrome)

When I opened the page in Chrome on my Mac (macOS 27.0.1), all 14 showed "Did not connect" (as of October 25, 2026).

The last line, "Your browser trusted 0 of 14 compromised certificate chains.", means Chrome did not trust a single certificate chaining to the affected intermediate CAs. As Google said, Chrome is protected.

Running curl (the bad example)

$ 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!

This is curl on the same Mac, in the terminal. The same servers that Chrome rejected, curl happily connected to. Since the same Mac could reach the same servers, this also shows that Chrome's "Did not connect" was a real rejection, not a network problem.

According to news reports and community write-ups, Apple blocked the affected intermediate CAs on October 19. It did this not through an OS update, but through a system that distributes a list of revoked certificates. So Safari and apps that use Apple's standard networking are protected (although there are reports that some setups can still connect).

However, terminal tools like curl, git, and python3 use a separate file, /etc/ssl/cert.pem, as their trust store, and they don't check that revocation list. This file has not been updated, and the emSign root was still in it on my Mac. On the same Mac, the browser is protected but the terminal tools are not. This is exactly the "browsers are relatively safe; everything else is the problem" situation.

At the time of writing, even the latest macOS has not updated the list that terminal tools use. I'd avoid using curl or scripts for anything important on untrusted networks such as public Wi-Fi.

Summary

  • If an intermediate CA's private key leaks, anyone can forge a certificate for any domain
  • There are two risks: trusting fake certificates (client side) and your own certificate breaking (server side)
  • Browsers were protected fairly quickly thanks to CT and fast blocking. The remaining risk is in non-browser clients
  • The OS, runtimes, libraries, and apps each have their own trust store. That's why you need to update everything, then restart and redeploy

At first, I was just patching things because I was told to. Writing this article helped me finally understand why the scope was so wide.

Security fixes need to happen fast, so understanding often comes later. Still, I want to take the time to catch up each time something like this happens.

References

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.