Private/New-Csr.ps1

function New-Csr {
    [CmdletBinding()]
    [OutputType('System.String')]
    param(
        [Parameter(Mandatory,Position=0)]
        [PSTypeName('PoshACME.PAOrder')]$Order
    )

    # Make sure we have an account configured
    if (-not (Get-PAAccount)) {
        throw "No ACME account configured. Run Set-PAAccount or New-PAAccount first."
    }

    # Order verification should have already been taken care of
    $keyFile = Join-Path $Order.Folder 'cert.key'
    $reqFile = Join-Path $Order.Folder 'request.csr'

    # Check for an existing key
    if (Test-Path $keyFile -PathType Leaf) {

        $keyPair = Import-Pem -InputFile $keyFile

        if ($Order.KeyLength -notlike 'ec-*') {
            $sigAlgo = 'SHA256WITHRSA'
        } else {
            $keySize = [int]$Order.KeyLength.Substring(3)
            if ($keySize -eq 256) { $sigAlgo = 'SHA256WITHECDSA' }
            elseif ($keySize -eq 384) { $sigAlgo = 'SHA384WITHECDSA' }
            elseif ($keySize -eq 521) { $sigAlgo = 'SHA512WITHECDSA' }
        }

    # Nope, new key needed
    } else {

        Write-Verbose "Creating new private key for the certificate request."

        $sRandom = [Org.BouncyCastle.Security.SecureRandom]::new()

        if ($Order.KeyLength -like 'ec-*') {

            # EC key
            Write-Debug "Creating BC EC keypair of type $($Order.KeyLength)"
            $keySize = [int]$Order.KeyLength.Substring(3)
            $curveOid = [Org.BouncyCastle.Asn1.Nist.NistNamedCurves]::GetOid("P-$keySize")

            if ($keySize -eq 256) { $sigAlgo = 'SHA256WITHECDSA' }
            elseif ($keySize -eq 384) { $sigAlgo = 'SHA384WITHECDSA' }
            elseif ($keySize -eq 521) { $sigAlgo = 'SHA512WITHECDSA' }

            $ecGen = [Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator]::new()
            $genParam = [Org.BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters]::new($curveOid,$sRandom)
            $ecGen.Init($genParam)
            $keyPair = $ecGen.GenerateKeyPair()

        } else {

            # RSA key
            Write-Debug "Creating BC RSA keypair of type $($Order.KeyLength)"
            $keySize = [int]$Order.KeyLength
            $sigAlgo = 'SHA256WITHRSA'

            $rsaGen = [Org.BouncyCastle.Crypto.Generators.RsaKeyPairGenerator]::new()
            $genParam = [Org.BouncyCastle.Crypto.KeyGenerationParameters]::new($sRandom,$keySize)
            $rsaGen.Init($genParam)
            $keyPair = $rsaGen.GenerateKeyPair()

        }

        # export the key to a file
        Export-Pem $keyPair $keyFile

    }

    # start building the cert request

    # create the subject
    if ($Order.Subject) {
        $subject = [Org.BouncyCastle.Asn1.X509.X509Name]::new($Order.Subject)
    } elseif ($Order.MainDomain.Length -le 64) {
        $subject = [Org.BouncyCastle.Asn1.X509.X509Name]::new("CN=$($Order.MainDomain)")
    } else {
        # CN's longer than 64 characters are invalid in a CSR, so just leave it empty
        # because the CN value is deprecated anyway
        $subject = [Org.BouncyCastle.Asn1.X509.X509Name]::GetInstance([Org.BouncyCastle.Asn1.DerSequence]::new())
    }

    # create a .NET Dictionary to hold our extensions because that's what BouncyCastle needs
    $extDict = New-Object 'Collections.Generic.Dictionary[Org.BouncyCastle.Asn1.DerObjectIdentifier,Org.BouncyCastle.Asn1.X509.X509Extension]'

    # create the extensions we care about
    $basicConstraints = New-Object Org.BouncyCastle.Asn1.X509.X509Extension($false, (New-Object Org.BouncyCastle.Asn1.DerOctetString(New-Object Org.BouncyCastle.Asn1.X509.BasicConstraints($false))))
    if ($Order.KeyLength -like 'ec-*') {
        # Only DigitalSignature on ECC certs because KeyEncipherment not supported
        $keyUsage = New-Object Org.BouncyCastle.Asn1.X509.X509Extension($true, (New-Object Org.BouncyCastle.Asn1.DerOctetString(New-Object Org.BouncyCastle.Asn1.X509.KeyUsage([Org.BouncyCastle.Asn1.X509.KeyUsage]::DigitalSignature))))
    } else {
        # DigitalSignature and KeyEncipherment on RSA certs
        $keyUsage = New-Object Org.BouncyCastle.Asn1.X509.X509Extension($true, (New-Object Org.BouncyCastle.Asn1.DerOctetString(New-Object Org.BouncyCastle.Asn1.X509.KeyUsage([Org.BouncyCastle.Asn1.X509.KeyUsage]::DigitalSignature -bor [Org.BouncyCastle.Asn1.X509.KeyUsage]::KeyEncipherment))))
    }
    # Client + Server Authentication
    $extKeyUsage = New-Object Org.BouncyCastle.Asn1.X509.X509Extension($false, (New-Object Org.BouncyCastle.Asn1.DerOctetString(New-Object Org.BouncyCastle.Asn1.X509.ExtendedKeyUsage([Org.BouncyCastle.Asn1.X509.KeyPurposeID]::IdKPServerAuth, [Org.BouncyCastle.Asn1.X509.KeyPurposeID]::IdKPClientAuth))))
    $ski = New-Object Org.BouncyCastle.Asn1.X509.X509Extension($false, (New-Object Org.BouncyCastle.Asn1.DerOctetString(New-Object Org.BouncyCastle.X509.Extension.SubjectKeyIdentifierStructure($keyPair.Public))))

    # create SANs based on the identifier types
    $genNames = @()
    $Order.identifiers | ForEach-Object {
        if ($_.type -eq 'dns') {
            $genNames += New-Object Org.BouncyCastle.Asn1.X509.GeneralName([Org.BouncyCastle.Asn1.X509.GeneralName]::DnsName, $_.value)
        }
        elseif ($_.type -eq 'ip') {
            $genNames += New-Object Org.BouncyCastle.Asn1.X509.GeneralName([Org.BouncyCastle.Asn1.X509.GeneralName]::IPAddress, $_.value)
        }
        else {
            Write-Warning "Skipping unexpected identifier type '$($_.type)' with value '$($_.value)'."
        }
    }
    $sans = New-Object Org.BouncyCastle.Asn1.X509.X509Extension($false, (New-Object Org.BouncyCastle.Asn1.DerOctetString(New-Object Org.BouncyCastle.Asn1.X509.GeneralNames(@(,$genNames)))))

    # add them to a DerSet object
    $extDict.Add([Org.BouncyCastle.Asn1.X509.X509Extensions]::BasicConstraints, $basicConstraints)
    $extDict.Add([Org.BouncyCastle.Asn1.X509.X509Extensions]::KeyUsage, $keyUsage)
    $extDict.Add([Org.BouncyCastle.Asn1.X509.X509Extensions]::ExtendedKeyUsage, $extKeyUsage)
    $extDict.Add([Org.BouncyCastle.Asn1.X509.X509Extensions]::SubjectAlternativeName, $sans)
    $extDict.Add([Org.BouncyCastle.Asn1.X509.X509Extensions]::SubjectKeyIdentifier, $ski)

    # add OCSP Must Staple if requested
    if ($Order.OCSPMustStaple) {
        Write-Debug "Adding OCSP Must-Staple"
        $mustStaple = New-Object Org.BouncyCastle.Asn1.X509.X509Extension($false, (New-Object Org.BouncyCastle.Asn1.DerOctetString(@(,[byte[]](0x30,0x03,0x02,0x01,0x05)))))
        $extDict.Add((New-Object Org.BouncyCastle.Asn1.DerObjectIdentifier('1.3.6.1.5.5.7.1.24')), $mustStaple)
    }

    # build the extensions DerSet
    $extensions = New-Object Org.BouncyCastle.Asn1.X509.X509Extensions($extDict)
    $extDerSet = New-Object Org.BouncyCastle.Asn1.DerSet(New-Object Org.BouncyCastle.Asn1.Pkcs.AttributePkcs([Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers]::Pkcs9AtExtensionRequest,(New-Object Org.BouncyCastle.Asn1.DerSet($extensions))))

    # create the request object
    $req = New-Object Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest($sigAlgo,$subject,$keyPair.Public,$extDerSet,$keyPair.Private)

    # export the csr to a file
    Export-Pem $req $reqFile

    # return the raw Base64 encoded version
    return (ConvertTo-Base64Url $req.GetEncoded())
}