Quite a lengthy post here with a lot of code in the hope that my experience of building an integrity-checking SSL (text-only for now) communication system will be of use to somebody else.
The way the system I have designed works is thus:
- Server sends banner
- Client sends login
- Server verifies and then either client or server is free to send commands with sequence numbers
The conditions are that the system must verify every single line of text sent via some kind of CRC (using MD5 here) and disconnect gracefully, raising an event to tell the host application so, if there is a problem.
First of all we need to define some kind of protocol between the client and server. Here's what I came up with for a skeleton.
ProtocolText.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Shared
{
public static class ProtocolText
{
static string _banner = "AUTH";
public static string BANNER
{
get { return _banner; }
set { _banner = value; }
}
static string _positive = "YES";
public static string Positive
{
get { return ProtocolText._positive; }
set { ProtocolText._positive = value; }
}
static string _negative = "NO";
public static string Negative
{
get { return ProtocolText._negative; }
set { ProtocolText._negative = value; }
}
static string _LOGINPrefix = "LOGIN";
public static string LOGINPrefix
{
get { return ProtocolText._LOGINPrefix; }
set { ProtocolText._LOGINPrefix = value; }
}
static string _QUIT = "GOODBYE";
public static string QUIT
{
get { return ProtocolText._QUIT; }
set { ProtocolText._QUIT = value; }
}
}
}
Next up, some form of wrapping "commands" inside an API. These are actually just text, but putting them into objects has obvious usability implications.
aCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Shared.Commands
{
public enum CommandType
{
BANNER,
AUTH,
QUIT,
PREPAREINDEX
}
public enum Response
{
TERMINAL,
ERROR,
WARNING,
INFORMATION,
SUCCESS
}
public abstract class aCommand
{
long _sequenceNumber = 0;
string[] _commandText = null;
bool _hasCommandsToSend = false;
string _information = string.Empty;
List<string> _response = new List<string>();
public string Information
{
get { return _information; }
}
public long SequenceNumber
{
get
{
return _sequenceNumber;
}
set
{
_sequenceNumber = value;
}
}
public string[] CommandText
{
get
{
return _commandText;
}
}
public bool HasCommandsToSend
{
get
{
return _hasCommandsToSend;
}
set
{
_hasCommandsToSend = value;
}
}
public List<string> ResponseText
{
get { return _response; }
}
protected void setCommandText(string[] commands)
{
_hasCommandsToSend = true;
_commandText = commands;
}
protected void setInformation(string info)
{
_information = info;
}
public void AddResponse(string msg)
{
_response.Add(msg);
}
public abstract CommandType CommandType { get; }
public abstract Response ResponseDone();
}
}
Also in our shared library (this is referenced by both the client and the server) we need the CRC checker.
CRC.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace Shared
{
public class CRC
{
static MD5CryptoServiceProvider cSP = new MD5CryptoServiceProvider();
public static string ComputeCRC(string message)
{
return BitConverter.ToString(cSP.ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(message)));
}
public static string ComputeCRC(Stream stream)
{
return BitConverter.ToString(cSP.ComputeHash(stream));
}
}
}
Now, the workhorse itself, the actual client. This is responsible for threading, SSL initiation (to some extent), CRC checking and passing message responses to the correct places.
aClient.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.IO;
using System.Threading;
using System.Text.RegularExpressions;
using System.Diagnostics;
using Shared.Commands;
namespace Shared
{
public enum ClientEvents
{
OnAuthFailure,
OnAuthSuccess
}
public abstract class aClient
{
TcpClient _client = null;
SslStream _ssl = null;
StreamReader _reader = null;
StreamWriter _writer = null;
bool _shutdown = false;
bool _isConnecting = false;
string _server = string.Empty;
int _sequenceNumber = 0;
int _connectionAttempts = 0;
List<aCommand> _commandQueue = new List<aCommand>();
ManualResetEvent _hasMessages = new ManualResetEvent(false);
ManualResetEvent _connectingWait = new ManualResetEvent(false);
Regex msgMatcher = new Regex(@"^(\d+)\s(.+)\sCRC(.+)$");
public delegate void ClientEvent(aClient client);
public event ClientEvent OnAuthFailure;
public event ClientEvent OnAuthSuccess;
public event ClientEvent OnShutdown;
protected State _state = State.Not_Connected;
public abstract void initSSL(SslStream ssl, string hostname);
public abstract void connectionInit();
public abstract void processResponse(Response response, aCommand command);
public abstract void processNewCommand(string commandText, long sequenceNumber);
protected enum State
{
Not_Connected,
Connected,
Authenticated
}
public aClient(TcpClient client, string hostname)
{
_client = client;
_server = hostname;
}
public void Start()
{
ThreadStart ts = new ThreadStart(messageLoop);
Thread t = new Thread(ts);
t.Start(