Creating PEM file through PowerShell

Viewed 6799

I am trying to code a script to create a PEM certificate file in powershell. I am not sure if what I did is totally wrong, but when I tried to use the PEM file in and socat OPENSSL it returned the error:

$ socat OPENSSL-LISTEN:1337,cert=cert.pem,verify=0 -

socat[1209] E SSL_CTX_use_certificate_file(): error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag
#create certificate
$cert = New-SelfSignedCertificate `
    -Subject "MYHOSTNAME" `
    -TextExtension @("2.5.29.17={text}DNS=MYHOSTNAME&IPAddress=192.168.1.100") `
    -KeySpec Signature `
    -HashAlgorithm SHA256 `
    -KeyExportPolicy Exportable

#publicKey
$PublicKey = $cert.GetPublicKey();
$PublicKeyB64 = [Convert]::ToBase64String($PublicKey,"InsertLineBreaks");

#privateKey
$RSACng  = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert);
$PrivateKey = $RSACng.Key;
$PrivateKeyByte = $PrivateKey.Export("PRIVATEBLOB");
$PrivateKeyB64 = [Convert]::ToBase64String($PrivateKeyByte,"InsertLineBreaks");

#createFile
$out = New-Object string[] -ArgumentList 6;
$out[0] = "-----BEGIN PRIVATE KEY-----";
$out[1] = $PrivateKeyB64;
$out[2] = "-----END PRIVATE KEY-----"

$out[3] = "-----BEGIN CERTIFICATE-----"
$out[4] = $publicKeyB64;
$out[5] = "-----END CERTIFICATE-----"

[IO.File]::WriteAllLines("C:\Users\Public\cert.pem",$out)

I am not sure if what I did is totally wrong, but I could not find any resource to help me proceed.

Some script that perform similar action of creating a PEM file in powershell or some tip about how to proceed could be value to help me fix this one.

4 Answers

You can also use CertUtil.exe. Go through this once

Certutil.exe is a command-line program, installed as part of Certificate Services.

So simple script for creating .PEM with powershell is as follows:

 $certpwd = '***'
$certpwd1 = ConvertTo-SecureString -String $certpwd -Force –AsPlainText
$certpwd2 = 'pass:' + $certpwd
$certname = $env:COMPUTERNAME
del c:\temp\$certname.*

$cert = New-SelfSignedCertificate -DnsName $certname -certStorelocation cert:\localmachine\my -KeyLength 2048  -KeyFriendlyName $certname -FriendlyName $friendlyName -HashAlgorithm sha256 -keyexportpolicy exportableencrypted -keyspec KeyExchange -NotAfter (Get-Date).AddYears(2) | Out-Null

$cert2=Get-ChildItem Cert:\LocalMachine\my |
Where-Object { $_.FriendlyName -match $friendlyName }


$path = ‘cert:\localMachine\my\’ + $cert2.thumbprint
Export-Certificate -cert $path -FilePath c:\temp\$certname.der -type CERT –noclobber | Out-Null
certutil -encode c:\temp\$certname.der c:\temp\$certname.pem | Out-Null

What the script does is

1)First it creates self signed certificate

2)Then Export it to some physical path in form of .der

3)Use Certutil for encoding and thus creatig .pem file(.pem will be saved in c:\temp\ in this example)

As commented, the data you are putting in the body of both PEM files is wrong.

For the certificate, it's easy, just use $cert.RawData (converted to base64 with linebreaks, as you already have).

For the privatekey, according to the doc the RSA abstract returned by GetRSAPrivateKey($cert) (not .Key) and thus its implementations like RSACng have methods ExportPkcs8PrivateKey() which should be the correct data to put in a PEM file of type BEGIN/END PRIVATE KEY and also ExportRSAPrivateKey() which should be the older format that is correct for a PEM file of type BEGIN/END RSA PRIVATE KEY -- but only in Core 3.0+ and Five which I don't have and thus can't test.

A workaround if you have openssl commandline is to Export-PfxCertificate to a file, which openssl pkcs12 [-nodes] can then convert to the PEM formats OpenSSL (and thus socat) likes. But if you have openssl commandline you can easily use it to generate the privatekey and (selfsigned/dummy) cert directly, without futzing with powershell.

I had trouble with both previous answers. After much searching and testing, I found a method that works with vanilla powershell and few (no?) permissions.

This script also gives the self signed cert as a pfx, der, and pem file.

$dir = "c:\temp\"

$subj = "CN=YOUR_SUBJECT" #need either DnsName or Subject when calling New-SelfSignedCertificate

$friendlyName = "YOUR_FRIENDLY_NAME"

$pfxFilePath = $dir, $friendlyName, ".pfx" -join ""

$derFilePath = $dir, $friendlyName, ".der" -join ""

$pemFilePath = $dir, $friendlyName, ".pem" -join ""

#create new certificate in cert:\currentuser\my and store in variable $cert
$cert = New-SelfSignedCertificate -CertStoreLocation cert:\currentuser\my -Subject $subj -KeyAlgorithm RSA -KeyLength 2048 -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -KeyExportPolicy Exportable -FriendlyName $friendlyName -NotAfter (Get-Date).AddYears(2)

#get password for pfx file
$pw = Read-Host "Enter Password:"

#export cert to pfx and write to file 
[io.file]::WriteAllBytes($pfxFilePath,$cert.Export(3, $pw))

#export cert to der and write to file
Export-Certificate -FilePath $derFilePath -Cert $cert

#encode der file as pem file and write to file
certutil -encode $derFilePath $pemFilePath | Out-Null

You are close, this is how it will work:

$cert = ...

# Public key to Base64
$CertBase64 = [System.Convert]::ToBase64String($cert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks)

# Private key to Base64
$RSACng = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$KeyBytes = $RSACng.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)
$KeyBase64 = [System.Convert]::ToBase64String($KeyBytes, [System.Base64FormattingOptions]::InsertLineBreaks)

# Put it all together
$Pem = @"
-----BEGIN PRIVATE KEY-----
$KeyBase64
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
$CertBase64
-----END CERTIFICATE-----
"@

# Output to file
$Pem | Out-File -FilePath cert.pem -Encoding Ascii

If you want the equivalent of openssl pkcs12 -in cert.pfx -out cert.pem -nodes (e. g. including the whole certificate chain), here you go:

$cert = ...

# Collect certificates
$CertChain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain
$CertChain.Build($cert)
$certs = $CertChain.ChainElements | ForEach-Object {$_.Certificate}
[System.Array]::Reverse($certs)

# Private key to Base64
$RSACng = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$KeyBytes = $RSACng.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)
$KeyBase64 = [System.Convert]::ToBase64String($KeyBytes, [System.Base64FormattingOptions]::InsertLineBreaks)

# Put it all together
$Pem = @"
-----BEGIN PRIVATE KEY-----
$KeyBase64
-----END PRIVATE KEY-----
"@
foreach ($cert in $certs) {
    $Pem += @"

-----BEGIN CERTIFICATE-----
$([System.Convert]::ToBase64String($cert.RawData, [System.Base64FormattingOptions]::InsertLineBreaks))
-----END CERTIFICATE-----
"@
}

# Output to file
$Pem | Out-File -FilePath cert.pem -Encoding Ascii
Related