Sending a HTTPS-Request with Client Certificate from Azure Function to external Webserver

664 views Asked by At

I am starting to get a little bit desperate. When sending an HTTPS-Request from my local machine using the following code, everything works fine and I get my expected response from the webserver.

# Get the certificate as secret (includes public and private key, so careful) from Azure Key Vault
$pfxCert = Get-AzKeyVaultSecret -VaultName $vaultName -Name $pfxCertName -AsPlainText

# Decode the Base64-encoded secret value to a byte array
$pfxBytes = [System.Convert]::FromBase64String($pfxCert)

# Output the plain text value for debugging
Write-Host("pfxBytesLength: ", $pfxBytes.Length)

# Create a X509Certificate2 object from the .pfx file and password
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(,[byte[]]$pfxBytes)

# Check if the certificate has a private key
Write-Host("Cert has private key: ", $cert.HasPrivateKey)

# Create a web request object
$request = [System.Net.WebRequest]::Create($URI)
$request.Method = "GET"

# Add the client certificate to the request
$request.ClientCertificates.Add($cert)

# Send the request and get the response
$response = $request.GetResponse()

# Get the response stream
$responseStream = $response.GetResponseStream()

# Create a reader to read the response content
$reader = New-Object System.IO.StreamReader($responseStream)

# Read the response content as a string
$responseContent = $reader.ReadToEnd()

# Close the reader, response stream, and response objects
$reader.Close()
$responseStream.Close()
$response.Close()

# Output the response content
$responseContent

However, when I want to run this code in an Azure Function (S1, Windows, Powershell 7.2) which works as my client, I get an error message saying:

[Error] EXCEPTION: The SSL connection could not be established, see inner exception.
Message: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot

I am clueless how to fix this problem.

I tried different approaches to overcome the issue:

  1. Completely deactivating remote server certificate validation using
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
  1. Implementing a custom SSL validation callback like so (I uploaded the ca-certs to the app filesystem)
# Create an array of CA certificate filepaths
        $caCertificates = @(
            "C:\home\ca-certificates\issuing_3.cer",
            "C:\home\ca-certificates\intermediate_2.cer",       
            "C:\home\ca-certificates\root_1.cer" 
        )
        
        # Load the CA certificates and create X509Certificate2 objects
        $caCertificateObjects = @()
        foreach ($caCertificatePath in $caCertificates) {
            $caCertificateBytes = [System.IO.File]::ReadAllBytes($caCertificatePath)
            $caCertificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(,$caCertificateBytes)
            $caCertificateObjects += $caCertificate
            Write-Host("Added ", $caCertificate.Thumbprint, " to certificates")
        }
        
        # Create an X509Chain object and add the CA certificates to the chain
        $certificateChain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
        $certificateChain.ChainPolicy.ExtraStore.AddRange($caCertificateObjects)
        
        # Output the ExtraStore contents
        $certificateChain.ChainPolicy.ExtraStore

        # Define the callback function for certificate validation
        $certificateValidationCallback = {
            param(
                [System.Object]$sender,
                [System.Security.Cryptography.X509Certificates.X509Certificate]$certificate,
                [System.Security.Cryptography.X509Certificates.X509Chain]$certificateChain,
                [System.Net.Security.SslPolicyErrors]$sslPolicyErrors
            )
        
            # Validate the remote server certificate against the custom CA certificate chain
            $isValid = $certificateChain.Build($certificate)
        
            if ($isValid) {
                return $true  # Accept the remote server certificate
            } else {
                return $false # Reject the remote server certificate
            }
        }
        
        # Register the callback function for certificate validation
        [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $certificateValidationCallback
  1. I tried to add the .pfx certificate through the Azure Portal by navigating to the Function App and under "Settings"->"Certificates" added the .pfx file from the Azure Key Vault under "bring your own certificates (.pfx)". Added the "WEBSITE_LOAD_CERTIFICATES" application setting and set it to "*". Finally I tried to send the HTTPS-Request via the Invoke-Restmethod Cmdlet like so
Invoke-Restmethod -Method "GET" -URI $URI -CertificateThumbprint $certThumbprint
  1. I also tried to skip the certificate check when using Invoke-Restmethod using -SkipCertificateCheck but to no avail.

  2. I am now using the HttpClient class and it is still not working:

# Get the certificate as secret (includes public and private key, so careful) from Azure Key Vault
$pfxCert = Get-AzKeyVaultSecret -VaultName $vaultName -Name $pfxCertName -AsPlainText

Write-Host("pfxCertType: ", $pfxCert.GetType())
Write-Host("pfxCertLength: ", $pfxCert.Length)

# Decode the Base64-encoded secret value to a byte array
$pfxBytes = [System.Convert]::FromBase64String($pfxCert)

# Output the plain text value for debugging
Write-Host("pfxBytesLength: ", $pfxBytes.Length)

$keyStorageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet

# Create a X509Certificate2 object from the .pfx file and password
$cert = New-Object X509Certificate2([byte[]]$pfxBytes, "", $keyStorageFlags)

$handler = New-Object HttpClientHandler

# Gets or sets a value that indicates if the certificate is automatically picked from the certificate store 
# or if the caller is allowed to pass in a specific client certificate.
$handler.ClientCertificateOptions.Manual
$handler.ClientCertificates.Add($cert)

Write-Host("Client Certificate Options: ", $handler.ClientCertificateOptions)
Write-Host("Client Certificates: ", $handler.ClientCertificates)

$client = New-Object HttpClient($handler)

$response = $client.GetAsync($URI).GetAwaiter().GetResult()
$responseContent = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()

Write-Host $response
$responseContent

Nothing worked for me. I am still stuck on the UntrustedRoot Error.

Does anyone have an idea what I am doing wrong?

Thank you so much for your time!

0

There are 0 answers