Private/Import-Pem.ps1
function Import-Pem { [CmdletBinding()] param( [Parameter(ParameterSetName='File',Mandatory,Position=0)] [string]$InputFile, [Parameter(ParameterSetName='String',Mandatory)] [string]$InputString ) # DER uses TLV (Tag/Length/Value) triplets. # First byte is the tag - https://en.wikipedia.org/wiki/X.690#Types # Second byte is either the total length of the value when less than 0x80 (128) # or the number of bytes that make up the value not counting the most significant bit (the 8) # So 0x77 (less than 0x80) means length is 119 (0x77) bytes # 0x82 (more than 0x80) means the length is the next 2 (0x82-0x80) bytes # Value starts the byte after the length bytes end if ('File' -eq $PSCmdlet.ParameterSetName) { # normalize the file path and read it in $InputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($InputFile) $pemStr = (Get-Content $InputFile) -join '' } else { $pemStr = $InputString.Replace("`n",'') } # private keys if ($pemStr -like '*-----BEGIN *PRIVATE KEY-----*' -and $pemStr -like '*-----END *PRIVATE KEY-----*') { $base64 = $pemStr.Substring($pemStr.IndexOf('KEY-----')+8) $base64 = $base64.Substring(0,$base64.IndexOf('-')) $keyBytes = [Convert]::FromBase64String($base64) # We need to identify enough of the DER encoded ASN.1 structure to differentiate between # RSA vs EC keys in order to call the right BouncyCastle libraries to import them. # On the keys we care about, the first tag is always a SEQUENCE (0x30) and the first # tag within that sequence is an INTEGER (0x02) which is a Version field. # Version = 1 always means an EC key # Version = 0 either means a PKCS1 RSA key or a PKCS8 key that could be either RSA or EC # Need to check the second item in the sequence to say for sure. # If Second tag is INTEGER, PKCS1 RSA key # If Second tag is a SEQUENCE, PKCS8 and need to check first child for Algorithm OID # Child = 1.2.840.113549.1.1.1 (RSA) [Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers]::RsaEncryption # Child = 1.2.840.10045.2.1 (EC) [Org.BouncyCastle.Asn1.X9.X9ObjectIdentifiers]::IdECPublicKey # throw if we don't find a SEQUENCE tag in the first byte if ($keyBytes[0] -ne 0x30) { throw "Invalid private key: No sequence in first byte" } # read in the bytes as an Asn1Sequence object $seq = [Org.BouncyCastle.Asn1.Asn1Sequence]::GetInstance($keyBytes) # check for RSA keys if ($seq[0] -eq 0 -and ($seq[1] -is [Org.BouncyCastle.Asn1.DerInteger] -or ($seq[1].Count -eq 2 -and $seq[1][0] -eq [Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers]::RsaEncryption)) ) { Write-Debug "Found RSA key type" # We can deal with either PKCS1 or PKCS8, because the PKCS1 can be extracted from PKCS8 if ($seq.Count -eq 3) { Write-Debug "Extracting RSA PKCS1 from PKCS8" $seq = [Org.BouncyCastle.Asn1.Asn1Sequence]::GetInstance($seq[2].GetOctets()) } # The resulting sequence should have 9 items, otherwise it's incomplete/malformed if ($seq.Count -ne 9) { throw "Invalid sequence in RSA private key" } # build the key parameters we'll need to build the AsymmetricCipherKey later $rsa = [Org.BouncyCastle.Asn1.Pkcs.RsaPrivateKeyStructure]::GetInstance($seq) $pubSpec = New-Object Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters($false,$rsa.Modulus,$rsa.PublicExponent) $privSpec = New-Object Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters( $rsa.Modulus, $rsa.PublicExponent, $rsa.PrivateExponent, $rsa.Prime1, $rsa.Prime2, $rsa.Exponent1, $rsa.Exponent2, $rsa.Coefficient) # check fo EC keys } elseif ($seq[0] -eq 1 -or ($seq[0] -eq 0 -and $seq[1].Count -eq 2 -and $seq[1][0] -eq [Org.BouncyCastle.Asn1.X9.X9ObjectIdentifiers]::IdECPublicKey) ) { Write-Debug "Found EC key type" # Haven't figured out how to extract the key from PKCS8 yet because it's not the same format # as a raw SEC1 key if ($seq.Count -eq 3) { throw "Unsupported PKCS8 EC key" } # Makes sure we're dealing with a raw SEC1 key rather than a PKCS8 container if ($seq.Count -ne 4) { "Unsupported EC key encoding" } # build the key parameters we'll need to build the AsymmetricCipherKey later $pKey = [Org.BouncyCastle.Asn1.Sec.ECPrivateKeyStructure]::GetInstance($seq) $ecPubKeyOid = [Org.BouncyCastle.Asn1.DerObjectIdentifier]([Org.BouncyCastle.Asn1.X9.X9ObjectIdentifiers]::IdECPublicKey) $algId = New-Object Org.BouncyCastle.Asn1.X509.AlgorithmIdentifier($ecPubKeyOid,$pKey.GetParameters()) $privInfo = New-Object Org.BouncyCastle.Asn1.Pkcs.PrivateKeyInfo($algId,$pKey.ToAsn1Object()) $privSpec = [Org.BouncyCastle.Security.PrivateKeyFactory]::CreateKey($privInfo) $pubKey = $pKey.GetPublicKey() if ($pubKey -ne $null) { $pubInfo = New-Object Org.BouncyCastle.Asn1.X509.SubjectPublicKeyInfo($algId,$pubKey.GetBytes()) $pubSpec = [Org.BouncyCastle.Security.PublicKeyFactory]::CreateKey($pubInfo) } else { $pubSpec = [Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator]::GetCorrespondingPublicKey([Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters]$privSpec) } } else { throw "Unsupported private key type" } # build the key and return it $newKey = New-Object Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair($pubSpec,$privSpec) return $newKey # certificates } elseif ($pemStr -like '*-----BEGIN CERTIFICATE-----*' -and $pemStr -like '*-----END CERTIFICATE-----*') { # For certs, we can use the native PemReader to make things easier if ('File' -eq $PSCmdlet.ParameterSetName) { try { $sr = New-Object IO.StreamReader($InputFile) $reader = New-Object Org.BouncyCastle.OpenSsl.PemReader($sr) $cert = $reader.ReadObject() } finally { if ($null -ne $sr) { $sr.Close() } } } else { # get the byte array from the pem string $base64 = $pemStr.Substring($pemStr.IndexOf('CERTIFICATE-----')+16) $base64 = $base64.Substring(0,$base64.IndexOf('-')) $certBytes = [Convert]::FromBase64String($base64) # let BC parse it $certParser = New-Object Org.BouncyCastle.X509.X509CertificateParser $cert = $certParser.ReadCertificate($certBytes) } return $cert # certificate requests } elseif ($pemStr -like '*-----BEGIN *CERTIFICATE REQUEST-----*' -and $pemStr -like '*-----END *CERTIFICATE REQUEST-----*') { # we can use the native PemReader for cert requests as well if ('File' -eq $PSCmdlet.ParameterSetName) { try { $sr = New-Object IO.StreamReader($InputFile) $reader = New-Object Org.BouncyCastle.OpenSsl.PemReader($sr) $csr = $reader.ReadObject() } finally { if ($null -ne $sr) { $sr.Close() } } } else { # get the byte aray from the pem string $base64 = $pemStr.Substring($pemStr.IndexOf('REQUEST-----')+12) $base64 = $base64.Substring(0,$base64.IndexOf('-')) $csrBytes = [Convert]::FromBase64String($base64) # let BC parse it $csr = New-Object Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest(@(,$csrBytes)) } return $csr } else { throw "Unsupported PEM type" } } |