I set up a request as follows:
var url = "https://api.the.url.com/path/ver/endpoint";
var certFile = "TheFile.pfx";
var certPass = "ThePassword";
var cert = new X509Certificate2(
Path.Combine(AppContext.BaseDirectory, certFile),
certPass, X509KeyStorageFlags.MachineKeySet);
var handler = new SocketsHttpHandler();
handler.SslOptions.ClientCertificates = new X509CertificateCollection();
handler.SslOptions.ClientCertificates.Add(cert);
handler.SslOptions.LocalCertificateSelectionCallback = (_,_,_,_,_) => cert;
var httpClient = HttpClientFactory.Create(handler);
var result = await httpClient.GetAsync(url);
result.EnsureSuccessStatusCode();
This occasionally fails on Windows, but on Linux it always fails:
The SSL connection could not be established, see inner exception.
Unable to write data to the transport connection: Connection reset by peer.
Connection reset by peer
When I looked at the tcp traffic, the handshake stops when (me) the client ACKs the server's Server Key Exchange/Server Hello Done
:
Disclaimer: These are windows traffic dump, I am still working on getting these on Linux
TCP 66 44222 → 443 [SYN, ECE, CWR] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
TCP 66 443 → 44222 [SYN, ACK, ECE] Seq=0 Ack=1 Win=14600 Len=0 MSS=1436 WS=1 SACK_PERM
TCP 54 44222 → 443 [ACK] Seq=1 Ack=1 Win=262656 Len=0
TLSv1.2 328 Client Hello
TCP 60 443 → 44222 [ACK] Seq=1 Ack=275 Win=14874 Len=0
TLSv1.2 1514 Server Hello
TCP 1514 443 → 44222 [ACK] Seq=1461 Ack=275 Win=14874 Len=1460 [TCP segment of a reassembled PDU]
TCP 54 44222 → 443 [ACK] Seq=275 Ack=2921 Win=262656 Len=0
TLSv1.2 1514 Certificate, Server Key Exchange
TLSv1.2 360 Certificate Request, Server Hello Done
TCP 54 44222 → 443 [ACK] Seq=275 Ack=4687 Win=262656 Len=0
TCP 109 443 → 44222 [RST, ACK] Seq=4687 Ack=275 Win=0 Len=55
Instead of RST, ACK
I am supposed to get something like this:
TLSv1.2 2043 Certificate, Client Key Exchange, Certificate Verify, Change Cipher Spec, Encrypted Handshake Message
TCP 60 443 → 44566 [ACK] Seq=4687 Ack=2264 Win=16863 Len=0
TLSv1.2 105 Change Cipher Spec, Encrypted Handshake Message
TLSv1.2 153 Application Data
TCP 60 443 → 44566 [ACK] Seq=4738 Ack=2363 Win=16962 Len=0
TLSv1.2 642 Application Data
TLSv1.2 88 Application Data
TCP 54 44566 → 443 [ACK] Seq=2363 Ack=5360 Win=261888 Len=0
TCP 54 44566 → 443 [RST, ACK] Seq=2363 Ack=5360 Win=0 Len=0
This code used to work for some time, and for some unknown reason stopped working. Running the equivalent cUrl
request seems to work as expected on the Linux machine. So it appears to be .NET related. I tried to use RestSharp
but I think under the hood it is using HttpClient
and suffered the same error. I ran nmap --script ssl-enum-ciphers
against the target url, and I changed the http handler to use the mentioned ssl protocol and cipher suites, but that also did not change the outcome.
What should I try next?
Update 1:
I managed to pull the tcp dump from the Linux system, and the RST
was caused by ssl handshake timeout exceeded
. I don't think we can skip the ssl verification using HttpClient
, only ignore its outcome. I tried with SslStream
which also does not have any such setting.
Update 2:
I added event listener and printing them in the console. Also, I ran the code in a linux vm so wireshark can sniff ALL traffic through the virtual interface (previously I was filtering for the target ip only).
I added an event listener around the HttpClient
; it prints events from System.Net
and System.Security
. I found out that after the server sends the Server Certificate/Server Hello Done
, System.Security.Cryptography.X509Certificates.X509Chain.OpenSsl
is attempting to fetch issuing certificate defined in the client certificate. The end point for that can't be reached causing the handshake timeout. The best solution was to replace the client certificate used to make the request. An alternative solution is to block this url in your firewall, this will cause the request to fail right away avoiding timeout.