Security.psm1

####################################################################################################################### #
# Author: Thinh Dinh #
# Publisher: Embotics Corporation #
# Copyright: � 2013 Embotics Corporation. All rights reserved. #
# #
# Credit goes to Tome Tanasovski for suggesting techniques used in this file #
# #
#######################################################################################################################
$global:key = (2,3,56,34,254,222,1,1,2,23,42,54,33,233,1,34,2,7,6,5,35,43,6,6,6,6,6,6,31,33,60,23)

#Update the Diagnostic module if changing these
$maxUsernameLength = 56
$machineKeyContainerName="SuperSecretProcessOnMachine"
<#
 
.SYNOPSIS
Helper function to encrypt crendential for uses in script
 
.DESCRIPTION
Requirements:
1. Store encrypted Powershell credential to disk so that it can be used to launch another powershell script.
2. The script may need to be launched by any user who belong to the Administrator group on the server.
3. Keep the password encrypted in memory using a SecureString.
 
For domain credentials, please use the username@domain format and not DOMAIN\username.
 
Steps
   1. Get credential from user as SecureString
   2. Convert the SecureString to a string using ConvertFrom-SecureString using a 32 byte key
   3. Convert the string returned from step 2 into an array of bytes
   4. Create an RSA machine key in the cryptographic service provider (CSP)
   5. Encrypt the bytes in step 3 using RSA
   6. Serialize the encrypted bytes to disk as clixml using Export-Clixml
    
.EXAMPLE
New-EncryptCredential "C:\\Keys\\encrypt_cred_key.xml"
#>

function New-EncryptCredential {
    param([string] $destFilePath=$(Throw "Enter a destination file for the encrypted credential."))
    Set-PSDebug -Strict
    Set-StrictMode �version Latest

    #Grab user credential
    $vcredential = $host.ui.PromptForCredential("Credential To Encrypt", "Enter your credential to encrypt.", "", "")
    
    #Convert the SecureString to a string using ConvertFrom-SecureString using a 32 byte key
    $securepass = $vcredential.Password | ConvertFrom-SecureString -Key $key
    
    #Pad the username so we can encrypt it along with the password
    #Padding characters are used as marker to split out the username and password during decryption
    $paddedUsername = $vcredential.UserName.padRight($maxUsernameLength ,' ')
    $bytes = [byte[]][char[]] ($paddedUsername + $securepass)            
    
    #Encrypt username and password
    $csp = New-Object System.Security.Cryptography.CspParameters
    $csp.KeyContainerName = $machineKeyContainerName
    $csp.Flags = $csp.Flags -bor [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore
    
    #Grant access to this key.
    #Note: The user who is generating this key might not be the same user who will be accessing it (for use)
    #For example. it's the V-Commander service account who will be trying to access this key when executing within the context of workflow step
    #Therefore we grant access here.
    $accessRule = New-Object System.Security.AccessControl.CryptoKeyAccessRule -ArgumentList "everyone",([System.Security.AccessControl.CryptoKeyRights]::FullControl),([System.Security.AccessControl.AccessControlType]::Allow)
    $cryptoSecurity = New-Object System.Security.AccessControl.CryptoKeySecurity
    $csp.cryptoKeySecurity = $cryptoSecurity
    $cryptoSecurity.setAccessRule($accessRule)
    
    $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 5120,$csp
    $rsa.PersistKeyInCsp = $true            
    $encrypted = $rsa.Encrypt($bytes,$true)

    #Serialize the encrypted bytes to disk as clixml
    $encrypted | Export-Clixml $destFilePath
}

<#
.SYNOPSIS
Helper function to decrypt crendential for uses in script
 
.DESCRIPTION
Requirements:
1. Store encrypted Powershell credential to disk so that it can be used to launch another powershell script.
2. The script may need to be launched by any user who belong to the Administrator group on the server.
3. Keep the password encrypted in memory using a SecureString.
 
Steps
   1. Read in the decrypted bytes from the clixml file using Import-Clixml
   2. Initiate an instance of RSA that is using the machine key we created during the encryption process
   3. Decrypt the bytes using RSA
   4. Convert the decrypted bytes back into a string by first converting the bytes into chars and then joining them togethter
   5. Convert the string into a secure string using ConvertTo-SecureString along with the 32 byte key used by the encryption script
   6. Create the credential by using a constructor of the PsCredential class that accepts a username and a secure string as a password
  
.EXAMPLE
$cred = New-DecryptCredential "C:\\Keys\\encrypt_cred_key.xml"
 
where $cred is a System.Management.Automation.PsCredential containing both the username and password
#>

function New-DecryptCredential {
    param([string] $keyFilePath = $(Throw "Enter the path to the encrypted credential file."))
    Set-PSDebug -Strict
    Set-StrictMode �version Latest

   #To help with debug when the current user has no permission
   Write-Debug "The current user is: ($([Environment]::UserName))"

   #Read in the decrypted bytes from the clixml file using Import-Clixml
   $encrypted = Import-Clixml $keyFilePath            
   
   $csp = New-Object System.Security.Cryptography.CspParameters
   $csp.KeyContainerName = "SuperSecretProcessOnMachine"
   $csp.Flags = $csp.Flags -bor [System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore
   
   try {
      $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 5120,$csp
   } catch {
       throw "Does current account have permission to access RSA machine key/keystore?. Cause: $($_.Exception.Message)"
   }
   
   $rsa.PersistKeyInCsp = $true
   
   #Decrypt the bytes using RSA [the decripted text contains both the username and password]
   try {
    $usernameAndPassword = [char[]]$rsa.Decrypt($encrypted, $true) -join ""
   } catch [Exception] {
    Write-Error "Unable to decrypt credential $keyFilePath because of $($_.Exception.GetType().FullName). Credential file invalid or corrupted?"; 
    throw $_.Exception
   }
   
   if ([string]::IsNullOrEmpty($usernameAndPassword)) {
        Write-Error "Unable to decrypt credential.";
        Throws "Unable to decrypt credential. Credential file invalid or corrupted?"
   }
   
   #Split out the username
   $username = $usernameAndPassword.subString(0,$maxUsernameLength ).trim()
   if ([string]::IsNullOrEmpty($username)) {
        Write-Error "Unable to decrypt username.";
        Throws "Unable to decrypt username. Credential file invalid or corrupted?"
   }
   
   #Split out the password
   $password = $usernameAndPassword.subString($maxUsernameLength ) | ConvertTo-SecureString -Key $key
   if ([string]::IsNullOrEmpty($password)) {
        Write-Error "Unable to decrypt password.";
        Throws "Unable to decrypt password. Credential file invalid or corrupted?"
   }
   
   #Create PsCredential object for use
   $cred = New-Object System.Management.Automation.PsCredential $username,$password            
   $cred  
}