< All postsTwitterRSS GitHub

Yubikey/Smartcard backed TLS servers

It has become clear that storing secrets in computers is hard. The best demo to the world that storing secrets on “online” computers is hard and sometimes devastating, is the large amounts of cryptocurrency theft due to critical keys being in memory of systems directly or indirectly connected to the internet.

For the last one and a half years, I’ve had a set of Yubikeys on my keychain. A Yubikey is a USB stick that acts like a two factor token, but can also act as a smart card.

Smart cards are neat, since they allow you to store sensitive cryptographic keys on another removable device, and they come with a guarantee that once they are programmed with a key they will not give it back to a system (they can be overwritten though)

This allows someone to separate a cryptographic key from the system it lives on. This is useful for things like SSH, since it means you can have a key that moves on your person, rather than a per machine key in the case that you use multiple machines to access systems.

Under the hood, all these smart cards are doing are the operations that require the private key, like data signing and decryption.

I figured that we could also retrofit them to provide for other roles where keys would normally be in memory (and stealable), Like TLS/HTTPS servers!

To start with, I took a spare yubikey I had never setup and used the GPG tooling on Linux to setup the card and generate keys.

ben@eshwil:~$ gpg2 --card-status
Reader ...........: 1050:0407:X:0
Application ID ...: D2760001240102010006069118380000
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 0xxxxxxx
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 0
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Here we see that the system is able to query the card and is empty and ready to program a key on to it.

ben@eshwil:~$ gpg2 --card-edit
gpg/card> admin
Admin commands are allowed

gpg/card> generate
Make off-card backup of encryption key? (Y/n) n

Please note that the factory settings of the PINs are
   PIN = '123456'     Admin PIN = '12345678'
You should change them using the command --change-pin

What keysize do you want for the Signature key? (2048) 
What keysize do you want for the Encryption key? (2048) 
What keysize do you want for the Authentication key? (2048) 
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 91
Key expires at Tue 31 Jul 2018 11:52:47 EDT
Is this correct? (y/N) Y

You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
    "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

Real name: yubitls.benjojo.co.uk
E-mail address: 
Comment: 
You selected this USER-ID:
    "yubitls.benjojo.co.uk"

Change (N)ame, (C)omment, (E)-mail or (O)kay/(Q)uit? O
gpg: /home/ben/.gnupg/trustdb.gpg: trustdb created
gpg: key 3FCD18FAB4FC1CF9 marked as ultimately trusted
gpg: directory '/home/ben/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/ben/.gnupg/openpgp-revocs.d/8236069819F168BB781D31B53FCD18FAB4FC1CF9.rev'
public and secret key created and signed.

Above we used the smart card itself to generate keys. We could have generated keys locally on the system and then uploaded keys to the card, however that would mean that at some point the keys that we are trying to secure would have been visible to a system connected to the internet. This is not optimal since if I had malware on my laptop while I did this, the keys could have been copied without me knowing.

Generation on the card ensures that the system provisioning the smart card never sees the sensitive key material, all of the generation is done on the card itself.

gpg/card> list

Reader ...........: 1050:0407:X:0
Application ID ...: D2760001240102010006069118380000
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 0xxxxxxx
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 0 3
Signature counter : 4
Signature key ....: 8236 0698 19F1 68BB 781D  31B5 3FCD 18FA B4FC 1CF9
      created ....: 2018-05-01 15:53:07
Encryption key....: 2E4F E811 2814 EA33 FA1E  C23F 99F1 667F C9BE 44A3
      created ....: 2018-05-01 15:53:07
Authentication key: 1A1A 6594 F7D6 5EA2 0E1C  C0AE 5786 AF8D 1462 E84D
      created ....: 2018-05-01 15:53:07
General key info..: pub  rsa2048/3FCD18FAB4FC1CF9 2018-05-01 yubitls.benjojo.co.uk
sec>  rsa2048/3FCD18FAB4FC1CF9  created: 2018-05-01  expires: 2018-07-31
                                card-no: 0006 06911838
ssb>  rsa2048/5786AF8D1462E84D  created: 2018-05-01  expires: 2018-07-31
                                card-no: 0006 06911838
ssb>  rsa2048/99F1667FC9BE44A3  created: 2018-05-01  expires: 2018-07-31
                                card-no: 0006 06911838

Now that we have our key setup, it’s worth going through where the key will be needed to serve as a HTTP/TLS server.

To start a TLS connection, the client states who it is expecting to see to the server, as well as some information about what the clients capabilities and requirements are. The server then replies with a certificate and a signature to prove it has the key for that certificate, plus information on what the clients capabilities are as well.

Assuming all is well, the two systems figure out a solution to connect, and then begin communicating with encryption enabled.

This means that for every fresh connection the server must do operations that involve the sensitive private key. In almost every case this means it has to be accessible in memory for a server to function (unless some other creative solutions are involved). However here we want to move this operation to a external bit of hardware.

Where most programming languages/runtimes link to OpenSSL, golang is different in that it has its own internal TLS stack internally. This is useful for us, since we want to make a “fake private key” that has the function calls of a regular private key, but actually does other logic with those function calls.

Lucky for us, someone has already implemented a library that implements a crypto.PrivateKey but where the backend is a GPG Agent. This is especially useful in our case since the Yubikey works with a GPG agent.

Now that we have a chain that works, we can create a Certificate Signing Request file for the domain we are going to use:

$ ./yubiTLS -csr.cn yubitls.benjojo.co.uk -signcsr
You appear to have not selected a key to use, or the key you selected
Does not exist in the agent at this time, Do you see your key in this list?
2018/05/02 15:39:33 Key: 09A25C1D64EF0E7170F9C3A0FAF9080B02216FD5 - {Keygrip:09A25C1D64EF0E7170F9C3A0FAF9080B02216FD5 Type:1 SerialNo:D2760001240102010006069118380000 CardID:OPENPGP.3 Cached:false Protection:2 Fingerprint:fe:96:e4:c2:68:f7:2f:75:a7:94:8d:1f:24:20:28:be TimeToLive: conn:<nil> publicKey:{N:0xc42000c340 E:65537}}

$ ./yubiTLS -csr.cn yubitls.benjojo.co.uk -signcsr -keyid 09A25C1D64EF0E7170F9C3A0FAF9080B02216FD5

$ ls *.csr
yubitls.benjojo.co.uk.csr

We can then use Let’s Encrypt via SSL For Free to get this CSR signed.

After verifying the domain, we tell the site we have a CSR:

After that, we obtain a SSL certificate for the key on our Yubikey!

Then we can provide these certificates to the server, and run the HTTPS demo server!

$ ./yubiTLS -keyid 09A25C1D64EF0E7170F9C3A0FAF9080B02216FD5 -crtpath cert.crt -cacrtpath bundle.crt
2018/05/02 15:49:57 Listening

The first time you visit it, it may ask for the PIN to unlock the Yubikey, it was amusing when this happened for the first time, because I knew at that point it worked :)

After we enter the pin, we get content served from a HTTPS server backed by a key contained on a USB stick!

If you want to try this out for yourself on a blank yubikey you can find the code on my github here: https://github.com/benjojo/yubiTLS

And if you enjoyed this, you will be glad to know that I am going to be at Recurse Center in NY for the next 7 weeks! Meaning you can follow my Twitter or RSS to keep up with the other silly (or sometimes sensible) things I will do!

Until next time!