Private/Get-CsrDetails.ps1

function Get-CsrDetails {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [Alias('CSRString')]
        [string]$CSRPath
    )

    # Given a PEM formatted CSR file path or raw string value, we need to parse the
    # request and return a hashtable with the following parsed details to the caller:
    #
    # Domain : The collection of FQDNs found in the CN and/or SAN attributes
    # KeyLength : The key length value using our Posh-ACME nomeclature
    # OCSPMustStaple : Boolean indicating whether the OCSP Must Staple flag is set
    # Base64Url : The Base64Url encoded bytes of the raw request
    # PemLines : The original lines from the file/string PEM request content

    # We're going to try to allow for CSRs being passed directly as a string value
    # in addition to a filesystem path. Since the BC PemReader currently requires
    # explicit BEGIN/END header and footer, we're going to assume that anything
    # missing them is a file path.
    if ($CSRPath -cnotlike '*CERTIFICATE REQUEST*') {

        Write-Debug "CSRPath didn't match CERTIFICATE REQUEST"

        # normalize the CSR path and make sure it exists
        $CSRPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($CSRPath)
        if (-not (Test-Path $CSRPath -PathType Leaf)) {
            throw "CSR file not found at $CSRPath"
        }

        $importParams = @{
            InputFile = $CSRPath
        }

        $details = @{
            PemLines = Get-Content $CSRPath
        }

    } else {

        $importParams = @{
            InputString = $CSRPath
        }

        $details = @{
            PemLines = $CSRPath.Trim() -split "(?:`r)?`n"
        }
    }

    # parse the CSR into a [Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest]
    Write-Debug "Attempting to import CSR pem"
    $csr = Import-Pem @importParams
    if ($csr -isnot [Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest]) {
        throw "Specified CSR was unable to be parsed as a certificate request."
    }
    $details.Base64Url = ConvertTo-Base64Url $csr.GetEncoded()

    # determine the KeyLength
    $pubKey = $csr.GetPublicKey()
    if ($pubKey -is [Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters]) {
        # RSA key, so KeyLength is just the bit length of the Modulus
        $details.KeyLength = $pubKey.Modulus.BitLength.ToString()
        if (-not (Test-ValidKeyLength $details.KeyLength)) {
            throw "RSA key length from CSR is out of the supported range. ($($details.KeyLength))"
        }
    } elseif ($pubKey -is [Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters]) {
        # EC key, make sure the curve is supported
        $curve = $pubKey.Parameters.Curve
        if ($curve -eq [Org.BouncyCastle.Asn1.Nist.NistNamedCurves]::GetByName('P-256').Curve) {
            $details.KeyLength = 'ec-256'
        } elseif ($curve -eq [Org.BouncyCastle.Asn1.Nist.NistNamedCurves]::GetByName('P-384').Curve) {
            $details.KeyLength = 'ec-384'
        } elseif ($curve -eq [Org.BouncyCastle.Asn1.Nist.NistNamedCurves]::GetByName('P-521').Curve) {
            $details.KeyLength = 'ec-521'
        } else {
            throw "Unsupported ECC curve. $($pubKey.Parameters.Curve.ToString())"
        }
    } else {
        throw "Unsupported key type."
    }
    Write-Debug "KeyLength = $($details.KeyLength)"

    # [Org.BouncyCastle.Asn1.Pkcs.CertificationRequestInfo]
    $csrInfo = $csr.GetCertificationRequestInfo()

    # grab the CN value
    $cn = ($csrInfo.Subject.GetValueList([Org.BouncyCastle.Asn1.X509.X509Name]::CN))[0]
    Write-Debug "CN = $cn"
    if ($cn) { $details.Domain = @($cn) }
    else { $details.Domain = @() }

    # grab the rest of the attributes [Org.BouncyCastle.Asn1.Asn1Set]
    # The Asn1Set is basically a nested collection of DerSequence objects
    $attr = $csrInfo.Attributes

    # Check if we have any attributes at all
    if ($attr.count -eq 0) {
        # throw if we have no CN
        if ($details.Domain.Count -eq 0) { throw "No Common Name (CN) or Subject Alternative Name (SAN) extensions found in certificate request." }

        Write-Warning "No Certficate Attributes found in CR."
        $details.OCSPMustStaple = $false
        return $details
    }

    # Find the sequence for "Certificate Extensions" (oid 1.2.840.113549.1.9.14)
    # [0] is the OID, [1] is the nested Asn1Set,
    # [1][0] should be the only DerSequence within the Ans1Set that contains additional nested DerSequence objects
    $extensions = ($attr | Where-Object { $_.Id -eq '1.2.840.113549.1.9.14'})[1][0]
    if (-not $extensions) {
        # throw if we have no names
        if ($details.Domain.Count -eq 0) { throw "No Common Name (CN) or Subject Alternative Name (SAN) extensions found in certificate request." }

        Write-Warning "No Certificate Extensions sequence found in CSR."
        $details.OCSPMustStaple = $false
        return $details
    }

    # Now find the sequence for "Subject Alternative Name" (oid 2.5.29.17)
    # [0] is the OID, [1] is the DerOctetString
    if ($sanSeq = $extensions | Where-Object { $_.Id -eq '2.5.29.17' }) {
        # convert to [Org.BouncyCastle.Asn1.X509.GeneralNames]
        $genNames = [Org.BouncyCastle.Asn1.X509.GeneralNames]::GetInstance([Org.BouncyCastle.Asn1.Asn1Object]::FromByteArray($sanSeq[1].GetOctets()))
        # and grab just the DNS names
        $SANs = ($genNames.GetNames() | Where-Object { $_.TagNo -eq 2 }).Name
    }
    if ($SANs) {
        Write-Debug "SANs = $(($SANs -join ','))"
        $details.Domain += $SANs | Where-Object { $_ -notin $details.Domain }
    }

    # throw if we have no names
    if ($details.Domain.Count -eq 0) { throw "No Common Name (CN) or Subject Alternative Name (SAN) extensions found in certificate request." }

    # Find the sequence for OCSP Must-Staple (oid 1.3.6.1.5.5.7.1.24)
    # and determine whether it's set
    if ($ocspSeq = $extensions | Where-Object { $_.Id -eq '1.3.6.1.5.5.7.1.24'}) {
        $details.OCSPMustStaple = ($ocspSeq[1].ToString() -eq '#3003020105')
    } else {
        $details.OCSPMustStaple = $false
    }
    Write-Debug "OCSP Must-Staple = $($details.OCSPMustStaple)"

    return $details
}