Update 11.01.2022: fixed number conversion in "__composePRIVATEKEYBLOB" function


Hello everyone!

Some time ago I wrote a script that converts PEM file to CryptoAPI compatible format: How to convert PEM file to a CryptoAPI compatible format. The script involves some non-PowerShell commands (certutil) which associates private key with a certificate instance. I received several feedback comments about avoiding certutil in favor of native PowerShell/.NET managed code. In this post I want to show some code that eliminates certutil from the script.

Just to recall what we generally do when converting PEM to X509Certificate2/PFX:

  • Read the certificate information from PEM file and instantiate a X509Certificate2 object;
  • Read PKCS#1 or PKCS#8 private key;
  • Convert PKCS#1/PKCS#9 private key to CryptoAPI PRIVATEKEYBLOB;
  • Associate PRIVATEKYEBLOB with an X509Certificate2 instance.

In the first version of such converter I successfully done first three steps. I didn’t know how to do the last step natively by using PowerShell/.NET and used certutil –mergePFX command to associate PRIVATEKYEBLOB with public certificate.

Recently I figured that X509Certificate2.PrivateKey property has setter accessor. and the page provides lots of hints. As per documentation, the property accepts either an RSACryptoServiceProvider or a DSACryptoServiceProvider objects.

RSACryptoServiceProvider class contains a ImportCspBlob(Byte[]) method which does the trick. It accepts binary PRIVATEKEYBLOB as a parameter! However, the key must be stored in some crypto provider and must have a container name within provider. So, if we look at constructors, we can find a suitable one: RSACryptoServiceProvider(CspParameters). So, we need to prepare crypto provider information and use this info during key import.

What information we need to provide in the CspParameters object? At a minimum, we must specify:

  • Provider name. We can use any, but I would suggest to use Microsoft Enhanced RSA and AES Cryptographic Provider as it supports a wide range of keys and key sizes.
  • Key container name. This is just a container name within CSP, so CryptoAPI can locate the right key among thousands which can be stored within CSP. Name format doesn’t really matter, so to maintains a name uniqueness, you are allowed to use GUIDs. The key association code looks like this:
$cspParams = New-Object Security.Cryptography.CspParameters -Property @{
    ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
    KeyContainerName = "pspki-" + [Guid]::NewGuid().ToString()
    KeyNumber = $KeySpec # 1 - AT_EXCHANGE, 2 - AT_SIGNATURE
}
# if we want to install the certificate to local machine store specify appropriate flag
# there is no need to specify additional flags when installing to current user store.
if ($Install -and $StoreLocation -eq "LocalMachine") {
    $cspParams.Flags += [Security.Cryptography.CspProviderFlags]::UseMachineKeyStore
}
# construct RSACryptoServiceProvider from CSP information
$rsa = New-Object Security.Cryptography.RSACryptoServiceProvider $cspParams
# load PRIVATEKEYBLOB into CSP
$rsa.ImportCspBlob($PrivateKey)
# attach private key to certificate
$Cert.PrivateKey = $rsa

By default, the key is exportable, so you can do with it whatever you want. Export policy and other key settings are configured in the CspParameters.Flags property.

Eventually, I reworked the script to provide more flexibility. This includes the following features:

  • An ability to export PEM to PFX file
  • An ability to install the certificate to the certificate store without intermediate PFX file
  • Do both, install to the store and export to a file
  • Specify the provider name you wish to use (default is Microsoft Enhanced RSA and AES Cryptographic Provider).

New function requires only one parameter: path to a PEM file. The rest is optional and depends on your needs.

The function returns an instance of X509Certificate2 class for reference only. Private key object is disposed and cannot be used in this state.

And the full function code:

Add-Type @"
namespace System.Security.Cryptography.X509Certificates {
    public enum X509KeySpecFlags {
        None = 0,
        AT_KEYEXCHANGE = 1,
        AT_SIGNATURE = 2
    }
}
"@
function Convert-PemToPfx {
[OutputType('[System.Security.Cryptography.X509Certificates.X509Certificate2]')]
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$InputPath,
        [string]$KeyPath,
        [string]$OutputPath,
        [Security.Cryptography.X509Certificates.X509KeySpecFlags]$KeySpec = "AT_KEYEXCHANGE",
        [Security.SecureString]$Password,
        [string]$ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider",
        [Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = "CurrentUser",
        [switch]$Install
    )
    if ($PSBoundParameters.Verbose) {$VerbosePreference = "continue"}
    if ($PSBoundParameters.Debug) {
        $Host.PrivateData.DebugForegroundColor = "Cyan"
        $DebugPreference = "continue"
    }

    #region helper functions
    function __normalizeAsnInteger ($array) {
        $padding = $array.Length % 8
        if ($padding) {
            $array = $array[$padding..($array.Length - 1)]
        }
        [array]::Reverse($array)
        [Byte[]]$array
    }
    function __extractCert([string]$Text) {
        if ($Text -match "(?msx).*-{5}BEGIN\sCERTIFICATE-{5}(.+)-{5}END\sCERTIFICATE-{5}") {
        $keyFlags = [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
        if ($Install) {
            if ($StoreLocation -eq "CurrentUser") {
               $keyFlags = $keyFlags -bor [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::UserKeySet
            } else {
               $keyFlags = $keyFlags -bor [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet
            }
        }
        $RawData = [Convert]::FromBase64String($matches[1])
            try {
                New-Object Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $RawData, "", $keyFlags
            } catch {throw "The data is not valid security certificate."}
            Write-Debug "X.509 certificate is correct."
        } else {throw "Missing certificate file."}
    }
    # returns [byte[]]
    function __composePRIVATEKEYBLOB($modulus, $PublicExponent, $PrivateExponent, $Prime1, $Prime2, $Exponent1, $Exponent2, $Coefficient) {
        Write-Debug "Calculating key length."
        $bitLen = "{0:X4}" -f $($modulus.Length * 8)
        Write-Debug "Key length is $($modulus.Length * 8) bits."
        [byte[]]$bitLen1 = Invoke-Expression 0x$($bitLen.Substring(0,2))
        [byte[]]$bitLen2 = Invoke-Expression 0x$($bitLen.Substring(2,2))
        [Byte[]]$PrivateKey = 0x07,0x02,0x00,0x00,0x00,0x24,0x00,0x00,0x52,0x53,0x41,0x32,0x00
        [Byte[]]$PrivateKey = $PrivateKey + $bitLen1 + $bitLen2 + $PublicExponent + ,0x00 + `
            $modulus + $Prime1 + $Prime2 + $Exponent1 + $Exponent2 + $Coefficient + $PrivateExponent
        $PrivateKey
    }
    # returns RSACryptoServiceProvider for dispose purposes
    function __attachPrivateKey($Cert, [Byte[]]$PrivateKey) {
        $cspParams = New-Object Security.Cryptography.CspParameters -Property @{
            ProviderName = $ProviderName
            KeyContainerName = "pspki-" + [Guid]::NewGuid().ToString()
            KeyNumber = [int]$KeySpec
        }
        if ($Install -and $StoreLocation -eq "LocalMachine") {
            $cspParams.Flags = $cspParams.Flags -bor [Security.Cryptography.CspProviderFlags]::UseMachineKeyStore
        }
        $rsa = New-Object Security.Cryptography.RSACryptoServiceProvider $cspParams
        $rsa.ImportCspBlob($PrivateKey)
        $Cert.PrivateKey = $rsa
        $rsa
    }
    # returns Asn1Reader
    function __decodePkcs1($base64) {
        Write-Debug "Processing PKCS#1 RSA KEY module."
        $asn = New-Object SysadminsLV.Asn1Parser.Asn1Reader @(,[Convert]::FromBase64String($base64))
        if ($asn.Tag -ne 48) {throw "The data is invalid."}
        $asn
    }
    # returns Asn1Reader
    function __decodePkcs8($base64) {
        Write-Debug "Processing PKCS#8 Private Key module."
        $asn = New-Object SysadminsLV.Asn1Parser.Asn1Reader @(,[Convert]::FromBase64String($base64))
        if ($asn.Tag -ne 48) {throw "The data is invalid."}
        # version
        if (!$asn.MoveNext()) {throw "The data is invalid."}
        # algorithm identifier
        if (!$asn.MoveNext()) {throw "The data is invalid."}
        # octet string
        if (!$asn.MoveNextCurrentLevel()) {throw "The data is invalid."}
        if ($asn.Tag -ne 4) {throw "The data is invalid."}
        if (!$asn.MoveNext()) {throw "The data is invalid."}
        $asn
    }
    #endregion
    $ErrorActionPreference = "Stop"
    
    $File = Get-Item $InputPath -Force -ErrorAction Stop
    if ($KeyPath) {$Key = Get-Item $KeyPath -Force -ErrorAction Stop}
    
    # parse content
    $Text = Get-Content -Path $InputPath -Raw -ErrorAction Stop
    Write-Debug "Extracting certificate information..."
    $Cert = __extractCert $Text
    if ($Key) {$Text = Get-Content -Path $KeyPath -Raw -ErrorAction Stop}
    $asn = if ($Text -match "(?msx).*-{5}BEGIN\sPRIVATE\sKEY-{5}(.+)-{5}END\sPRIVATE\sKEY-{5}") {
        __decodePkcs8 $matches[1]
    } elseif ($Text -match "(?msx).*-{5}BEGIN\sRSA\sPRIVATE\sKEY-{5}(.+)-{5}END\sRSA\sPRIVATE\sKEY-{5}") {
        __decodePkcs1 $matches[1]
    }  else {throw "The data is invalid."}
    # private key version
    if (!$asn.MoveNext()) {throw "The data is invalid."}
    # modulus n
    if (!$asn.MoveNext()) {throw "The data is invalid."}
    $modulus = __normalizeAsnInteger $asn.GetPayload()
    Write-Debug "Modulus length: $($modulus.Length)"
    # public exponent e
    if (!$asn.MoveNext()) {throw "The data is invalid."}
    # public exponent must be 4 bytes exactly.
    $PublicExponent = if ($asn.GetPayload().Length -eq 3) {
        ,0 + $asn.GetPayload()
    } else {
        $asn.GetPayload()
    }
    Write-Debug "PublicExponent length: $($PublicExponent.Length)"
    # private exponent d
    if (!$asn.MoveNext()) {throw "The data is invalid."}
    $PrivateExponent = __normalizeAsnInteger $asn.GetPayload()
    Write-Debug "PrivateExponent length: $($PrivateExponent.Length)"
    # prime1 p
    if (!$asn.MoveNext()) {throw "The data is invalid."}
    $Prime1 = __normalizeAsnInteger $asn.GetPayload()
    Write-Debug "Prime1 length: $($Prime1.Length)"
    # prime2 q
    if (!$asn.MoveNext()) {throw "The data is invalid."}
    $Prime2 = __normalizeAsnInteger $asn.GetPayload()
    Write-Debug "Prime2 length: $($Prime2.Length)"
    # exponent1 d mod (p-1)
    if (!$asn.MoveNext()) {throw "The data is invalid."}
    $Exponent1 = __normalizeAsnInteger $asn.GetPayload()
    Write-Debug "Exponent1 length: $($Exponent1.Length)"
    # exponent2 d mod (q-1)
    if (!$asn.MoveNext()) {throw "The data is invalid."}
    $Exponent2 = __normalizeAsnInteger $asn.GetPayload()
    Write-Debug "Exponent2 length: $($Exponent2.Length)"
    # coefficient (inverse of q) mod p
    if (!$asn.MoveNext()) {throw "The data is invalid."}
    $Coefficient = __normalizeAsnInteger $asn.GetPayload()
    Write-Debug "Coefficient length: $($Coefficient.Length)"
    # creating Private Key BLOB structure
    $PrivateKey = __composePRIVATEKEYBLOB $modulus $PublicExponent $PrivateExponent $Prime1 $Prime2 $Exponent1 $Exponent2 $Coefficient
    #region key attach and export routine
    $rsaKey = __attachPrivateKey $Cert $PrivateKey
    if (![string]::IsNullOrEmpty($OutputPath)) {
        if (!$Password) {
            $Password = Read-Host -Prompt "Enter PFX password" -AsSecureString
        }
        $pfxBytes = $Cert.Export("pfx", $Password)
        Set-Content -Path $OutputPath -Value $pfxBytes -Encoding Byte
    }
    #endregion
    if ($Install) {
        $store = New-Object Security.Cryptography.X509Certificates.X509Store "my", $StoreLocation
        $store.Open("ReadWrite")
        $store.Add($Cert)
        $store.Close()
    }
    $rsaKey.Dispose()
    $Cert
}

The code relies on my new ASN.1 binary parser which is available at GitHub: Asn1DerParser.NET. Click on file and press “Raw” button to download the DLL.

HTH


Share this article:

Comments:

Aftab

I've downloaded the additional dll and unblocked the file, its in the same folder as the function in a ps1 file, however I keep get the following error:

Unable to find type [Security.Cryptography.X509Certificates.X509KeySpecFlags]

I've tried running PowerShell as admin but I don't think that is the issue, it can't seem to find the type on my system, I'm running Windows 10 if that makes a difference, as I've tried on a Server 2012 R2 box with the same results, any ideas?

Vadims Podāns

I updated the post. It was missing one custom enum type. Now it should work.

Carl Reid

I had the same problem as "Aftab" where I would receive the error:

Convert-PemToPfx : Unable to find type [Security.Cryptography.X509Certificates.X509KeySpecFlags]

 

I did a little digging into this and this error is because the type is defined in the PKI.Core assembly rather than .NET framework.

 

I fixed the error by first loading the assembly PKI.Core into the AppDomain.

 

After this error I got a simmilar one for the type SysadminsLV.Asn1Parser.Asn1Reader

New-Object : Cannot find type [SysadminsLV.Asn1Parser.Asn1Reader]: verify that the assembly containing this type is loaded.

 

Again this was fixed by first loading the assembly SysadminsLV.Asn1Parser into the AppDomain.

 

Perhaps the article could be updated to include these or is there a better way of fixing this problem.

Alexander

Is it possible / planned to add russian cryptographic algorithms (GOST 2012 / 2001)?

Vadims Podāns

There are no such plans. Currently, I'm planning to add ellyptic curve cryptography only.

Chris Lynch

Thank you for researching and providing a solution for us CryptoAPI n00bs.

@Carl Reid, the script just needs to have the Add-Type and [System.Reflectin.Assymbly]::LoadFile() call to load the extension class at the top of the script.  Or in my case, I just replaced the Enum type variable with the integer I wanted, as I only needed the ability to create a PFX/Pkcs12 compliant file.

Ernest

Hello Vadmins

Thanks very much for posting, I am trying to use the script but I get an error. Before getting to the error I am trying to do something slightly different but close to what your script does

I have a PEM file for standard certificate e.g. -----BEGIN CERTIFICATE-----blah blah base64 blah blah----END CERTIFICATE----- this PEM has No private key information

I want to import the PEM into the CurrentUser\My store (or LocalMachine does not matter for my perposes) but importantly I want to use the "Microsoft Enhanced RSA and AES Cryptographic Provider"  if I import the certificate using the MMC or basic PowerShell it appears to end up in the CAPI store (as it were) and therefore I cannot use the certificates public key to check a SHA256 signature as this cryptographic primiative is not available to the CAPI store (as I understand it) but only to CAPI2 

I have gotten around this by using openssl on windows like so

openssl pkcs12 -export -in MyCert.crt -out MyCert.pfx -nodes -nokeys -CSP “Microsoft Enhanced RSA and AES Cryptographic Provider” -passout pass:secret

however I then need to import the PFX to my store, then once in the store I have to get back out again into a certifcate object so I can use the following method of the public key

$Certificate.PublicKey.Key.VerifyData([byte[]][char[]]$DataString, 'SHA256', $SignatureBytes)

basically checking the DataString is signed OK using (SHA256), this works OK but is a lot of work

I found if I just get the base64 certificate and convert it to a certificate object I do not have the PublicKey.Key.VerifyData method, so basically need to install into the store first then get back again, then I have the method, but for SHA265 need to get from the CAPI2 store so needs to be added to the CAPI2 store in the first place.

can you script take a PEM without a private key and add it to the CAPI2 store please?

when I run the script I first load your PSPKI module then I add the following like to the script     [System.Reflection.Assembly]::LoadFile("C:\unix\SysadminsLV.Asn1Parser.dll")

then I run the script and using the single parameter -inputPath e.g.

Convert-PemToPFX -InputPath C:\temp\MyCert.cer  

I get the following error

The data is invalid.
At C:\Unix\Convert.ps1:146 char:9
+     else { throw "The data is invalid." }
+            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (The data is invalid.:String) [], RuntimeException
    + FullyQualifiedErrorId : The data is invalid.

Looking at the PowerShell code in your script I believe it is throwing this error was my PEM does not contain a private key

the reason it does not contain a private key is because what I am really trying to do is just import a standard PEM to the CAPI2 store (Microsoft Enhanced RSA and AES Cryptographic Provider)

I would be very grateful for your advice, thanks in advance

Ernest

 

 

 

 

 

 

Vadims Podāns

Few notes:

> [System.Reflection.Assembly]::LoadFile("C:\unix\SysadminsLV.Asn1Parser.dll")

it is no longer supported in PowerShell. Instead, you have to use Add-Type cmdlet.

> CAPI2 store (Microsoft Enhanced RSA and AES Cryptographic Provider)

mentioned provider is still CAPI1 provider. CAPI2 providers are key storage providers. Your question is not related to this script, and the script won't help you.

What you can try is to try manually load public key to CngKey .NET class to load the key to CNG provider and then use this object to validate SHA2 signatures. Or call directly NCrypt functions via p/invoke.

Ernest

Hello Vadims

Thanks very much for taking the time to reply, I get the basic concept of what you are saying and will have a go at working it out.

In the mean time if you can have a think about how much you would charge to create a PowerShell function to take an input of a certificate [System.Security.Cryptography.X509Certificates.X509Certificate2]   (again I do not have the private key material) and allow the public key to be used to verify a SHA256 signature.

Then if I cannot figure I have a plan B e.g. ask my project manager to get the money to pay you via our supplier (as we did last time for the tuition) for the function

Thanks Vadmins 

Vadims Podāns

You can do it this way:

$key = [Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($cert)

This line will return public key loaded into CNG provider and you can use this object to verify the signature. More details: https://msdn.microsoft.com/en-us/library/system.security.cryptography.rsa(v=vs.110).aspx

Ernest

Hi Vadims

Thanks very much, I really appreciate you taking the time,

looks at the overloads on the VerifyData method require a parameter for Padding, I presume I need to pass it something like [System.Security.Cryptography.RSASignaturePadding]::Pkcs1

There is only a few options that I can see for padding (if I am thinking about it in the right way with my pea sized brain), I will give the above padding a try first

I feel like I owe you something for all your kind help we can't be all as bright as you Vadims :) , weather in the UK is awful :(  but I am off to join Ana in sunny Portugal in two weeks :) so you are always welcome at our house there (good wine, food, weather).

Cheers

Ernest

 

 

Michael

I attempted to set this up. I found a couple of problems. 

First the Add-Type line needs to be the first line in the script and not inside the function.

Second, no matter what I set for the $Keyspec flag, It is always set to AT_SIGNATURE.

I debugged it for a while and can see the value being set to one on the property to 1 for AT_EXCHANGE but for whatever reason, the resulting certificate has it set to 2 AT_SIGNATURE.

 

Vadims Podāns

> First the Add-Type line needs to be the first line in the script and not inside the function.

yes, you are correct. I updated the code.

Michael

After some more digging I found that if I add install and , I always get the following error 
Convert-PemToPfx : Method invocation failed because [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags] does not contain a method named 'op_Addition'.
At C:\tmp\converttopfx.ps1:182 char:1
+ Convert-PemToPfx -InputPath cert.pem -KeyPath cert.key -OutputPath cert.pfx -ins ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (op_Addition:String) [Convert-PemToPfx], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound,Convert-PemToPfx
 

Running Windows 2012 and Powershell 5

Vadims Podāns

This code perfectly runs on PowerShell v5, though is incompatible with versions prior to PowerShell 4.0. I updated the code with compatible syntax.

Michael

I would say that the code runs fine but the implementation is slightly off.

If I run with the -KeySpec AT_EXCHANGE, It always creates the certficate with a KeySpec of -AT_SIGNATURE

I confirm this by running the following command to verify the setup

certutil –v –store "my" "<certificate name in the certificate store>"

I always get:


 ProviderType = 18
 Flags = 20 (32)
   CRYPT_MACHINE_KEYSET -- 20 (32)
   KeySpec = 2 -- AT_SIGNATURE
 

Vadims Podāns

I need to do more tests. I can't tell now what is wrong with KeySpec value.

Winanjaya

Hi,

Do you have C# example for converting PEM to PFX?

please help

thanks

Vadims Podāns

No, I don't have C# equivalent of this script.

Chris Lynch

For those that have looked to use this code in say PowerShell Core, do know that the line where the private key is being set (within the __attachPrivateKey private function) was not backported in the X509Certificate2 API.  Instead, there is a helper extension method, CopyWithPrivateKey() that should be used.  The following code works on a PowerShell Core 7.0.2 system:

 

            # This is here due to .NetCore changes to the X509Certificate2 API.  The PrivateKey property cannot be set, and getter is only available for backwards compat. (https://github.com/dotnet/runtime/issues/27346)

            if ($PSVersionTable.PSEdition -eq "Core")

            {

 

                [void][Reflection.Assembly]::Load("System.Security.Cryptography.X509Certificates")

                $Cert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey($Cert.RawData, $rsa)

 

            }

 

            else

            {

 

                $Cert.PrivateKey = $rsa

 

            }

Vadims Podāns

@Chris Lynch

Thanks for update! I'm aware about this limitation in .NET Core, the article was written before first .NET Core release and wasn't updated to reflect this change.

Chris Lynch

Thanks for update! I'm aware about this limitation in .NET Core, the article was written before first .NET Core release and wasn't updated to reflect this change.

You're welcome.  This was really to help others that have been using your wonderful code here to handle PEM certs and creating PFX files, and struggling with .Net Core and/or PowerShell Core.

Chris Lynch

One more thing for your readers:

The __attachPrivateKey private function should not be useing the same $Cert parameter.  That winds up stepping over the $Cert variable within the script, and somehow loses the private key.  So, I would change the paramerter $Cert to $_Cert, and ensure the line $Cert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey($Cert.RawData, $rsa) is actually $Cert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey($_Cert.RawData, $rsa).

I have been pulling out what little hair I have left over the past day trying to figure out why using the CopyWithPrivateKey does indeed create a new cert with the private key, but then the PFX created does not.

Chris Lynch

Actually, let me just provide a Gist here.

Vadims Podāns

Yes, I understand the problem, but not sure what is wrong with these variables. Because when you specify a variable at function scope, it won't propagate from outer scopes. Though for safety I've updated PSPKI to handle this: https://github.com/PKISolutions/PSPKI/commit/8a7919233e21f2355677993ba04f443729ca4640

Scott Spradlin

Big thanks to Chris Lynch for his updates to this module! We still use his code he shared in his Gist because I believe it has more changes than what's been implemented. I'd suggest you guys work together and think about Chris' suggestions.

Also, there's still a typo in the code. About row 85 or so, you'll find "$scriptCert.PrivateKey = $rsa" for us Powershell <7.0 folks but that line is missing a colon specifying the scope for $Cert. The line should read "$script:Cert.PrivateKey = $rsa"

Thanks for everyone's efforts.

Vadims Podāns

Hey Scott, good catch! I've fixed missing colon: https://github.com/PKISolutions/PSPKI/commit/2c2e2be929cedab0c2032bf1551d886a03bc940c

> I believe it has more changes than what's been implemented.

No, it does not have extra changes.

Jari Turkia

Finally I got the RSA private key import working in PowerShell Core. My application is using a derived Convert-PemToPfx to do the trick (see https://github.com/PKISolutions/PSPKI/issues/64 for reasoning).

My changes are in __attachRSAPrivateKey() and there I'm using CngKey and RSACng for the import. After failing a lot on the import on PS core, this version of mine finally does the trick.

Feel free to utilize the approach in PSPKI, I certainly borrowed tons of your code in my app.

Jari Turkia

Apologies. The Github link was missing in my previous comment.

https://github.com/HQJaTu/RDP-cert-tools/blob/main/update-RDP-cert.ps1

Yatish Shah

Hi Vadims,

This is very helpful. The script is ruuning fine however I don't know if it is an issue or you did it intentionally.

The private key should not be exportable.

Open Certificate Export Wizard(certmgr.msc, select this certificate -> All Task-> Export )

"Yes, export the private key" this option should be disabled.

Can you tell which property need to set to make this option disabled in certifiacte export wizard.

Thanks,

Yatish

 

Shiva

Hi,

 

Really appreciate sharing this info.  I am using this example to convert a private key but in this case, the length of the key is 3072 and it is ending an error  when executing the following code:
    [byte[]]$bitLen2 = Invoke-Expression 0x$([int]$bitLen.Substring(2,2))

Cannot convert value "0C" to type "System.Int32". Error: "Input string was not in a correct format."
At C:\Users\kirashiv\Documents\CertRequest\codesign_2022\CertModule.ps1:231 char:47
+ ... byte[]]$bitLen1 = Invoke-Expression  0x$([int]$bitLen.Substring(0,2))
+                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvalidCastFromStringToInteger
 
0x : The term '0x' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is 
correct and try again.
At line:1 char:1
+ 0x
+ ~~
    + CategoryInfo          : ObjectNotFound: (0x:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

 

Can you please advise what can be done to add support for increased key length?


Regards,

-Shiva

Vadims Podāns

Hello Shiva, please check updated version. There was a bug in the script and I believe it is now fixed.


Post your comment:

Please, solve this little equation and enter result below. Captcha