fix(welcome): enforce BitLocker TPM+PIN — set FVE startup-PIN policy, add protector if auto-DE pre-encrypted, strip TPM-only protector
All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (pull_request) Successful in 4m31s

This commit is contained in:
sysadmin
2026-06-09 11:15:13 +01:00
parent 4f3e25e816
commit a47345887c
2 changed files with 54 additions and 4 deletions

View File

@@ -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);
}
}

View File

@@ -36,6 +36,24 @@ public class ApplyServicesTests
s.Contains("Enable-BitLocker") && s.Contains("TpmAndPinProtector")), It.IsAny<CancellationToken>()));
}
[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<string>(s =>
s.Contains("UseAdvancedStartup") && s.Contains("UseTPMPIN")), It.IsAny<CancellationToken>()));
// 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<string>(s =>
s.Contains("Add-BitLockerKeyProtector")), It.IsAny<CancellationToken>()));
// Removes any TPM-only protector so the device requires the PIN at pre-boot.
run.Verify(r => r.RunAsync("powershell.exe", It.Is<string>(s =>
s.Contains("Remove-BitLockerKeyProtector")), It.IsAny<CancellationToken>()));
}
[Fact]
public async Task BootstrapService_removes_autologon_and_account()
{