Export certificate from object with private key Export-Clixml - powershell

I'm trying to store a few objects as Clixml from PowerShell.
I can successfully store my array and certificate, but the private key is not exported with it.
Example:
PS > $cert.HasPrivateKey
true
PS > $outObject = $array
PS > $outObject += $cert
PS > $outObject | Export-Clixml -Path .\file.xml
PS > $inObject = Import-Clixml -Path .\file.xml
PS > $newcert = $inObject | Where-Object { $_.GetType().Name -like "*X509Certificate2" }
PS > $newcert.HasPrivateKey
false
I noted that there is a method for $cert.PrivateKey:
ExportParameters Method System.Security.Cryptography.RSAParameters ExportParameters(bool includePrivateParameters)
This script is not specifically running in Windows and the certificate isn't installed in the CABI store, only the variable imported from Get-PfxCertificate.
Long story short, I'm building a module that connects to an API with client authentication. I'm trying to pull client authentication from the Clixml file.

The private key is not a part of X509Certificate2 object, thus it is not exported along with the public certificate. The private key is linked to the public certificate.
In order to export a certificate with a private key, you have to serialize the certificate and private key object before passing it to Export-CliXml.
Use the X509Certificate2.Export(X509Content​Type, Secure​String) method to export the certificate with the associated private key to PFX (PKCS#12 container). The private key material is password-protected.
Use the X509Certificate2.Import(Byte[], Secure​String, X509Key​Storage​Flags) method to import the certificate and associated private key after calling the Import-CliXml cmdlet.
This is the only option you have. Also, be aware that this approach works only when the private key is exportable. If the private key is non-exportable, the Export method will fail.

By converting the certificate object to PFX format (as suggested by Crypt32) and saving my objects in a hash table I was able to successfully export and import the array and certificate with private key.
PS > $cert.HasPrivateKey
true
PS > $pfx = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx,'Pa$$w0rd')
PS > $outObject = #{
>> myArray = $array
>> myCert = $pfx
>> }
PS > Export-Clixml -InputObject $outObject -Path .\file.xml
PS > $inObject = Import-Clixml -Path .\file.xml
PS > $newCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New($inObject.myCert,'Pa$$w0rd')
PS > $newCert.HasPrivateKey
true

Related

Powershell equivalent of 'openssl dgst -sha512 -sign'

For API access, I need to sign a JSON file with 'openssl dgst -sha512 -sign'. I need to run this in a powershell script.
Currently, I am using the following method:
Write the file to a temp file, and re-read it to be certain that there is no conversion problem
Invoke openssl to sign the file
Read the signature file
Call the API
$body = #{
"login" = "customer"
"nonce" = Get-Random -Minimum ([int]([math]::pow(10,8))) -Maximum ([int]([math]::pow(10,9)-1))
"read_only" = "true"
"expiration_time" = "30 seconds"
"label" = "Test"
"global_key" = "true"
} | ConvertTo-Json
Remove-Item -Force "D:\temp\body.tmp" -ErrorAction Continue
Remove-Item -Force "D:\temp\body.dgst" -ErrorAction Continue
$body | Out-File -FilePath "D:\temp\body.tmp" -Encoding ascii
# Re-read data from the file after it's been re-encoded to ascii (as thats what we signed)
$body = [System.IO.File]::ReadAllBytes("D:\temp\body.tmp")
# Sign with the private key of the signing certificate
& D:\apps\openssl\bin\openssl.exe dgst -sha512 -sign "D:\powershell\TransIP\sign.txt" -out "D:\temp\body.dgst" "D:\temp\body.tmp"
$headers = #{
Signature = ([Convert]::ToBase64String([System.IO.File]::ReadAllBytes("D:\temp\body.dgst")))
"Content-Type" = "application/json"
}
I've been searching for a native Powershell/.NET method for SHA512 digest signing, but I can't seem to find anything in the Security class to do this.
Does anyone know a native Powershell method (thus eliminating the dependency of an external openssl installation), or is the above method the only method for signing?
Documentation of the API that I'm trying to use: https://api.transip.nl/rest/docs.html (section 'Authentication'). I've already asked TransIP for support, but they don't support Powershell.
Documentation of 'openssl dgst': https://www.openssl.org/docs/man1.1.1/man1/dgst.html
Joost

Import-PfxCertificate no-ops, but no error or anything

I'm trying to import a PFX file into the local certificate store. However, Import-PfxCertificate just does nothing at all. No return value, no error, nothing:
I can double click on the PFX file in Explorer and import it with the same password, which works. Something about the PowerShell CmdLet isn't working. I've also tried other stores, such as Cert:\LocalMachine\My and TrustedPeople. Running it with -Verbose -Debug doesn't show anything extra. Nothing in the Application or Security event logs either. I'm also running as an admin. Ideas?
The Pfx file might have a cert chain. Treating it as a collection would be a better way of handling the certificate store. See installing cert chain for the C# this was based off;
[string] $certPath = '.\test.pfx';
[string] $certPass = 'MyPassword';
# Create a collection object and populate it using the PFX file
$collection = [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]::new();
$collection.Import($certPath, $certPass, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet);
try {
# Open the Store My/Personal
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My');
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite);
foreach ($cert in $collection) {
Write-Host ("Subject is: '{0}'" -f $cert.Subject )
Write-Host ("Issuer is: '{0}'" -f $cert.Issuer )
# Import the certificate into an X509Store object
# source https://support.microsoft.com/en-au/help/950090/installing-a-pfx-file-using-x509certificate-from-a-standard-net-applic
if ($cert.Thumbprint -in #($store.Certificates | % { $_.Thumbprint } )) {
Write-Warning "Certificate is already in the store"
# Force the removal of the certificate so we have no conflicts, not required if this is the first install
$store.Remove($cert)
}
# Add in the certificate
$store.Add($cert);
}
} finally {
if($store) {
# Dispose of the store once we are done
$store.Dispose()
}
}

GetRSAPrivateKey UniqueName returns empty string

I am generating self signed certifcate using powershell script and importing it to the certificate store. Now I need to assign a new user in manage private keys section for the certificate.
I was trying the following code sample to do so.
$certificate = (import the certificate)
## Identify the user you'll be granting permission to
$grantee_name = 'dev\Batman'
$grantee = New-Object System.Security.Principal.NTAccount($grantee_name)
## Get the location and permission-of the cert's private key
$privatekey_rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
$privatekey_file_name = $privatekey_rsa.key.UniqueName
$privatekey_path = "${env:ALLUSERSPROFILE}\Microsoft\Crypto\Keys\${privatekey_file_name}"
$privatekey_file_permissions = Get-Acl -Path $privatekey_path
## Grant the user 'read' access to the key
$access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule($grantee, 'Read', 'None', 'None', 'Allow')
$privatekey_file_permissions.AddAccessRule($access_rule)
Set-Acl -Path $privatekey_path -AclObject $privatekey_file_permissions
In my case
$privatekey_file_name = $privatekey_rsa.key.UniqueName
returns empty string.
Attaching screenshot as well.
When you specify -path and -password parameters, private key is not persisted and the certificate is not installed. In order to install the certificate, you need to remove mentioned parameters and use -StoreLocation parameter to indicate in which store the certificate must be installed.

Set Binary Registry Key to GUID Value using Powershell or Powershell Desired State Configuration

We were instructed to update a REG_BINARY registry key using a GUID value, but we want to do this using PowerShell or PowerShell DSC so it can be automated. The end result should look like this:
As that is the hex representation of the GUID:
GUID: {01234567-89AB-CDEF-0123-456789ABCDEF}
Hex: 67-45-23-01-AB-89-EF-CD-01-23-45-67-89-AB-CD-EF
The following will create a new binary key using PowerShell
$value = [guid]::Parse('01234567-89AB-CDEF-0123-456789ABCDEF')
$path = 'HKLM:Software\Company\Product'
New-ItemProperty -Path $path -Name MyGUIDKey -PropertyType Binary -Value $value.ToByteArray()
If the key already exists you can update it's value by replacing the last line with
Set-ItemProperty -Path $path -Name MyGUIDKey -Value $value.ToByteArray()
If you want to create or update they key using PowerShell DSC your configuration should look like:
Registry SetBinaryKeyToGuidValue
{
Key = 'HKEY_LOCAL_MACHINE\Software\Company\Product'
ValueName = 'MyGUIDKey'
ValueData = #([BitConverter]::ToString([guid]::Parse('01234567-89AB-CDEF-0123-456789ABCDEF').ToByteArray()).Replace("-", [String]::Empty))
ValueType = 'Binary'
}
The ValueData is very particular about what format is used, and it should be and Array of Hex strings like: #('001122FF'). If you use any other format you will get an error message like:
PowerShell DSC resource MSFT_RegistryResource failed to execute
Set-TargetResource functionality with error message: (ERROR) Parameter
'ValueData' has an invalid value
'01234567-89AB-CDEF-0123-456789ABCDEF' for type 'Binary'

Grant IIS 7.5 Application Pool Read Permission to Certificate Private Key by Using PowerShell

I searched all around and could not find a lot of information, basically I have Windows 2008 R2, I created PowerShell script to load a PFX file to certificate store of Local Machine already.
Now I need to grant permission of my app pool to read the private key of the certificate by using PowerShell.
In the old way Windows 2003, I just need to get the actual file sitting in C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\ folder, but it looks like Win 2008 uses a different folder.
Anybody has some solution?
-- Update my version of code --
function Grant-CertificatePermissions([string]$certSubject,[string]$user,[string]$permissionType,[string]$permission = $args[3])
{
$getCert = Get-LocalMachineCertificate $certSubject
$keypath = Get-CertificateStorePath
$certHash = $getCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$certFullPath = $keypath+$certHash
$certAcl = Get-Acl -Path $certFullPath
try
{
$accessRule=new-object System.Security.AccessControl.FileSystemAccessRule $user, $permissionType, $permission
$certAcl.AddAccessRule($accessRule)
}
catch [System.Exception]
{
throw "Invalid User Id Or Permission"
}
Set-Acl $certFullPath $certAcl
}
function Get-LocalMachineCertificate([string]$subject, [string]$certificateStoreLocation, [string]$certificateStoreName)
{
$getCert = Get-ChildItem -Recurse Cert:\$certificateStoreLocation\$certificateStoreName | Where-Object {$_.Subject -eq $subject}
if(!$getCert)
{
throw "Certificate Not Found"
}
return $getCert
}
function Get-CertificateStorePath
{
$commonCertPathStub = "\Microsoft\Crypto\RSA\MachineKeys\"
$programData = $Env:ProgramData
if(!$programData)
{
$programData = $Env:ALLUSERSPROFILE + "\Application Data"
}
$keypath = $programData + $commonCertPathStub
return $keypath
}
In my Get-CertificateStorePath function I get value as C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\, after I get certificate hash, the complete file looks like C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\d82829f7770ea5d85ef978dea67f302d_4cca7190-7e9f-46d7-b180-6656fec432e2, when I execute Get-Acl line I have exception Cannot find path 'C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\d82829f7770ea5d85ef978dea67f302d_4cca7190-7e9f-46d7-b180-6656fec432e2' because it does not exist..
I browsed that folder, I indeed could not find such a file.
-- Update --
function Import-PfxCertificate ([String]$certPath,[String]$certificateStoreLocation ,[String]$certificateStoreName, $pfxPassword)
{
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$pfx.Import($certPath, $pfxPassword, "Exportable,PersistKeySet")
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($certificateStoreName,$certificateStoreLocation)
$store.open("MaxAllowed")
$store.add($pfx)
$store.close()
return $pfx
}
2008 R2 uses C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Via PowerShell you can see the certs available to IIS here:
cert:\LocalMachine\My
You can cd into that location and look for your cert. Once you find it you can view its private key ID using:
$cert = get-item 2779B37AE3625FD8D2F9596E285C7CDC15049D87
$cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
This will contain the long hexadecimal filename from the MachineKeys folder.
You can then change the file permissions using the Set-Acl cmdlet.
You can also view the permissions via the Certificates MMC mmc/add snapin/certificates/computer account/local computer and then certificates/personal/certificates/[your cert]/all tasks/manage private keys
If you drag and drop a certificate from User Store\Personal to Computer Store\Personal, the $getCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName value is wrong. There is no file with that value. Import the certificate again in the right store and it will work.
here is a complete powershell script to give permission to any user on certificate's private key.
How to Grant permission to user on Certificate private key using powershell?
Above link has script and example how to run it on powershell console window.
If you are using ApplicationPoolIdentity then you username will be 'IIS AppPool\AppPoolNameHere'
Note: You will need to use ' ' as there is a space between IIS and AppPool.