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
沒有留言:
張貼留言