2

I've been given a server application that is listening for a client connection. The following openssl command does connect...

openssl s_client -key provided.key -cert provided.crt -CAfile provided.pem -connect 127.0.0.1:59123

I have a C# application that creates a TcpClient and attempts to authenticate an SslStream. The BeginAuthenticateAsClient requires a certificate. Obviously, the provided .crt file isn't good enough as there needs to be the provided .key involved. I've tried using openssl to create a .pfx file from the .crt and .key file but that didn't work (file created but server closed the connection). This cannot be too hard to mimic the open ssl command in .net but I haven't found anything online that works or even comes close to what I'm trying to do. Does anyone know how to do this or is there a different approach I should be using? P.S. Third party software is not really an option.

jww
  • 97,681
  • 90
  • 411
  • 885
  • Did you ever find a solution to this @Hendrik? I currently have a similar issue. – M0rty Sep 06 '17 at 22:27
  • Unfortunately not. I'm still struggling with this. I imported the openssl C# wrapper and couldn't find any information on how to use it to actually connect to a server. Tons of information about creating keys can be found but nothing on using it to actually connect. – Hendrik Vis Sep 11 '17 at 14:39
  • I found a way around my issue. I just set the `RemoteCertificateValidationCallback` to always return true. Obviously not the correct way for Production but will help for testing purposes. I will post my solution below, this may not be the solution to your issue but hopefully it helps. – M0rty Sep 11 '17 at 23:58

2 Answers2

0
openssl pkcs12 -export -out provided.pfx -inkey provided.key -in provided.crt

...

var coll = new X509Certificate2Collection();
var cert = new X509Certificate2("provided.pfx", "WhateverPasswordYouGaveIt");
coll.Add(cert);

// do TcpClient stuff
var sslStream = new SslStream(tcpClient.GetStream());
const SslProtocols allowedProtocols = SslProtocols.Tls12 | SslProtocols.Tls11;
sslStream.AuthenticateAsClient("127.0.0.1", coll, allowedProtocols, checkCertificateRevocation: true);

For the -CAFile equivalent you'll need to do a custom callback and compare the certificate in chain.ChainElements[chain.ChainElements.Length - 1].Certificate to the one you expect.

bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • Thanks bartonjs. I'm pretty much doing that and the server closes my connection. It would be nice if the rejection was specified as to what caused it but I just get the ole "remote party has closed the transport stream" when I call EndAuthenticateAsClient. – Hendrik Vis Aug 17 '17 at 19:29
  • The openssl command works and connects. I try it programmatically in C# and it doesn't connect. The only difference between your suggestion and what I'm doing is I'm asynchronous. – Hendrik Vis Aug 17 '17 at 19:32
  • OK, this is where I'm confused. The server needs the private key from the client right? The server is set to not require the client to have a certificate. How then on the client side do I feed the key without a certificate? – Hendrik Vis Aug 17 '17 at 19:52
  • The server doesn't need the private key. It needs the certificate (public data) and the client to do work which proves it has the private key. I'd recommend hosting a session with `openssl s_server` and trying to connect, OpenSSL should report where things are going wrong. – bartonjs Aug 17 '17 at 21:05
  • This is where my ignorance on the whole SSL thing comes into play. As you suggested, I used openssl s_server with the provided server key and cert files and attempted a connection using my software. I get the following error on the server side: 3524:error:1408A0C1:SSL routines:ssl3_get_client_hello:no shared cipher:.\ssl\s3 _srvr.c:1411: BUT... If I connect using openssl with my provided client key and crt, I connect fine. There is something in the C# that I'm missing. – Hendrik Vis Aug 18 '17 at 16:00
  • Ah, you're probably working on an older framework which by default uses TLS 1.0 or SSL3 as the only protocols it wants to speak. I've updated my answer to show the usage of the overload which takes protocols. If you can get away with just `Tls12` that's best, but you might also need `Tls11`, so I threw it in there. – bartonjs Aug 18 '17 at 16:05
  • Unfortunately, I receive the same error. On the client side, the authentication callback (as client) throws an exception stating "A call to SSPI failed, see inner exception. The message received was unexpected or badly formatted". I think I'm up against too many variables and need to start from square one. I have a key file and a certificate file for both the server and client side. When I use openssl s_server and s_client, the two sides can talk to each other (after password entry). The goal is to get a C# application to talk to the server no matter how that happens. Any suggestions? – Hendrik Vis Aug 18 '17 at 18:36
  • Hi @HendrikVis , you issue seems same as mine. I do have a C# SSLStream client and an OpenSSL server c++. The moment it executes sslStream.AuthenticateAsClient("ServerCertificateName"); it gives the error. Error message: A call to SSPI failed, see inner exception. InnerException : {"The message received was unexpected or badly formatted"}. Can you share the fix steps if you have solved this issue. Thanks. – S.Pradeep Feb 02 '22 at 11:18
0

As mentioned, this is only for testing purposes as validating the server certificate should never just return true.

SslStream test class:

    public string TestingSSL(string sessionId)
    {
        TcpClient client = new TcpClient("name of the server", port);

        //Create certificate
        var collection = new X509Certificate2Collection();

        var certificate = new X509Certificate2("provided.pfx", "Passwordyouchoosewhencreatingthe.pfx", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
        collection.Add(certificate);

        const SslProtocols allowedProtocols = SslProtocols.Tls12 | SslProtocols.Tls11;
        // Create an SSL stream that will close the client's stream.
        SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificateTrue), null);

        // The server name must match the name on the server certificate.
        try
        {
            sslStream.AuthenticateAsClient("name of the server", collection, allowedProtocols, checkCertificateRevocation: true);
        }
        catch (AuthenticationException e)
        {
            if (e.InnerException != null)
            {
                return e.InnerException.ToString();
            }
        }

        // Read message from the server.
        string serverMessage = ServerResponseMessage(sslStream);

        byte[] sessionBill = Encoding.UTF8.GetBytes("sessions.bill\r\n");
        byte[] sessionBillId = Encoding.UTF8.GetBytes($",session_uuid,{sessionId}\r\n");

        // Send command to the server. 
        sslStream.Write(sessionBill, 0, sessionBill.Length);
        sslStream.Flush();
        sslStream.Write(sessionBillId, 0, sessionBillId.Length);
        sslStream.Flush();

        string sessionBillResponseMessage = ServerResponseMessage(sslStream);

        // Close the client connection.
        client.Close();

        return sessionBillResponseMessage;
    }


    public static bool ValidateServerCertificateTrue(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        return true;
    }


    static string ServerResponseMessage(SslStream sslStream)
    {     
        byte[] buffer = new byte[2048];
        StringBuilder messageData = new StringBuilder();

        int bytes = -1;

        bytes = sslStream.Read(buffer, 0, buffer.Length);
        // Use Decoder class to convert from bytes to UTF8
        // in case a character spans two buffers.
        Decoder decoder = Encoding.UTF8.GetDecoder();
        char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];

        decoder.GetChars(buffer, 0, bytes, chars, 0);
        messageData.Append(chars);     

        return messageData.ToString();
    }
M0rty
  • 985
  • 3
  • 15
  • 35
  • Thanks M0rty, This works as long as the server is using .NET however, my server is using openssl and nothing seems to work. – Hendrik Vis Sep 20 '17 at 15:17