DOTNET Core   Socket   TLS/SSL  
▌Introduction
This
article will shows how to enhance a Socket Server/Client to supports TLS/SSL
streaming.
TLS/SSL encrypts the sensitive data
  being sent between client and server, and authenticate a server. 
 | 
 
To enable
TLS/SSL streaming in Socket, here are the requirements:
2.  Must add the cert to trusted root certificates to bypass
“invalid CA“ error or ignore checking the self-signed certificate in client
side.
The
source code is on my Github: KarateJB/DotNetCore.Socket.Sample,
which includes the sample codes of Socket/SSL-Socket server and client. 
▌Environment
▋.NET Core 3.1.300
▌Implement
▋(Optional) Create a self-signed certificate
$ MakeCert -ss Root -sr LocalMachine -a SHA256 -n "CN=127.0.0.1,CN=localhost" -sv local.pvk local.cer -pe -e 12/31/2099 -len 2048
$ pvk2pfx.exe -pvk local.pvk -spc local.cer -pfx local.pfx
Reference:
[ASP.Net
Core] Self-signed SSL certificate
The .pfx file (contains public and
private keys) is for a Socket server.
The .cer
file (contains public key) is for a Socket client.
▋Server side
First
lets create a state-object class for storing buffered streaming data.
▋SslStramState.cs
public class SslStreamState : IStateObject
{
        /// <summary>
        /// Client Socket
        /// </summary>
        public SslStream SslStream = null;
        private const int FixedBufferSize = 1024;
        /// <summary>
        /// Constructor
        /// </summary>
        public SslStreamState()
        {
            this.Buffer = new byte[this.BufferSize];
            this.Content = new StringBuilder();
        }
        /// <summary>
        /// Size of receive buffer
        /// </summary>
        public int BufferSize
        {
            get { return FixedBufferSize; }
        }
        /// <summary>
        /// Receive buffer
        /// </summary>
        public byte[] Buffer { get; set; }
        /// <summary>
        /// Received data string
        /// </summary>
        public StringBuilder Content { get; set; }
}
Then create
a TCP listener (System.Net.Sockets.TcpListener)
to listen for the SSL stream (System.Net.Security.SslStream)
as following,
▋SslSocketServer.cs
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
public static class SslSocketServer
{
        public static TcpListener TcpListener = null;
        private const int Port = 6667;
        private const int MaxQueuedClientNumber = 100; // Max Queued Clients (that will be waiting for Server to accept and serve)
        static SslSocketServer()
        {
            TcpListener = new TcpListener(System.Net.IPAddress.Any, Port);
        }
        internal static void Start()
        {
            TcpListener.Start(MaxQueuedClientNumber);
        }
        internal static void Stop()
        {
            TcpListener.Stop();
            IsClosed = true;
        }
        internal static void Listen()
        {
            TcpListener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), TcpListener);
        }
        private static void AcceptCallback(IAsyncResult ar)
        {
            TcpListener listener = (TcpListener)ar.AsyncState;
            TcpClient handler = listener.EndAcceptTcpClient(ar);
            // SslStream sslStream = new SslStream(client.GetStream(), true);
            var sslStream = new System.Net.Security.SslStream(
                handler.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
            sslStream.AuthenticateAsServer(new X509Certificate2("Certs/local.pfx", string.Empty), false, SslProtocols.Tls12, false);
            var state = new SslStreamState();
            state.SslStream = sslStream;
            sslStream.BeginRead(state.Buffer, 0, state.BufferSize, new AsyncCallback(ReadCallback), state);
        }
        private static void ReadCallback(IAsyncResult ar)
        {
            var content = string.Empty;
            // Retrieve the state object and the SslStream from the asynchronous state object
            SslStreamState state = (SslStreamState)ar.AsyncState;
            System.Net.Security.SslStream handler = state.SslStream;
            // Read data from the client socket.
            int bytesRead = handler.EndRead(ar);
            if (bytesRead > 0)
            {
                // There  might be more data, so store the data received so far
                state.Content.Append(Encoding.ASCII.GetString(
                    state.Buffer, 0, bytesRead));
                // Check for end-of-file tag. If it is not there, read more data
                content = state.Content.ToString();
                if (content.IndexOf("<EOF>") > -1)
                {
                    // Handle the request (Implement the request handler)
                    using var requestHandler = new MyRequestHandler();
                    requestHandler.HandleAsync(state).Wait();
                    // Echo something back to the client
                    Send(handler, $"Received data on {DateTime.Now.ToString()}");
                }
                else
                {
                    // Not all data received. Get more
                    handler.BeginRead(state.Buffer, 0, state.BufferSize, new AsyncCallback(ReadCallback), state);
                }
            }
        }
        private static void Send(System.Net.Security.SslStream 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.Write(byteData, 0, byteData.Length);
        }
         private static bool ValidateServerCertificate(
            object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            // For self-signed certificate, return true.
            return true;
            if (sslPolicyErrors == SslPolicyErrors.None)
            {
                return true;
            }
            LoggerProvider.Logger.Error($"Certificate validation error: {sslPolicyErrors}");
            return false;
        }
}
▋Run the TLS/SSL
Socket Server
Here is
an example of using the Worker
Service to startup the Socket server as following,
public class SslSocketWorker : BackgroundService
{
        protected override async Task ExecuteAsync(CancellationToken cancelToken)
        {
            SslSocketServer.Start();
            while (!cancelToken.IsCancellationRequested)
            {
                SslSocketServer.Listen();
            }
            SslSocketServer.Stop();
              await Task.CompletedTask;
        }
    }
▋Client side
The
client side creates a TcpClient(System.Net.Sockets.TcpClient)
to establish the SSL streaming.
▋SslSocketClient.cs
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
public class SslSocketClient
{
        private const string Host = "127.0.0.1";
        private const int Port = 6667;
        protected async Task SendAsync(byte[] clientData)
        {
            TcpClient client = new TcpClient(Host, Port);
            SslStream sslStream =
                new SslStream(client.GetStream(),
                false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
            var certs = new X509Certificate2Collection();
            certs.Add(new X509Certificate2("Certs/local.cer"));
            await sslStream.AuthenticateAsClientAsync("localhost", certs, System.Security.Authentication.SslProtocols.Tls12, false);
            sslStream.Write(clientData);
            sslStream.Flush();
            // Receive response from server
            byte[] rtnBytes = new byte[1024]; // Data buffer for incoming data
            int bytesRec = sslStream.Read(rtnBytes);
            Console.WriteLine($"Echoed from server: {Encoding.ASCII.GetString(rtnBytes, 0, bytesRec)}");
            sslStream.Close();
            client.Close();
            await Task.CompletedTask;
        }
        private static bool ValidateServerCertificate(
            object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            // For self-signed certificate, always return true.
            return true;
            if (sslPolicyErrors == SslPolicyErrors.None)
            {
                return true;
            }
            return false;
        }
}
An
example for sending a message to server and retrieve the respond message back.
▋MsgSender.cs
public class MsgSender : SslSocketClient
{
        private const string CLIENT_MSG = "Hello, there.";
        public async Task SendMsgAsync()
        {
            // Encode the data string into a byte array
            byte[] byteData = Encoding.ASCII.GetBytes($"{CLIENT_MSG}<EOF>");
            // Send
            await base.SendAsync(byteData);
        }
}
▌Reference

沒有留言:
張貼留言