|
6 | 6 | # on GitHub-hosted runners until GitHub ships fixed images. |
7 | 7 | # |
8 | 8 | # On Linux: upgrades ca-certificates via apt so the compromised emSign ECC Root |
9 | | -# CA - G3 anchor is removed from /etc/ssl/certs/ca-certificates.crt. |
10 | | -# On Windows: adds the compromised root to Cert:\LocalMachine\Disallowed so |
11 | | -# schannel-based clients (git, gh, PowerShell/.NET) reject chains through it. |
| 9 | +# CA - G3 anchor is removed from /etc/ssl/certs/ca-certificates.crt. Ubuntu's |
| 10 | +# bundle does not include AAA Certificate Services, so the Sectigo cross-signs |
| 11 | +# do not chain to a trusted root there. |
| 12 | +# On Windows: adds the eight compromised intermediates and their eight Sectigo |
| 13 | +# cross-signs (impacted-intermediates.json) to Cert:\LocalMachine\Disallowed so |
| 14 | +# schannel-based clients (git, gh, PowerShell/.NET) reject chains through them, |
| 15 | +# including chains to AAA Certificate Services, which Windows still trusts. |
12 | 16 | # |
13 | 17 | # This action is a no-op on self-hosted runners (runner.environment != 'github-hosted') |
14 | 18 | # because self-hosted runner operators own and update their own host images. |
|
19 | 23 | name: Harden OS certificate trust (eMudhra/emSign CA stopgap) |
20 | 24 | description: > |
21 | 25 | Temporary per-job stopgap for CVE-2026-111943. Upgrades ca-certificates on |
22 | | - GitHub-hosted Linux runners and adds the compromised emSign ECC Root CA - G3 |
23 | | - to the Disallowed store on GitHub-hosted Windows runners. No-op on self-hosted. |
| 26 | + GitHub-hosted Linux runners and adds the compromised eMudhra intermediates |
| 27 | + and cross-signs to the Disallowed store on GitHub-hosted Windows runners. |
| 28 | + No-op on self-hosted. |
24 | 29 |
|
25 | 30 | runs: |
26 | 31 | using: composite |
27 | 32 | steps: |
28 | 33 |
|
29 | 34 | # ------------------------------------------------------------------------- |
30 | | - # Windows path: add the compromised root to LocalMachine\Disallowed. |
| 35 | + # Windows path: add the compromised intermediates to LocalMachine\Disallowed. |
31 | 36 | # Takes effect immediately for all schannel-based clients in subsequent steps |
32 | 37 | # (git, gh, PowerShell/.NET, WinHTTP). Must precede actions/checkout. |
33 | 38 | # ------------------------------------------------------------------------- |
34 | | - - name: Distrust emSign ECC Root CA - G3 (Windows, GitHub-hosted) |
| 39 | + - name: Distrust compromised eMudhra intermediates (Windows, GitHub-hosted) |
35 | 40 | if: ${{ runner.os == 'Windows' && runner.environment == 'github-hosted' }} |
36 | 41 | shell: pwsh |
| 42 | + env: |
| 43 | + ACTION_PATH: ${{ github.action_path }} |
37 | 44 | run: | |
38 | 45 | $ErrorActionPreference = 'Stop' |
39 | | - $targetFP = '86A1ECBA089C4A8D3BBE2734C612BA341D813E043CF9E8A862CD5C57A36BBE6B' |
40 | | -
|
41 | | - # Use -ErrorAction Stop for Root (an unreadable store is a failure, not |
42 | | - # a "clean" state). Use SilentlyContinue for Disallowed, which may be |
43 | | - # empty on a fresh runner. GetCertHashString('SHA256') is available in |
44 | | - # PS7 (shell: pwsh) and avoids the manual SHA256::Create() block. |
45 | | - function Find-CertBySHA256FP([string]$StoreName, [switch]$Required) { |
46 | | - $ea = if ($Required) { 'Stop' } else { 'SilentlyContinue' } |
47 | | - Get-ChildItem "Cert:\LocalMachine\$StoreName" -ErrorAction $ea | |
48 | | - Where-Object { $_.GetCertHashString('SHA256').ToUpper() -eq $targetFP } | |
49 | | - Select-Object -First 1 |
50 | | - } |
51 | | -
|
52 | | - $inRoot = Find-CertBySHA256FP 'Root' -Required |
53 | | - $inDisallowed = Find-CertBySHA256FP 'Disallowed' |
54 | | -
|
55 | | - if (-not $inRoot) { |
56 | | - Write-Host "::notice::emSign ECC Root CA - G3 absent from LocalMachine\Root; image already fixed." |
57 | | - exit 0 |
58 | | - } |
59 | | - if ($inDisallowed) { |
60 | | - Write-Host "::notice::emSign ECC Root CA - G3 already in LocalMachine\Disallowed; image already distrusted." |
61 | | - exit 0 |
62 | | - } |
63 | | -
|
64 | | - Write-Host "::notice::emSign ECC Root CA - G3 in LocalMachine\Root and not in Disallowed; adding to Disallowed store." |
65 | | - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Disallowed', 'LocalMachine') |
66 | | - $store.Open('ReadWrite') |
67 | | - $store.Add($inRoot) |
68 | | - $store.Close() |
69 | | -
|
70 | | - if (-not (Find-CertBySHA256FP 'Disallowed')) { |
71 | | - Write-Error "emSign ECC Root CA - G3 was not found in LocalMachine\Disallowed after adding; aborting." |
72 | | - exit 1 |
| 46 | + . (Join-Path $env:ACTION_PATH 'emudhra_intermediates.ps1') |
| 47 | +
|
| 48 | + $certs = @(Get-EmudhraIntermediates) |
| 49 | + try { |
| 50 | + # Adding is idempotent, so no early exit when the image is already fixed. |
| 51 | + $store = [System.Security.Cryptography.X509Certificates.X509Store]::new('Disallowed', 'LocalMachine') |
| 52 | + $store.Open('ReadWrite') |
| 53 | + try { |
| 54 | + foreach ($cert in $certs) { $store.Add($cert) } |
| 55 | + } finally { |
| 56 | + $store.Close() |
| 57 | + } |
| 58 | +
|
| 59 | + $notDistrusted = @($certs | Where-Object { -not (Test-ExplicitlyDistrusted $_) } | ForEach-Object { $_.EmudhraLabel }) |
| 60 | + if ($notDistrusted.Count -gt 0) { |
| 61 | + Write-Error "Not explicitly distrusted after adding to LocalMachine\Disallowed: $($notDistrusted -join '; ')" |
| 62 | + exit 1 |
| 63 | + } |
| 64 | + Write-Host "Distrusted $($certs.Count) compromised eMudhra intermediates and cross-signs." |
| 65 | + } finally { |
| 66 | + $certs | ForEach-Object { $_.Dispose() } |
73 | 67 | } |
74 | | - Write-Host "emSign ECC Root CA - G3 successfully added to LocalMachine\Disallowed." |
75 | 68 |
|
76 | 69 | # ------------------------------------------------------------------------- |
77 | 70 | # Linux path: upgrade ca-certificates via apt. |
|
0 commit comments