Private/ConvertFrom-NSPSslPrivateKeyPem.ps1

function ConvertFrom-NSPSslPrivateKeyPem {
    # Unencrypted RSA private key PEM -> RSAParameters. Reads PKCS#8 (BEGIN PRIVATE KEY: this module,
    # openssl 3) and PKCS#1 (BEGIN RSA PRIVATE KEY: openssl 1.x, the WSL-era Rebase scripts).
    [CmdletBinding()]
    [OutputType([System.Security.Cryptography.RSAParameters])]
    param([Parameter(Mandatory)][string]$Text)

    $block = @(ConvertFrom-NSPSslPem -Text $Text | Where-Object Label -Match 'PRIVATE KEY$') | Select-Object -First 1
    if (-not $block) { throw 'No private key found in the key file.' }
    if ($block.Label -eq 'ENCRYPTED PRIVATE KEY' -or -not $block.Bytes) { throw 'The key file is password-protected; decrypt it first (openssl pkey -in <file> -out <new file>).' }

    $der = $block.Bytes
    if ($block.Label -eq 'PRIVATE KEY') {
        # PrivateKeyInfo: SEQUENCE { version, AlgorithmIdentifier, OCTET STRING { RSAPrivateKey } }
        $outer = Read-NSPSslDerElement -Bytes $der
        $version = Read-NSPSslDerElement -Bytes $outer.Content
        $algorithm = Read-NSPSslDerElement -Bytes $outer.Content -Offset $version.Next
        $oid = Read-NSPSslDerElement -Bytes $algorithm.Content
        if ([BitConverter]::ToString($oid.Content) -ne '2A-86-48-86-F7-0D-01-01-01') { throw 'The key file holds a non-RSA key; only RSA keys are supported.' }
        $der = (Read-NSPSslDerElement -Bytes $outer.Content -Offset $algorithm.Next).Content
    } elseif ($block.Label -ne 'RSA PRIVATE KEY') {
        throw "Unsupported key type: $($block.Label)."
    }

    # RSAPrivateKey: SEQUENCE { version, n, e, d, p, q, dp, dq, qinv }
    $sequence = (Read-NSPSslDerElement -Bytes $der).Content
    $values = [Collections.Generic.List[byte[]]]::new()
    $offset = 0
    while ($offset -lt $sequence.Length -and $values.Count -lt 9) {
        $element = Read-NSPSslDerElement -Bytes $sequence -Offset $offset
        $content = $element.Content
        $start = 0
        while ($start -lt $content.Length - 1 -and $content[$start] -eq 0) { $start++ }
        $values.Add([byte[]]$content[$start..($content.Length - 1)])
        $offset = $element.Next
    }
    if ($values.Count -lt 9) { throw 'The RSA private key is incomplete.' }

    # .NET wants D the modulus' length and the CRT values half of it, left-padded with zeros.
    $pad = {
        param([byte[]]$Value, [int]$Length)
        if ($Value.Length -ge $Length) { return , $Value }
        , [byte[]]((New-Object byte[] ($Length - $Value.Length)) + $Value)
    }
    $size = $values[1].Length
    $half = [int][Math]::Ceiling($size / 2)
    $parameters = New-Object System.Security.Cryptography.RSAParameters
    $parameters.Modulus = $values[1]
    $parameters.Exponent = $values[2]
    $parameters.D = & $pad $values[3] $size
    $parameters.P = & $pad $values[4] $half
    $parameters.Q = & $pad $values[5] $half
    $parameters.DP = & $pad $values[6] $half
    $parameters.DQ = & $pad $values[7] $half
    $parameters.InverseQ = & $pad $values[8] $half
    $parameters
}