Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

credlib.ps1 Powershell library for credentials

My powershell library for some heavily-used credential tasks.

# Filename: \\rdputil1\scripts\Functions\credlib.ps1
# License: CC-BY-SA 4.0
# Author: bgstack15
# Startdate: 2019-12-31 15:53
# Title: Shared Credentials library
# Purpose: Provide some reusable credential functions
# History: 
# Usage:
# Reference:
#    https://foxdeploy.com/2017/01/13/adding-tab-completion-to-your-powershell-functions/
#    original implementation of storing crypted passwords \\rdputil1\e$\modules\EncryptModule\PassEncryptModule.psm1
# Improve:
# Dependencies:
#    \\rdputil1\e$\scripts\Functions\loglib.ps1

#. \\rdputil1\scripts\Functions\loglib.ps1

${global:SecurePath} = "\\rdputil1\e$\test"

Function Set-Master-Encryption-Key {
    Param(
        [Parameter(Mandatory=$true)][string]$String
    )
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($String)
    If ($bytes.Length -ne 24) {
        Throw "String must be 24 characters long."
    }
    $bytes | Out-File "${global:SecurePath}\keys\EncryptionMaster.key"
}

Function Set-Shared-Password {
    Param(
        [Parameter(Mandatory=$true)][string]$Password,
        [Parameter(Mandatory=$true)][string]$PasswordCategory
    )
    $encryptedFile = "${global:SecurePath}\EncryptedData\$($PasswordCategory)"
    $key = Get-Content "${global:SecurePath}\keys\EncryptionMaster.key"
    $Password | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString -key $key | Out-File $encryptedFile
}

Function Get-Shared-Password {
    [CmdletBinding()]
    Param () # necessary to prevent error
    DynamicParam {
        $ParameterName = 'PasswordCategory'
        $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $ParameterAttribute.Mandatory = $true
        #$ParameterAttribute.Position = 1 # unneccessary, but a good example
        $AttributeCollection.Add($ParameterAttribute)

        $arrSet = ( Get-ChildItem "$(${global:SecurePath})\EncryptedData" ).name
        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
        $AttributeCollection.Add($ValidateSetAttribute)

        $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
        return $RuntimeParameterDictionary
    }

    Begin { $PasswordCategory = $PsBoundParameters[$ParameterName] } # actual variable population based on input

    Process {
        $key = Get-Content "$(${global:SecurePath})\keys\EncryptionMaster.key"
        $passFile = Get-content (Get-ChildItem -Path "$(${global:SecurePath})\EncryptedData\$($PasswordCategory)") | ConvertTo-SecureString -Key $key
        if ($passFile) {
            return [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($passFile))
        } else {
            return "ERROR: Unable to decrypt data"
        }
    }
}

# Get-Shared-Credential -User "serviceaccount@${global:domain}" -PasswordCategory "O365"
# this one wraps around the Get-Shared-Password
Function Get-Shared-Credential {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)][string] $User
    )
    DynamicParam {
        $ParameterName = 'PasswordCategory'
        $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
        $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
        $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
        $ParameterAttribute.Mandatory = $true
        #$ParameterAttribute.Position = 1 # unneccessary, but a good example
        $AttributeCollection.Add($ParameterAttribute)

        $arrSet = ( Get-ChildItem "$(${global:SecurePath})\EncryptedData" ).name
        $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
        $AttributeCollection.Add($ValidateSetAttribute)

        $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
        $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
        return $RuntimeParameterDictionary
    }

    Begin { $PasswordCategory = $PsBoundParameters[$ParameterName] } # actual variable population based on input

    Process {
        $password = Get-Shared-Password -PasswordCategory $PasswordCategory | ConvertTo-SecureString -AsPlainText -Force
        if ($pass -contains "ERROR") {
            If ("${global:today}" -ne "") {
                # if loglib has been loaded
                Log "ERROR: Unable to decrypt password";
                Log "ERROR: Exiting Script";
                EXIT
            } Else {
                Throw "Unable to decrypt password. Exiting script."
            }
        }
        $credential = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $user, $password
        Return $credential
    }
}

Function Set-Shared-Credential {
    Param (
        [Parameter(Mandatory=$true)][string] $Password,
        [Parameter(Mandatory=$true)][string] $PasswordCategory
    )
    Set-Shared-Password -Password $Password -PasswordCategory $PasswordCategory
}

# Get-Basic-Base64-Auth-String returns "Basic 28931725917259725" which is the 
Function Get-Basic-Base64-Auth-String {
    Param (
        [Parameter(Mandatory=$true)] [System.Management.Automation.PSCredential] $Credential,
        [bool] $NoBasic = $false
    )
    $userauth = [System.Text.Encoding]::UTF8.GetBytes("$($Credential.Username):$( Get-Password-Insecurely -Credential $Credential )")
    $return = [System.Convert]::ToBase64String($userauth)
    if (!$NoBasic) { $return = "Basic $return" }
    Return "$return"
}

# Get-Password-Insecurely -Credential $Credential
# returns the password from it. Obviously this is insecure as yogurt.
Function Get-Password-Insecurely {
    Param (
        [Parameter(Mandatory=$true)] [System.Management.Automation.PSCredential] $Credential
    )
    Return [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.Password))
}

# Get-Basic-Base64-Auth-Headers -Credential $Credential
# returns a hashtable with {"Authorization" = "Basic q192419284391724"}
Function Get-Basic-Base64-Auth-Headers {
    Param (
        [Parameter(Mandatory=$true)] [System.Management.Automation.PSCredential] $Credential
    )
    Return @{Authorization = $(Get-Basic-Base64-Auth-String -Credential $Credential) }
}

function Test-Cred {
    # Ripped straight from https://www.powershellbros.com/test-credentials-using-powershell-function/
    [CmdletBinding()]
    [OutputType([String])]

    Param ( 
        [Parameter( 
            Mandatory = $false, 
            ValueFromPipeLine = $true, 
            ValueFromPipelineByPropertyName = $true
        )][Alias('PSCredential')][ValidateNotNull()][System.Management.Automation.PSCredential][System.Management.Automation.Credential()]$Credentials
    )
    $Domain = $null
    $Root = $null
    $Username = $null
    $Password = $null

    If($Credentials -eq $null) {
        Try { $Credentials = Get-Credential "domain\$env:username" -ErrorAction Stop ; }
        Catch {
            $ErrorMsg = $_.Exception.Message
            Write-Warning "Failed to validate credentials: $ErrorMsg "
            Pause
            Break
        }
    }

    # Checking module
    Try {
        # Split username and password
        $Username = $credentials.username
        $Password = $credentials.GetNetworkCredential().password

        # Get Domain
        $Root = "LDAP://" + ([ADSI]'').distinguishedName
        $Domain = New-Object System.DirectoryServices.DirectoryEntry($Root,$UserName,$Password)
    }
    Catch { $_.Exception.Message ; Continue ; }

    If(!$domain) { Write-Warning "Something went wrong" }
    Else {
        If ($domain.name -ne $null)
             { return "Authenticated" }
        Else { return "Not authenticated" }
    }
}

The "Shared" functions depend on a master encryption key. You really only need the Set-Master-Encryption key once. From then on, you use Get/Set-Shared- Password, and Get/Set-Shared-Credential. The Set-Shared-Password stores an encrypted string that is retrievable with the master encryption key. The Get- Shared-Credential builds a PSCredential object. Some of these functions contain the DynamicParam components necessary for autocompletion. This is a nice feature for when you have a large amount of credentials stored and you want to see what's available. Lots of functions depend on Get-Basic- Base64-Auth-Headers, which returns a hashtable of "Authorization: Basic 129834712743192742" needed for many http calls. The Test-Cred function is ripped off straight from Test credentials using PowerShell function - Powershellbros.com and coincidentally shows a much more convenient way to take a password out of a PSCredential object! Many of my other Powershell libraries depend on credlib, including: Powershell library for Bitbucket Cloud and Server

Comments