SHA-256: 2596bb6dda83c66d96c5a252d7b2ae1f24cc1ae3f1890223b4f3591a8d52bf10
Workaround for the eMudhra CA certificate compromise
On October 20, 2026, Mozilla publicly disclosed that the private keys of eight eMudhra intermediate CA certificates were compromised. Zulu trusts the root certificates these intermediates chain to and doesn’t check revocation by default, so it would accept certificates made with those keys.
New Zulu releases contain a fix (see Release Notes for the version numbers). Earlier versions which are still in use, can be fixed by replacing the block list file in every Zulu installation with the updated one below, then restart the Java applications that use it. Browsers already block these certificates, so legitimate connections should not be affected.
1. Download the file for your Zulu version
| Platform | Download |
|---|---|
Linux, macOS, containers |
|
Windows |
SHA-256: |
Don’t use a file whose checksum doesn’t match. Don’t open or save it in a text editor: some editors add an invisible marker that makes Java ignore the whole list.
2. Find the file to replace
The new file replaces the one with the same name in the installation’s lib/security directory.
You can find this in the following default locations:
| Installation | Directory |
|---|---|
Linux, Azul APT repository |
|
Linux, downloaded DEB |
|
Linux, RPM |
|
macOS (DMG, PKG, Homebrew) |
|
Windows (MSI) |
|
On a Zulu 8 JDK, add jre/ before lib/security.
For archives and other installs, run that installation’s java -XshowSettings:properties -version and use the java.home value followed by /lib/security.
Appendix C: Find every Zulu installation lists every Zulu installation on a machine.
If the directory contains neither blocked.certs nor blacklisted.certs, contact Azul Support.
3. Replace the file
In every case, back up the original as <file>.bak-emudhra, copy the new file over it (a moved file can keep permissions Java can’t read), and restart every Java application that uses the installation.
The examples use Zulu 17 and blocked.certs.
Linux and macOS
Run the script from Appendix A: Linux and macOS script once per installation:
sudo sh zulu-emudhra-workaround.sh /usr/lib/jvm/zulu17-ca-amd64/bin/java
Or by hand:
cd /usr/lib/jvm/zulu17-ca-amd64/lib/security
curl -fsSLo /tmp/blocked.certs https://docs.azul.com/assets/2026-10/emudhra-block-unix.certs
sha256sum /tmp/blocked.certs # must match step 1; on macOS: shasum -a 256
sudo cp -p blocked.certs blocked.certs.bak-emudhra
sudo cp /tmp/blocked.certs blocked.certs && sudo chmod 644 blocked.certs
Windows
Run the script from Appendix B: Windows script in PowerShell as Administrator:
powershell -ExecutionPolicy Bypass -File .\zulu-emudhra-workaround.ps1 -Java "C:\Program Files\Zulu\zulu-17\bin\java.exe"
Or by hand:
-
In
C:\Program Files\Zulu\zulu-17\lib\security, renameblocked.certstoblocked.certs.bak-emudhra. -
Copy and paste the downloaded file into the folder. Don’t drag it, which moves the file.
-
In Command Prompt, run
certutil -hashfile "C:\Program Files\Zulu\zulu-17\lib\security\blocked.certs" SHA256and compare with step 1. If the file isn’t found, fix its name; browsers may save it asblocked (1).certs. -
Restart Java applications, including Windows services such as application servers.
Containers
Patch the image or the deployment, not a running container. Find the file’s path in the image:
id=$(docker create <image> x)
docker export "$id" | tar -tvf - | grep -E 'lib/security/(blocked|blacklisted)\.certs'
docker rm -v "$id"
Then rebuild the image:
FROM <image>
COPY --chmod=644 blocked.certs /<path from the listing>
Or mount the file without rebuilding, for example in Kubernetes:
kubectl create configmap zulu-blocked-certs --from-file=blocked.certs
# in the pod template
containers:
- name: <container>
volumeMounts:
- name: zulu-blocked-certs
mountPath: /<path from the listing>
subPath: blocked.certs
readOnly: true
volumes:
- name: zulu-blocked-certs
configMap:
name: zulu-blocked-certs
With Docker, run chmod 644 on the host file and add -v /opt/blocked.certs:/<path from the listing>:ro to docker run, using :ro,z on SELinux hosts.
Windows containers can’t mount single files, so rebuild them instead.
Other environments
CI jobs that download Zulu, such as actions/setup-java: run the script right after the JDK is set up, for example sh ./zulu-emudhra-workaround.sh "$JAVA_HOME/bin/java".
Runtimes built with jlink: copy the file into the runtime’s lib/security after every build.
Patching the JDK doesn’t help, because jlink takes the file from jmods.
Applications that bundle Java (jpackage, vendor products): update runtime/lib/security in your own builds and re-sign them on macOS.
For vendor applications, ask the vendor.
CRaC: create new checkpoints after the change. Existing checkpoints keep the old list.
4. Verify
The installed file’s SHA-256 must match step 1, and the applications must have restarted since the change. The scripts check the checksum themselves.
| Where | Command |
|---|---|
Linux |
|
macOS |
|
Windows |
|
Container image (for a running container or a mounted file, use the container’s name instead of $id):
id=$(docker create <image> x)
docker cp -L "$id:/<path from the listing>" - | tar -xOf - | sha256sum
docker rm -v "$id"
5. Undo, and after the fix
To undo, copy <file>.bak-emudhra back over the file and restart the applications.
The permanent fix ships in a new Zulu release soon.
Check the release notes for availability.
After upgrading, delete the backups and remove the COPY lines, mounts and CI steps added for this workaround.
Left in place, they would hide future updates to the list.
A Zulu update without the fix restores the original file. Apply the workaround again after such an update.
Appendix A: Linux and macOS script
Save as zulu-emudhra-workaround.sh.
It checks that the runtime is Zulu, picks the right file, verifies the checksum, keeps a backup, and restores it if the update fails.
It’s safe to run twice.
#!/bin/sh
# Applies the eMudhra workaround to one Azul Zulu installation.
# Usage: sudo sh zulu-emudhra-workaround.sh /full/path/to/bin/java
set -eu
URL="https://docs.azul.com/assets/2026-10/emudhra-block-unix.certs"
EXPECTED="2596bb6dda83c66d96c5a252d7b2ae1f24cc1ae3f1890223b4f3591a8d52bf10"
JAVA_BIN="${1:-java}"
sha256() {
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
else shasum -a 256 "$1" | cut -d' ' -f1; fi
}
if ! "$JAVA_BIN" -version 2>&1 | grep -q 'Runtime Environment.*Zulu'; then
echo "ERROR: $JAVA_BIN is not an Azul Zulu runtime." >&2; exit 1
fi
JAVA_HOME_DIR=$("$JAVA_BIN" -XshowSettings:properties -version 2>&1 | sed -n 's/^ *java\.home = //p')
if [ -z "$JAVA_HOME_DIR" ]; then
echo "ERROR: could not determine java.home for $JAVA_BIN." >&2; exit 1
fi
SEC_DIR="$JAVA_HOME_DIR/lib/security"
if [ -f "$SEC_DIR/blocked.certs" ]; then
FILE=blocked.certs
elif [ -f "$SEC_DIR/blacklisted.certs" ]; then
FILE=blacklisted.certs
else
echo "ERROR: no blocked.certs or blacklisted.certs in $SEC_DIR. Contact Azul Support." >&2; exit 1
fi
TARGET="$SEC_DIR/$FILE"
TMP=$(mktemp)
trap 'rm -f "$TMP"' EXIT
curl -fsSL -o "$TMP" "$URL"
if [ "$(sha256 "$TMP")" != "$EXPECTED" ]; then
echo "ERROR: checksum mismatch for the downloaded $FILE. Nothing was changed." >&2; exit 1
fi
[ -e "$TARGET.bak-emudhra" ] || cp -p "$TARGET" "$TARGET.bak-emudhra"
cp "$TMP" "$TARGET"
chmod 644 "$TARGET"
if [ "$(sha256 "$TARGET")" = "$EXPECTED" ]; then
echo "OK: updated $TARGET (original saved as $FILE.bak-emudhra)."
echo "Restart all Java applications that use $JAVA_HOME_DIR."
else
cp -p "$TARGET.bak-emudhra" "$TARGET"
echo "ERROR: $TARGET was not updated correctly; the original was restored." >&2; exit 1
fi
Appendix B: Windows script
Save as zulu-emudhra-workaround.ps1.
It does the same as Appendix A: Linux and macOS script.
If Group Policy enforces an execution policy, -ExecutionPolicy Bypass is ignored: run Unblock-File on the script first, or sign it.
# zulu-emudhra-workaround.ps1
# Applies the eMudhra workaround to one Azul Zulu installation. Run as Administrator.
param([Parameter(Mandatory = $true)][string]$Java)
$Url = 'https://docs.azul.com/assets/2026-10/emudhra-block-windows.certs'
$Expected = '101200f8ff64a4375a03601cf0200d0f9fec449d1a173aaf1459a5765ae7e5ae'
function Fail($msg) { Write-Host "ERROR: $msg" -ForegroundColor Red; exit 1 }
if (-not (Test-Path -LiteralPath $Java -PathType Leaf)) { Fail "$Java not found." }
$out = & { $ErrorActionPreference = 'Continue'; & $Java -XshowSettings:properties -version 2>&1 } | ForEach-Object { "$_" }
if (-not ($out -match 'Runtime Environment.*Zulu')) { Fail "$Java is not an Azul Zulu runtime." }
$javaHome = $out | ForEach-Object { if ($_ -match '^\s*java\.home = (.+)$') { $Matches[1].Trim() } } | Select-Object -First 1
if (-not $javaHome) { Fail "Could not determine java.home for $Java." }
$sec = Join-Path $javaHome 'lib\security'
$file = 'blocked.certs', 'blacklisted.certs' | Where-Object { Test-Path -LiteralPath (Join-Path $sec $_) } | Select-Object -First 1
if (-not $file) { Fail "No blocked.certs or blacklisted.certs in $sec. Contact Azul Support." }
$target = Join-Path $sec $file
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
if ([Net.WebRequest]::DefaultWebProxy) {
[Net.WebRequest]::DefaultWebProxy.Credentials = [Net.CredentialCache]::DefaultNetworkCredentials
}
$tmp = Join-Path $env:TEMP "$file.download"
try { Invoke-WebRequest -Uri $Url -OutFile $tmp -UseBasicParsing -ErrorAction Stop }
catch { Fail "Download failed: $($_.Exception.Message)" }
if ((Get-FileHash -LiteralPath $tmp -Algorithm SHA256).Hash -ne $Expected) {
Remove-Item -LiteralPath $tmp; Fail "Checksum mismatch for the downloaded $file. Nothing was changed."
}
try {
if (-not (Test-Path -LiteralPath "$target.bak-emudhra")) {
Copy-Item -LiteralPath $target -Destination "$target.bak-emudhra" -ErrorAction Stop
}
Copy-Item -LiteralPath $tmp -Destination $target -Force -ErrorAction Stop
} catch { Fail "Could not update $target. Run PowerShell as Administrator. $($_.Exception.Message)" }
Remove-Item -LiteralPath $tmp
if ((Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash -eq $Expected) {
Write-Host "OK: updated $target (original saved as $file.bak-emudhra)."
Write-Host "Restart all Java applications that use $javaHome."
} else {
Copy-Item -LiteralPath "$target.bak-emudhra" -Destination $target -Force
Fail "$target was not updated correctly; the original was restored."
}
Appendix C: Find every Zulu installation
These commands list Zulu installations by reading their release files; they don’t run anything they find.
On macOS, first give Terminal Full Disk Access.
For an UNKNOWN line, check whether that installation’s java -version mentions Zulu.
Linux and macOS
sudo sh -c 'find / \( -path /proc -o -path /sys -o -path /dev -o -path /System/Volumes -o -path /run/containerd -o -path /var/lib/docker -o -path /var/lib/containerd -o -path /var/lib/containers \) -prune -o -type f \( -name blocked.certs -o -name blacklisted.certs \) -path "*/lib/security/*" -print 2>/dev/null | while IFS= read -r f; do h=${f%/lib/security/*}; if grep -qsE "Azul|Zulu" "$h/release" "$h/../release"; then echo "Zulu: $f"; elif ! grep -qs "^IMPLEMENTOR=" "$h/release" "$h/../release"; then echo "UNKNOWN: $f (no vendor in release file; check by hand)"; fi; done'
Windows (PowerShell as Administrator)
$roots = Get-CimInstance Win32_LogicalDisk -Filter 'DriveType=3' | ForEach-Object { $_.DeviceID + '\' }
Get-ChildItem -Path $roots -Recurse -Force -File -Include blocked.certs, blacklisted.certs -ErrorAction SilentlyContinue |
Where-Object { $_.DirectoryName -like '*\lib\security' } |
ForEach-Object {
$f = $_.FullName
$jh = Split-Path (Split-Path $_.DirectoryName)
$rel = foreach ($r in "$jh\release", "$jh\..\release") {
if (Test-Path -LiteralPath $r -PathType Leaf) { Get-Content -LiteralPath $r -Raw } }
if ($rel -match 'Azul|Zulu') { "Zulu: $f" }
elseif (-not ($rel -match '(?m)^IMPLEMENTOR=')) { "UNKNOWN: $f (no vendor in release file; check by hand)" }
}