As you may have seen on services such as MyOpenId, you can either login on your account with a password, or you can generate a client identification SSL certificate and let your users identify with one of the most secure way (as long as they don’t get their keys stolen).

SSL identification?

The idea is pretty simple. Usually communications between a SSL client and server are done via a public/private key system. While I never checked in depth, I assume it’s pretty much like for mails: the client generates a client certificate, connects to the server, gets the server’s public key, sends its public key to the server, then use its own private key and the server’s public key to exchange data.
SSL identification happens there: instead of generating a key, the client will use a previously generated private key, and send its public key alongside a certificate previously obtained from the server. The server will be able to check the certificate, which proves that the client’s key has been signed by the server CA (another private certificate held by the server, only used to make client certificates).

This way, the server will know that the key used by the client was signed by him and can be trusted.

How did this happen? Who gave its key to the client? How can you do the same on your website with PHP? What? Why are you talking about PHP 5.3.1?

Client certificate generation process

Before doing anything, you’ll need a certificate authority (CA). OpenSSL is bundled with a script for this, however the default config (at least on gentoo) will mark CA as being “not a CA”.

In order to achieve this, here are the few steps you’ll need to follow:

  1. Edit /etc/ssl/openssl.conf (or whatever your openssl file is) and change CA:FALSE to CA:TRUE (if you use vi, type :%s/CA:FALSE/CA:TRUE/). Save.
  2. Choose a location for your CA. You will most likely want to choose a directory the webserver will be able to access, but without putting it in the website’s root (don’t be stupid!)
  3. Generate your CA. For this, OpenSSL made things easy. Type: /etc/ssl/misc/CA.sh -newca and follow direction
  4. Edit back your openssl.conf file to replace CA:TRUE with CA:FALSE, or you will be making LOTS of CA certificates.
  5. Decrypt your CA key. While it’s not recommanded, avoiding the trouble of providing your CA key’s passphrase each time you do something might be nice. Just ensure you keep it safe, or anyone will be able to make certificates your webserver will trust.
    $ openssl rsa -in demoCA/private/cakey.pem -out demoCA/private/cakey.pem
    This will ask for  the passphrase, and will save the  key without any passphrase. Personally I use libssh2, and have the webserver login as a “casign” user, which is allowed to sudo as the CA user, but only with the command to sign a single key. This way, if anything happens, I’ll still have the signed keys in my CA index and will be able to revoke them.

As the client certificate generation process on MSIE is too much troubles (you’ll need to invoke ActiveX elements such as Xenroll.cab) I’ll only speak about the process made by Netscape, and compatible with both Firefox and Opera according to documentation found on internet (maybe also compatible with google chrome, or webkit, didn’t test yet).

So, on Netscape (and compatible browers), you have an not really common HTML tag: <keygen>. This tag will make the browser display a select box, typically with the encryption level choice. As High Grade certificates are generated within seconds on most recent systems, it’s usually the best choices. You can even make the element invisible (display: none) to avoid to the visitor the trouble of choosing.

Tag example:

<keygen name="spkac"/>

So, nothing fancy, however just having this tag in a form will cause the variable “spkac” being posted with a bunch of base64-encoded data. SPKAC is a netscape standard for a “signing request”, for the private key the browser just generated. It contains informations such as a public key and a signature. What we need to do is to provide the browser with a certificate, made by our CA.

While signing the key, we are able to customize parts of the final certificate, making it easier for the user to choose a certificate when the time comes. A certificate typically contains the following fields:

  • commonName: This is the “main” name for the certificate, which will be displayed in the browser’s dropdown box.
  • emailAddress: An email address. I personally use it to store <uuid>@mydomain.tld. The uuid is unique to the key, and when the user comes back, I can identify him by looking up the uuid part in my database.
  • organizationName: The name of your company (usually the same as the CA)
  • organizationalUnitName: just put whatever you want
  • localityName: a city, typically the same as your CA, as for the rest too…
  • stateOrProvinceName
  • countryName

To make OpenSSL like you, you will have to create a text file containing on each line one of those elements, followed by the equal symbol, then the value you wish to give to this property. Finally, add a line “SPKAC = ” followed by the base64 data previously sent by the browser (remember to strip linebreaks, or OpenSSL won’t be happy).

Once the text file is ready, you can generate the key!

openssl ca -spkac my_spkac_file -out certificate_file -days 365

This will create a certificate valid for 365 days, certifying your CA acknowledges the browser’s key as genuine. Now that you got a “certificate_file”, you need to send it back to the browser. This is done easily with PHP, just don’t forget this before sending the content of the file to the browser:

header('Content-Type: application/x-x509-user-cert');

As you are most likely targetting firefox/opera, you can probably use data URI by encoding the gibberish generated by OpenSSL in base64 and inserting something like:

<iframe width="1" height="1" style="display: none;"
  src="data:application/x-x509-user-cert;base64,MII..."></iframe>

Firefox displays a “certificate installed” alert box when this happens, dunno about Opera.

Anyway, you have now installed a certificate on your client browser, now you will maybe want to ask for authentication…

Client authentication part

As on most configurations, the client will be asked to choose a certificate each time he loads a page, we usually make only one path to use SSL authentification, and store the information in session once the user is identified. It might also not be a good idea to always force the user to identify as it might bother him to see his browser asking for a key.

Before anything, you need to make sure your website is protected with SSL. Asking for an SSL key exchange without SSL is like asking for a kebab without meat.

You’ll also have to tell Apache about your CA. Copy the demoCA/cacert.pem file in a directory accessible to apache, and create a special symlink with the certificate’s hash. In order to know exactly how you should do your symlink, OpenSSL provides a nice tool.

$ /etc/ssl/misc/c_hash cacert.pem
8aafa88f.0 => cacert.pem
$ ln -s cacert.pem 8aafa88f.0

You can have more than one CA if you wish to. The folder where you put those is to be provided to Apache’s mod_ssl with directive  SSLCACertificatePath.

Now that apache is able to recognize an SSL certificate, you just need to create a directory for openssl authentification, and put the following in the .htaccess file :

SSLVerifyClient optional

This will force apache to renegociate the SSL link when a request to your directory is done, and ask if the browser is able to provide a certificate. You can then check data from $_SERVER (in PHP) to find details from the certificate (phpinfo() is a good way to find out what’s stored there. Basically you can get back all the data you stored previously).

Once you verified data provided there, you can store in session details about the user, and direct him back to a normal part of your website.

What’s next?

Now that you’re (in theory) able to manage authentification for your Firefox/Opera visitors, you could start looking at MSIE (and its pkcs#10 keys), and improve visitor experience on your website using SSL keys.

Right now I’m trying to see how hard it would be to give the ability to sign SPKAC keys via the PHP’s OpenSSL ext with the help of Pierre. This is not going to be easy, and won’t be available anyway before PHP 5.3.1 (this is just a personal hope), but once provided/made easy for both Netscape-based browsers and MSIE, we might start seeing software made in PHP taking advantage of this kind of ability.

Troubleshooting

  • I double-checked the apache config, but I still get to choose any of the certificates installed on the brower, even ones I didn’t generate.
    You forgot to edit your openssl config to put CA:TRUE. Remember to put it back to CA:FALSE once you generated your SSL key.
  • I have a problem not mentionned here.
    Just contact me, we might be able to fix this…
  • I have too many beers in my fridge.
    Just contact me, I might be able to fix this…