13

I have a multi threaded socket listener. It listens on a port. It receives data from HTTP web sites and sends response according to their request. It works good.

Now I want to do same thing with an HTTPS web site. So I changed it to be able to read and write via SslStream instead of socket.

The web site owner gave me a pfx file and a password. I placed it into the trusted certificates list of my local certification store.

The certification file is retreived from the store successfully. But after calling AuthenticateAsServer method my handler socket seems to be empty. So I cannot see any data in it inside the ReceiveCallBack method.

How can I apply pfx certificate to my SslStream socket?

Here is my code:

using System;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.IO;

namespace SslStreamTestApp
{
    public class SslStreamSocketListener
    {
        private static readonly ManualResetEvent _manualResetEvent = new ManualResetEvent(false);
        private readonly string pfxSerialNumber = "12345BLABLABLA";

        X509Certificate _cert = null;

        private void GetCertificate()
        {
            X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);

            store.Open(OpenFlags.ReadOnly);

            X509Certificate2Collection certificateCollection = store.Certificates.Find(X509FindType.FindBySerialNumber, pfxSerialNumber, true);

            if (certificateCollection.Count == 1)
            {
                _cert = certificateCollection[0];
            }
        }

        private void StartListening()
        {
            GetCertificate();

            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 9002);

            if (localEndPoint != null)
            {
                Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                if (listener != null)
                {
                    listener.Bind(localEndPoint);
                    listener.Listen(5);

                    Console.WriteLine("Socket listener is running...");

                    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
                }
            }
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            _manualResetEvent.Set();

            Socket listener = (Socket)ar.AsyncState;

            Socket handler = listener.EndAccept(ar);

            SslStream stream = new SslStream(new NetworkStream(handler, true));
            stream.AuthenticateAsServer(_cert, false, System.Security.Authentication.SslProtocols.Ssl3, true);

            StateObject state = new StateObject();
            state.workStream = stream;

            stream.BeginRead(state.buffer, 0, StateObject.BufferSize, ReceiveCallback, state);

            listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
        }

        private void ReceiveCallback(IAsyncResult result)
        {
            StateObject state = (StateObject)result.AsyncState;
            SslStream stream = state.workStream;

            // Check how many bytes received
            int byteCount = stream.EndRead(result);

            Decoder decoder = Encoding.UTF8.GetDecoder();

            char[] chars = new char[decoder.GetCharCount(state.buffer, 0, byteCount)];

            decoder.GetChars(state.buffer, 0, byteCount, chars, 0);

            state.sb.Append(chars);

            string check = state.sb.ToString();

            if (state.sb.ToString().IndexOf("<EOF>") == -1 && byteCount != 0)
            {
                // We didn't receive all data. Continue receiving...
                stream.BeginRead(state.buffer, 0, state.buffer.Length, new AsyncCallback(ReceiveCallback), stream);
            }
            else
            {
                string[] lines = state.sb.ToString().Split('\n');

                // send test message to client
                byte[] test = Encoding.UTF8.GetBytes("\"infoMessage\":\"test\"");
                stream.BeginWrite(test, 0, test.Length, WriteAsyncCallback, stream);
            }
        }

        private void WriteAsyncCallback(IAsyncResult ar)
        {
            SslStream sslStream = (SslStream)ar.AsyncState;

            Console.WriteLine("Sending message to client...");

            try
            {
                sslStream.EndWrite(ar);
            }
            catch (IOException)
            {
                Console.WriteLine("Unable to complete send.");
            }
            finally
            {
                sslStream.Close();
            }
        }
    }

    public class StateObject
    {
        public SslStream workStream = null;
        public const int BufferSize = 1024;
        public byte[] buffer = new byte[BufferSize];
        public StringBuilder sb = new StringBuilder();
    }
}

EDIT: I think I have some problem about SSL logic. PFX file belongs to HTTPS web site. But requests come from HTTPS to my socket listener. Then my socket listener sends response. In this situation, am I server or client? Will I use AuthenticateAsServer or AuthenticateAsClient?

It does not throw error if I call AuthenticateAsServer. It becomes Authenticated with no problem. But I cannot carry the data in handler socket to ReceiveCallBack method via the SslStream of my StateObject.

Orkun Bekar
  • 1,447
  • 1
  • 15
  • 36
  • Is there any reason you start to receive on the handler itself, and not on `SslStream stream`? The sender most likely sends encrypted data anyway. – Caramiriel Apr 27 '15 at 12:51
  • I don't want to make big change in my code because it works good. So, I'm looking if there is a way of using socket for HTTPS. – Orkun Bekar Apr 27 '15 at 12:56
  • I'm not an expert on low level transport, but I guess that's impossible. Can't expect reading a 'raw' socket and get plain-text while receiving, if the client is using SSL. You'll need something to parse/decode the SSL stream (thats what `SslStream` is for). You can adapt existing code to use `Stream`/`NetworkStream`. Then `SslStream` should be fairly easy. – Caramiriel Apr 27 '15 at 13:02
  • I think that should be an answer not a comment. Maybe you are right. – Orkun Bekar Apr 27 '15 at 13:06
  • @Caramiriel I changed it to SslStream but it still cannot read data. Check my edited question. – Orkun Bekar May 06 '15 at 08:47
  • @OrkunBekar: What do you mean with `But after calling AuthenticateAsServer method my handler socket seems to be empty`? – jgauffin May 06 '15 at 12:38
  • Your receive code is also flawed as the buffer might contain more than one message (and you do currently not handle that). – jgauffin May 06 '15 at 12:42
  • At first handler socket's available value is greater than 0. That means it contains data which came from client. But in ReceiveCallBack method the byteCount is always 0. – Orkun Bekar May 06 '15 at 12:56
  • 0 means that the other end point disconnected. Are you sure that the other side is working correctly? – jgauffin May 06 '15 at 13:45
  • Yes, I increase the timeout of web side so I don't think there is a problem. – Orkun Bekar May 06 '15 at 14:14
  • Add some try-catch statements and examine the exceptions. The exception message may indicate the cause of your problem. – JohnH May 06 '15 at 14:30
  • Actually there are try catch blocks but I removed them to give you a minimal code example. There is no error. Just 0 bytes in ReceiveCallBack method. Maybe I move wrong stream to receive method I don't know. – Orkun Bekar May 06 '15 at 14:45

1 Answers1

4

You can load the pfx file direct with:

byte[] pfxData = File.ReadAllBytes("myfile.pfx");

cert = new X509Certificate2(pfxData, "password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);

If you are loading it from the cert store its importand that the key is exportable for the application.

Info: SSL version 3.0 is no longer secure. Maybe thats why u cant get a connection. Chrome and Firefox disabled the support.

Tip: Write a simple SSL-Client to send some "Hello World" bytes.

I tested your code with a self signed cert in pfx format over a c# client and the chrome browser. both clients could connect and send/receive data.

the only thing i changed i set the SSLProtocol to TLS

stream.AuthenticateAsServer(_cert, false, System.Security.Authentication.SslProtocols.Tls, true);

Info: HTTPS is the HTTP protocol over a tcp/tls connection. You have a raw tcp/tls connection without the http protocol.

TIP: Dont forget to open the Firewall Port 9002 !

  • How can I make it exportable for the application? It is stored in trusted publishers of cert store. I opened it via cert store also I added it to the solution and tried opening it directly from there but I could not achieve. I tried Tls instead of SSL3 but didn't help. – Orkun Bekar May 07 '15 at 07:01
  • You can test for the privatekey with: if(!cert.HasPrivateKey) throw new Exception("private key not loaded."); I will test your code in some hours. – Andreas Dirnberger May 07 '15 at 13:58