SSL is cryptic. This tutorial will get your C# program communicating in SSL using SslStream.
Note: Please leave a comment if the code is unreadable. WordPress seems to have entirely busted preformatted text. Even if you use HTML mode.
SSL encrypts communication between two nodes (a client and server). Without going into too much detail, SSL requires that the client authenticate the server against a list of trusted providers before communicating (think VeriSign, etc.). The server can authenticate the client, but that is typically not necessary in most communication scenarios.
To learn way too much about SSL theory, see SSL in Wikipedia. Fortunately with .net, you don’t need a complete understanding to implement SSL.
Shout outs
I would like to thank the folks who finally showed me the light:
I’m basically pulling together the things that they wrote into one easy place.
Getting started
You will need to download Microsoft Visual C# Express to code this. Depending on your install directory, you can find makecert.exe in C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin.
C# coding for SSL is simple but it requires some preparation. Since SSL is based on trust of certificates issued by root authorities, you will need to:
- Create your own root authority
- Create a certificate using that authority
- Set your client machine to trust certificates signed by the root authority you created
Making a certificate
Remember that you need to buy a certificate from a trusted root authority before your apps will work in the mainstream. This tutorial will get you working in a test environment.
Start the MMC to manage local certificates:
- Start > Run > type "mmc.exe" > hit enter
- File menu > Add/Remove Snap-in > click the Add button > Select "Certificates" > Press "Add"
- Select "Computer Account" > Click Next > Select "Local Computer" > Click Finish
- Click Close button > Click OK
Now things should look like this:
Fire up a command line to create certificates:
- Start > Run > "cmd" >hit enter
- cd "C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin" > hit enter
- Replace the parameters between %’s with your own names. Make sure %root_authority_name% matches in both instances.
- C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin>makecert -pe -n "CN=%root_authority_name%" -ss my -sr LocalMachine -a sha1 -sky signature -r %output_filename%.cer
- C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin>makecert -pe -n "CN=%certificate_name%" -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -in "%root_authority_name%" -is MY -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 %certificate_output_file%.cer
- If each of the commands reports "Success" at the end, congratulations! You now have a root authority and a signed certificate. Check in the C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin folder to find the two new .cer files you just created.
Meanwhile, you should see the following changes in your console under the Personal folder (you may need to refresh):
You will also notice the presence of a couple .cer files in the C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\Bin folder. One for your root authority and the other for the certificate you generated.
In this case, the "Daniel Root Authority" certificate is the root authority that issued the certificate. The "mdumps" certificate is the certificate I issued to my test server machine. For this test, my server will use the mdumps.cer file as its SSL certificate. The client machine I am using must trust the root authority to trust the certificate.
To make the client trust the root authority, copy the root authority certificate into the "Trusted Root Certification Authority" folder on the client:
You will need to copy the root certificate into the trusted location of every client that will be running your server.
Writing some code
Now the coding begins.
For simplicity, you may download my sample project with the code to get this working. You will need to copy your server certificate file (.cer) into the running directory of the executable. Also, change the hostname and certificate strings to match your configuration.
Use the following namespaces for creating sockets, encrypting communications, and storing certificates:
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
The server
When a server instantiates an SslStream object, it must link that stream to the certificate for authentication and encryption. A simple example:
// Load the server certificate
X509Certificate serverCert = new X509Certificate(certificateFilename);
// Start listening for a connection
Console.WriteLine("Server: waiting for client connection.");
listener.Start();
clientConnection = listener.AcceptTcpClient();
// Once a client connects, associate it with an SslStream
Console.WriteLine("Server: Client has connected.");
secureStream = new SslStream(clientConnection.GetStream());
// Set the server certificate
secureStream.AuthenticateAsServer(serverCert);
Please note that other methods of accessing the certificate exist outside of the filename method. In this case, the file method is the easiest.
The AuthenticateAsServer method will throw an Exception if it encounters an error.
The client
The client authenticates the server, in this case against the server’s hostname. Notice how the client does not load a certificate, instead it compares the server’s certificate with its list of trusted root authorities (that you set up in the MMC earlier):
// Connect
Console.WriteLine("Client: Connecting to server ");
client.Connect(serverHostname, serverPortNum);
// Authenticate the server
Console.WriteLine("Client: Authenticating server");
secureStream = new SslStream(client.GetStream());
secureStream.AuthenticateAsClient(serverHostname);
// Server has authenticated
Console.WriteLine("Client: Server authenticated!");
The AuthenticateAsServer method will throw an Exception if it encounters an error. It is possible to perform custom handling of the authentication on the client side, but that is beyond the scope of this article.
Sending and receiving messages
I wrote a couple simple methods that allow you to send a string of nearly any size. Note that the method first sends an integer (known size) describing the size of the message it will send:
static void SendMessage(SslStream stream, string message)
{
byte[] messageLengthBytes = BitConverter.GetBytes(message.Length);
// Send the size of the object
stream.Write(messageLengthBytes);
ASCIIEncoding encoder = new ASCIIEncoding();
byte[] messageBytes = encoder.GetBytes(message);
stream.Write(messageBytes);
}
static string GetMessage(SslStream stream)
{
byte[] byteStreamSize = new byte[sizeof(int)];
stream.Read(byteStreamSize, 0, sizeof(int));
int streamSize = BitConverter.ToInt32(byteStreamSize, 0);
byte[] messageOutput = new byte[streamSize];
int offset = 0;
while (offset < streamSize)
{
offset += stream.Read(messageOutput, offset, streamSize - offset);
}
string output = ASCIIEncoding.ASCII.GetString(messageOutput);
return output;
}
Closing thoughts
SSL adds an extra layer of security to your communications, but it requires careful management of the certificate files. With this added layer comes added complexity. Fortunately, .net abstracts much of this work into an object that, with a little preparation, you can treat as any other Stream object.
Now you can focus on what your application is supposed to be doing!