I have been tasked with automating installation of LetsEncrypt certficates on several Windows Server 2016 systems.
One requirement is to copy the ACL from the old private key to the new one, and so my script first identifies the unique container name from 'C:\ProgramData\Microsoft\Crypto'
.
Assuming that I have set the value of $Thumbprint to the correct certifciate thumbprint - which I have... then I believe should be able to locate the private key in the filesystem using:
(Get-Item "CERT:\LocalMachine\My\$Thumbprint").PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
This works great when I import the PFX using certlm.msc but not when I have imported the PFX using Powershell Import-PFXCertificate
?
When imported using this cmdlet the .HasPrivateKey
property is True, but the .PrivateKey property itself is empty.
I have the script working using certutil with the -ImportPfx
switch which is ok, however will not accept the SecureString password.
Any help or ideas would be much appreciated.
To mods:
Symptoms here are the same as this > (PowerShell Import Pfx, and Private Key "Lost"), however the conditions are not. I am not defining my own function to import the PFX, I am using the native Powershell cmdlet Import-PfxCertificate (https://learn.microsoft.com/en-us/powershell/module/pki/import-pfxcertificate?view=windowsserver2016-ps) and therefore am unable to edit the x509 flags as described in that resolution.
In fact, it's not a PowerShell problem and keys are not lost. The problem is in underlying .NET Framework APIs.
(Get-Item "CERT:\LocalMachine\My$Thumbprint").PrivateKey
this call is obsolete since .NET 4.6. and is completely broken in .NET 4.7. See my blog post on this topic: Accessing and using certificate private keys in .NET Framework/.NET Core
What you actually, have to do is to use X509Certificate2 extension methods which will return one of the following types: RSACng
, DSACng
or ECDsaCng
. In PowerShell, extension methods are invoked as static methods, for example:
$rsaCngKey = [System.Security.Cryptography.RSA]::GetRSAPrivateKey($cert)
These types (with "Cng" suffix) are CNG implementations and not supported by X509Certificate2.PrivateKey
. In C#, this property will throw an InvalidCastException
exception, but PowerShell swallows exceptions from property getters and return $null
instead, which may look like "lost" key.
Every CNG private key object contains Key
property (for example, RSACng.Key) which is of type of CngKey
.
CngKey
type has a pair of methods: GetProperty and SetProperty which you shall use in order to read and write key ACL by specifying a Security Descr
as a property name.