Asynchronous Socket With TLS/SSL

11,808

Solution 1

Not sure how to do it in C#, but it can be done in Java using the SSLEngine. This API is generally acknowledged as rather difficult to program. So, yes, it's possible to use asynchronous sockets for SSL/TLS, but I'm not sure what the equivalent of Java's SSLEngine would be. Perhaps there is another (better?) API for this in C#.

There are a few problems that you will almost inevitably encounter along this path (based on Java experience, but this would apply similarly in C#):

While SSLSockets tend to do a fair job at behaving similarly to normal Sockets, there are slight edge cases due to the nature of SSL/TLS. The effect of these differences is even more important with asynchronous I/O. Some of these problems are described in this (rather long) answer to "Properly closing SSLSocket" (in Java).

In addition, some SSL/TLS behaviours are already ill-defined with respect to the application layer, and get a bit messier with asynchronous behaviour. I have client-certificate renegotiation (or renegotiation in general) in mind. Using SSL/TLS, either party can in principle initiate a renegotiation handshake. This is done for example if you protect only one directory with client-certificates in Apache Httpd, or if only a portion of a web-app requires CLIENT-CERT in a Java container. When using client-certificate authentication, even IIS uses re-negotation by default. This consists of doing a second handshake during the SSL/TLS connection (effectively to get more information from the client here: its client-certificate).

When it works (usually with blocking I/O), the traffic looks like this (here both SSL and HTTP layers):

C->S SSL Client Hello
S->C SSL Server Hello, Certificate, Server Hello Done
C->S SSL Client Key Exchange, Change Cipher Spec, Finished
S->C SSL Change Cipher Spec
(then encrypted)
C->S SSL Finished
C->S HTTP GET /.../
S->C SSL Hello Request
C->S SSL Client Hello
S->C SSL Server Hello, Certificate, Certificate Request, Server Hello Done
C->S SSL Certificate, Client Key Exchange, Certificate Verify, Change Cipher
Spec, Finished
S->C SSL Change Cipher Spec
C->S SSL Finished
S->C HTTP 200 OK

Re-negotiating in asynchronous mode is quite tricky, since the re-negotiation should apply to both sides of the traffic at the same time. Therefore, the underlying properties of the SSL/TLS session may change during its use by the application layer (which isn't normally expected to handle this). One side could still be sending data assuming certain SSL/TLS settings, while the re-negotiation occurs, thereby affecting both sides.

The implementation of all this can be difficult, as shown in this Grizzly issue, for example.

Solution 2

Well I was looking for an answer myself to support TLS / SSL over tcp/ip sockets using C# and ran across this using Stream Sockets

Share:
11,808
Javanese Girl
Author by

Javanese Girl

Updated on June 04, 2022

Comments

  • Javanese Girl
    Javanese Girl almost 2 years

    I saw examples programs on MSDN: Asynchronous Socket as below. I've tried the program and run normally. Is it possible Asynchronous Socket to be modified in order to support TLS/SSL? How to do it?

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    
    // State object for reading client data asynchronously
    public class StateObject {
        // Client  socket.
        public Socket workSocket = null;
    
        // Size of receive buffer.
        public const int BufferSize = 1024;
    
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
    
        // Received data string.
        public StringBuilder sb = new StringBuilder();  
    }
    
    public class AsynchronousSocketListener {
        // Thread signal.
        public static ManualResetEvent allDone = new ManualResetEvent(false);
    
        public static void StartListening() {
            // Data buffer for incoming data.
            byte[] bytes = new Byte[1024];
    
            // Establish the local endpoint for the socket.
            // The DNS name of the computer
            // running the listener is "host.contoso.com".
            IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
            IPAddress ipAddress = ipHostInfo.AddressList[0];
            IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);
    
            // Create a TCP/IP socket.
            Socket listener = new Socket(AddressFamily.InterNetwork,
            SocketType.Stream, ProtocolType.Tcp );
    
            // Bind the socket to the local endpoint and listen for incoming connections.
            try {
                listener.Bind(localEndPoint);
                listener.Listen(100);
    
                while (true) {
                    // Set the event to nonsignaled state.
                    allDone.Reset();
    
                    // Start an asynchronous socket to listen for connections.
                    Console.WriteLine("Waiting for a connection...");
    
                    listener.BeginAccept(new AsyncCallback(AcceptCallback), listener );
    
                    // Wait until a connection is made before continuing.
                    allDone.WaitOne();
                }
            } 
            catch (Exception e) {
                Console.WriteLine(e.ToString());
            }
    
            Console.WriteLine("\nPress ENTER to continue...");
            Console.Read();    
        }
    
        public static void AcceptCallback(IAsyncResult ar) {
            // Signal the main thread to continue.
            allDone.Set();
    
            // Get the socket that handles the client request.
            Socket listener = (Socket) ar.AsyncState;
            Socket handler = listener.EndAccept(ar);
    
            // Create the state object.
            StateObject state = new StateObject();
            state.workSocket = handler;
            handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
            new AsyncCallback(ReadCallback), state);
        }
    
        public static void ReadCallback(IAsyncResult ar) {
            String content = String.Empty;
    
            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject state = (StateObject) ar.AsyncState;
            Socket handler = state.workSocket;
    
            // Read data from the client socket. 
            int bytesRead = handler.EndReceive(ar);
    
            if (bytesRead > 0) {
                // There  might be more data, so store the data received so far.
                state.sb.Append(Encoding.ASCII.GetString(
                state.buffer,0,bytesRead));
    
                // Check for end-of-file tag. If it is not there, read 
                // more data.
                content = state.sb.ToString();
                if (content.IndexOf("<EOF>") > -1) {
                    // All the data has been read from the 
                    // client. Display it on the console.
                    Console.WriteLine("Read {0} bytes from socket. \n Data : {1}",
                    content.Length, content );
                    // Echo the data back to the client.
                    Send(handler, content);
                } 
                else {
                    // Not all data received. Get more.
                    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReadCallback), state);
                }
            }
        }
    
        private static void Send(Socket handler, String data) {
            // Convert the string data to byte data using ASCII encoding.
            byte[] byteData = Encoding.ASCII.GetBytes(data);
    
            // Begin sending the data to the remote device.
            handler.BeginSend(byteData, 0, byteData.Length, 0,
            new AsyncCallback(SendCallback), handler);
        }
    
        private static void SendCallback(IAsyncResult ar) {
            try {
                // Retrieve the socket from the state object.
                Socket handler = (Socket) ar.AsyncState;
    
                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
                Console.WriteLine("Sent {0} bytes to client.", bytesSent);
    
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();
    
            } 
            catch (Exception e) {
                Console.WriteLine(e.ToString());
            }
        }
    
    
        public static int Main(String[] args) {
            StartListening();
            return 0;
        }
    }