8000 SECURITY: jdk-21, jdk-25, jdk-27: distrust the compromised eMudhra intermediates (CVE-2026-111943) by bryan-minimal · Pull Request #949 · gominimal/pkgs · GitHub
[go: up one dir, main page]

Skip to content

SECURITY: jdk-21, jdk-25, jdk-27: distrust the compromised eMudhra intermediates (CVE-2026-111943) - #949

Merged
bryan-minimal merged 1 commit into
mainfrom
security/cve-2026-111943-jdk-blocked-certs
Oct 21, 2026
Merged

bryan-minimal merged 1 commit into
mainfrom
security/cve-2026-111943-jdk-blocked-certs

Conversation

@bryan-minimal
Copy link
Copy Markdown
Member

Security: CVE-2026-111943 (eMudhra intermediate CA key compromise)

Mozilla bug 2083449. The private keys of eight eMudhra intermediate CAs (EC P-384) were compromised. Each key is certified twice, so 16 certificates are affected:

  • one issued by emSign Root TLS/CS CA - G3 (ECDSA-signed). It reaches Java's trust store through a cross-certificate from emSign ECC Root CA - G3, which NSS 3.131.1 removed today.
  • one cross-signed by Sectigo's AAA Certificate Services (RSA-signed). OpenJDK's own cacerts includes this root as comodoaaaca.

OpenJDK and Temurin have shipped nothing. Our three Temurin JDKs are therefore fixed at build time. Go's fix is golang/go@7f86f95, an SPKI blocklist. Java's equivalent is lib/security/blocked.certs.

What changes

packages/jdk/cve-2026-111943.sh runs in each of jdk-21, jdk-25 and jdk-27, right after the image is copied into $OUTPUT_DIR:

  1. cacerts. It removes emSign ECC Root CA - G3 (86A1ECBA…6BBE6B) and AAA Certificate Services (D7A7A0FB…699EF4). Entries are matched by SHA-256 fingerprint, not by alias. Temurin stores the emSign root as cn_emsign_ecc_root_ca__g3,o_emudhra_technologies_limited,ou_emsign_pki,c_in [jdk], OpenJDK as emsigneccrootcag3. The store is rewritten password-less with the JDK's own KeyStore API, which is what keytool -delete calls. The rewritten store is then reloaded and must equal the original minus exactly those entries.

  2. blocked.certs. It appends the 48 fingerprints in packages/ca-ce 8000 rtificates/cve-2026-111943-blocked-sha256.txt, de-duplicated and sorted. The script refuses to run if the header is not Algorithm=SHA-256 or if any line is not a 64-character uppercase hex fingerprint. It also fails if an existing entry is lost or a new one does not land. The JDK checks this file on every PKIX validation, both for each certificate in the path and for the trust anchor, whatever trust store the application uses. Applications that bring their own trust store (AAA is still in Apple's and Microsoft's) are protected too.

  3. Proof with the JDK being packaged. packages/jdk/JdkDistrust.java runs as a single-file source program. It runs PKIX validation of EM DV TLS CA - G3C against its real issuers (committed public certificates in cve-2026-111943-fixtures.pem), with a fixed validation date and revocation off:

    • before the append, the certificate must validate. This shows the fixtures and the harness work.
    • after the append, every encoding must fail with Untrusted certificate, so the failure comes from blocked.certs and nothing else.
    • both times, an unrelated, unblocked control chain must still validate. After the fix, the default TrustManagerFactory must offer neither root and still offer at least 80.

    Any failure fails the build.

Why 48 fingerprints, not 24

The CVE data lists 24: the 16 certificates, plus the ECDSA-malleated twin (s → n−s) of each of the 8 emSign-issued ones. OpenJDK's own BlockedCertsConverter (the tool that generates blocked.certs upstream) also emits each certificate with the signatureAlgorithm NULL parameter toggled. That field is outside the signed TBS, so anyone can flip it.

The build log shows Java accepts that variant. Before the append, all four EM DV TLS CA - G3C encodings, -algnull ones included, validate on Temurin 21, 25 and 27. OpenSSL 3 rejects the -algnull ones, but Java does not. A 24-line list would have left one working re-encoding of every compromised certificate. The 48 are the converter's full set: 8 × 2 for the RSA-signed certificates and 8 × 4 for the ECDSA-signed ones.

Malleability is decided by the issuer's signature algorithm. BlockedCertsConverter decides it from the subject's public key type. That works by coincidence for the emSign-issued half. For the AAA half (EC subject, RSA signature) it would either fail or emit a bogus fingerprint. Writing fingerprints directly avoids the converter and that bug.

The list is generated from the PEMs, and each generated original and malleated fingerprint was checked against the published ones. It lives with ca-certificates as the one shared list for every trust store we patch. cve-2026-111943.ncl next to it exports it as a Local; a Local's path resolves against the .ncl that spells it, so no .. is needed.

Before / after (verified)

cacerts before cacerts after emSign ECC Root CA - G3 AAA Certificate Services blocked.certs
jdk-21 (Temurin 21.0.12.1+1) 118 117 present → removed not present (Temurin's store is Mozilla-derived) 38 → 86
jdk-25 (Temurin 25.0.4.1+1) 118 117 present → removed not present 38 → 86
jdk-27 (Temurin 27+35) 120 119 present → removed not present 38 → 86

"Before" comes from parsing the cacerts in each pinned tarball, both arches (byte-identical across arches). "After" comes from the build logs. Temurin also carries the other emSign roots (C1, C3, G1). They are untouched: they are not part of this compromise.

Tests

  • Build-time gate, which CI does run: the pre/post PKIX checks above. A clean-room --rebuild --verbose of all three packages in a pkgbuild session (aarch64) passed. Each log shows 6/6 compromised encodings VALID before, then Untrusted certificate after, with the control chain VALID both times.
  • Standalone test cve_2026_111943 on each JDK: keytool -list -cacerts still has ≥ 80 entries and neither root, and blocked.certs keeps its header and lists one malleated fingerprint. min check --packages jdk-21 jdk-25 jdk-27 in that session: all 15 checks Pass for each, including standalone tests and fmt. Note that pkgs CI skips standalone tests, so they ran only in the session.
  • mip dump evaluates on amd64 and arm64. Spec hashes change for the three JDKs and their consumers (jdk, android-sdk, ghidra, gradle, maven, jdtls, tlaplus, dev-language-servers), which pick the fix up through jdk.

Not in this PR

🤖 Generated with Claude Code

…termediates (CVE-2026-111943)

The private keys of eight eMudhra intermediate CAs were compromised (Mozilla
bug 2083449). Each key is certified twice: by emSign Root TLS/CS CA - G3 with
ECDSA, and cross-signed by Sectigo's AAA Certificate Services with RSA. OpenJDK
and Temurin have shipped nothing, so each JDK is fixed at build time by the
shared packages/jdk/cve-2026-111943.sh:

- cacerts: remove emSign ECC Root CA - G3 (86A1ECBA...) and AAA Certificate
  Services (D7A7A0FB...), selected by SHA-256 fingerprint. Temurin's store
  has the first under a Mozilla-style alias and lacks the second; OpenJDK's
  own store has both (emsigneccrootcag3, comodoaaaca).
- blocked.certs: append the 48 fingerprints in
  packages/ca-certificates/cve-2026-111943-blocked-sha256.txt, the one list
  for every trust store we patch. That is every encoding OpenJDK's
  BlockedCertsConverter would emit for the 16 certificates (as issued, NULL
  sig-alg params toggled, ECDSA s -> n-s), with malleability decided by the
  issuer's signature algorithm rather than the subject key.
- JdkDistrust.java, run with the JDK being packaged, validates the
  compromised certificates against their issuers with PKIX before (they must
  validate) and after (they must fail as "Untrusted certificate"), checks an
  unrelated control chain still validates, and checks the rewritten cacerts
  reloads password-less with only the two roots gone. Any failure fails the
  build.

Each JDK also gets a standalone test on the installed tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bryan-minimal
bryan-minimal requested a review from a team as a code owner October 20, 2026 22:48
@coderabbitai
coderabbitai Bot commented Oct 20, 2026
Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 11 billable files and costs up to $2.75.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 5 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available. Your 35 included PR review attempts over the past 7 days set your current allowance at 4 reviews per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: gominimal/pkgs/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 8feb0ef0-b8d0-4f62-9218-021382459d87
📥 Commits

Reviewing files that changed from the base of the PR and between 24009b6 and 6357069.

⛔ Files ignored due to path filters: 1
  • packages/jdk/cve-2026-111943-fixtures.pem is excluded by !**/*.pem
📒 Files selected for processing: 11
  • packages/ca-certificates/cve-2026-111943-blocked-sha256.txt
  • packages/ca-certificates/cve-2026-111943.ncl
  • packages/jdk-21/build.ncl
  • packages/jdk-21/build.sh
  • packages/jdk-25/build.ncl
  • packages/jdk-25/build.sh
  • packages/jdk-27/build.ncl
  • packages/jdk-27/build.sh
  • packages/jdk/JdkDistrust.java
  • packages/jdk/cve-2026-111943.ncl
  • packages/jdk/cve-2026-111943.sh
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0