PrivateFunctions/New-JwtRsaSignature.ps1

function New-JwtRsaSignature
{
    <#
        NOTES: For certs to be useable by this method you must set the CSP to the signing certificate to be'Microsoft Enhanced RSA and AES Cryptographic Provider'.
                The following certutil command demonstrates this:
                certutil.exe -csp "Microsoft Enhanced RSA and AES Cryptographic Provider" -importpfx <target cert path> NoExport
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false,Position=0)]
        [ValidateLength(16,8192)][Alias("JWT", "Token")][String]$JsonWebToken,

        [Parameter(Mandatory=$true,Position=1)][Alias("Certificate", "Cert")]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningCertificate,

        [Parameter(Position=2,Mandatory=$true)]
        [ValidateSet("SHA256","SHA384","SHA512")]
        [String]$HashAlgorithm,

        [Parameter(Position=3,Mandatory=$false)]
        [Switch]$VerifyCertificate
    )

    PROCESS
    {
        [string]$stringSig = ""

        $decodeExceptionMessage = "Unable to decode JWT."
        $ArgumentException = New-Object -TypeName ArgumentException -ArgumentList $decodeExceptionMessage

        [bool]$isValidJwt = Test-JwtStructure -JsonWebToken $JsonWebToken
        if (-not($isValidJwt))
        {
            Write-Error -Exception $ArgumentException -Category InvalidArgument -ErrorAction Stop
        }
        else
        {
            if (($JsonWebToken.Split(".").Count) -ne 2)
            {
                Write-Error -Exception $ArgumentException -Category InvalidArgument -ErrorAction Stop
            }
        }

        $thumbprint = $SigningCertificate.Thumbprint

        if ($PSBoundParameters.ContainsKey($VerifyCertificate))
        {
            if (!($SigningCertificate.Verify()))
            {
                $verificationErrorMessage = "Certificate with thumbprint {0} failed verification. Check certificate chain, expiration, and CRL access." -f $thumbprint
                Write-Error -Exception ([CryptographicException]::new($verificationErrorMessage)) -Category SecurityError -ErrorAction Stop
            }
        }

        # Create an instance of the RSAPKCS1SignatureFormatter class that will ultimately be used to generate the signature:
        $rsaSigFormatter = [RSAPKCS1SignatureFormatter]::new()

        # Determine if executing context has read access to private key:
        [bool]$certHasPrivateKey = $false
        if ($null -ne $SigningCertificate.PrivateKey.KeyExchangeAlgorithm)
        {
            $certHasPrivateKey = $true
        }

        # Exception message for missing or inaccessible private key:
        $privateKeyErrorMessage = "Private key either not found or inaccessible for certificate with thumbprint {0}." -f $thumbprint

        if ($certHasPrivateKey)
        {
            try
            {
                $rsaSigFormatter.SetKey($SigningCertificate.PrivateKey)
            }
            catch
            {
                Write-Error -Exception ([CryptographicException]::new($privateKeyErrorMessage)) -Category SecurityError -ErrorAction Stop
            }
        }
        else
        {
            Write-Error -Exception ([CryptographicException]::new($privateKeyErrorMessage)) -Category SecurityError -ErrorAction Stop
        }

        # Set the RSA hash algorithm based on the RsaHashAlgorithm passed:
        $rsaSigFormatter.SetHashAlgorithm($HashAlgorithm.ToString())

        # Convert the incoming string $JsonWebToken into a byte array:
        [byte[]]$message = [Encoding]::UTF8.GetBytes($JsonWebToken)

        # The byte array that will contain the resulting hash to be signed:
        [byte[]]$messageDigest = $null

        # Create a SHA256, SHA384 or SHA512 hash and assign it to the messageDigest variable:
        switch ($HashAlgorithm)
        {
            "SHA256"
            {
                $shaAlg = [SHA256]::Create()
                $messageDigest = $shaAlg.ComputeHash($message)
                break
            }
            "SHA384"
            {
                $shaAlg = [SHA384]::Create()
                $messageDigest = $shaAlg.ComputeHash($message)
                break
            }
            "SHA512"
            {
                $shaAlg = [SHA512]::Create()
                $messageDigest = $shaAlg.ComputeHash($message)
                break
            }
            default
            {
                $shaAlg = [SHA512]::Create()
                $messageDigest = $shaAlg.ComputeHash($message)
                break
            }
        }

        # Create the signature:
        [byte[]]$sigBytes = $null
        try
        {
            $sigBytes = $rsaSigFormatter.CreateSignature($messageDigest)
        }
        catch
        {
            $signingErrorMessage = "Unable to sign $JsonWebToken with certificate with thumbprint {0}. Ensure that CSP for this certificate is 'Microsoft Enhanced RSA and AES Cryptographic Provider' and try again. Additional error info: {1}" -f $thumbprint, $_.Exception.Message
            Write-Error -Exception ([CryptographicException]::new($signingErrorMessage)) -Category SecurityError -ErrorAction Stop
        }

        # Return the Base64 URL encoded signature:
        $stringSig = ConvertTo-Base64UrlEncodedString -Bytes $sigBytes

        return $stringSig
    }
}