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

One thought on “credlib.ps1 Powershell library for credentials

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.