diff --git a/Invoke-SecureBootRemediation.ps1 b/Invoke-SecureBootRemediation.ps1 index c2450a4..8054129 100644 --- a/Invoke-SecureBootRemediation.ps1 +++ b/Invoke-SecureBootRemediation.ps1 @@ -46,7 +46,10 @@ param( [switch]$CheckOnly, [switch]$AssumeYes, [switch]$Force, - [switch]$AutoRestart + [switch]$AutoRestart, + # Písmeno pro dočasný mount ESP (default S:). Změňte pokud S: je obsazeno. + [ValidatePattern('^[A-Za-z]$')] + [string]$EspDriveLetter = 'S' ) $ErrorActionPreference = 'SilentlyContinue' @@ -520,16 +523,19 @@ function Get-TaskExists { #region ── Kategorizace ─────────────────────────────────────────────────────── function Test-RemediationComplete { - # Ověří všechny 4 post-remediation podmínky úspěchu. + # Ověří všechny podmínky úspěchu: 4 registry + ověření bootloaderu. + # Boot Manager ověření: Capable=2 (registry) NEBO podpis souboru (Is2023). param($R) - $cert = $R.Certificates - $reg = $R.Registry + $cert = $R.Certificates + $reg = $R.Registry + $bm = $R.BootManager $certsOk = $cert.KEK.Has2023 -and $cert.DB.Has2023WindowsUEFI $auVal = [int]$reg.AvailableUpdates $auOk = ($null -ne $reg.AvailableUpdates) -and ($auVal -eq 0 -or $auVal -eq 0x4000) $statusOk = $reg.UEFICA2023StatusText -eq 'Updated' $errorOk = ($null -eq $reg.UEFICA2023Error) -or ([int]$reg.UEFICA2023Error -eq 0) - return $certsOk -and $auOk -and $statusOk -and $errorOk + $bmOk = ($reg.WindowsUEFICA2023Capable -eq 2) -or ($bm -and $bm.Checked -and $bm.Is2023) + return $certsOk -and $auOk -and $statusOk -and $errorOk -and $bmOk } function Get-RemediationCategory { @@ -578,14 +584,19 @@ function Get-RemediationCategory { Label='Aktualizace pozastavena — problém firmwaru, zkontrolujte update u OEM' } } - # ── Hotovo (všechny 4 post-remediation podmínky) ────────────────────────── + # ── Hotovo (registry + bootloader ověřen) ───────────────────────────────── if (Test-RemediationComplete -R $Result) { + $bm = $Result.BootManager + $bmReason = if ($reg.WindowsUEFICA2023Capable -eq 2) { 'Capable=2' } ` + elseif ($bm -and $bm.Checked -and $bm.Is2023) { 'soubor ověřen' } ` + else { '' } + $doneLabel = if ($bmReason) { "HOTOVO — 2023 certifikáty + bootloader ověřen ($bmReason)" } ` + else { 'HOTOVO — 2023 certifikáty nasazeny' } if ($cert.AnyExpiring2011) { return @{ Code='OK_TRANSITION'; Color='Green' - Label='HOTOVO — 2023 certifikáty nasazeny (staré 2011 ještě přítomné, je to normální)' } + Label="$doneLabel (staré 2011 ještě přítomné, je to normální)" } } - return @{ Code='OK'; Color='Green' - Label='HOTOVO — 2023 certifikáty nasazeny, servisování dokončeno' } + return @{ Code='OK'; Color='Green'; Label=$doneLabel } } # ── Boot Manager staged, čeká na restart ───────────────────────────────── @@ -597,18 +608,36 @@ function Get-RemediationCategory { $certsRequiredOk = $cert.KEK.Has2023 -and $cert.DB.Has2023WindowsUEFI $auNow = [int]$reg.AvailableUpdates $errOk = ($null -eq $reg.UEFICA2023Error) -or ([int]$reg.UEFICA2023Error -eq 0) + $bm = $Result.BootManager + $bmOk = ($reg.WindowsUEFICA2023Capable -eq 2) -or ($bm -and $bm.Checked -and $bm.Is2023) - # ── Certy přítomny + AU=0x0 → hotovo ───────────────────────────────────── - # UEFICA2023Status může být NotStarted u strojů kde certy byly nainstalovány - # starší cestou (před zavedením nové servicing infrastruktury). Pokud jsou - # požadované certifikáty v DB/KEK a AvailableUpdates=0x0, považujeme za hotovo. - if ($certsRequiredOk -and $auNow -eq 0 -and $errOk) { + # ── Certy přítomny + AU=0x0 + bootloader ověřen → hotovo ───────────────── + # Poznámka: null AU (chybějící Servicing klíč) se neuznává jako hotovo. + if ($certsRequiredOk -and ($null -ne $reg.AvailableUpdates) -and $auNow -eq 0 -and $errOk -and $bmOk) { + $bmReason = if ($reg.WindowsUEFICA2023Capable -eq 2) { 'Capable=2' } ` + elseif ($bm -and $bm.Checked -and $bm.Is2023) { 'soubor ověřen' } ` + else { '' } + $doneLabel = if ($bmReason) { "Certifikáty aplikovány — bootloader ověřen ($bmReason)" } ` + else { 'Certifikáty aplikovány — AvailableUpdates=0x0' } if ($cert.AnyExpiring2011) { return @{ Code='OK_TRANSITION'; Color='Green' - Label='HOTOVO — 2023 certifikáty nasazeny (staré 2011 ještě přítomné, je to normální)' } + Label="$doneLabel (staré 2011 ještě přítomné)" } } - return @{ Code='OK'; Color='Green' - Label='HOTOVO — 2023 certifikáty nasazeny, AvailableUpdates=0x0' } + return @{ Code='OK'; Color='Green'; Label=$doneLabel } + } + + # ── Certy přítomny, AU=0x0, ale bootloader dosud nepoužívá 2023 CA ─────── + # Systém potřebuje spustit task znovu → staged BM 2023 → restart. + if ($certsRequiredOk -and ($null -ne $reg.AvailableUpdates) -and $auNow -eq 0 -and $errOk -and -not $bmOk) { + $bmNote = if ($bm -and $bm.Checked -and $bm.Is2011) { + "soubor podepsán starší CA: $($bm.IssuerCA)" + } elseif ($bm -and $bm.Error) { + "soubor: $($bm.Error)" + } else { + "Capable=$($reg.WindowsUEFICA2023Capable)" + } + return @{ Code='UPDATE_BOOTMANAGER'; Color='Yellow' + Label="Certifikáty v DB/KEK — bootloader dosud nepoužívá 2023 CA ($bmNote)" } } # ── Starý build: certy přítomny, ale servicing infrastruktura chybí ────── @@ -633,6 +662,187 @@ function Get-RemediationCategory { #endregion +#region ── Detekce bootloaderu ──────────────────────────────────────────────── + +function Get-EspBootmgfwCopy { + # Zkopíruje bootmgfw.efi z aktivního ESP do %TEMP% a vrátí výsledek. + # Logika převzata z referenčního scriptu (mathisokle/SecureBoot-CA2023-Automatic-Update). + # Nikdy nepracuje přímo s live souborem — vždy nejprve kopie. + $out = @{ Success=$false; TempPath=$null; Source=$null; Error=$null } + + $tempDir = Join-Path $env:TEMP 'SecureBootCA2023' + if (-not (Test-Path -LiteralPath $tempDir)) { + New-Item -Path $tempDir -ItemType Directory -Force | Out-Null + } + $dst = Join-Path $tempDir ('bootmgfw_{0}_{1}.efi' -f $env:COMPUTERNAME, ([guid]::NewGuid().ToString('N'))) + + $dl = $EspDriveLetter.ToUpper() + $driveRoot = "${dl}:" + + # Pokus 1: ESP má přiřazené písmeno — prohledat všechna písmena A–Z + foreach ($d in [char[]]('A'..'Z')) { + $p = "${d}:\EFI\Microsoft\Boot\bootmgfw.efi" + if (Test-Path -LiteralPath $p -ErrorAction SilentlyContinue) { + try { + Copy-Item -LiteralPath $p -Destination $dst -Force -ErrorAction Stop + $out.Success = $true; $out.TempPath = $dst; $out.Source = $p + return $out + } catch { $out.Error = $_.Exception.Message } + } + } + + # Pokus 2: mountvol $EspDriveLetter: /S + # Připojí aktivní EFI System Partition na zadané písmeno (default S:). + # Pokud je písmeno obsazeno, změní je parametrem -EspDriveLetter. + if (-not (Get-PSDrive -Name $dl -ErrorAction SilentlyContinue)) { + Add-LogLine ("ESP mount: mountvol ${driveRoot} /S") + & mountvol $driveRoot /S 2>&1 | Out-Null + Start-Sleep -Seconds 2 + } + + if (-not (Test-Path -LiteralPath "${driveRoot}\" -ErrorAction SilentlyContinue)) { + $out.Error = "Nepodařilo se připojit ESP na ${driveRoot} — zkuste jiné písmeno parametrem -EspDriveLetter" + return $out + } + + try { + $src = "${driveRoot}\EFI\Microsoft\Boot\bootmgfw.efi" + if (-not (Test-Path -LiteralPath $src -ErrorAction SilentlyContinue)) { + $out.Error = "bootmgfw.efi nenalezen na ${driveRoot} (ESP připojen, ale soubor chybí)" + return $out + } + Add-LogLine ("ESP copy: ${src} → ${dst}") + Copy-Item -LiteralPath $src -Destination $dst -Force -ErrorAction Stop + $out.Success = $true; $out.TempPath = $dst; $out.Source = $src + } catch { + $out.Error = "Chyba kopírování z ESP: $($_.Exception.Message)" + } finally { + if (Get-PSDrive -Name $dl -ErrorAction SilentlyContinue) { + Add-LogLine ("ESP dismount: mountvol ${driveRoot} /D") + & mountvol $driveRoot /D 2>&1 | Out-Null + } + } + return $out +} + +function Get-BootManagerEvidence { + # Ověří CA verzi Boot Manageru (bootmgfw.efi) třemi vrstvami důkazů: + # 1. certutil -dump — čte embedded PKCS#7 blob přímo z PE, bez local cert store + # 2. X509Chain — záloha pokud certutil chybí (závisí na local store) + # 3. Event ID 1799 — "Boot Manager signed with Windows UEFI CA 2023 was installed" + # + # Vrátí hashtable s: Checked, Is2023, Is2011, CAVersion, IssuerCA, + # SignerSubject, Source, Method, Error + + $out = [ordered]@{ + Checked = $false # podařilo se ověřit? + Is2023 = $false # bootloader podepsán 2023 CA? + Is2011 = $false # bootloader podepsán 2011 CA? + CAVersion = $null # '2023', '2011', nebo $null + IssuerCA = $null # název CA (krátký) + SignerSubject = $null # leaf cert Subject z AuthSig (pro log) + Source = $null # původní cesta na ESP + Method = $null # 'certutil', 'x509chain', 'event1799' + Error = $null # chybová zpráva + } + + # ── Krok 1: Zkopírovat bootmgfw.efi z aktivního ESP ─────────────────────── + $espCopy = Get-EspBootmgfwCopy + if (-not $espCopy.Success) { + $out.Error = $espCopy.Error + } else { + $out.Source = $espCopy.Source + $file = $espCopy.TempPath + try { + # ── Krok 2a: certutil -dump (primární) ──────────────────────────── + $certutil = Get-Command certutil.exe -ErrorAction SilentlyContinue + if ($certutil) { + $raw = & certutil.exe -dump $file 2>&1 | Out-String + $out.Checked = $true + $out.Method = 'certutil' + if ($raw -match 'Windows Production PCA 2023') { + $out.Is2023 = $true; $out.CAVersion = '2023' + $out.IssuerCA = 'Windows Production PCA 2023' + } elseif ($raw -match 'Windows UEFI CA 2023') { + $out.Is2023 = $true; $out.CAVersion = '2023' + $out.IssuerCA = 'Windows UEFI CA 2023' + } elseif ($raw -match 'Windows Production PCA 2011') { + $out.Is2011 = $true; $out.CAVersion = '2011' + $out.IssuerCA = 'Windows Production PCA 2011' + } elseif ($raw -match 'Windows UEFI CA 2011') { + $out.Is2011 = $true; $out.CAVersion = '2011' + $out.IssuerCA = 'Windows UEFI CA 2011' + } + # Pokud certutil nenašel žádnou CA → out.CAVersion zůstane $null + } + + # ── Krok 2b: Get-AuthenticodeSignature ──────────────────────────── + # Vždy — pro SignerSubject do logu. Když certutil nebyl dostupný, + # X509Chain slouží jako záloha pro Is2023/Is2011. + try { + $sig = Get-AuthenticodeSignature -FilePath $file -ErrorAction Stop + if ($sig.SignerCertificate) { + $out.SignerSubject = $sig.SignerCertificate.Subject + if (-not $certutil) { + # X509Chain fallback — závisí na local cert store + $out.Checked = $true; $out.Method = 'x509chain' + $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain + $chain.ChainPolicy.RevocationMode = ` + [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck + $chain.Build($sig.SignerCertificate) | Out-Null + foreach ($el in $chain.ChainElements) { + $s = $el.Certificate.Subject + if ($s -like '*Windows UEFI CA 2023*' -or $s -like '*Windows Production PCA 2023*') { + $out.Is2023 = $true; $out.CAVersion = '2023' + $out.IssuerCA = ($s -replace '^CN=([^,]+).*','$1').Trim() + break + } + } + if (-not $out.Is2023) { + foreach ($el in $chain.ChainElements) { + $s = $el.Certificate.Subject + if ($s -like '*Windows Production PCA 2011*' -or $s -like '*Windows UEFI CA 2011*') { + $out.Is2011 = $true; $out.CAVersion = '2011' + $out.IssuerCA = ($s -replace '^CN=([^,]+).*','$1').Trim() + break + } + } + } + } + } + } catch { } # AuthSig je doplňkový — chybu ignorujeme + + } catch { + $out.Error = $_.Exception.Message + } finally { + Remove-Item $file -Force -ErrorAction SilentlyContinue + } + } + + # ── Krok 3: Event ID 1799 — terciální evidence ──────────────────────────── + # Zpráva "Boot Manager signed with Windows UEFI CA 2023 was installed successfully" + # Používá se jen jako podpora pokud certutil/chain ještě neprokázaly 2023. + if (-not $out.Is2023) { + try { + $ev1799 = Get-WinEvent -FilterHashtable @{ LogName='System'; Id=1799 } ` + -MaxEvents 5 -ErrorAction SilentlyContinue | + Where-Object { $_.Message -match 'Windows UEFI CA 2023.*installed successfully' } | + Select-Object -First 1 + if ($ev1799) { + $out.Is2023 = $true + $out.CAVersion = '2023' + $out.IssuerCA = 'Windows UEFI CA 2023' + $out.Method = if ($out.Method) { "$($out.Method)+event1799" } else { 'event1799' } + $out.Checked = $true + } + } catch { } + } + + return $out +} + +#endregion + #region ── Detekce — orchestrace ────────────────────────────────────────────── function Invoke-Detection { @@ -655,6 +865,7 @@ function Invoke-Detection { OperatingMode = 'Unknown' BitLocker = $null Certificates = $null + BootManager = $null Registry = Get-RegistryStatus EventLog = Get-EventLogStatus TaskExists = Get-TaskExists @@ -666,6 +877,9 @@ function Invoke-Detection { if ($R.SecureBoot.IsUEFI -and $R.SecureBoot.IsSupported) { $R.OperatingMode = Get-SecureBootMode $R.Certificates = Get-CertificateStatus + + # Verifikace Boot Manageru (certutil + X509Chain + Event 1799) + $R.BootManager = Get-BootManagerEvidence } else { $R.Certificates = [ordered]@{ KEK = [ordered]@{ @@ -683,6 +897,11 @@ function Invoke-Detection { DbxRevokesPCA2011 = $false AnyExpiring2011 = $false } + $R.BootManager = @{ + Checked = $false; Is2023 = $false; Is2011 = $false + CAVersion = $null; IssuerCA = $null; SignerSubject = $null + Source = $null; Method = $null; Error = 'Secure Boot nedostupný' + } } $R.BitLocker = Get-BitLockerInfo @@ -720,7 +939,7 @@ function Show-Prerequisites { } function Show-Certificates { - param($Cert) + param($Cert, $BM) Write-Head 'CERTIFIKÁTY' # Najdi expiry datum pro přítomné povinné certifikáty @@ -755,6 +974,34 @@ function Show-Certificates { Add-LogLine ("[ ] $($item.Label) — chybí (volitelný)") } } + + # ── Boot Manager — verze CA z verifikace souboru ───────────────────────── + Write-Host '' + $bmLabel = 'Boot Manager (bootmgfw.efi)' + $bmMethod = if ($BM -and $BM.Method) { " [$($BM.Method)]" } else { '' } + + if ($BM -and $BM.Is2023) { + $detail = "aktuální — CA: $($BM.IssuerCA)$bmMethod" + Write-Host (" [{0}] {1,-44} {2}" -f $SYM_DONE, $bmLabel, $detail) -ForegroundColor Green + Add-LogLine "[+] Boot Manager: $detail" + } elseif ($BM -and $BM.Is2011) { + $detail = "ZASTARALÝ — CA: $($BM.IssuerCA) → nutná aktualizace$bmMethod" + Write-Host (" [{0}] {1,-44} {2}" -f $SYM_FAIL, $bmLabel, $detail) -ForegroundColor Red + Add-LogLine "[!] Boot Manager: $detail" + } elseif ($BM -and $BM.Checked) { + # Ověření proběhlo ale CA nebyla identifikována + $signerShort = if ($BM.SignerSubject) { + ($BM.SignerSubject -replace '^CN=([^,]+).*','$1').Trim() + } else { '?' } + $detail = "CA nezjištěna — signer: $signerShort$bmMethod" + Write-Host (" [{0}] {1,-44} {2}" -f $SYM_PENDING, $bmLabel, $detail) -ForegroundColor DarkGray + Add-LogLine "[ ] Boot Manager: $detail" + } elseif ($BM) { + $errNote = if ($BM.Error) { " ($($BM.Error))" } else { '' } + $detail = "nedostupný$errNote" + Write-Host (" [{0}] {1,-44} {2}" -f $SYM_PENDING, $bmLabel, $detail) -ForegroundColor DarkGray + Add-LogLine "[ ] Boot Manager: $detail" + } } function Show-Registry { @@ -807,15 +1054,20 @@ function Show-Events { function Show-Status { # Zobrazí všechny 4 sekce + celkový stav. + # STAV se nezobrazí, pokud některá hard prerekvizita selhala — ta musí být + # vyřešena dřív, než má kategorie (computed z neúplných dat) jakýkoliv smysl. param($R, $Prereqs) Show-Prerequisites -Prereqs $Prereqs - Show-Certificates -Cert $R.Certificates + Show-Certificates -Cert $R.Certificates -BM $R.BootManager Show-Registry -Reg $R.Registry Show-Events -Evt $R.EventLog - Write-Host '' - Write-Host ' STAV: ' -ForegroundColor Gray -NoNewline - Write-Host $R.CategoryLabel -ForegroundColor $R.CategoryColor - Add-LogLine ("STAV: {0}" -f $R.CategoryLabel) + $anyHardFail = [bool]@($Prereqs | Where-Object { $_.Hard -and -not $_.Ok }).Count + if (-not $anyHardFail) { + Write-Host '' + Write-Host ' STAV: ' -ForegroundColor Gray -NoNewline + Write-Host $R.CategoryLabel -ForegroundColor $R.CategoryColor + Add-LogLine ("STAV: {0}" -f $R.CategoryLabel) + } } function Show-TwoRestartBlock { @@ -1193,8 +1445,8 @@ $cycle = if ($prev) { [int]$prev.Cycle } else { 0 } $certsReady = $result.Certificates.KEK.Has2023 -and $result.Certificates.DB.Has2023WindowsUEFI if ($certsReady -and ($result.Category -like 'OK*') -and -not $Force) { Write-Host '' - Write-Host ' Povinné 2023 certifikáty (Windows UEFI CA 2023 + KEK 2K CA 2023) jsou' -ForegroundColor Green - Write-Host ' již přítomny v UEFI databázích. Na tomto serveru není nutná žádná akce.' -ForegroundColor Green + Write-Host (" {0}" -f $result.CategoryLabel) -ForegroundColor $result.CategoryColor + Write-Host ' Na tomto serveru není nutná žádná remediace.' -ForegroundColor Green Write-Host ' (Tip: parametr -Force vynutí remediaci i v tomto stavu.)' -ForegroundColor DarkGray Write-Host ''; Write-Rule 'Cyan' Write-Host (" Log: {0}" -f $script:LogFile) -ForegroundColor DarkGray @@ -1209,6 +1461,13 @@ if ($certsReady -and $Force) { Write-Host ' ! Certifikáty jsou přítomny, ale -Force umožňuje spustit remediaci znovu.' -ForegroundColor Yellow } +# ── UPDATE_BOOTMANAGER: certy OK, ale bootloader nepoužívá 2023 CA ─────────── +if ($result.Category -eq 'UPDATE_BOOTMANAGER') { + Write-Host '' + Write-Host ' Certifikáty jsou v UEFI databázích, ale Boot Manager dosud nepoužívá 2023 CA.' -ForegroundColor Yellow + Write-Host ' Remediace spustí task, který Boot Manager 2023 připraví — poté bude nutný restart.' -ForegroundColor Gray +} + # ── AU=0x4100: Boot Manager staged, stačí RESTART ──────────────────────────── $auNow = [int]$result.Registry.AvailableUpdates if ($auNow -eq 0x4100) {