0

I can successfully connect to a company server using openssl:

openssl s_client -connect {ip_address}:{tcp_port} -cert client.pem -key client.key -CAfile ca.cer

The output shows the negotiated protocol is TLS 1.2.

I'd like to implement this connection in C#. Here is my current attempt. Note that I provide the client's certificate and private key, but I does not check the server certificate:

var clientPrivateKey = RSA.Create();
clientPrivateKey.ImportFromPem(File.ReadAllText("client.key"));
var clientCertificate = new X509Certificate2("client.pem").CopyWithPrivateKey(clientPrivateKey);
var clientCertificates = new X509Certificate2Collection(clientCertificate);

using (var tcpClient = new TcpClient(ipAddress, tcpPort))
using (var sslStream = new SslStream(tcpClient.GetStream()))
{
  sslStream.AuthenticateAsClient(new SslClientAuthenticationOptions
  {
    AllowRenegotiation = true,
    EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13,
    CertificateRevocationCheckMode = X509RevocationMode.NoCheck,
    ClientCertificates = clientCertificates,
    TargetHost = ipAddress, // ???
    RemoteCertificateValidationCallback = (_, _, _, _) => true
  });
}

Unfortunately sslStream.AuthenticateAsClient throws this error:

Received an unexpected EOF or 0 bytes from the transport stream. (System.IO.IOException)

I have read everything about this problem on MSDN, StackOverflow etc. Some observations:

Although the openssl command line says the protocol is TLS 1.2, I tried other values, and also tried not setting the EnabledSslProtocols value, but nothing helped. I also tried changing or omitting AllowRenegotiation and CertificateRevocationCheckMode, but that did not help either.

The TargetHost value cannot be null, but I don't know what it should be. I tried an empty string, the CN from the server's certificate, even the server's IP address, but nothing helped. Interestingly, the openssl command line above says Can't use SSL_get_servername, which is suspicious but I don't know what it means exactly.

-- Thank you for your help in advance!


UPDATE: I checked the traffic using tcpdump. In case of the C# code, there is a SYN/SYN-ACK/ACK, a FIN-ACK/ACK initiated by the server at 5 seconds, and a Client Hello from the client at 30 seconds. The client not only waits for 30 seconds, but also tries to use TLS 1.0, whatever settings I use.

kol
  • 27,881
  • 12
  • 83
  • 120
  • On a socket "received 0 bytes" is indeed the EOF signalling mechanism, and means the remote end has half-closed the connection (they may or may not still be receiving, but they definitely are done transmitting). – Ben Voigt Jul 31 '23 at 22:27
  • @BenVoigt Yes, that's what tcpdump shows (see my UPDATE above). Interestingly, if I set clientCertificates to null, the Client Hello won't wait 30 seconds but I will get "Authentication failed". I think if there is a client cert, something times out before Client Hello -- but what could that be? – kol Jul 31 '23 at 22:30
  • I would say that your client is expecting the server to start out by transmitting a challenge, and the server is waiting for your client to send a request. Both are waiting, no one talks until the server times out and closes the connection. Basically, the two ends are not using the same protocol. – Ben Voigt Jul 31 '23 at 22:38
  • You've already run wireshark to capture the failing exchange from C#. Now capture the successful connection setup from `openssl`. Which end sends first? – Ben Voigt Jul 31 '23 at 22:40
  • @BenVoigt The openssl client sends a TLS 1.2 Client Hello immediately after the SYN/SYN-ACK/ACK. Then the server sent an ACK and the Server Hello, etc. – kol Jul 31 '23 at 22:42
  • And what OS are you running on? Googling "AuthenticateAsClient" suggests that it relies on a service point manager which on non-Windows OS may need to be provided by the application. – Ben Voigt Jul 31 '23 at 22:51
  • It's Ubuntu 22.04. – kol Jul 31 '23 at 22:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/254738/discussion-between-kol-and-ben-voigt). – kol Jul 31 '23 at 22:52
  • Try doing it `async`. So `using var tcpClient = new TcpClient(); await tcpClient.ConnectAsync(ipAddress, tcpPort); using var sslStream = new SslStream(tcpClient.GetStream()); await sslStream.AuthenticateAsClientAsync(....` Also the `TargetHost` should be the name from the certificate, but you want to look at the Subject Alternative Name SAN field, not the CN. – Charlieface Aug 01 '23 at 00:21
  • @Charlieface Thanks, but async didn't help. I checked the SAN field, it contains 2 IP addresses and 3 DNS names, and I have already tested one of the IP addresses and 1 of the DNS names (the CN is the same as one of them). At the moment it looks like the problem is caused by that AuthenticateAsClient hangs for 30 secs if I use this particular client certificate and private key. I'm trying to track down why. – kol Aug 01 '23 at 12:04

0 Answers0