Thursday, December 26, 2013

Client Certificates and the HTML5 keygen Tag

Preface

recently I was thinking about a way of authenticating clients against my server application. 
The challenge was: last thing we wanted is to have the user go through yet another boring sign up procedure. One of the things our product is trying to do is save you the pain of signing up, and therefore asking the user to sign up to our service would be... ironic.

So when looking for ways of doing things, one of the top solutions on the list seem to be using "Client Certificates". Everyone knows server certificates used in SSL, but what the f*ck are client certificates?

Well - client certificates are, in a sense, the reverse direction of server certificate - instead of your client verifying that the server is who they say they are (your bank, for that matter), in this flow - the server verifies that the client is who they say they are. 

The mechanism of verification is the same for client certificates as it is for server ones. The main difference is the implementation - where do the certificates get stored? how they are sent to the server? is this method cross-device (can I migrate my client certificate elsewhere?). 


Problem

Well, up until recently, client certificates where a pain in the ass. The main pitfull of the scheme is that it requires a lot of user interaction. It required the user to perform setup to install the certificate. The average user, of course, does not want to know about certificates or encryption. Users just want to log in quickly. 

Solution

But with HTML5, comes the revival of the keygen html tag, which can in fact solve the main pitfull of using client certificates. Using this HTML tag in a form, you can have the browser generate the key pair: the private key is stored in your browser/OS certificate store, the public key is sent to a CA (your server can act as the CA). The CA (or your server with the CA key and certificate) generates the certificate for the client and the certificate is then imported in the browser. This certificate is then sent out to your server for it to verify the client. The browser sees the "Content-Type: application/x-x509-user-cert" response header, and realized this response contains a certificate to be stored in the certificate store.

Client:

<html>
<form method="post" action="/auth">
   <keygen name="RSA public key" challenge="123456789" KEYTYPE="RSA">
   <input type="submit" name="createcert" value="Generate">
</form>
</html>

This simple form prompts the user to select key strength, and on submit, posts the data to our server (/auth route). Our server receives the public key, signs it with a CA certificate and CA key, and returns the certificate to the browser for store.

Server:

saves this file as ca.csr:

-----BEGIN CERTIFICATE REQUEST-----
<put here the result of RSA Pubic Key from /auth>
-----END CERTIFICATE REQUEST-----

assuming you have the certificate and key for the CA, sign the client request and generate client.crt:

openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt


Useful Information:

1. Here is a complete working example for a PHP server that processes a keygen request:
PHP backend: https://openweb.or.kr/html5/download.txt
HTML Frontend: https://openweb.or.kr/html5/e_keygen.php

2. why-is-nobody-using-ssl-client-certificates:
pilif.github.io/2008/05/why-is-nobody-using-ssl-client-certificates/

3. using client certificates with Nginx (contains useful information about generating certificates for testing purposes):
http://blog.nategood.com/client-side-certificate-authentication-in-ngi

4. ssl client auth with node js:
http://nategood.com/nodejs-ssl-client-cert-auth-api-rest

Cheers,
Or