Skip to content
harry-suryapambagya

eMudhra CA compromise: my Mac's curl still trusted the bad certificates

Eight eMudhra intermediate CA keys got compromised. Browsers already block them, but the curl that ships with macOS still trusted them. How I tested it and what I did about it.

6 min read
On this page5 sections

If you missed it: someone found that the private keys of eight eMudhra intermediate CA certificates can be recovered from the public certificates themselves. The keys were generated badly. Full write-up is at emudhra.we-have-a-problem.com.

An intermediate CA signs website certificates. Whoever has its private key can sign a certificate for any domain, and any client that trusts that intermediate will happily accept it. So, a MITM on anything you like.

Short version of the timeline:

  • April 2025: the certificates were created
  • 17 Oct 2026: reported to the root programs; Firefox and Chrome block them
  • 19 Oct: Apple blocks them
  • 20 Oct: Microsoft blocks them, and the write-up goes public

The roots involved are emSign ECC Root CA - G3 and AAA Certificate Services (the old Comodo root).

Browsers are fine, the CLI is another story

Browsers got the block lists pushed to them, and they also require Certificate Transparency logs, which these intermediates never had. So Firefox, Chrome, Edge and Safari were never really at risk.

The stuff we use every day in the terminal is different. curl, Python, pip, Node, CI runners, Docker images: a lot of them carry their own CA bundle, don’t check revocation, and don’t care about CT. They keep trusting the bad intermediates until that bundle gets updated.

The write-up has test hosts signed through the compromised intermediates. If your client connects without complaining, it still trusts them.

So I tried it with the curl that comes with macOS:

harsxv@Gwenhwyfar ~ ❯curl https://dv-g3b-emudhra.we-have-a-problem.com/
Your client successfully connected to the site. That's bad!
harsxv@Gwenhwyfar ~ ❯curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
Your client successfully connected to the site. That's bad!

Yep. Both hosts use the same intermediate (EM DV TLS CA - G3B), the first one chains up to emSign ECC Root CA - G3, the second one through a copy cross-signed by AAA Certificate Services. My curl trusted both.

Why: /usr/bin/curl reads its CA list from /etc/ssl/cert.pem, which ships with macOS, and Apple hasn’t updated that file yet. Apple’s block covers Safari and apps that use the system’s own certificate checks. Not this file.

Fix: use Homebrew’s curl

Homebrew’s curl uses Homebrew’s ca-certificates, and that package already has the fix (mine was 2026-10-20).

harsxv@Gwenhwyfar ~ ❯brew install curl
==> Would install 1 formula:
curl 8.23.0
==> Would install 2 dependencies for curl:
libnghttp3  1.18.0
libngtcp2   1.25.0_1
...
🍺  /opt/homebrew/Cellar/curl/8.23.0: 558 files, 5MB
==> Caveats
curl is keg-only, which means it was not symlinked into /opt/homebrew,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have curl first in your PATH, run:
fish_add_path /opt/homebrew/opt/curl/bin

It’s keg-only, so installing it doesn’t change anything yet. You still have to put it in front of /usr/bin in your PATH. I’m on fish:

harsxv@Gwenhwyfar ~ ❯fish_add_path /opt/homebrew/opt/curl/bin

That one sticks across new shells. On bash or zsh, put this in your ~/.bashrc or ~/.zshrc:

Terminal window
export PATH="/opt/homebrew/opt/curl/bin:$PATH"

Same test again:

harsxv@Gwenhwyfar ~ ❯curl https://dv-g3b-emudhra.we-have-a-problem.com/
curl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the webpage mentioned above.
harsxv@Gwenhwyfar ~ ❯curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
curl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the webpage mentioned above.

Good. This is one of the few times you actually want curl to fail.

The rest of my Mac

While I was at it, I checked the other things I use:

  • Node v26.12.0: rejects the test hosts. The write-up still lists the official Node fix as pending.
  • Python 3.15 from Homebrew: rejects them.
  • Go 1.27.3: already a fixed version.
  • uv 0.11.11: fixed in 0.13.4, so that one needed an update.
  • pip 26.2.1: fixed in 26.2.2.

uv was the easy one. I installed it with the standalone installer, so it updates itself:

harsxv@Gwenhwyfar ~ ❯uv self update
info: Checking for updates...
success: Upgraded uv from v0.11.11 to v0.13.4! https://github.com/astral-sh/uv/releases/tag/0.13.4
harsxv@Gwenhwyfar ~ ❯uv --version
uv 0.13.4 (7456b60bc 2026-10-20 aarch64-apple-darwin)

pip is more annoying. If it’s Homebrew’s Python, pip install -U pip refuses with externally-managed-environment, and Homebrew hasn’t shipped a newer Python with the new pip yet. So brew upgrade python@3.15 once it shows up in brew outdated. Inside your virtualenvs you can upgrade pip right away:

Terminal window
python -m pip install -U pip

Servers

On Linux it’s just a ca-certificates update. Patched versions from the write-up:

Distro ca-certificates
Ubuntu 22.04 / 24.04 / 26.04 / 26.10 20260816ubuntu0.22.04.1 / …24.04.1 / …26.04.1 / …26.10.1
Debian 12 / 13 / sid 20250419~deb12u2 / 20250419+deb13u1 / 20260816+nmu1
Fedora 2026.2.92_v9.0.319
Arch (ca-certificates-mozilla) 3.131.1-1
Alpine 20261020-r0
Gentoo 20260816.3.125
FreeBSD (ca_root_nss) not yet

Don’t forget your Docker images, they carry their own ca-certificates and need a rebuild. Same for anything that bundles its own CA list: certifi 2026.10.19, rustls webpki-roots 1.0.10, Electron 44.9.1 / 43.8.1 / 42.11.16, plus Composer’s ca-bundle, Elixir’s castore and Perl’s Mozilla::CA. Node and Bun fixes were still pending when I wrote this.

Easiest check, on any box:

Terminal window
curl https://dv-g3b-emudhra.we-have-a-problem.com/

“That’s bad!” means it still trusts them. A certificate error means you’re good.