SecretManagement.KeePass.Extension/Public/Connect-KeepassDatabase.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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
using namespace KeePassLib
using namespace KeePassLib.Keys
using namespace KeePassLib.Serialization
using namespace KeePassLib.Interfaces
using namespace System.Runtime.InteropServices

function Connect-KeePassDatabase {
    <#
    .SYNOPSIS
    Open a connection to a keepass database
    #>

    param (
        #Path to the Keepass database
        [Parameter(Mandatory)][String]$Path,
        #Prompt for a master password
        [Switch]$UseMasterPassword,
        #The master password to unlock the database
        [SecureString]$MasterPassword,
        #The path to the key file for the database
        [String]$KeyPath,
        #Whether to use a secure key stored via DPAPI in your windows profile
        [Switch]$UseWindowsAccount,
        #Create a new database at the specified path. Will error if a database does not exist at the specified path
        [Switch]$Create,
        #Allow clobbering an existing database
        [Switch]$AllowClobber
    )

    $DBCompositeKey = [CompositeKey]::new()

    if (-not $MasterPassword -and -not $KeyPath -and -not $UseWindowsAccount) {
        Write-Verbose "No vault authentication mechanisms specified. Assuming you wanted to prompt for the Master Password"
        $UseMasterPassword = $true
    }

    if ($UseMasterPassword -and -not $MasterPassword) {
        $CredentialParams = @{
            Username = 'Keepass Master Password'
            Message = "Enter the Keepass Master password for: $Path"
        }
        #PS7+ Only
        if ($PSEdition -ne 'Desktop') {
            $CredentialParams.Title = 'Keepass Master Password'
        }
        $MasterPassword = (Get-Credential @CredentialParams).Password
    }

    #NOTE: Order in which the CompositeKey is created is important and must follow the order of : MasterKey, KeyFile, Windows Account
    if ($MasterPassword) {
        $DBCompositeKey.AddUserKey(
            [KcpPassword]::new(
                #Decode SecureString
                [Marshal]::PtrToStringUni([Marshal]::SecureStringToBSTR($MasterPassword))
            )
        )
    }

    if ($KeyPath) {

        if (-not (Test-Path $KeyPath)) {
            if ($Create) {
                #Create a new key
                [KcpKeyFile]::Create(
                    $KeyPath, 
                    $null
                )
            } else {
                #Will emit a path not found error
                Resolve-Path $KeyPath
            }
        } else {
            Write-Verbose "A keepass key file was already found at $KeyPath. Reusing this key for safety. Please manually delete this key if you wish to use a new one"
        }

        $resolvedKeyPath = Resolve-Path $KeyPath
        # Assume UNC path if no drive present.
        if ($Null -eq $resolvedKeyPath.Drive) {
            $dbCompositeKey.AddUserKey(
                [KcpKeyFile]::new(
                    $resolvedKeyPath.ProviderPath, #Path to keyfile
                    $true #Error if it is a database file
                )
            )
        } else {
            $dbCompositeKey.AddUserKey(
                [KcpKeyFile]::new(
                    $resolvedKeyPath, #Path to keyfile
                    $true #Error if it is a database file
                )
            )
        }
    }

    if ($UseWindowsAccount) {
        if ($PSVersionTable.PSVersion -gt '5.99.99' -and -not $IsWindows) {
            throw [NotSupportedException]'The -UseWindowsAccount parameter is only supported on a Windows Platform'
        }
        $DBCompositeKey.AddUserKey([KcpUserAccount]::new())
    }

    $ParentPath = (Resolve-Path ($Path | Split-Path)).ProviderPath
    $DBFile = $Path | Split-Path -Leaf
    $resolvedPath = Join-Path -Path $ParentPath -ChildPath $DBFile

    $DBConnection = [PWDatabase]::new()
    $DBConnectionInfo = [IOConnectionInfo]::FromPath($resolvedPath)

    if ($Create) {
        if (-not $AllowClobber -and (Test-Path $resolvedPath)) {
            throw "-Create was specified but a database already exists at $resolvedPath. Please specify -AllowClobber to overwrite the database."
        }
        $DBConnection.New(
            $DBConnectionInfo,
            $DBCompositeKey
        )
        $DBConnection.Save($null)
    }

    #Establish the connection

    $DBConnection.Open(
        $DBConnectionInfo,
        $DBCompositeKey,
        $null #No status logger
    )
    if (-not $DBConnection.IsOpen) { throw "Unable to connect to the database at $resolvedPath. Please check you supplied proper credentials" }
    $DBConnection
}