From 3f1ea6aa63b7b4205c0ecfa3b83cc1533e51c9b1 Mon Sep 17 00:00:00 2001 From: sysadmin Date: Tue, 9 Jun 2026 20:15:49 +0100 Subject: [PATCH] fix(bitlocker): add recovery-password protector + save the key (was unrecoverable) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VM e2e: full wizard ran end-to-end and enrolled TPM+PIN, but BitLockerService only created TPM+PIN with NO recovery protector — a forgotten/mistyped PIN bricks the drive (hit exactly that on the VM). Add a RecoveryPassword protector and save the 48-digit key to ProgramData AND the unencrypted EFI System Partition (readable even when the OS volume is locked, e.g. for offline recovery/verification). PRODUCT TODO (follow-up): escrow the recovery key to SilverSync + display it in the wizard's Done step so the end-user records it. Co-Authored-By: Claude Opus 4.8 --- .../SilverOS.Welcome.Core/Apply/BitLockerService.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs index 2551ccd..fb6792c 100644 --- a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs +++ b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs @@ -38,6 +38,16 @@ public sealed class BitLockerService(IProcessRunner runner) : IBitLockerService "if ($kp | Where-Object { $_.KeyProtectorType -eq 'TpmPin' }) { ", "$kp | Where-Object { $_.KeyProtectorType -eq 'Tpm' } | ForEach-Object { ", "Remove-BitLockerKeyProtector -MountPoint $mp -KeyProtectorId $_.KeyProtectorId | Out-Null } }; ", + // 4. Add a RECOVERY-PASSWORD protector so a forgotten/mistyped PIN is recoverable — + // TPM+PIN alone is an unrecoverable brick. Save the 48-digit key to ProgramData + // AND to the unencrypted EFI System Partition (readable even when the OS volume is + // locked). PRODUCT TODO: escrow to SilverSync + show it in the wizard's Done step. + "if (-not ((Get-BitLockerVolume -MountPoint $mp).KeyProtector | Where-Object { $_.KeyProtectorType -eq 'RecoveryPassword' })) { ", + "Add-BitLockerKeyProtector -MountPoint $mp -RecoveryPasswordProtector | Out-Null }; ", + "$rp=((Get-BitLockerVolume -MountPoint $mp).KeyProtector | Where-Object { $_.KeyProtectorType -eq 'RecoveryPassword' } | Select-Object -First 1).RecoveryPassword; ", + "if ($rp) { New-Item -ItemType Directory -Force 'C:\\ProgramData\\SilverMetal' | Out-Null; ", + "Set-Content -Path 'C:\\ProgramData\\SilverMetal\\bitlocker-recovery.txt' -Value $rp; ", + "try { mountvol Q: /S; Set-Content -Path 'Q:\\SilverMetal-Recovery.txt' -Value $rp; mountvol Q: /D } catch {} }; ", // Outcome check: fail loudly (non-zero exit) if a TPM+PIN protector is not present at // the end — this is what actually matters (a benign non-terminating warning alone // must not pass, and a real failure must not stay silent).