Using certificate based ssh authentication

Sat 20 Sep 2014

What is a Certificate?

A certificate is a form of Public Key Cryptography that allows you to trust someone.
The certificate authority's (CA) private key is used to sign the public key of anyone or anything that is trusted by the CA.
Anybody can then decide to trust the same people as the CA by adding the CA's public key to their keyring.

Why should I use one?

One of the main reasons is that CA signing works in both directions.

A user can check that a machine is trusted instead of relying on adding individual hosts to ~/.ssh/known_hosts.
This is useful if you are using the same IP address but you are recreating a machine for testing purposes and you don't want to be bugged by SSH key checking messages.

A machine can check that a user is trusted instead of relying on adding user's public key's to ~/.ssh/authorized_keys for each user that should be allowed to login.
Signing keys also allows very fine grained control over many aspects that key based authentication doesn't.

The reason I have switched from key based to cert based authentication is that it is much easier to handle access controls access your personal machines when you are regularly creating new VM's and VPS's.
You only need to add the CA's public key to a server rather than all of your user's public keys to the correct
~/.ssh/authorized_keys file.

Setting it up.

For this you will need:

  • 2+ machines running your preferred form of Linux (I used Debian 7)
  • Openssh 5.4+
  • Basic knowledge of the command line

In this example I will be using the machine I am writing this on as both the CA and the user I want to trust.
This isn't totally secure but will work perfectly fine with a small number of users.

First we will create a user CA which will allow the users to sign into servers without a prompt.

The first step is to create your CA's keypair.

$ mkdir -p ~/.ssh/ca
$ cd ~/.ssh/ca
$ ssh-keygen -f ca -C "Comment on the keys"
    Generating public/private rsa key pair.
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in ca.
    Your public key has been saved in ca.pub.
$ ls
    ca  ca.pub

You should provide a strong passphrase when asked as this will mean that if attacker gains access to your keys your security is not compromised.

The next step is to place the public key on the machine(s) that should trust the users. Ansible Playbook featuring this.

$ scp ca.pub user@example.com:/home/user
$ ssh user@example.com "sudo cp ~/ca.pub /etc/ssh" 
$ ssh user@example.com "sudo sed -i '/TrustedUserCAKeys/d' /etc/ssh/sshd_config"
$ ssh user@example.com "echo 'TrustedUserCAKeys /etc/ssh/ca.pub' | sudo tee -a /etc/ssh/sshd_config"
$ ssh user@example.com "sudo /etc/init.d/ssh restart"
 # The above will only work on a machine set up with passwordless sudo
 # Otherwise ssh to the machine and run the commands manually

This sets up the server to trust signed keys from the CA, so the next step is to sign our users public key.

If you already have a set of user keys you should skip the first step.

$ ssh-keygen 
    Generating public/private rsa key pair.
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in ~/.ssh/id_rsa.
    Your public key has been saved in ~/.ssh/id_rsa.pub
    # This generates a keypair with default settings
    # Like with the CA keypair you should use a strong password for 
    # added security.
$ mkdir signed_users
$ cd signed_users
$ cp ~/.ssh/id_rsa.pub user.pub
$ ssh-keygen -s ~/.ssh/ca/ca -I "Comment" -n "user" user.pub
    Enter passphrase:       
    # Enter the passphrase you used to create the ca keypair
    # You can add more than one user by comma separating them
    # You will only be allowed to login as the users specified 
    # in this list and it is required
$ chmod 600 user-cert.pub
    # Set the correct permissions on your signed key
$ ls
    user.pub    user-cert.pub
$ cp user-cert.pub ~/.ssh/id_rsa-cert.pub

You should keep a copy of user.pub safe because if you ever want to revoke a users trusted status you need to know their public key.

You can easily repeat this for each machine you use regularly.

If you use an automation tool to set up new machines it is simple to set up a task that will create and sign a keypair allowing you to log in to any of your machines from it straight away.

There are many other options you can add to the signed key to restrict and secure the user further. Some examples are:

  • Setting an expiration on the signed key
  • Only allowing the key to be used from certain address's
  • Forcing a specific command to be run instead of a shell when using the certificate

Revoking users

If you lose access to a set of user keys it is fairly easy to revoke their access to your machines.

$ ssh user@example.com "[ -f /etc/ssh/ssh_revoked_keys ] || sudo install -m 644 -o root -g root /etc/ssh/ssh_revoked_keys"
$ cat user_to_revoke.pub | ssh user@example.com "sudo tee -a /etc/ssh/ssh_revoked_keys"
$ ssh user@example.com "sudo sed -i '/RevokedKeys/d' /etc/ssh/sshd_config"
$ ssh user@example.com "echo 'RevokedKeys /etc/ssh/ssh_revoked_keys' | sudo tee -a /etc/ssh/sshd_config"
$ ssh user@example.com "sudo /etc/init.d/ssh restart"
    # Again this requires passwordless sudo

This does have to be run on each of your machines but it is still less work than combing through authorized_keys files for each user account on every machine you have.

Final notes.

  • Keep you CA private key safe.
    This can be used to sign new keys that can then access your machines.
  • Use passphrase's and a SSH agent.
    These improve security so that an attacker must have access to a key and know the passphrase before using it. Using an SSH agent means you only have to input your passphrase once across machine reboots.
  • You should try to follow the principle of least privilege for user accounts and give each one their own signed key.