diff --git a/windows/welcome/src/SilverOS.Welcome.App/wwwroot/css/app.css b/windows/welcome/src/SilverOS.Welcome.App/wwwroot/css/app.css index 555a055..13309f1 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/wwwroot/css/app.css +++ b/windows/welcome/src/SilverOS.Welcome.App/wwwroot/css/app.css @@ -877,3 +877,27 @@ h1:focus { outline: none; } padding-left: env(safe-area-inset-left); } } + +/* ── BitLocker recovery key (Done step) ─────────────────────────────── */ +.recovery-panel { + margin: 1.25rem 0; + padding: 1rem 1.25rem; + border: 1px solid var(--clr-accent); + border-radius: var(--radius-sm, 8px); + background: var(--clr-accent-glow, rgba(0,212,255,0.10)); +} +.recovery-panel h3 { margin: 0 0 0.5rem; color: var(--clr-accent); font-family: var(--font-mono); } +.recovery-key { + font-family: var(--font-mono); + font-size: 1.05rem; + letter-spacing: 0.04em; + color: var(--clr-text-hi); + background: rgba(0,0,0,0.30); + padding: 0.75rem 1rem; + border-radius: var(--radius-sm, 8px); + white-space: pre-wrap; + word-break: break-all; + user-select: all; + margin: 0.5rem 0; +} +.recovery-note { color: var(--clr-text-lo); } diff --git a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs index fb6792c..f47dc8c 100644 --- a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs +++ b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BitLockerService.cs @@ -31,7 +31,12 @@ public sealed class BitLockerService(IProcessRunner runner) : IBitLockerService "$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 } ", + // NO -SkipHardwareTest: let BitLocker run its hardware test on the next reboot so it + // VALIDATES the TPM+PIN unseal against the real boot measurements before encrypting. + // -SkipHardwareTest seals immediately against possibly-wrong PCRs -> drops to recovery + // on first boot (E_FVE_SECURE_BOOT_CHANGED, PCR 11). The wizard's end-of-flow reboot + // is that validation pass, so the PIN works on first boot instead of bouncing. + "Enable-BitLocker -MountPoint $mp -EncryptionMethod XtsAes256 -TpmAndPinProtector -Pin $p } ", "elseif (-not ($v.KeyProtector | Where-Object { $_.KeyProtectorType -eq 'TpmPin' })) { ", "Add-BitLockerKeyProtector -MountPoint $mp -TpmAndPinProtector -Pin $p }; ", "$kp=(Get-BitLockerVolume -MountPoint $mp).KeyProtector; ", diff --git a/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor b/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor index 082ca0f..f4bb24c 100644 --- a/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor +++ b/windows/welcome/src/SilverOS.Welcome.UI/Components/Routes.razor @@ -37,7 +37,7 @@ break; case 1: - + break; case 2: diff --git a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/DoneStep.razor b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/DoneStep.razor index a42f027..35ed602 100644 --- a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/DoneStep.razor +++ b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/DoneStep.razor @@ -2,11 +2,40 @@

All Done!

-

Your SilverOS device is configured and ready. Click below to restart and start using it.

+

Your SilverMetal device is configured and ready.

+ + @if (!string.IsNullOrWhiteSpace(_recoveryKey)) + { +
+

⚠ Save your BitLocker recovery key

+

+ This is the only way back into your drive if you ever forget your PIN. + Write it down or photograph it now and keep it somewhere safe and separate from this device. +

+
@_recoveryKey
+

Also saved to C:\ProgramData\SilverMetal\bitlocker-recovery.txt on this device.

+
+ } + +

Click below to restart and start using it.

@code { + private string? _recoveryKey; + + protected override void OnInitialized() + { + // The BitLocker step saved the 48-digit recovery key to ProgramData; surface it + // here so the user records it before finishing (TPM+PIN alone is unrecoverable). + try + { + const string path = @"C:\ProgramData\SilverMetal\bitlocker-recovery.txt"; + if (File.Exists(path)) _recoveryKey = File.ReadAllText(path).Trim(); + } + catch { /* best-effort display */ } + } + private async Task RestartNow() { await ProcessRunner.RunAsync("cmd.exe", "/c shutdown /r /t 5", CancellationToken.None); diff --git a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/FlavourStep.razor b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/FlavourStep.razor index 18a893f..7892d3a 100644 --- a/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/FlavourStep.razor +++ b/windows/welcome/src/SilverOS.Welcome.UI/Components/Steps/FlavourStep.razor @@ -19,13 +19,19 @@ @code { [Parameter] public IReadOnlyList Flavours { get; set; } = Array.Empty(); - protected override void OnInitialized() + /// Notifies the wizard host when the selection changes so it re-evaluates + /// the Next button (otherwise Next stays disabled until a back/forward re-render). + [Parameter] public EventCallback OnSelected { get; set; } + + protected override async Task OnInitializedAsync() { State.Flavour ??= Flavours.FirstOrDefault(f => f.IsDefault); + await OnSelected.InvokeAsync(); } - void Select(FlavourManifest f) + async Task Select(FlavourManifest f) { State.Flavour = f; + await OnSelected.InvokeAsync(); } }