<# .SYNOPSIS Read-only check for the eMudhra intermediate CA compromise (Oct 2026). https://scan.theadminstack.com/emudhra/ .DESCRIPTION Answers three questions about this Windows machine: 1. Does Windows trust the compromised CAs? It builds a chain for three of them (TLS via the emSign root, TLS via the Sectigo AAA cross-sign, and code signing) and reports whether Windows blocks them. It also counts the 16 certificates in Untrusted Certificates and reads when the automatic root list last synced. 2. Do apps that bring their own CA bundle trust them? It checks Python certifi, Node.js, Java cacerts and Git for Windows (OpenSSL backend), plus runtime versions. 3. Does this machine serve or sign with eMudhra certificates? It checks LocalMachine\My, WebHosting and IIS/HTTP.sys bindings, and Authenticode signatures with -CodeSigningScan. It changes nothing and sends nothing anywhere unless you pass -Online. Run it from an elevated PowerShell so it can read the machine stores and HTTP.sys bindings. .PARAMETER Online Also connect to three public test sites (emudhra.we-have-a-problem.com) with Windows' own TLS stack. Their operator records successful connections by user agent. .PARAMETER CodeSigningScan One or more folders to scan for Authenticode signatures issued by eMudhra code-signing CAs. .PARAMETER NoCertScan Skip the machine certificate store and IIS binding scan. .PARAMETER Deep Also sweep the usual app install folders (Program Files, Program Files (x86), ProgramData, C:\Python*, and per-user installs under AppData\Local\Programs, AppData\Local\pipx and AppData\Roaming\Python): every CA bundle an app ships (every Python certifi included), every Java runtime's cacerts and version, Electron apps, and Go programs with the Go version each one was built with. It reads file headers only and never runs what it finds. Expect a few minutes. .PARAMETER Path Extra folders to include in the -Deep sweep. .NOTES The Windows chain test reads the public certificates of the compromised CAs from emudhra-compromised.p7b in the same folder as this script: https://scan.theadminstack.com/emudhra/certs/emudhra-compromised.p7b .EXAMPLE .\emudhra-check.ps1 -Deep | Set-Clipboard #> [CmdletBinding()] param( [switch]$Online, [string[]]$CodeSigningScan, [switch]$NoCertScan, [switch]$Deep, [string[]]$Path, [int]$MaxFiles = 20000 ) $ErrorActionPreference = 'SilentlyContinue' $script:out = New-Object System.Collections.Generic.List[string] function W([string]$s) { $script:out.Add($s) } function Get-Sha256Hex([byte[]]$b) { $h = [System.Security.Cryptography.SHA256]::Create() (($h.ComputeHash($b) | ForEach-Object { $_.ToString('X2') }) -join '') } function Clean([string]$s) { if ($null -eq $s) { '' } else { ($s -replace '[\r\n]+', ' ').Trim() } } # ------------------------------------------------------------------ reference data (public) $Compromised = [ordered]@{ 'A99B289464F3B44343270A7A1E910F02370483FE' = 'EM DV TLS CA - G3B (via emSign Root TLS CA - G3)' '1396BE1372BC93DFA2A433CA964DBA91548A6E57' = 'EM DV TLS CA - G3B (via AAA Certificate Services)' '5F014E0DB3BB8F950E0598AB280846D383FC57BE' = 'EM OV TLS CA - G3B (via emSign Root TLS CA - G3)' '5DFA19197D1C1CF629AF7C0CD865543C5050227C' = 'EM OV TLS CA - G3B (via AAA Certificate Services)' 'F33ACD1F5D31AA81AC18C72C928446226D3D5914' = 'EM EV TLS CA - G3B (via emSign Root TLS CA - G3)' '19069BD6533BFF93FB67C71FB3376B4EA7402C3B' = 'EM EV TLS CA - G3B (via AAA Certificate Services)' '8AD23BC78845D6FAAE1D534BAC89F0E36BA55A11' = 'EM DV TLS CA - G3C (via emSign Root TLS CA - G3)' '4B0944C14F8C2CCBA4E4F32104AD920FA15EE331' = 'EM DV TLS CA - G3C (via AAA Certificate Services)' '7FCC31AAB874CE6C8EE8584F54CFD08646B54117' = 'EM OV TLS CA - G3C (via emSign Root TLS CA - G3)' '305FC1E253DCAB71B95D4995DAE3CB09D562F22A' = 'EM OV TLS CA - G3C (via AAA Certificate Services)' 'C988F3D0CC1629FFC43165D6464C9B6EF73D6E48' = 'EM EV TLS CA - G3C (via emSign Root TLS CA - G3)' '8588708E875A1F8AA83524F3559080F7E17068C7' = 'EM EV TLS CA - G3C (via AAA Certificate Services)' 'D33195A244046A3263FB5E0E83C6803A5B2E7BC3' = 'EM OV CS CA - G3B (via emSign Root CS CA - G3)' 'EE26D7AE3CC71718F6E07D7D0095076F0C158F63' = 'EM OV CS CA - G3B (via AAA Certificate Services)' '1F4A5D178F8B6A8EF0F6784155BD5FEA91158B12' = 'EM OV CS CA - G3C (via emSign Root CS CA - G3)' '7FDE64753A957B98F172FD2AB0D922551909C7BA' = 'EM OV CS CA - G3C (via AAA Certificate Services)' } # Roots: SHA-1 thumbprint (Windows stores) and SHA-256 (bundles) $RootEccSha1 = '3043FA4FF257DCA0C380EE2E58EA78B23FE6BBC1'; $RootEccSha256 = '86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B' $RootAaaSha1 = 'D1EB23A46D17D68FD92564C2F1F1601764D8E349'; $RootAaaSha256 = 'D7A7A0FB5D7E2731D771E9484EBCDEF71D5F0C3E0A2948782BC83EE0EA699EF4' $RootTlsSha1 = '39D3236E4A609AB6190CBFA1AD02B3CC61DFC2C3'; $RootTlsSha256 = '7DD78D5F4F13459A83DFF9ABBBA62EDBAF6F2D102BF257FD712F4D9F2746ED8D' $RootCsSha1 = '1F74B6010A219FD9CA058D07AA053D15EFD9300D' # SHA-256 of each root's PEM base64 text (whitespace removed), for matching inside PEM bundles $PemIdEcc = '34FB35B2CA2E809EB644772FCEA051B0F2B864E4F9F55BDACCA3619E1D043B9D' $PemIdAaa = '548470A91DEEBC57FECCA81BD0445B98B35485CD836930F1D482A3701C620653' $PemIdTls = 'B48CD7CB1A86797622489CDC5E293EB2E6C891212D50512F7CFA2BFD54F76643' # ------------------------------------------------------------------ host $os = Get-CimInstance Win32_OperatingSystem $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) W '#emudhra-check v1 powershell' W '[host]' W "hostname=$env:COMPUTERNAME" W 'platform=windows' W 'os_id=windows' W "os_version=$($os.Version)" W "os_name=$(Clean $os.Caption)" W "elevated=$(if ($isAdmin) { 1 } else { 0 })" W "powershell=$($PSVersionTable.PSVersion)" W "deep=$(if ($Deep) { 1 } else { 0 })" # ------------------------------------------------------------------ Windows trust function Test-Chain($c) { if (-not $c) { return 'skipped' } try { $ch = New-Object System.Security.Cryptography.X509Certificates.X509Chain # Revocation is left out on purpose: the question is whether Windows' trust lists block it. $ch.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck $ok = $ch.Build($c) $st = ($ch.ChainStatus | ForEach-Object { $_.Status.ToString() }) -join ',' if ($st -match 'ExplicitDistrust') { 'blocked' } elseif ($ok) { 'trusted' } elseif ($st -match 'UntrustedRoot|PartialChain') { 'untrusted' } else { "error($st)" } } catch { 'error' } } # Public certificates of the compromised CAs, read from the companion file (no keys involved). $p7b = Join-Path $PSScriptRoot 'emudhra-compromised.p7b' $testCerts = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection if ($PSScriptRoot -and (Test-Path -LiteralPath $p7b)) { try { $testCerts.Import($p7b) } catch {} } function Pick([string]$subject, [string]$issuer) { $testCerts | Where-Object { $_.Subject -match $subject -and $_.Issuer -match $issuer } | Select-Object -First 1 } $disallowed = @() foreach ($loc in 'LocalMachine', 'CurrentUser') { $disallowed += Get-ChildItem "Cert:\$loc\Disallowed" | ForEach-Object { $_.Thumbprint.ToUpper() } } $found = @($Compromised.Keys | Where-Object { $disallowed -contains $_ }) $rootThumbs = @(Get-ChildItem Cert:\LocalMachine\Root | ForEach-Object { $_.Thumbprint.ToUpper() }) + @(Get-ChildItem Cert:\LocalMachine\AuthRoot | ForEach-Object { $_.Thumbprint.ToUpper() }) $au = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\AuthRoot\AutoUpdate' $pol = Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\SystemCertificates\AuthRoot' function FileTime($v) { if ($v -is [byte[]] -and $v.Length -ge 8) { [DateTime]::FromFileTimeUtc([BitConverter]::ToInt64($v, 0)).ToString('yyyy-MM-dd HH:mm') + ' UTC' } else { '' } } $tDv = Test-Chain (Pick 'EM DV TLS CA - G3B' 'emSign Root TLS'); $tAaa = Test-Chain (Pick 'EM DV TLS CA - G3B' 'AAA Certificate'); $tCs = Test-Chain (Pick 'EM OV CS CA - G3B' 'emSign Root CS') $worst = 'blocked' if ($testCerts.Count -eq 0) { $worst = 'skipped' } foreach ($t in $tDv, $tAaa, $tCs) { if ($t -eq 'skipped') { continue }; if ($t -eq 'trusted') { $worst = 'trusted' } elseif ($t -like 'error*' -and $worst -ne 'trusted') { $worst = 'error' } elseif ($t -eq 'untrusted' -and $worst -eq 'blocked') { $worst = 'untrusted' } } W '[windows]' W "disallowed_found=$($found.Count)" W "disallowed_total=$($Compromised.Count)" W "chain_test=$worst" W "chain_tls_emsign=$tDv" W "chain_tls_aaa=$tAaa" W "chain_codesigning=$tCs" W "root_ecc_g3=$(if ($rootThumbs -contains $RootEccSha1) { 1 } else { 0 })" W "root_aaa=$(if ($rootThumbs -contains $RootAaaSha1) { 1 } else { 0 })" W "root_tls_g3=$(if ($rootThumbs -contains $RootTlsSha1) { 1 } else { 0 })" W "root_cs_g3=$(if ($rootThumbs -contains $RootCsSha1) { 1 } else { 0 })" if ($au) { W "ctl_last_sync=$(FileTime $au.LastSyncTime)" W "disallowed_last_sync=$(FileTime $au.DisallowedCertLastSyncTime)" if ($null -ne $au.EnableDisallowedCertAutoUpdate) { W "disallowed_auto_update=$($au.EnableDisallowedCertAutoUpdate)" } } W "auto_update_disabled=$(if ($pol -and $pol.DisableRootAutoUpdate -eq 1) { 1 } else { 0 })" # ------------------------------------------------------------------ bundles used by other software $script:seenBundles = @{} function Test-PemBundle([string]$name, [string]$path, [string]$kind) { if (-not $path -or -not (Test-Path -LiteralPath $path)) { return } $full = (Resolve-Path -LiteralPath $path).ProviderPath if ($script:seenBundles[$full]) { return } $script:seenBundles[$full] = 1 $text = Get-Content -LiteralPath $path -Raw if ($text -notmatch 'BEGIN CERTIFICATE') { return } # Each PEM block is identified by the SHA-256 of its base64 text with whitespace removed, so # nothing is decoded. Reference values were computed the same way from the three root certificates. $ids = @() foreach ($m in [regex]::Matches($text, '-----BEGIN CERTIFICATE-----([\s\S]*?)-----END CERTIFICATE-----')) { $ids += Get-Sha256Hex ([Text.Encoding]::ASCII.GetBytes(($m.Groups[1].Value -replace '\s', ''))) } W '[store]' W "name=$name"; W "path=$path"; W "kind=$kind"; W "certs=$($ids.Count)" W "has_ecc_g3=$(if ($ids -contains $PemIdEcc) { 1 } else { 0 })" W "has_aaa=$(if ($ids -contains $PemIdAaa) { 1 } else { 0 })" W "has_root_tls_g3=$(if ($ids -contains $PemIdTls) { 1 } else { 0 })" W 'verify_tls_g3=skipped'; W 'verify_aaa=skipped' } function Runtime([string]$key, [string]$version, [string]$path, [string]$store) { if (-not $version) { return } W '[runtime]'; W "key=$key"; W "version=$version"; W "path=$path" if ($store) { W "store=$store" } } if ($env:SSL_CERT_FILE) { Test-PemBundle 'SSL_CERT_FILE' $env:SSL_CERT_FILE 'pem-bundle' } if ($env:REQUESTS_CA_BUNDLE) { Test-PemBundle 'REQUESTS_CA_BUNDLE' $env:REQUESTS_CA_BUNDLE 'pem-bundle' } if ($env:CURL_CA_BUNDLE) { Test-PemBundle 'CURL_CA_BUNDLE' $env:CURL_CA_BUNDLE 'pem-bundle' } if ($env:NODE_EXTRA_CA_CERTS) { Test-PemBundle 'NODE_EXTRA_CA_CERTS' $env:NODE_EXTRA_CA_CERTS 'pem-bundle' } $node = Get-Command node -ErrorAction SilentlyContinue | Select-Object -First 1 if ($node) { $nv = (& $node.Source -v 2>$null) -replace '^v', '' $nodeStore = if ("$env:NODE_OPTIONS" -match 'use-system-ca|use-openssl-ca') { 'system' } else { 'bundled' } Runtime 'node' $nv $node.Source $nodeStore $js = "const {X509Certificate}=require('crypto');const tls=require('tls');const w={e:'$RootEccSha256',a:'$RootAaaSha256',t:'$RootTlsSha256'};const g={e:0,a:0,t:0};for(const p of tls.rootCertificates){try{const f=new X509Certificate(p).fingerprint256.replace(/:/g,'');for(const k in w)if(f===w[k])g[k]=1;}catch{}}console.log([g.e,g.a,g.t,tls.rootCertificates.length].join(' '))" $r = (& $node.Source -e $js 2>$null) if ($r -match '^(\d) (\d) (\d) (\d+)') { W '[store]'; W "name=Node.js built-in roots ($nv)"; W "path=$($node.Source)"; W 'kind=node'; W "certs=$($Matches[4])" W "has_ecc_g3=$($Matches[1])"; W "has_aaa=$($Matches[2])"; W "has_root_tls_g3=$($Matches[3])" } } $py = $null foreach ($cand in @(@('py', '-3'), @('python'), @('python3'))) { $cmd = Get-Command $cand[0] -ErrorAction SilentlyContinue | Where-Object { $_.Source -notmatch 'WindowsApps' } | Select-Object -First 1 if ($cmd) { $py = @($cmd.Source) + @($cand | Select-Object -Skip 1); break } } if ($py) { $pyExe = $py[0]; $pyArgs = @($py | Select-Object -Skip 1) $pv = & $pyExe @pyArgs -c 'import sys;print("%d.%d.%d"%sys.version_info[:3])' 2>$null Runtime 'python' $pv $pyExe '' $cf = @(& $pyExe @pyArgs -c 'import certifi;print(certifi.__version__);print(certifi.where())' 2>$null) if ($cf.Count -ge 2) { Runtime 'certifi' $cf[0] $cf[1] ''; Test-PemBundle "Python certifi $($cf[0])" $cf[1] 'certifi' } $pip = (& $pyExe @pyArgs -m pip --version 2>$null) -split ' ' if ($pip.Count -ge 2) { Runtime 'pip' $pip[1] "$pyExe -m pip" '' } } $java = Get-Command java -ErrorAction SilentlyContinue | Select-Object -First 1 if ($java) { $jvLine = (& $java.Source -version 2>&1 | Out-String) if ($jvLine -match 'version "([^"]+)"') { Runtime 'java' $Matches[1] $java.Source '' } $kt = Join-Path (Split-Path $java.Source) 'keytool.exe' if (Test-Path $kt) { $ks = (& $kt -list -cacerts -storepass changeit 2>$null | Out-String) if ($ks) { $fl = [regex]::Matches($ks, 'SHA-?256\)?:\s*([0-9A-F:]+)') | ForEach-Object { ($_.Groups[1].Value -replace ':', '').ToUpper() } W '[store]'; W "name=Java cacerts ($($Matches[1]))"; W 'path=keytool -cacerts'; W 'kind=java-keystore' W "has_ecc_g3=$(if ($fl -contains $RootEccSha256) { 1 } else { 0 })" W "has_aaa=$(if ($fl -contains $RootAaaSha256) { 1 } else { 0 })" W "has_root_tls_g3=$(if ($fl -contains $RootTlsSha256) { 1 } else { 0 })" } } } $go = Get-Command go -ErrorAction SilentlyContinue | Select-Object -First 1 if ($go) { $gv = (& $go.Source version 2>$null) -replace '^go version go(\S+).*$', '$1'; Runtime 'go' $gv $go.Source 'embedded' } $git = Get-Command git -ErrorAction SilentlyContinue | Select-Object -First 1 if ($git) { $backend = (& $git.Source config --get http.sslBackend 2>$null) if ($backend -ne 'schannel') { $ca = (& $git.Source config --get http.sslCAInfo 2>$null) if (-not $ca) { $ca = Join-Path (Split-Path (Split-Path $git.Source)) 'mingw64\etc\ssl\certs\ca-bundle.crt' } Test-PemBundle "Git for Windows ($(if ($backend) { $backend } else { 'openssl' }) backend)" $ca 'pem-bundle' } } foreach ($t in 'uv', 'conda', 'bun', 'deno') { $c = Get-Command $t -ErrorAction SilentlyContinue | Select-Object -First 1 if ($c) { $v = ((& $c.Source --version 2>$null) | Select-Object -First 1) -replace '^[^\d]*([\d.]+).*$', '$1'; Runtime $t $v $c.Source '' } } function Read-Bytes([string]$file, [long]$offset, [int]$count) { $fs = $null try { $fs = [IO.File]::OpenRead($file); [void]$fs.Seek($offset, 'Begin') $buf = New-Object byte[] $count; $n = $fs.Read($buf, 0, $count) if ($n -lt $count) { return $null }; return $buf } catch { return $null } finally { if ($fs) { $fs.Dispose() } } } function Get-GoVersion([string]$file) { $h = Read-Bytes $file 0 4096 if (-not $h -or $h[0] -ne 0x4D -or $h[1] -ne 0x5A) { return $null } # "MZ" $pe = [BitConverter]::ToInt32($h, 0x3C) if ($pe -lt 0 -or $pe + 24 -gt 4096) { return $null } $sections = [BitConverter]::ToUInt16($h, $pe + 6); $optSize = [BitConverter]::ToUInt16($h, $pe + 20) $t = $pe + 24 + $optSize for ($i = 0; $i -lt $sections -and ($t + 40 * $i + 40) -le 4096; $i++) { $o = $t + 40 * $i $name = [Text.Encoding]::ASCII.GetString($h, $o, 8).TrimEnd([char]0) if ($name -ne '.data' -and $name -ne '.go.buildinfo') { continue } $raw = [BitConverter]::ToUInt32($h, $o + 20) $b = Read-Bytes $file $raw 64 if (-not $b -or [Text.Encoding]::ASCII.GetString($b, 2, 12) -ne 'Go buildinf:') { continue } if (($b[15] -band 2) -eq 0) { return '1.17-or-older' } # pre-1.18 layout $len = $b[32] if ($len -gt 0 -and $len -lt 31) { return ([Text.Encoding]::ASCII.GetString($b, 33, $len) -replace '^go', '') } } return $null } # ------------------------------------------------------------------ deep sweep of app folders (-Deep) if ($Deep) { $roots = @($env:ProgramFiles, ${env:ProgramFiles(x86)}, $env:ProgramData) foreach ($u in Get-ChildItem 'C:\Users' -Directory -Force -ErrorAction SilentlyContinue) { # per-user app installs only (not browser profiles or other user data) foreach ($s in 'AppData\Local\Programs', 'AppData\Local\pipx', 'AppData\Roaming\Python') { $roots += (Join-Path $u.FullName $s) } } foreach ($c in Get-ChildItem 'C:\' -Directory -Filter 'Python*' -ErrorAction SilentlyContinue) { $roots += $c.FullName } if ($Path) { $roots += $Path } $roots = @($roots | Where-Object { $_ -and (Test-Path -LiteralPath $_) } | Select-Object -Unique) $skip = '[\\/](\.git|WinSxS|WindowsApps|Temp|INetCache|Packages[\\/]Microsoft\.)([\\/]|$)' $bundleRx = '^(cacert.*\.pem|ca-bundle.*\.(crt|pem)|ca-certificates\.crt|cert\.pem|tls-ca-bundle\.pem|roots\.pem|curl-ca-bundle\.crt|trusted.*\.pem)$' $goCmd = Get-Command go -ErrorAction SilentlyContinue | Select-Object -First 1 $goRoot = if ($goCmd) { (& $goCmd.Source env GOROOT 2>$null) } else { $null } $script:nb = 0; $script:nj = 0; $script:ne = 0; $script:ng = 0; $jseen = @{} foreach ($r in $roots) { Get-ChildItem -LiteralPath $r -Recurse -File -Force -ErrorAction SilentlyContinue | ForEach-Object { $f = $_; $n = $f.Name; $p = $f.FullName if ($p -match $skip) { return } # 1. CA bundles shipped by apps if (($n -match $bundleRx) -or (($f.Extension -eq '.pem' -or $f.Extension -eq '.crt') -and $f.Length -gt 60KB -and $f.Length -lt 4MB)) { if ($script:nb -ge 500) { return } $cnt = @(Select-String -LiteralPath $p -Pattern 'BEGIN CERTIFICATE' -SimpleMatch).Count if ($cnt -lt 20) { return } $script:nb++ if ($p -match '[\\/]pip[\\/]_vendor[\\/]certifi[\\/]cacert\.pem$') { Test-PemBundle "pip's vendored certifi ($($p -replace '[\\/]pip[\\/]_vendor[\\/].*$', ''))" $p 'certifi' } elseif ($p -match '[\\/]certifi[\\/]cacert\.pem$') { $cv = '' $m = Select-String -LiteralPath (Join-Path $f.DirectoryName '__init__.py') -Pattern '__version__\s*=\s*["'']([^"'']+)' -ErrorAction SilentlyContinue | Select-Object -First 1 if ($m) { $cv = $m.Matches[0].Groups[1].Value } Test-PemBundle "Python certifi $cv ($($p -replace '[\\/]certifi[\\/]cacert\.pem$', ''))" $p 'certifi' } else { $label = (($p -split '[\\/]') | Where-Object { $_ } | Select-Object -Skip 1 -First 3) -join '\' Test-PemBundle "App bundle: $label" $p 'app-bundle' } return } # 2. Java runtimes: cacerts + version if ($n -eq 'cacerts' -and $p -match '[\\/]lib[\\/]security[\\/]cacerts$') { $jh = ($p -replace '[\\/]lib[\\/]security[\\/]cacerts$', '') -replace '[\\/]jre$', '' if ($jseen[$jh] -or $script:nj -ge 50) { return } $jseen[$jh] = 1; $script:nj++ $jv = '' $rel = Join-Path $jh 'release' if (Test-Path -LiteralPath $rel) { $m = Select-String -LiteralPath $rel -Pattern '^JAVA_VERSION="([^"]+)"' | Select-Object -First 1; if ($m) { $jv = $m.Matches[0].Groups[1].Value } } Runtime 'java' $jv $jh '' $kt = @((Join-Path $jh 'bin\keytool.exe'), (Join-Path $jh 'jre\bin\keytool.exe'), (Join-Path $jh 'bin/keytool')) | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1 if (-not $kt) { $g = Get-Command keytool -ErrorAction SilentlyContinue | Select-Object -First 1; if ($g) { $kt = $g.Source } } if ($kt) { $ks = (& $kt -list -keystore $p -storepass changeit 2>$null | Out-String) $fl = [regex]::Matches($ks, 'SHA-?256\)?:\s*([0-9A-Fa-f:]+)') | ForEach-Object { ($_.Groups[1].Value -replace ':', '').ToUpper() } if ($fl) { W '[store]'; W "name=Java cacerts $(if ($jv) { "($jv) " })$jh"; W "path=$p"; W 'kind=java-keystore' W "has_ecc_g3=$(if ($fl -contains $RootEccSha256) { 1 } else { 0 })" W "has_aaa=$(if ($fl -contains $RootAaaSha256) { 1 } else { 0 })" W "has_root_tls_g3=$(if ($fl -contains $RootTlsSha256) { 1 } else { 0 })" } } return } # 3. Electron apps (version file next to the app's executable) if ($n -eq 'app.asar' -and $p -match '[\\/]resources[\\/]app\.asar$') { $d = Split-Path (Split-Path $p) $vf = Join-Path $d 'version' if ((Test-Path -LiteralPath $vf) -and $script:ne -lt 200) { $ev = ((Get-Content -LiteralPath $vf -TotalCount 1) -replace '\s', '') if ($ev) { $script:ne++; Runtime 'electron' $ev $d '' } } return } # 4. Go programs: read the PE section table (first 4 KB), then 64 bytes at the start of # .data, where Go 1.18+ keeps its build-info block with the Go version. if ($f.Extension -eq '.exe' -and $f.Length -gt 1MB -and $f.Length -lt 400MB -and $script:ng -lt 300) { if ($goRoot -and $p.StartsWith($goRoot, [StringComparison]::OrdinalIgnoreCase)) { return } $gv = Get-GoVersion $p if ($gv) { $script:ng++; W '[runtime]'; W 'key=go'; W "version=$gv"; W "path=$p"; W 'store=embedded'; W 'kind=binary' } } } } W "#deep_roots=$($roots -join ';')" W "#deep_bundles_tested=$($script:nb)" W "#deep_java_runtimes=$($script:nj)" W "#deep_electron_apps=$($script:ne)" W "#deep_go_binaries=$($script:ng)" } # ------------------------------------------------------------------ certificates this machine serves if (-not $NoCertScan) { $bind = @{} $ns = (netsh http show sslcert 2>$null | Out-String) foreach ($blk in ($ns -split '(\r?\n){2,}')) { if ($blk -match '(IP:port|Hostname:port|Central Certificate Store)\s*:\s*(\S+)' ) { $ep = $Matches[2] if ($blk -match 'Certificate Hash\s*:\s*([0-9a-fA-F]{40})') { $h = $Matches[1].ToUpper(); if ($bind[$h]) { $bind[$h] += ", $ep" } else { $bind[$h] = $ep } } } } foreach ($store in 'Cert:\LocalMachine\My', 'Cert:\LocalMachine\WebHosting') { foreach ($c in Get-ChildItem $store) { if ($c.Issuer -notmatch 'eMudhra|emSign|InCommon Intermediate CA - [DOE]VG3C') { continue } $aki = '' $ext = $c.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.35' } | Select-Object -First 1 if ($ext) { $f = $ext.Format($false); if ($f -match 'KeyID=([0-9a-fA-F ]+)') { $aki = ($Matches[1] -replace ' ', '').ToUpper() } } W '[servercert]' W "location=$store$(if ($bind[$c.Thumbprint.ToUpper()]) { ' ยท HTTP.sys ' + $bind[$c.Thumbprint.ToUpper()] })" W "subject=$(Clean $c.Subject)" W "issuer=$(Clean $c.Issuer)" W "aki=$aki" W "not_after=$($c.NotAfter.ToUniversalTime().ToString('yyyy-MM-dd HH:mm')) UTC" W "sha256=$(Get-Sha256Hex $c.RawData)" } } } # ------------------------------------------------------------------ Authenticode (optional) if ($CodeSigningScan) { $n = 0 foreach ($root in $CodeSigningScan) { Get-ChildItem -LiteralPath $root -Recurse -File -Include *.exe, *.dll, *.sys, *.msi, *.ps1, *.psm1, *.cab, *.cat, *.msix, *.appx -ErrorAction SilentlyContinue | Select-Object -First $MaxFiles | ForEach-Object { $n++ $sig = Get-AuthenticodeSignature -LiteralPath $_.FullName if ($sig.SignerCertificate -and $sig.SignerCertificate.Issuer -match 'eMudhra|emSign|EM OV CS CA') { $aki = '' $ext = $sig.SignerCertificate.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.35' } | Select-Object -First 1 if ($ext -and ($ext.Format($false) -match 'KeyID=([0-9a-fA-F ]+)')) { $aki = ($Matches[1] -replace ' ', '').ToUpper() } W '[codesign]' W "path=$($_.FullName)" W "signer=$(Clean $sig.SignerCertificate.Subject)" W "signer_issuer=$(Clean $sig.SignerCertificate.Issuer)" W "signer_aki=$aki" W "status=$($sig.Status)" } } } W "#signed_files_scanned=$n" } # ------------------------------------------------------------------ optional live test if ($Online) { try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch {} foreach ($h in 'dv-g3b-emudhra.we-have-a-problem.com', 'dv-g3b-aaa-emudhra.we-have-a-problem.com', 'dv-g3b-noct-emudhra.we-have-a-problem.com') { W '[probe]'; W "host=$h"; W 'client=windows (Schannel)' try { $r = Invoke-WebRequest -Uri "https://$h/" -UseBasicParsing -TimeoutSec 12 if ($r.Content -match 'successfully connected') { W 'result=connected' } else { W 'result=error'; W "detail=unexpected response $($r.StatusCode)" } } catch { $msg = ($_.Exception.ToString()) if ($msg -match 'trust|SSL|TLS|certificate|secure channel|revoked|UntrustedRoot|RemoteCertificate') { W 'result=blocked' } else { W 'result=error'; W "detail=$(Clean ($_.Exception.Message))" } } } } W '#end' $script:out