#Requires -Version 5.1 $ErrorActionPreference = 'Stop' . (Join-Path $PSScriptRoot 'Initialize-SecurityEnv.ps1') $temp = Join-Path ([System.IO.Path]::GetTempPath()) ('easyai-key-recovery-' + [Guid]::NewGuid().ToString('N')) [System.IO.Directory]::CreateDirectory($temp) | Out-Null $envFile = Join-Path $temp '.env'; $keyFile = Join-Path $temp 'keys.env'; $logFile = Join-Path $temp 'result.log' function Assert-KeyTest([bool]$Value, [string]$Label) { if (-not $Value) { throw "FAILED: $Label" } } try { [System.IO.File]::WriteAllText($envFile, '') Initialize-SecurityEnv -Path $envFile -Mode new *> $logFile $original = Get-Content $envFile -Raw $ring = Get-EnvValue $original 'CONFIG_CREDENTIAL_ENCRYPTION_KEYRING' $second = [Convert]::FromBase64String((New-SecuritySecret)) $second = [Convert]::ToBase64String($second[0..31]) $multi = $ring.Substring(0, $ring.Length - 1) + ',"v2":"' + $second + '"}' $content = Set-EnvValue $original 'CONFIG_CREDENTIAL_ENCRYPTION_KEYRING' ("'" + $multi + "'") $content = Set-EnvValue $content 'CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID' 'v2' $content = Set-EnvValue $content 'UNRELATED_CONFIG' 'keep-me' [System.IO.File]::WriteAllText($envFile, $content) & (Join-Path $PSScriptRoot 'Manage-CredentialKeys.ps1') -Mode export -Path $keyFile -EnvPath $envFile *>> $logFile Assert-KeyTest ((Get-EnvValue (Get-Content $keyFile -Raw) 'CONFIG_CREDENTIAL_ENCRYPTION_KEYRING') -ceq $multi) 'full ring exported' $refused = $false try { & (Join-Path $PSScriptRoot 'Manage-CredentialKeys.ps1') -Mode export -Path $keyFile -EnvPath $envFile *>> $logFile } catch { $refused = $true } Assert-KeyTest $refused 'existing backup never overwritten' [System.IO.File]::WriteAllText($envFile, (Set-EnvValue $original 'UNRELATED_CONFIG' 'keep-me')) & (Join-Path $PSScriptRoot 'Manage-CredentialKeys.ps1') -Mode restore -Path $keyFile -EnvPath $envFile *>> $logFile $restored = Get-Content $envFile -Raw foreach ($name in @('CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY','CONFIG_JWT_SECRET')) { Assert-KeyTest ((Get-EnvValue $restored $name) -ceq (Get-EnvValue $original $name)) 'original config master and JWT retained' } Assert-KeyTest ((Get-EnvValue $restored 'CONFIG_CREDENTIAL_ENCRYPTION_KEYRING') -ceq $multi) 'both original keys restored' Assert-KeyTest ((Get-EnvValue $restored 'CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID') -ceq 'v2') 'active key restored' Assert-KeyTest ((Get-EnvValue $restored 'UNRELATED_CONFIG') -ceq 'keep-me') 'unrelated config preserved' $backups = @(Get-ChildItem $temp -Force -Filter '.env.pre-credential-restore.*') Assert-KeyTest ($backups.Count -eq 1) 'previous env saved' $bad = Join-Path $temp 'bad.env' [System.IO.File]::WriteAllText($bad, "CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID=v2`nCONFIG_CREDENTIAL_ENCRYPTION_KEYRING='{}'`n") $refused = $false try { & (Join-Path $PSScriptRoot 'Manage-CredentialKeys.ps1') -Mode restore -Path $bad -EnvPath $envFile *>> $logFile } catch { $refused = $true } Assert-KeyTest $refused 'invalid recovery refused' Assert-KeyTest ((Get-Content $envFile -Raw) -ceq $restored) 'invalid recovery never mutates env' Assert-KeyTest (-not (Get-Content $logFile -Raw).Contains($second)) 'no key leaked to log' if ($env:OS -eq 'Windows_NT') { $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value foreach ($file in @($envFile, $keyFile, $backups[0].FullName)) { $acl = Get-Acl -LiteralPath $file Assert-KeyTest $acl.AreAccessRulesProtected 'private DACL' foreach ($rule in $acl.Access) { Assert-KeyTest ($rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -eq $sid) 'only current user access' } } } Write-Host 'Credential key backup/restore self-test passed.' } finally { Remove-Item -LiteralPath $temp -Recurse -Force }