feat(welcome): SilverOS Welcome first-logon wizard (flavour engine + apply orchestrator + MAUI UI + image bake) #4
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user