SecretManagement.Chromium.Extension/Public/Test-SecretVault.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
using namespace 'System.Security.Cryptography'

function Test-SecretVault {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName,Mandatory)]
        [string]$VaultName,

        [Parameter(ValueFromPipelineByPropertyName)]
        [hashtable]$AdditionalParameters = (Get-Secretvault $VaultName).VaultParameters #This intelligent default is here because if you call test-secretvault from other commands it doesn't populate like it does when called from SecretManagement
    )

    Write-Verbose "SecretManagement: Testing Vault ${VaultName}"

    #Basic Sanity Checks
    if (-not $VaultName) { throw 'You must specify a Vault Name to test' }

    if (-not $AdditionalParameters.DataPath) {
        #TODO: Interactive vault selection with Out-ConsoleGridView
        throw "Vault ${VaultName}: No vaults autodetected. You must specify the Path vault parameter as a path to your Chromium Database. Hint for Chrome: `$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Login Data"
    }
    if (-not $AdditionalParameters.StatePath) {
        try {
            $candidateStatePath = Join-Path $AdditionalParameters.DataPath "../../Local State" -Resolve -ErrorAction stop
            Write-Verbose "Autodetected Local State file at $candidateStatePath"
            $AdditionalParameters.StatePath = $candidateStatePath
        } catch {
            throw "Vault ${VaultName}: You must specify the StatePath parameter as a path to your Chromium Database. Hint for Chrome: `$env:LOCALAPPDATA\Google\Chrome\User Data\Local State"
        }
    }
    
    $dbFile = Get-Item $AdditionalParameters.DataPath -ErrorAction Stop
    $tempDBFile = Join-Path ([io.path]::GetTempPath()) "ChromeVault-$PID-$VaultName.dbcache"
    if ((Test-Path $tempDBFile) -and $dbFile.LastWriteTime -eq (Get-Item $tempDBFile).LastWriteTime -and $dbFile.Length -eq (Get-Item $tempDBFile).Length) {
        Write-Debug "${VaultName}: Temp DB $tempDBFile is still a valid cache"
    } else {
        #Make a copy because Chromium locks the DB file at the SQLite level and this will freeze the module trying to open it
        Write-Debug "${VaultName}: Source DB has been updated, copying to $tempDBFile"
        $tempDB = Copy-Item -ErrorAction Stop -Path $dbFile -Destination $tempDBFile -PassThru
        $tempDB.LastWriteTime = $dbFile.LastWriteTime
    }
    

    $db = ReallySimpleDatabase\Get-Database -Path $tempDBFile -WarningAction SilentlyContinue
    try {
        $db.open()
        #Loose check if it is a chromium database
        #TODO: Check table schema
        if (-not $db.getTable('logins')) {
            throw "$tempDBFile is not a valid Chromium password database (Logins table not found)"
            $db.close()
            Remove-Item $tempDBFile
        }
        $SCRIPT:__VAULT[$VaultName] = $db
    } catch {
        throw
    } finally {
        $db.close()
    }
    
    #Extract the local state encryption key if present
    if ($AdditionalParameters.StatePath) {
        $localStateInfo = Get-Content -Raw $AdditionalParameters.StatePath | ConvertFrom-Json 
        if ($localStateInfo) { 
            $encryptedkey = [convert]::FromBase64String($localStateInfo.os_crypt.encrypted_key) 
        }
        if ($encryptedkey -and [string]::new($encryptedkey[0..4]) -eq 'DPAPI') {
            # Not present in Windows PowerShell 5, nor in PS Core V6
            if ($PSVersionTable.PSVersion -lt '7.0.0') {
                throw [NotSupportedException]'Chromium v80 or later AES-encrypted passwords were detected, currently we cannot decrypt these with Windows Powershell or PS6. Please use Powershell 7'
            }
            $masterKey = [ProtectedData]::Unprotect(($encryptedkey | Select-Object -Skip 5),  $null, 'CurrentUser')
            $SCRIPT:__VAULT["$VaultName-Key"] = [AesGcm]::new($masterKey)
        } else { Write-Warning 'Could not get key for new-style encyption. Will try with older Style' }
    }

    return $true
}