Jump to content

Import KeePass into Private Lists


support

Recommended Posts

Purpose:

Passwordstate currently has a built in feature to import KeePass data into a folder structure with shared lists, but currently it's not possible to import into Private Lists within the User Interface.  This script below will allow you to import into Private Lists for a single user.

 

Note:

This script is using the Windows API, so you must be logged into a Windows machine, with a domain account that has a login into Passwordstate.  All Passwords Lists will be created as Private lists, and only that user will see the imported Folder and Password Lists.

 

Requirements:

You will need to create a Password List Template with the settings below.  Take note of the Password List Template ID when creating this List.

2023-12-14_10-49-24.png

 

2023-12-14_10-51-00.png

 

2023-12-14_10-51-08.png

 

How to Run the Script:

In a section below is the entire contents of the script you'll need to open in Powershell ISE.  You'll need to modify the top three lines of the script with the relevant information to your environment, Passwordstate URL, path to KeePass XML file, and the Password List Template ID for the template you created above:

 

2023-12-14_11-21-57.png

 

 

Results:

After running the script, you will see a new folder in the root of Passwords Home, which will be named after the exact name of your KeePass database.  All nested Lists will be Private, and only the user who ran the script will see this folder structure and nested Password Lists/Passwords.

2023-12-14_10-53-24.png

 

Powershell Script Contents:

 

$global:PasswordstateURL = "https://support.clickdemo.com"
$KeePassFile = "C:\Data\Import\Keepass\Database.xml"
$global:PasswordstateTemplateID = "79"


add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::TLS12


switch -regex ($global:PasswordstateLinkToTemplate)
{
    "(Y(e|es)?|True)" {$global:PasswordstateLinkToTemplate = $true; break}
    default {$global:PasswordstateLinkToTemplate = $false; break}
}
switch -regex ($global:PasswordstateTakePermsFromTemplate)
{
    "(Y(e|es)?|True)" {$global:PasswordstateTakePermsFromTemplate = $true;break}
    default {
        $global:PasswordstateTakePermsFromTemplate = $false
        break
    }
}

#region Passwordstate Functions
Function CreatePasswordstateFolder() {
    Param ( 
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Name,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Description,
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$ParentFolderID       
    ) 
 
    Begin { 
    
    } 
 
    Process { 
        $Body = @{
            FolderName = $Name
            Description = $Description
            NestUnderFolderID = $ParentFolderID
        }

        $jsonBody = $Body | ConvertTo-Json

        $PasswordstateURLFull = "$($global:PasswordstateURL)/winapi/folders"
        $result = Invoke-Restmethod -Method POST -Uri $PasswordstateURLFull -ContentType "application/json" -Body ([System.Text.Encoding]::UTF8.GetBytes($jsonBody)) -UseDefaultCredentials
        $output = $result.FolderID
    }

    End {
        Write-Output $output
    }

}

Function CreatePasswordstatePasswordlist() {
    Param ( 
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Name,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Description,
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$ParentFolderID
    ) 
 
    Begin {  } 
 
    Process { 
        $Body = @{
            PasswordList = $Name
            Description = $Description
            ApplyPermissionsForUserID = "$env:USERDOMAIN\$env:USERNAME"
            Permission = if($global:PasswordstateTakePermsFromTemplate -eq $False) {"A"} else {$null}
            CopySettingsFromTemplateID = $global:PasswordstateTemplateID
            LinkToTemplate = $global:PasswordstateLinkToTemplate
            PrivatePasswordList = "true"
            NestUnderFolderID = $ParentFolderID
            SiteID = "0"
        }

        $jsonBody = $Body | ConvertTo-Json

        $PasswordstateURLFull = "$($global:PasswordstateURL)/winapi/passwordlists"
        $result = Invoke-Restmethod -Method POST -Uri $PasswordstateURLFull -ContentType "application/json" -Body ([System.Text.Encoding]::UTF8.GetBytes($jsonBody)) -UseDefaultCredentials
        $output = $result.PasswordListID 
    }

    End {
        Write-Output $output
    }

}

Function AddPasswordstatePasswordToPasswordlist() {
    Param ( 
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [Int]$PasswordListID,
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Title,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Username,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Password,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Description,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Notes,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$URL,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$OTP
    ) 
 
    Begin { 
        If ($Notes) {
            $Notes = ConvertTextToHtml -text $Notes
        }
    } 
 
    Process { 
        $Body = @{
            PasswordListID = $PasswordListID
            Title = $Title
            Username = $Username
            Password = $Password
            Notes = $Notes
            URL = $URL
            OTPUri = $OTP
            Description = $Description
        }
        $jsonBody = $Body | ConvertTo-Json
        
        $PasswordstateURLFull = "$($global:PasswordstateURL)/winapi/passwords"
        #We are calling the WinAPI here, as you cannot add in passwords into a Private Password List wiht a System Wide API Key. This script will not work when running inside Passwordstate, it must be run outside of Passwordstate, in Powershell, under the identity of the user who wants to do the import.  Must be an AD account.
        $result = Invoke-Restmethod -Method Post -Uri $PasswordstateUrlFull -ContentType "application/json" -Body ([System.Text.Encoding]::UTF8.GetBytes($jsonBody)) -UseDefaultCredentials
        $output = $result.PasswordID 
    }

    End {
        Write-Output $output
    }

}
#endregion

#region GeneralFunctions
Function ConvertTextToHtml() {
    Param ( 
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$text          
    ) 
 
    Begin {  } 
 
    Process { 
        $html = $($text -replace "\n", "<br>")
    }

    End {
        Write-Output $html
    }

}

Function CreateFoldersRecursive() {
    Param ( 
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$PasswordstateParentFolderID, 
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [Object]$KeePassGroup,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Tree        
    ) 
 
    Begin {        
        $Tree = $Tree + "/" + $KeePassGroup.Name
    } 
 
    Process { 
        $Tree = $Tree + "/" + $KeePassGroup.Name
        
        #Check if folder has children; if yes, create folder, else create password list (terminal-folder)
        If ($KeePassGroup.Group) {
            #Create subfolder in Passwordstate
            $passwordStateNewFolderID = CreatePasswordstateFolder -Name $KeePassGroup.Name -Description $KeePassGroup.Notes -ParentFolderID $PasswordstateParentFolderID

            #Check if there are passwords in this folder; if yes, create a password list beneath the folder with the same name and all its passwords
            If ($KeePassGroup.Entry) {
                $passwordStateNewPasswordlistID = CreatePasswordstatePasswordlist -Name $KeePassGroup.Name -Description $KeePassGroup.Notes -ParentFolderID $passwordStateNewFolderID
                AddPasswordsToPasswordlist -PasswordListID $passwordStateNewPasswordlistID -KeePassEntries $KeePassGroup.Entry -Tree $Tree
            }

            #do so for all SubFolders
            ForEach ($group in $KeePassGroup.Group) {
                CreateFoldersRecursive -PasswordstateParentFolderID $passwordStateNewFolderID -KeePassGroup $group -Tree $Tree
            }
        } Else {
            $passwordStateNewPasswordlistID = CreatePasswordstatePasswordlist -Name $KeePassGroup.Name -Description $KeePassGroup.Notes -ParentFolderID $PasswordstateParentFolderID
            If ($KeePassGroup.Entry) {
                AddPasswordsToPasswordlist -PasswordListID $passwordStateNewPasswordlistID -KeePassEntries $KeePassGroup.Entry -Tree $Tree
            }
        }        
    }
}

Function AddPasswordsToPasswordlist() {
    Param ( 
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$PasswordListID,
            [Parameter(Mandatory=$True,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [Object]$KeePassEntries,
            [Parameter(Mandatory=$False,ValueFromPipeline=$False,ValueFromPipelinebyPropertyName=$False)]
            [String]$Tree 
    ) 
 
    Begin {   } 
 
    Process {
        ForEach($KeePassEntry in $KeePassEntries) {
            $Title = ($KeePassEntry.String | Where-Object { $_.Key -eq "Title" }).Value
            $Username = ($KeePassEntry.String | Where-Object { $_.Key -eq "Username" }).Value
            $Password = ($KeePassEntry.String | Where-Object { $_.Key -eq "Password" }).Value.'#text'
            $Description = ($KeePassEntry.String | Where-Object { $_.Key -eq "Notes" }).Value
            $URL = ($KeePassEntry.String | Where-Object { $_.Key -eq "URL" }).Value
            $Secret = ($KeePassEntry.String | Where-Object { $_.Key -eq "TimeOtp-Secret-Base32" }).Value.'#text'
            if (($Title -eq "") -AND ($Username -ne "")) { $Title = $Username }
            if ($Title -eq "") { $Title = "no name" }
            if (!$Title){ $Title = "no name" }
            
   
            if (!(Test-Path variable:secret) -or ($Secret -eq '' -or $Secret -eq $null))
            {
            # Set OTP to be empty as there is no data to import
            $OTP = ''
            }
            else
            {
            # As we have detected a "Secret" in this KeePass record, we now build the OTP string. Algorithm, Digits and Period values may not exist in KeePass XML file, so we will need to set those with default velues
            $Issuer = $Title #Keepass does not store Issuer for the OTP entries, so we must assume the Title of the KeePass Record is the Issuer
            
            $Algorithm = ($KeePassEntry.String | Where-Object { $_.Key -eq "TimeOtp-Algorithm" }).Value
            if ($Algorithm -eq ''){$Algorithm = "HMAC-SHA-1"}

            $Digits = ($KeePassEntry.String | Where-Object { $_.Key -eq "TimeOtp-Length" }).Value
            if ($Digits -eq ''){$Digits = "6"}

            $Period = ($KeePassEntry.String | Where-Object { $_.Key -eq "TimeOtp-Period" }).Value
            if ($Period -eq ''){$Period = "30"}
            
            $OTP = "otpauth://totp/$Issuer" + "?secret=$Secret" + "&algorithm=$Algorithm" + "&digits=$Digits" + "&period=$Period"

            }
            $result = AddPasswordstatePasswordToPasswordlist -PasswordListID $PasswordListID -Title $Title -Username $Username -Password $Password -Notes $Description -URL $URL -OTP $OTP
        }        
    }

    End { }

}
#endregion


try
{    
    [xml]$XmlDocument = Get-Content -Path $KeePassFile
    $KeePassRoot = $XmlDocument.ChildNodes.Root.Group
    
    CreateFoldersRecursive -PasswordstateParentFolderID 0 -KeePassGroup $KeePassRoot
#CreateFoldersRecursive -PasswordstateParentFolderID $global:PasswordstateImportFolderID -KeePassGroup $KeePassRoot
        
    Write-Output "Success"
}
catch
{
    switch -wildcard ($error[0].Exception.ToString().ToLower())
    {
        default { Write-Output "Failed to import passwords. Error = " $error[0].Exception }
    }
}

 

 

 

 

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...