<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>trust-store on Daniel Fedick</title>
    <link>https://blog.fedick.net/tags/trust-store/</link>
    <description>Recent content in trust-store on Daniel Fedick</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 22 Oct 2026 00:00:00 -0400</lastBuildDate>
    <atom:link href="https://blog.fedick.net/tags/trust-store/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>TLS X.509 Error. Assume Chaos.</title>
      <link>https://blog.fedick.net/posts/emudhra-eight-bad-ca-keys/</link>
      <pubDate>Thu, 22 Oct 2026 00:00:00 -0400</pubDate>
      <guid>https://blog.fedick.net/posts/emudhra-eight-bad-ca-keys/</guid>
      <description>eMudhra generated eight intermediate CA keys so weakly that anyone could recover them. Browsers blocked them within hours, but curl, Java, Python and CI pipelines kept trusting them. How to check your trust stores and make cert rotation boring with Vault.</description>
      <content:encoded><![CDATA[<p>I was flying back from San Francisco, and as we were landing for my layover my phone started buzzing. Then it kept buzzing. By the time we got to the gate I had hundreds of PagerDuty pages, all TLS X.509 errors. The certificate on a lot of my front-facing applications had expired because I forgot to rotate it.</p>
<p>That&rsquo;s the problem with rotating certs by hand every 90 days. You don&rsquo;t do it often enough for it to be part of your workflow, so it&rsquo;s easy to forget. When you do remember, you&rsquo;re looking up the docs again, and sometimes the docs are out of date. It&rsquo;s slow and it&rsquo;s manual.</p>
<p>The right way is to make the process automated and resilient to chaos. The eMudhra incident is a good example of chaos. If your certs come from automation, the fix is boring: restart your application and it gets a new credential.</p>
<p>This post walks through what happened with the eMudhra intermediate certificate authorities (CAs), why browsers were fine and a lot of other software wasn&rsquo;t, how to check your own trust stores and what routine rotation looks like with Vault.</p>
<h2 id="what-happened">What happened</h2>
<p>On October 17, 2026 a researcher named Wayne reported to the browser root programs that the private keys for eight eMudhra intermediate CA certificates were compromised. To prove it, he signed the message &ldquo;We have a problem here - Wayne&rdquo; with each of the eight keys. Mozilla logged the report at 20:06 UTC that day. (<a href="https://emudhra.we-have-a-problem.com/">disclosure site</a>, <a href="https://blog.mozilla.org/security/2026/10/20/protecting-firefox-users-following-the-compromise-of-emudhra-intermediate-certificates/">Mozilla</a>)</p>
<p>If you don&rsquo;t live in public key infrastructure (PKI) every day, an intermediate CA is the middle link in the chain. Your device trusts a short list of root CAs, the roots sign intermediates and the intermediates sign the certificates websites actually use. Anyone holding an intermediate&rsquo;s private key can sign a certificate for any site, and your device will accept it as long as it trusts the root above it. These eight chain to roots trusted by the Mozilla, Microsoft, Apple and Chrome root programs.</p>
<p>What bugs me most is that the CA generated the keys incorrectly, so anyone could recover a private key from the public certificate. That has been possible since the certificates were created in April 2025, which means the keys were exposed for about 18 months before anyone said anything. The CA has since revoked them. Mozilla&rsquo;s incident report says the private keys were supposed to be random numbers across a huge range, but all eight were numbers below 256, so anyone could find them in seconds by trying the first 256 possible keys. (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=2083449">Bugzilla 2083449</a>)</p>
<h2 id="browsers-vs-everything-else">Browsers vs. everything else</h2>
<p>The browsers moved fast. Mozilla pushed a block through OneCRL, its central list of revoked certificates, and finished rolling it out to Firefox users with standard settings by 22:59 UTC the same day, less than three hours after the report. Chrome blocked them on the 17th too, Apple on the 19th and Microsoft on the 20th. Between them the vendors used OneCRL, CRLite and CRLSets. Mozilla also shipped Firefox 158.0.1 and ESR builds for people whose settings blocked the OneCRL update, plus NSS 3.131.1, which removes the root that signed the affected intermediates.</p>
<p>There&rsquo;s another reason browsers were safe. Firefox, Chrome, Edge and Safari enforce Certificate Transparency (CT), which means a certificate has to show up in a public log before they trust it. There are no CT log entries for these intermediates since April 2025, so CT-enforcing browsers could not have been attacked.</p>
<p>Most other software doesn&rsquo;t get any of that. Your Go services, Java apps, curl in a build job and Python scripts usually check a certificate against a bundled list of roots and stop there. No revocation check, no CT check. They keep trusting the bad intermediates until someone updates the bundle or the runtime. Even macOS gets caught here. The OS blocks the intermediates, but the built-in curl, git, python3 and openssl use <code>/etc/ssl/cert.pem</code>, which doesn&rsquo;t check revocation and hadn&rsquo;t been updated. Pinning and DNS Certification Authority Authorization (CAA) records can help catch a fake cert, but only when the app uses them, and most don&rsquo;t.</p>
<p>Do federal teams enforce CT outside the browser? In my experience almost never, and I couldn&rsquo;t find federal guidance that asks them to. NIST SP 800-52 Rev. 2, the federal TLS guide, only says servers with publicly trusted certs should support the CT extension and says nothing about clients checking it. (<a href="https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r2.pdf">NIST SP 800-52 Rev. 2</a>) Nobody knows yet if attackers used these keys against non-browser clients.</p>
<figure>
    <img loading="lazy" src="/images/emudhra-trust-flow.svg"
         alt="eMudhra flow: a weak intermediate key is recovered from the public cert and an attacker mints a cert for any site. Browsers block it through revocation lists and CT. Non-browser clients that skip those checks trust it. The defense is trust store updates plus short-lived certs."/> 
</figure>

<h2 id="trust-stores-in-containers-and-federal-systems">Trust stores in containers and federal systems</h2>
<p>A typical agency app has five or six trust stores. There&rsquo;s the OS bundle in the base image, the Java cacerts file, Python&rsquo;s certifi, Node&rsquo;s bundled roots, Go&rsquo;s view of the system store and maybe an appliance or two with its own list. Each one updates on its own schedule, if it updates at all.</p>
<p>In most teams I&rsquo;ve worked with, nobody really owns the CA bundle in the base image. It gets whatever was in the upstream image the day someone last rebuilt it. That&rsquo;s fine until a day like October 17.</p>
<p>The fixes are rolling out. Ubuntu removed the two roots that cross-signed eMudhra, emSign Root TLS CA - G3 and Comodo AAA Certificate Services, in ca-certificates 20260816ubuntu0 for 22.04, 24.04, 26.04 and 26.10. Releases 20.04 and older get it through Ubuntu Pro ESM. Alpine 20261020-r0, Chainguard 20260909-r3, Go 1.27.3 and 1.26.10, Node.js 22.23.4, 24.22.1 and 26.12.1, Python certifi 2026.10.19 and a long list of Java builds are patched. Bottlerocket, Bun and Erlang certifi were still pending when I checked. One side effect: removing those roots also breaks legitimate certificates that chain to them, so a few hosts could become unreachable.</p>
<p>For RHEL shops, Red Hat&rsquo;s bulletin <a href="https://access.redhat.com/security/vulnerabilities/RHSB-2026-012">RHSB-2026-012</a> rates this Important and lists RHEL 6 through 10 as affected. That includes containers built from RHEL and UBI images, RHEL CoreOS, OpenShift and OpenStack. As of the morning of October 22, Red Hat&rsquo;s tracker for CVE-2026-114406 still lists ca-certificates as affected on RHEL 6 through 10 with no RHEL errata yet. The only fix out is RHSA-2026:86595 for Red Hat Hardened Images, released October 20. (<a href="https://access.redhat.com/security/cve/CVE-2026-114406">Red Hat CVE-2026-114406</a>) Until the RHEL packages land, Red Hat&rsquo;s workaround blocks the two roots in the system trust store, and it says to rebuild customer containers once the base images update.</p>
<p>Air-gapped and offline networks are the hard case. They don&rsquo;t get OneCRL or automatic Windows trust list updates, so someone has to carry the new bundle in and rebuild. I haven&rsquo;t found STIG or FedRAMP language that sets a deadline for trust store updates. The Department of War (DoW) STIGs check that the DoW root CAs are installed, under CCI-000185, but they don&rsquo;t say how fast a distrusted root has to come out. NIST SP 800-53 has SC-17 for PKI certificates and SI-2 for flaw remediation, and both leave the timing up to the organization. (<a href="https://www.stigviewer.com/stigs/microsoft_windows_11_v2r7/2026-02-12/finding/V-253427">Windows 11 STIG V-253427</a>, <a href="https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final">NIST SP 800-53 Rev. 5</a>)</p>
<h2 id="auditing-your-trust-store">Auditing your trust store</h2>
<p>If I had a team on this today, I&rsquo;d start with the easiest check. The disclosure site runs test hosts that serve the bad chains. A certificate error is the result you want.</p>

<figure class="code" data-lang="text">
<figcaption><span class="lang">text</span><button class="copy" type="button">Copy</button></figcaption><pre tabindex="0"><code>curl https://dv-g3b-emudhra.we-have-a-problem.com/
curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/</code></pre>
</figure><p>Next, check what&rsquo;s installed. On Debian or Ubuntu:</p>

<figure class="code" data-lang="text">
<figcaption><span class="lang">text</span><button class="copy" type="button">Copy</button></figcaption><pre tabindex="0"><code>dpkg -l &#39;ca-certificates&#39; | grep ^ii</code></pre>
</figure><p>On RHEL or UBI:</p>

<figure class="code" data-lang="text">
<figcaption><span class="lang">text</span><button class="copy" type="button">Copy</button></figcaption><pre tabindex="0"><code>rpm -q ca-certificates</code></pre>
</figure><p>Then patch it:</p>

<figure class="code" data-lang="text">
<figcaption><span class="lang">text</span><button class="copy" type="button">Copy</button></figcaption><pre tabindex="0"><code>sudo apt update &amp;&amp; sudo apt install --only-upgrade ca-certificates</code></pre>
</figure><p>Your laptop passing doesn&rsquo;t mean your images pass, so run the same check inside them:</p>

<figure class="code" data-lang="text">
<figcaption><span class="lang">text</span><button class="copy" type="button">Copy</button></figcaption><pre tabindex="0"><code>docker run --rm &lt;image&gt; curl -sS https://dv-g3b-emudhra.we-have-a-problem.com/</code></pre>
</figure><p>I ran this on October 22 against the current public images. Only <code>ubi9/ubi-minimal</code> has curl out of the box, and it connected, so that image still trusts the bad chain. Ubuntu 24.04, Debian slim, Alpine, distroless and Chainguard&rsquo;s <code>static</code> and <code>wolfi-base</code> don&rsquo;t ship curl at all. Ubuntu 24.04 and Debian slim don&rsquo;t ship a CA bundle either, so they get one when you install <code>ca-certificates</code>. With curl and ca-certificates installed, both refused the connection. Chainguard&rsquo;s <code>curl</code> image refused it too.</p>
<p>For images without curl, copy the bundle out and test it from the host:</p>

<figure class="code" data-lang="text">
<figcaption><span class="lang">text</span><button class="copy" type="button">Copy</button></figcaption><pre tabindex="0"><code>c=$(docker create &lt;image&gt; x)
docker cp -L $c:/etc/ssl/certs/ca-certificates.crt ./bundle.pem
docker rm $c
curl --cacert ./bundle.pem https://dv-g3b-emudhra.we-have-a-problem.com/</code></pre>
</figure><p>On RHEL and UBI the bundle path is <code>/etc/pki/tls/certs/ca-bundle.crt</code>. Done this way, Alpine and both Chainguard bundles refused the connection. The <code>gcr.io/distroless/static-debian12</code> and <code>ubi9/ubi-minimal</code> bundles connected, so both still need an update.</p>
<p>For anything offline, the disclosure site has a chains.zip with the PEM chains for all twelve test hosts and the OpenSSL steps to check them. There are also manual workarounds to distrust the affected roots on Debian, Ubuntu, Alpine, RHEL, Arch, NixOS, macOS, Windows, NetBSD and FreeBSD before a package lands. The site says the Linux ones are for expert users only, and enterprise Windows devices should go through IT.</p>
<p>After the cleanup, make it a gate check in CI by putting the curl test in your image build. If it connects, fail the build. Good looks like every image and runtime getting a certificate error from those test hosts, and a named owner for each trust store.</p>
<h2 id="routine-key-rotation">Routine key rotation</h2>
<p>eMudhra&rsquo;s keys sat there for 18 months. If those intermediates had lived for weeks instead of years, the window would have been a lot smaller and fewer certs would have depended on them.</p>
<p>Public certificates are already heading that way. Under CA/Browser Forum Ballot SC-081v3, a public TLS certificate issued on or after March 15, 2026 can be valid for at most 200 days. That drops to 100 days on March 15, 2027 and 47 days on March 15, 2029. (<a href="https://cabforum.org/working-groups/server/baseline-requirements/requirements/">CA/B Forum Baseline Requirements</a>)</p>
<p>Most teams I work with rotate internal intermediates about every five years, if they rotate them at all. I want rotation to be boring. If it runs on a schedule and the tooling handles it, nobody has to remember. Ubuntu&rsquo;s approach of pulling whole roots also shows the cost of not being able to swap fast: good certs break along with bad ones. Being able to swap a CA quickly is what limits the damage.</p>
<h2 id="making-rotation-boring-with-vault">Making rotation boring with Vault</h2>
<figure>
    <img loading="lazy" src="/images/emudhra-vault-pki-rotation.svg"
         alt="Vault PKI: the root CA signs the intermediate CA, the agents role issues agent certs with a 1h TTL and 24h max, and a new intermediate is signed, made default and tidied up on a schedule."/> 
</figure>

<p>There are two kinds of certs to keep short-lived: the ones from your own internal CA and the ones from a public CA. HashiCorp Vault handles both.</p>
<p>For internal CAs, use the <a href="https://developer.hashicorp.com/vault/docs/secrets/pki">PKI secrets engine</a>. You run the root and intermediates, issue short-lived certs to services and agents, and swap intermediates on a schedule with the <a href="https://developer.hashicorp.com/vault/docs/secrets/pki/rotation-primitives">rotation primitives</a>. Since Vault 1.14 it also works as an ACME server, so certbot or cert-manager can renew internal certs against your own CA.</p>
<p>For public CAs, use the <a href="https://developer.hashicorp.com/vault/docs/secrets/pki-external-ca">PKI External CA secrets engine</a> (Vault Enterprise). It speaks ACME to CAs like Let&rsquo;s Encrypt, GlobalSign, Sectigo and DigiCert, can answer DNS-01 challenges through your DNS provider, and hands the certs to your apps.</p>
<p>There&rsquo;s no federal rule for how long an internal intermediate should live. NIST SP 800-57 Part 1 suggests one to three years of use for a private signing key. (<a href="https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-57pt1r5.pdf">NIST SP 800-57 Part 1 Rev. 5</a>) For an internal intermediate in a federal environment I&rsquo;d start with something like one year and work it down as the automation proves itself.</p>
<p>Either way, renewal becomes a scheduled job instead of a calendar reminder. When the next CA has a bad day, swapping it is a config change and a renewal cycle.</p>
<h2 id="closing-takeaway">Closing takeaway</h2>
<p>If you run federal IT, do one thing this week. Run the curl check from inside every base image you ship and fix the ones that connect. After that, give every trust store an owner and put the check in CI so the next incident like this one is just a rebuild.</p>
<p>Resources:</p>
<ul>
<li><a href="https://emudhra.we-have-a-problem.com/">eMudhra disclosure site and checker</a></li>
<li><a href="https://blog.mozilla.org/security/2026/10/20/protecting-firefox-users-following-the-compromise-of-emudhra-intermediate-certificates/">Mozilla Security Blog post</a></li>
<li><a href="https://ubuntu.com/blog/emudhra-ca-compromise-fixes-available">Ubuntu fixes</a></li>
<li><a href="https://access.redhat.com/security/vulnerabilities/RHSB-2026-012">Red Hat RHSB-2026-012</a></li>
<li><a href="https://developer.hashicorp.com/vault/docs/secrets/pki">Vault PKI secrets engine</a></li>
<li><a href="https://developer.hashicorp.com/vault/docs/secrets/pki-external-ca">Vault PKI External CA secrets engine</a></li>
</ul>
<p>Did I miss anything? Let me know!</p>
<p>Views are my own and do not represent my employer.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
