
Offline scanner for the October 2026 eMudhra/emSign CA key compromise, finds affected certificates across OS trust stores, CA bundles, Java cacerts, NSS and container images.
Detection tooling for the October 2026 eMudhra / emSign intermediate CA
private-key compromise. Eight intermediate CAs were issued with P-384 keys
whose private scalars were tiny integers (recovered values 4, 56, 66, 115, 155, 156, 230, 237), so anyone can trivially forge certificates that chain to them.
Browsers and OS trust stores pushed distrust quickly - but most non-browser
trust stores don't update automatically, which is what this tool finds.
Status: the incident is live, so treat the bundled IOCs as a snapshot (every run prints their
generateddate). To update them, refreshtools/sources/*.jsonfrom the upstream tracker and re-run the generator (below).
trustscan (Python) and trustscan.ps1 (Windows/PowerShell) inventory
the trust stores and CA bundles on a host and flag anything matching the affected
certs. Pure-stdlib + cryptography; no other dependencies.
Needs Python 3 and cryptography (the only dependency):
pip install -r requirements.txt
# scan this host's trust stores and CA bundles
python trustscan.py
# machine-readable for ticketing
python trustscan.py --format csv > findings.csv
# Windows-native: cert stores (both scopes) + GPO roots + env bundles
pwsh -File trustscan.ps1
Exit codes: 0 = clean, 1 = finding(s), 2 = error. INFO-only results (see
--flag-aaa below) exit 0.
Every run ends with a one-line caveat about the AAA cross-sign path (see
Scope & limitations). In text mode it is part
of the report; in json/csv mode it goes to stderr so the machine-readable
output stays clean.
Every certificate found at any location below is fingerprinted and checked four ways:
| Match | Severity | Meaning |
|---|---|---|
compromised_ca_cert | CRITICAL | exact compromised intermediate (cert SHA-256) |
compromised_key_reuse | CRITICAL | a different cert carrying a compromised key (SPKI SHA-256) - catches the Sectigo cross-signs and any re-issue |
issued_by_compromised_ca | HIGH | a leaf/sub-CA whose AuthorityKeyIdentifier points at a compromised intermediate - forgeable |
distrust_root_present | CRITICAL | a distrusted emSign root is still trusted here. Its own key is intact - but the intermediate keys are public, so while this root is trusted a forged cert for any domain validates for every client that doesn't check revocation (most non-browser clients). Remove it / update the bundle. On its own this does not close the AAA cross-sign path (below) |
One further check is opt-in (--flag-aaa / -FlagAaa):
| Match | Severity | Meaning |
|---|---|---|
cross_sign_root_present | INFO | the legitimate Sectigo root AAA Certificate Services is present. It cross-signed the eight intermediates, so it anchors their second trust path; its own key is intact. Mozilla and Chrome dropped it in April 2025, but Windows, Apple, older Linux bundles and many Java keystores still trust it. Reported only on request, for teams deciding whether to remove it (Debian's update for this incident does); never affects the exit code |
By default AAA Certificate Services is not flagged: its own key is intact and dropping it is a deliberate choice, not a default remediation. See the limitations below for why this matters.
trustscan looks| Source | What it is | Locations checked |
|---|---|---|
certifi | Python CA bundle | certifi.where() (resolved at runtime) |
bundles → distro | OS ca-certificates aggregates | Debian/Arch/Alpine /etc/ssl/certs/ca-certificates.crt · RHEL /etc/pki/tls/certs/ca-bundle.crt, /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem, consolidated Java cacerts, admin anchors/ dir · SUSE /etc/ssl/ca-bundle.pem, /var/lib/ca-certificates/ca-bundle.pem · Debian source dir /usr/share/ca-certificates/ · /etc/ssl/cert.pem |
bundles → FreeBSD | ca_root_nss | /usr/local/share/certs/ca-root-nss.crt · /usr/local/etc/ssl/cert.pem |
bundles → Homebrew / MacPorts | brew / port ca-certificates, openssl | /opt/homebrew/etc/ca-certificates/cert.pem · /opt/homebrew/etc/openssl*/cert.pem · /usr/local/etc/... (Intel) · /opt/local/etc/{openssl,ssl}/cert.pem |
bundles → curl | standalone curl bundle | /usr/local/share/curl/curl-ca-bundle.crt · /opt/local/share/curl/curl-ca-bundle.crt · C:\Program Files\Git\...\ca-bundle.crt |
bundles → conda | each env's ssl/cacert.pem | $CONDA_PREFIX · /opt/conda · ~/anaconda3, ~/miniconda3, ~/miniforge3 (incl. envs/*) |
bundles → AWS CLI v2 | bundled botocore | /usr/local/aws-cli/.../botocore/cacert.pem · C:\Program Files\Amazon\AWSCLIV2\... |
bundles → cloud SDKs / registry / ecosystem | more vendored bundles | gcloud / Azure CLI third_party/certifi · container-registry /etc/{docker,containers}/certs.d/*/ · Perl Mozilla::CA · Erlang/Elixir certifi |
bundles → pip | pip's vendored certifi | <pip>/_vendor/certifi/cacert.pem (resolved via import pip) |
nss | Firefox / Thunderbird / Chrome-Linux + system NSS DB | ~/.mozilla/firefox/*/cert9.db · ~/.thunderbird/*/cert9.db · ~/snap/firefox/common/.mozilla/firefox/*/cert9.db · ~/.pki/nssdb/cert9.db · /etc/pki/nssdb/cert9.db · %APPDATA%\Mozilla\Firefox\Profiles\*\cert9.db · %APPDATA%\Thunderbird\Profiles\*\cert9.db |
| Source | Locations checked |
|---|---|
os (Linux) | /etc/ssl/certs · /usr/local/share/ca-certificates · /etc/pki/tls/certs · /etc/pki/ca-trust/extracted/pem (recursed), plus Android/AOSP /system/etc/security/cacerts, /apex/com.android.conscrypt/cacerts, cacerts-added/ (hash-named files) |
os (macOS) | security find-certificate -a -p on default keychains + SystemRootCertificates.keychain |
os (Windows, Python) | ssl.enum_certificates for ROOT, CA, MY, TrustedPublisher, AuthRoot + Group-Policy-pushed LocalMachine roots (registry) |
java | $JAVA_HOME + JVM roots (/usr/lib/jvm/*, /opt/java/*, /Library/Java/JavaVirtualMachines/*, C:\Program Files\Java\*, Adoptium, Corretto, Microsoft) → .../lib/security/cacerts |
env | files in SSL_CERT_FILE, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, NODE_EXTRA_CA_CERTS; directory in SSL_CERT_DIR |
containers (opt-in) | Docker /var/lib/docker/overlay2/*/diff, podman (root + rootless, incl. other users), containerd overlayfs snapshots - scans each layer's aggregate bundle; needs root, location names the layer |
apptrust (opt-in) | app-server & data-infra Java truststores (Tomcat, WildFly/JBoss, Kafka, Elasticsearch) and app-bundled JREs under /opt/*/jre/.../cacerts, C:\Program Files\*\jre\... - private vendor-bundled JREs the host Java scan misses |
--path targets | config-referenced stores (nginx, Apache, HAProxy, Oracle sqlnet.ora, LDAP), and per-project bundles (Composer vendor/composer/ca-bundle/...) - point --path at them |
paths | any files/directories you pass |
--rootfs <dir> | the distro / curl / Java / Android store locations inside an extracted image or firmware rootfs |
envvariables hold a path, not cert data -trustscanreads the file(s) at that path.
--pathcertifi is not the only Python package that ships its own CA roots. These
bundle their own *.pem inside site-packages, independent of both certifi and
the OS store:
| Package | Bundled file |
|---|---|
| botocore (AWS CLI / boto3) | botocore/cacert.pem |
| grpcio (gRPC) | grpc/_cython/_credentials/roots.pem |
| httplib2 | httplib2/cacerts.txt (odd extension - pass the file directly) |
| requests (legacy <2.16) | requests/cacert.pem |