Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Convert PKCS7 to PEM in shell, python, powershell, and go

I wrote these functions to aid myself when working with some know PKCS7 blocks from which I wanted to extract the certificates.

Powershell

Function Convert-Pkcs7-To-Pem {
   <#
      Convert Pkcs7 format to list of base64 PEM blocks like `openssl pkcs7 -print_certs`. If importing from Get-Contents -Path file, be sure to do a -join "`n" as well.
      Reference: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-pkcs7.html
   #>
   [CmdletBinding(SupportsShouldProcess)]
   Param(
      [Parameter(Mandatory=$True)]$inPkcs7
   )
   Process {
      Try {
         $inPkcs7 = Convert-FileContents-To-String -Verbose:$false -inItem $inPkcs7 -joiningElement "`n"
         Add-Type -AssemblyName System.Security
         $newPkcs7b64 = [Convert]::FromBase64String($inPkcs7.Replace("-----BEGIN CERTIFICATE-----","").Replace("-----END CERTIFICATE-----",""))
         $newCerts = [Security.Cryptography.Pkcs.SignedCms]::new()
         $newCerts.decode($newPkcs7b64)
         # output resembles the `openssl pkcs7 -print_certs`
         $newCerts.Certificates | ForEach { "subject=$($_.Subject)`nissuer=$($_.Issuer)`n$($_.ExportCertificatePem())" }
      }
      Catch {
          $_
      }
   }
}
Function Convert-FileContents-To-String {
   <#
      This safely converts a Get-Contents -Path (of type Object[] basetype System.Array) to string, and passes through a string.
   #>
   [CmdletBinding(SupportsShouldProcess)]
   Param(
      [Parameter(Mandatory=$True)]$inItem,
      [Parameter(Mandatory=$False)][string]$itemName = "input",
      [Parameter(Mandatory=$False)][string]$joiningElement = "\n"
   )
   Process {
      $outItem = $inItem
      $inItemType = $inItem.GetType().Name
      Switch ($inItemType) {
         "Object[]" {
             Write-Verbose "Fixing line endings of $($itemName) with -Join `"$($joiningElement)`"."
             $outItem = $inItem -join $joiningElement
         }
         "String" {
             Write-Verbose "$($itemName) is already a string."
         }
         default {
             Write-Warning "This `$inItem.GetType().Name is not configured: $inItemType"
         }
      }
      $outItem
   }
}

Shell

This is the canonical output and the basis for the generated output of the other functions here.

printf '%s' "${pkcs7}" | openssl pkcs7 -print_certs

Python

import cryptography
from cryptography.hazmat.primitives.serialization import pkcs7
from cryptography.hazmat.primitives import serialization

def ConvertPkcs7ToPem(newPkcs7):
   newPem = ""
   newCerts = pkcs7.load_pem_pkcs7_certificates(str.encode(newPkcs7))
   for eachCert in newCerts:
      newPem += eachCert.subject.rfc4514_string() + "\n"
      newPem += eachCert.issuer.rfc4514_string() + "\n"
      newPem += eachCert.public_bytes(serialization.Encoding.PEM).decode()
   return newPem

Go

The golang one took me the longest because I barely know what I'm doing with Go and the nuances of x509 versus pem. I know this code heavily depends on knowing that my input is only ever going to contain CERTIFICATE blocks. I don't know how one would prove which type of PEM block the bytes are, if you are dealing with unspecified input.

import (
   "encoding/pem"
   "fmt"
   "go.mozilla.org/pkcs7" // run `go get go.mozilla.org/pkcs7`
)

func ConvertPkcs7ToPem(inPkcs7 string) (string, error) {
   // Assume the string already has newlines handled correctly, for now
   // We know the entire possible contents of our pkcs7 is just certificates (and not private keys or a mix of various things)
   // Reference: make PEM block myself https://stackoverflow.com/questions/56074289/how-to-get-a-string-out-of-x509-certificate-public-key-in-go
   var outString string = "";
   pemAll, _ := pem.Decode([]byte(inPkcs7))
   p7new, err := pkcs7.Parse(pemAll.Bytes)
   if err != nil {
      return "FATAL during pkcs7.Parse", err
   }
   for _, x := range p7new.Certificates {
      allBlock := pem.Block{
         Type: "CERTIFICATE",
         Bytes: x.Raw,
      }
      allBlockPem := string(pem.EncodeToMemory(&allBlock))
      outString += fmt.Sprintf("subject=%s\n",x.Subject)
      outString += fmt.Sprintf("issuer=%s\n",x.Issuer)
      outString += fmt.Sprintf("%s\n",allBlockPem)
   }
   return outString, nil
}

Comments