There have been a few questions over on SO recently about securely storing credentials in Powershell scripts. Because ConvertFrom-SecureString
uses DPAPI it makes the output only valid for that user, which makes creating credentials for service accounts etc. occasionally awkward.
I have had this script lying around for a while, that creates a private key for ConvertFrom-SecureString
stores that and the password in a custom object, and then encrypts the whole thing with a self signed certificate.
While it all works perfectly well, I've always been a little nervous about how it creates the certificate. While I (think I) understand what the parameters to New-SelfSignedCertificate
are doing I'm still not convinced it is configured in the most appropriate / secure way (obviously after this, the certificate is exported / deleted / secured / imported where needed).
For starters, I know backticking is generally frowned upon, but I prefer it because it aids readability.
This is the main function I'm concerned with. Would a different provider be 'better', should I be using elliptic curve, is the concept solid. etc.
#Requires –Version 5
function New-Certificate
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
$CertificateSubject
)
New-SelfSignedCertificate -TextExtension @("2.5.29.37={text}1.3.6.1.4.1.311.80.1") `
-Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" `
-DnsName $CertificateSubject `
-CertStoreLocation "Cert:\CurrentUser\My" `
-KeyExportPolicy ExportableEncrypted `
-KeyUsage DataEncipherment `
-KeyUsageProperty All `
-KeyLength 2048
}
# New-Certificate -CertificateSubject "My Test Certificate"
From there there are three functions, that I'd be curious of your thoughts on.
For a simple export function, I considered putting a path checker there. If it fails, then it is being used interactively, so it's just easier to fix as it's called.
function Export-PublicCredentials
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
$CertificateSubject,
[Parameter(Mandatory=$true)]
$filePath
)
$Certificate = (Get-ChildItem Cert:\CurrentUser\My\ `
-DocumentEncryptionCert | Where-Object {$_.Subject -match $CertificateSubject} )
Export-Certificate -Cert $Certificate -FilePath $filePath
}
Then the real part, create a key, store the credential, wrap it all up in a cert:
function Encrypt-Credentials
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
$CertificateSubject
)
$Credential = Get-Credential
$key = New-Object byte[](32)
$rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
$rng.GetBytes($key)
$SecureStringWithKey = $Credential.Password | ConvertFrom-SecureString -Key $key
$thumbprint = (Get-ChildItem Cert:\CurrentUser\My\ `
-DocumentEncryptionCert |
Where-Object {$_.Subject -match $CertificateSubject} ).Thumbprint
$object = (New-Object pscustomobject |
Add-Member -PassThru NoteProperty -Name "Key" -Value $key |
Add-Member -PassThru NoteProperty -Name "Username" -Value $Credential.UserName |
Add-Member -PassThru NoteProperty -Name "SecureStringWithKey" -Value $SecureStringWithKey
)
$json = $object | ConvertTo-Json
$EncryptedKey = Protect-CmsMessage -Content $json -To $thumbprint
Write-Output $EncryptedKey
}
$EncrypteString = Encrypt-Credentials -CertificateSubject "My Test Certificate"
Finally, what would be the most called parameter, the decryption. The idea here is that the encrypted string is put somewhere accessible (A HTTP share, or a local disk etc) the encrypted string is decrypted and deserialised and then you get a credential object, as well as the key to pass back to ConvertTo-SecureString
to get the PSCredential object back.
function Decrypt-Credentials
{
[CmdletBinding()]
[OutputType([System.Management.Automation.PSCredential])]
Param
(
[Parameter(Mandatory=$true)]
$EncryptedCredentials
)
$AuthObject = (Unprotect-CmsMessage -Content $EncryptedCredentials | ConvertFrom-Json)
$secureStringPassword = $AuthObject.SecureStringWithKey |
ConvertTo-SecureString -Key $AuthObject.Key.value
$cred = new-object -typename System.Management.Automation.PSCredential `
-argumentlist $AuthObject.Username, $secureStringPassword
Write-Output $cred
}
#Decrypt-Credentials -EncryptedCredentials $EncrypteString