Wednesday, February 4, 2009

HTTPS with client certificate authentication in .NET CF

The struggle with .NET CF 1.0 continues.

The HTTPS is just ordinary HTTP sent over a secure TLS (extension of SSL) connection. The secure connection is initiated in following way:

  1. Server and client negotiate strongest cipher algorithms supported by both parties
  2. Server sends its certificate to the client
  3. Client can verify correctness of the certificate by verifying digital signature of the Certification Authority that issued the certificate
  4. Client generates a random number (master secret), encrypts it with server’s public key and sends it to server. Only server can decrypt this message with its private key.
  5. Both parties derive a key for symmetric algorithm from the master secret. Following communication is encrypted using this key.

After completing those steps, client and server have a secure way to communicate. Additionally the client knows, that server is trusted. The server on the other hand doesn’t have any information about the client. It often is required to authenticate the client before allowing it to access confidential resources. This can be done in many different ways, one of which is a client certificate. This option is supported directly in TLS – the client sends its certificate right after it receives certificate from server. To confirm, that it owns this certificate, the client also has to prove, that it owns the private key associated with certificate. In order to do this, after establishing master secret the client sends digital signature over all messages exchanged with server up to that point. Server verifies this signature using client’s public key.

I’m developing a client that has to authenticate at HTTPS web service with client certificate. Unfortunately this scenario is not supported in .NET CF prior to version 3.5. While looking for a way to work around this problem, I came by library called SecureBlackbox. The package called SSLBlackbox supports my scenario and additionally provides very comprehensive support for X509 certificates, which .NET CF lacks. What is interesting, the library developers don’t use any external APIs to perform the cryptography. The implemented all algorithms from scratch. To evaluate this library I needed a test environment. I decided to use simple ASP.NET page hosted on IIS server that would display client certificate information associated with request. The code comes from MSDN:

protected void Page_Load(object sender, EventArgs e)
{
    HttpClientCertificate cs = Request.ClientCertificate;
    if (cs != null)
    {
        Response.Write("ClientCertificate Settings:<br>");
        Response.Write("Certificate = " + cs.Certificate + "<br>");
        Response.Write("Cookie = " + cs.Cookie + "<br>");
        Response.Write("Flags = " + cs.Flags + "<br>");
        Response.Write("IsPresent = " + cs.IsPresent + "<br>");
        Response.Write("Issuer = " + cs.Issuer + "<br>");
        Response.Write("IsValid = " + cs.IsValid + "<br>");
        Response.Write("KeySize = " + cs.KeySize + "<br>");
        Response.Write("SecretKeySize = " + cs.SecretKeySize + "<br>");
        Response.Write("SerialNumber = " + cs.SerialNumber + "<br>");
        Response.Write("ServerIssuer = " + cs.ServerIssuer + "<br>");
        Response.Write("ServerSubject = " + cs.ServerSubject + "<br>");
        Response.Write("Subject = " + cs.Subject + "<br>");
        Response.Write("ValidFrom = " + cs.ValidFrom + "<br>");
        Response.Write("ValidUntil = " + cs.ValidUntil + "<br>");
        Response.Write("What's this = " + cs.ToString() + "<br>");
    }
    else
    {
        Response.Write("No certificate");
    }
}

I also needed to generate certificate for server and client. I used makecert.exe according to hints from Manu Cohen-Yashar’s post. The only modification I made is to generate a personal client certificate rather a one for machine. To do this just specify CurrentUser instead of LocalMachine as a target certificate store.

Next step is to configure IIS, I use version 7. You need to configure HTTPS binding with created server certificate – configured at web site level. Then you have to add an application under this web site for previously created ASP.NET page. Then, for this application you need to set SSL setting so that SSL is enabled and client certificates are required:

image

The easiest way to verify if server’s setup is ok is to browse create page with Internet Explorer. Since IE uses certificates installed in personal store, no additional setup is required for this browser. You can also use Firefox for example, but you need to export the client certificate to PFX file (with private key) and import it to the browser. When a page requests client certificate, the browser will let you choose certificate to be sent. Following is the result from my page:

image

Ok, after setting up the server, it was time to write some client code.

class Program
{
    static TElX509Certificate cert = new TElX509Certificate();
    
    static void Main(string[] args)
    {
        SBUtils.Unit.SetLicenseKey("<license key here>");
        var stream = File.OpenRead(@"<client certificate file>.pfx");
cert.LoadFromStreamPFX(stream, "<private key password here>", 0); stream.Close();
TElHTTPSClient https = new TElHTTPSClient(); https.OnData += new SBSSLCommon.TSBDataEvent(https_OnData); https.OnCertificateValidate += new SBSSLCommon.TSBCertificateValidateEvent(https_OnCertificateValidate); https.OnCertificateNeededEx += new SBClient.TSBCertificateNeededExEvent(https_OnCertificateNeededEx); int code = https.Get("https://myurl/"); Console.WriteLine(code); Console.ReadLine(); https.Close(true); } static bool wasCert = false; static void https_OnCertificateNeededEx(object Sender, ref TElX509Certificate Certificate) { if (wasCert) return; Certificate = cert; wasCert = true; } static void https_OnCertificateValidate(object Sender, TElX509Certificate X509Certificate, ref bool Validate) { Validate = true; } static void https_OnData(object Sender, byte[] Buffer) { Console.WriteLine(Encoding.UTF8.GetString(Buffer)); } }

Am I the only one that gets impression that syntax could be improved here? Anyway this works just fine. If requested URL requires client certificate, the OnCertificateNeededEx event is called and you can return the certificate. This method is supposed to be called over and over until it returns null in Certificate parameter. This way you can return whole certificate chain. A developer is also required to validate server’s certificate (OnCertificateValidate event). I just return true here, so all certificates are accepted – not a production ready solution.

All would be great if not for performance issues on mobile devices. It seems that HTTPS handshake requires a lot of computation power, which mobile devices lack. I got results up to 14s for first handshake! I’m still trying to get around this. Also the library’s size is considerable – over 2mb is significant on mobile device.

DotNetKicks Image

2 comments:

John Mike said...

Thanks Boss,
This is very benificial information for me.

John Mike said...

Thanks Boss,
This is very benificial information for me.

Share