Search code examples
powershellx509certificatelets-encryptwindows-server-2016pfx

No Private Key when importing PFX via Import-PFXCertificate?


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.


Solution

  • 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.