2

I am trying to send a Client-Certificate authenticated HTTPS-Request to a Web Server.

The only way I was able to get a "200" Response from this Server was with the following code using the Webrequest Class:

using namespace System.Security.Cryptography.X509Certificates

$certPath = '.\client_cert.pfx'
$URI = 'https://example-api.com/api'

$cert = New-Object X509Certificate2($certPath, $certPassword)

Write-Host "Cert Has Private Key: $($cert.HasPrivateKey)"

$request = [System.Net.WebRequest]::Create($URI)
$request.Method = "GET"
$request.ClientCertificates.Add($cert)

$response = $request.GetResponse()
$responseStream = $response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($responseStream)
$responseContent = $reader.ReadToEnd()

$reader.Close()
$responseStream.Close()
$response.Close()

$responseContent

The server responds with "200 - OK" and returns the information wanted.

However I have read that this class is obsolete and that I should rather use the HttpClient Class.

I tried to replicate the above request like so:

using namespace System.Security.Cryptography.X509Certificates
using namespace System.Security.Authentication
using namespace System.Net.Http

$certPath = '.\client_cert.pfx'
$URI = 'https://example-api.com/api'

$cert = New-Object X509Certificate2($certPath, $certPassword)

Write-Host("Cert has private key: ", $cert.HasPrivateKey)

$handler = New-Object HttpClientHandler

$handler.ClientCertificateOptions.Manual
$handler.ClientCertificates.Add($cert)

# write-host for debugging
Write-Host("Client Certificate Options: ", $handler.ClientCertificateOptions)
Write-Host("Client Certificates: ", $handler.ClientCertificates)

$client = New-Object HttpClient($handler)
$client.HttpClientHandler
$response = $client.GetAsync($URI).GetAwaiter().GetResult()
$response

Unfortunately, the server answers with "403 - Forbidden" even though I use the exact same certificate file.

I also tried replicating the call with the built-in cmdlet Invoke-Webrequest like so:

using namespace System.Security.Cryptography.X509Certificates

$certPath = '.\client_cert.pfx'
$URI = 'https://example-api.com/api'

$cert = New-Object X509Certificate2($certPath, $certPassword)

Write-Host("Cert has private key: ", $cert.HasPrivateKey)

$response = Invoke-Restmethod -Method "GET" -URI $URI -Certificate $cert
#$response2 = Invoke-Restmethod -Method "GET" -URI $URI -CertificateThumbprint $cert.Thumbprint

$response

But this also always yields "403 - Forbidden".

Can someone tell me where the mistake is? Because I am clearly missing it.

Why does the obsolete Webrequest Class work, while Invoke-Webrequest and HttpClient Class don't?

ABF
  • 57
  • 9
  • As an aside: It's best to avoid pseudo method syntax. `Write-Host(...)` -> `Write-Host ...`, and `New-Object X509Certificate2($certPath, $certPassword)` -> `New-Object X509Certificate2 $certPath, $certPassword` See [this answer](https://stackoverflow.com/a/65208621/45375) for details. – mklement0 Jul 14 '23 at 14:03
  • 1
    It may not be the cause of your problem, but note that you should always pass _full_ file-system paths to .NET methods and constructors, given that .NET's working directory usually differs from PowerShell's: `New-Object X509Certificate2 (Convert-Path $certPath), $certPassword` or, using v5+ syntax (where method syntax _is_ necessary): `[X509Certificate2]::new((Convert-Path $certPath), $certPassword)` – mklement0 Jul 14 '23 at 14:06

0 Answers0