Tests/VSAModule.Persistence.Tests.ps1
|
# Persistent-connection encryption is platform-sensitive (F-60): ConvertTo-/ConvertFrom-SecureString # with no -Key is Windows DPAPI, but on Linux/macOS (no DPAPI) that same no-key form "succeeds" while # being trivially reversible -- obfuscation, not encryption. The fix selects a protect/unprotect # strategy once at module load: DPAPI on Windows, AES with a runtime-derived, never-stored key # elsewhere. This mirrors the F-27 cert-bypass load-time strategy-selection pattern. # Computed during Discovery, independently of the module, so the -Skip conditions below resolve to # the running platform's path even before Import-Module has run. $script:IsWindowsPlatform = if (Test-Path -Path Variable:IsWindows) { $IsWindows } else { $true } BeforeAll { $script:ModuleRoot = Split-Path -Path (Split-Path -Parent $PSCommandPath) Import-Module (Join-Path $script:ModuleRoot 'VSAModule.psd1') -Force } Describe "Persistent-connection encryption strategy is platform-detected (F-60)" { AfterEach { [Environment]::SetEnvironmentVariable('VSAConnection', $null) } Context "source guarantees (platform-independent)" { It "detects the platform once at import via a script-scope flag" { $psm1 = Get-Content -Path (Join-Path $script:ModuleRoot 'VSAModule.psm1') -Raw $psm1 | Should -Match '\$script:VSAIsWindows\s*=' } It "the Windows branch uses ConvertTo-/ConvertFrom-SecureString with no -Key (DPAPI)" { $psm1 = Get-Content -Path (Join-Path $script:ModuleRoot 'VSAModule.psm1') -Raw $psm1 | Should -Match 'ConvertFrom-SecureString\s+-SecureString\s+\$secureString\s*\)' } It "the non-Windows branch uses ConvertTo-/ConvertFrom-SecureString with a derived -Key" { $psm1 = Get-Content -Path (Join-Path $script:ModuleRoot 'VSAModule.psm1') -Raw $psm1 | Should -Match 'ConvertFrom-SecureString\s+-SecureString\s+\$secureString\s+-Key\s+\$key' $psm1 | Should -Match 'ConvertTo-SecureString\s+-String\s+\$Protected\s+-Key\s+\$key' } It "the derived key is never persisted (no Set-Content/env-var write of the key material)" { $psm1 = Get-Content -Path (Join-Path $script:ModuleRoot 'VSAModule.psm1') -Raw $psm1 | Should -Not -Match 'SetEnvironmentVariable\([^)]*\$key' $psm1 | Should -Not -Match 'Set-Content[^\n]*\$key' } } Context "load-time selection" { It "exposes the two strategy scriptblocks" { InModuleScope VSAModule { $script:VSAProtectData | Should -BeOfType ([scriptblock]) $script:VSAUnprotectData | Should -BeOfType ([scriptblock]) } } } Context "protect/unprotect round-trip on the current platform" { It "Protect-VSAConnectionData / Unprotect-VSAConnectionData round-trip a sample serial blob" { InModuleScope VSAModule { $serial = @('https://vsa.example.com', 'TOK123', 'PAT456', 'alice', '2030-01-01T00:00:00.0000000Z', 'False', 'True') -join "`t" $plainB64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($serial)) $protected = Protect-VSAConnectionData -Data $plainB64 $protected | Should -Not -BeNullOrEmpty $roundTripped = Unprotect-VSAConnectionData -EncryptedData $protected $roundTripped | Should -Be $plainB64 } } It "Set-VSAPersistentConnection / Get-VSAPersistent* round-trip a full VSAConnection" { InModuleScope VSAModule { $c = [VSAConnection]::new('https://vsa.example.com', 'alice', 'TOK123', 'PAT456', [datetime]'2030-01-01T00:00:00Z', $true, $true) Set-VSAPersistentConnection -Connection $c Get-VSAPersistentURI | Should -Be 'https://vsa.example.com' Get-VSAPersistentUserName | Should -Be 'alice' Get-VSAPersistentToken | Should -Be 'TOK123' Get-VSAPersistentPAT | Should -Be 'PAT456' Get-VSAPersistentIgnoreCertErrors | Should -BeTrue } } } Context "Windows / DPAPI behavior" -Skip:(-not $script:IsWindowsPlatform) { It "the persisted blob decrypts with the platform's no-key ConvertFrom-SecureString (DPAPI)" { InModuleScope VSAModule { $c = [VSAConnection]::new('https://vsa.example.com', 'alice', 'TOK123', 'PAT456', [datetime]'2030-01-01T00:00:00Z', $false, $true) Set-VSAPersistentConnection -Connection $c $raw = [Environment]::GetEnvironmentVariable('VSAConnection') { ConvertTo-SecureString -String $raw -ErrorAction Stop } | Should -Not -Throw } } } Context "Linux/macOS / derived-key AES behavior" -Skip:$script:IsWindowsPlatform { It "the derived key is 32 bytes and re-derives identically across calls" { InModuleScope VSAModule { $k1 = & $script:VSADeriveLocalKey $k2 = & $script:VSADeriveLocalKey $k1.Length | Should -Be 32 [System.BitConverter]::ToString($k1) | Should -Be ([System.BitConverter]::ToString($k2)) } } It "the persisted blob does NOT decrypt via a plain no-key ConvertTo-SecureString" { InModuleScope VSAModule { $c = [VSAConnection]::new('https://vsa.example.com', 'alice', 'TOK123', 'PAT456', [datetime]'2030-01-01T00:00:00Z', $false, $true) Set-VSAPersistentConnection -Connection $c $raw = [Environment]::GetEnvironmentVariable('VSAConnection') { ConvertTo-SecureString -String $raw -ErrorAction Stop } | Should -Throw } } } } |