0

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!

ABF
  • 57
  • 9
  • Don't use `WebRequest`, use `HttpClient`. – Ian Kemp May 30 '23 at 14:51
  • Certificates never get transferred between client and server. Only the name of the certificates get transferred. The certificates must be loaded before the request is sent on both client and server. TLS the server sends a certificate block with list of names of possible certificates. The client then looks up names in stores to find matching name. Client code can choose any of the names or a particular name. – jdweng May 30 '23 at 15:20
  • I am now using the HttpClient class (see original post point 5) it is still not working. @IanKemp – ABF May 31 '23 at 09:39

0 Answers0