Jump to navigation

Poupou's Corner of the Web

Looking for perfect security? Try a wireless brick.
Otherwise you may find some unperfect stuff here...

Weblog

Server-side mutual authentication

This is an addendum to a previous blog entry where we created a simple SSL server using Mono.Security.dll. This week I discovered (well someone gave me hints ;-) that mutual authentication didn't work in the SslServerStream class. That also means that this feature hasn't been used in the past, maybe for lack of documentation (and/or a lack of bug reports about it ;-). Note that you'll need SVN HEAD to try this sample until Mono 1.1.9 is out.

So we're gonna add support for mutual authentication in the previous sample. First thing is to create the server's certificate. This is identical to the first steps of the previous entry. Next we'll also need a client certificate, which can also be created using the makecert tool.

% whoami poupou

That first step was in case you're not sure about who you are (like early mornings ;-). Sadly makecert output a CER file (for the certificate) and a PVK file (for the private key). wget, the tool we're gonna use to test the server, depends on OpenSSL so it wants a PEM encoded file. Luckily both Mono and OpenSSL supports PKCS#12. I could build a quick convertion tool but it was easier, and much more useful, to add PKCS#12 support to Mono's makecert utility.

% makecert -eku 1.3.6.1.5.5.7.3.2 -n "CN=poupou" -p12 poupou.p12 s3kr3t Mono MakeCert - version 1.1.8.0 X.509 Certificate Builder Copyright 2002, 2003 Motus Technologies. Copyright 2004-2005 Novell. BSD licensed. Success

The parameters used to build this test certificate are:

-eku 1.3.6.1.5.5.7.3.2
This indicates that your certificate is intended for client-side authentication.
-n "CN=poupou"
This is generally verified the server against a database of users (if the certificate is accepted).
-p12 poupou.p12 s3kr3t
The PKCS#12 file, poupou.p12 will be created and encrypted using the s3kr3t password. This is the new option and isn't compatible with MS version of makecert.

The poupou.p12 file can then be converted to a PEM file using OpenSSL.

% openssl pkcs12 -in poupou.p12 -out poupou.pem -nodes Enter Import Password: MAC verified OK

The -nodes option will generate a PEM file that doesn't requires a password (i.e. it won't be encrypted). We're now have all the pieces required by the server.

using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using Mono.Security.Authenticode; using Mono.Security.Protocol.Tls; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace SslHttpServer { class SslHttpServer { private static X509Certificate _certificate = null; private static string certfile; private static string keyfile; static void Main (string [] args) { certfile = (args.Length > 1) ? args [0] : "ssl.cer"; keyfile = (args.Length > 1) ? args [1] : "ssl.pvk"; Socket listenSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint localEndPoint = new IPEndPoint (IPAddress.Any, 4433); Socket requestSocket; listenSocket.Bind (localEndPoint); listenSocket.Listen (10); while (true) { try { requestSocket = listenSocket.Accept (); using (NetworkStream ns = new NetworkStream (requestSocket, FileAccess.ReadWrite, true)) { using (SslServerStream s = new SslServerStream (ns, Certificate, true, false)) { s.PrivateKeyCertSelectionDelegate += new PrivateKeySelectionCallback (GetPrivateKey); s.ClientCertValidationDelegate += new CertificateValidationCallback (VerifyClientCertificate); StreamReader reader = new StreamReader (s); StreamWriter writer = new StreamWriter (s, Encoding.ASCII); string line; // Read request header do { line = reader.ReadLine (); if (line != null) Console.WriteLine (line); } while (line != null && line.Length > 0); string answer = String.Format ("HTTP/1.0 200{0}Connection: close{0}" + "Content-Type: text/html{0}Content-Encoding: {1}{0}{0}" + "<html><body><h1>Hello {2}!</h1></body></html>{0}", "\r\n", Encoding.ASCII.WebName, s.ClientCertificate == null ? "World" : s.ClientCertificate.GetName ()); // Send response writer.Write (answer); writer.Flush (); s.Flush (); ns.Flush (); } } } catch (Exception ex) { Console.WriteLine ("---------------------------------------------------------"); Console.WriteLine (ex.ToString ()); } } } private static X509Certificate Certificate { get { if (_certificate == null) _certificate = X509Certificate.CreateFromCertFile (certfile); return _certificate; } } // note: makecert creates the private key in the PVK format private static AsymmetricAlgorithm GetPrivateKey (X509Certificate certificate, string targetHost) { PrivateKey key = PrivateKey.CreateFromFile (keyfile); return key.RSA; } private static bool VerifyClientCertificate (X509Certificate certificate, int[] certificateErrors) { if (certificate != null) { Console.WriteLine (certificate.ToString (true)); } else { Console.WriteLine ("No client certificate provided."); } foreach (int error in certificateErrors) Console.WriteLine ("\terror #{0}", error); return true; } } }

As you can see the differences with the previous sample are very small. The SslServerStream constructor was changed to requires client certificates (true instead of false) and we added a callback to validate the client certificate, the VerifyClientCertificate method. Compiling the server is also identical and only requires to add a reference to Mono.Security.dll.

% mcs msslserver.cs /r:Mono.Security.dll

We're now ready to start the server (substitute your own server certificate and private key files). Note that this server listen to port 4433.

% mono msslserver.exe pollux.cer pollux.pvk

Now, using another terminal or computer, we can use wget to test the server. First we try to connect using a client certificate, poupou.pem.

% wget --sslprotocol=2 --sslcertfile=poupou.pem https://localhost:4433 --21:48:30-- https://localhost:4433/ => `index.html' Resolving localhost... localhost Connecting to localhost:4433... connected. HTTP request sent, awaiting response... 200 Length: unspecified [text/html] [ <=> ] 53 --.--K/s 21:48:34 (6.32 MB/s) - `index.html' saved [53] % cat index.html <html><body><h1>Hello CN=poupou!</h1></body></html>

Now if we didn't provide a client certificate the server will choose to accept, or not, the connection. Our test server always accept any certificate (including nonce) so we get a more generic Hello World!.

% wget --sslprotocol=2 https://localhost:4433 --21:52:24-- https://localhost:4433/ => `index.html.1' Resolving localhost... localhost Connecting to localhost:4433... connected. HTTP request sent, awaiting response... 200 Length: unspecified [text/html] [ <=> ] 49 --.--K/s 21:52:25 (11.68 MB/s) - `index.html.1' saved [49] % cat index.html.1 <html><body><h1>Hello World!</h1></body></html>

That's all! and believe me this is much easier to use than to code ;-)


7/1/2005 10:48:47 | Comments

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.