From a47345887ca81af78464f4df0db398d271623884 Mon Sep 17 00:00:00 2001 From: sysadmin Date: Tue, 9 Jun 2026 11:15:13 +0100 Subject: [PATCH] =?UTF-8?q?fix(welcome):=20enforce=20BitLocker=20TPM+PIN?= =?UTF-8?q?=20=E2=80=94=20set=20FVE=20startup-PIN=20policy,=20add=20protec?= =?UTF-8?q?tor=20if=20auto-DE=20pre-encrypted,=20strip=20TPM-only=20protec?= =?UTF-8?q?tor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Apply/BitLockerService.cs | 40 +++++++++++++++++-- .../ApplyServicesTests.cs | 18 +++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs index 37edef2..3cb61ae 100644 --- a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs +++ b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs @@ -1,8 +1,40 @@ namespace SilverOS.Welcome.Core.Apply; public sealed class BitLockerService(IProcessRunner runner) : IBitLockerService { - public Task EnableAsync(string pin, CancellationToken ct = default) => - runner.RunAsync("powershell.exe", - $"-NoProfile -ExecutionPolicy Bypass -Command \"$p=ConvertTo-SecureString '{pin.Replace("'", "''")}' -AsPlainText -Force; " + - "Enable-BitLocker -MountPoint $env:SystemDrive -EncryptionMethod XtsAes256 -TpmAndPinProtector -Pin $p -SkipHardwareTest\"", ct); + public Task EnableAsync(string pin, CancellationToken ct = default) + { + var p = pin.Replace("'", "''"); + + // 1. Set the FVE "Require additional authentication at startup" policy so the + // TPM+PIN protector is permitted. Without this, Enable-BitLocker -TpmAndPinProtector + // silently degrades to TPM-only (boots with no PIN prompt). + // 2. Add the TPM+PIN protector — Enable-BitLocker if the volume is still decrypted, + // or Add-BitLockerKeyProtector if Windows automatic device-encryption already + // encrypted it with a TPM-only protector. + // 3. Remove any TPM-only protector (only once a TPM+PIN protector is confirmed present) + // so the device actually requires the PIN at pre-boot. + var script = string.Concat( + "$fve='HKLM:\\SOFTWARE\\Policies\\Microsoft\\FVE'; ", + "New-Item -Path $fve -Force | Out-Null; ", + "New-ItemProperty -Path $fve -Name UseAdvancedStartup -Value 1 -PropertyType DWord -Force | Out-Null; ", + "New-ItemProperty -Path $fve -Name EnableBDEWithNoTPM -Value 0 -PropertyType DWord -Force | Out-Null; ", + "New-ItemProperty -Path $fve -Name UseTPM -Value 2 -PropertyType DWord -Force | Out-Null; ", + "New-ItemProperty -Path $fve -Name UseTPMPIN -Value 2 -PropertyType DWord -Force | Out-Null; ", + "New-ItemProperty -Path $fve -Name UseTPMKey -Value 2 -PropertyType DWord -Force | Out-Null; ", + "New-ItemProperty -Path $fve -Name UseTPMKeyPIN -Value 2 -PropertyType DWord -Force | Out-Null; ", + "$mp=$env:SystemDrive; ", + "$p=ConvertTo-SecureString '", p, "' -AsPlainText -Force; ", + "$v=Get-BitLockerVolume -MountPoint $mp; ", + "if ($v.VolumeStatus -eq 'FullyDecrypted') { ", + "Enable-BitLocker -MountPoint $mp -EncryptionMethod XtsAes256 -TpmAndPinProtector -Pin $p -SkipHardwareTest } ", + "elseif (-not ($v.KeyProtector | Where-Object { $_.KeyProtectorType -eq 'TpmPin' })) { ", + "Add-BitLockerKeyProtector -MountPoint $mp -TpmAndPinProtector -Pin $p }; ", + "$kp=(Get-BitLockerVolume -MountPoint $mp).KeyProtector; ", + "if ($kp | Where-Object { $_.KeyProtectorType -eq 'TpmPin' }) { ", + "$kp | Where-Object { $_.KeyProtectorType -eq 'Tpm' } | ForEach-Object { ", + "Remove-BitLockerKeyProtector -MountPoint $mp -KeyProtectorId $_.KeyProtectorId | Out-Null } }"); + + return runner.RunAsync("powershell.exe", + $"-NoProfile -ExecutionPolicy Bypass -Command \"{script}\"", ct); + } } diff --git a/windows/welcome/tests/SilverOS.Welcome.Tests/ApplyServicesTests.cs b/windows/welcome/tests/SilverOS.Welcome.Tests/ApplyServicesTests.cs index 6ee446a..2a19434 100644 --- a/windows/welcome/tests/SilverOS.Welcome.Tests/ApplyServicesTests.cs +++ b/windows/welcome/tests/SilverOS.Welcome.Tests/ApplyServicesTests.cs @@ -36,6 +36,24 @@ public class ApplyServicesTests s.Contains("Enable-BitLocker") && s.Contains("TpmAndPinProtector")), It.IsAny())); } + [Fact] + public async Task BitLockerService_sets_fve_pin_policy_and_strips_tpm_only_protector() + { + var run = Ok(); + await new BitLockerService(run.Object).EnableAsync("123456"); + // Sets the FVE "require additional authentication at startup" policy so the + // TPM+PIN protector actually applies (otherwise it silently degrades to TPM-only). + run.Verify(r => r.RunAsync("powershell.exe", It.Is(s => + s.Contains("UseAdvancedStartup") && s.Contains("UseTPMPIN")), It.IsAny())); + // Handles a volume already encrypted by Windows auto-device-encryption (TPM-only) + // by adding the TPM+PIN protector instead of failing. + run.Verify(r => r.RunAsync("powershell.exe", It.Is(s => + s.Contains("Add-BitLockerKeyProtector")), It.IsAny())); + // Removes any TPM-only protector so the device requires the PIN at pre-boot. + run.Verify(r => r.RunAsync("powershell.exe", It.Is(s => + s.Contains("Remove-BitLockerKeyProtector")), It.IsAny())); + } + [Fact] public async Task BootstrapService_removes_autologon_and_account() {