Yubikey for personal use

2024/08/07

At work, we use SSH to connect to our infrastructure using PIV and a Yubikey to comply with the PCI DSS standard. I’ve seen a post from Christian Stankowic (aka “stdevel”) on Mastodon showing a Yubikey for personal use, so I decided to give it a try. This blog post is the result of how I use a Yubikey outside of work.

Parts:

I’m not paid to promote these products.

Disclaimer

Modifying security keys may be dangerous. I cannot be responsible of any data loss that may have been caused. Use the commands with caution.

Requirements

I’m running on Ubuntu 24.04 (Noble Numbat) at the time of writing. The easiest way to install Yubikey Manager to set up the Yubikey is to use the PPA.

sudo add-apt-repository ppa:yubico/stable
sudo apt-get update
sudo apt-get install yubikey-manager
ykman --version

If you have another system, please follow the official documentation.

SSH with FIDO2

At home, I self-host multiple services from a distributed file storage, finances to my home lab. They run on various hosts accessible via SSH. But instead of relying on PIV, like at work, I wanted to try something more secure and supposed to be easy to use: FIDO2.

The components:

In short, it’s a regular public and private key system, using eliptic-curve cryptography (“ECC”), but instead of storing the private key on on your client, an access key (with “sk” suffix for “Security Key”) is used to access the private key on the Yubikey.

Like regular SSH keys, a password can protect the private key itself.

With FIDO2 on the Yubikey, there are two more security mechanisms to be aware of:

Note that the minimum version of OpenSSH with FIDO2 support is 8.2, which means at least Debian 11. If you have older versions around, it’s time to upgrade!

PIN

By default, the FIDO PIN is not defined. You should generate a strong PIN from a password manager like KeepPassXC.

read -s PIN
echo -ne $PIN | wc -c
ykman fido access change-pin --new-pin "${PIN}"

SSH keys

The next step is to create a pair of SSH keys. This operation has to be repeated for each client. The Yubikey is able to store multiple private keys. One private key should not be re-used by multiple clients because that would mean the access key, which must stay private, on the client (~/.ssh/id_ed25519_sk) used to unlock the private key on the Yubikey, has to be moved around, which is a bad practice.

I’ve chosen the Ed25519 algorithm instead of ECSDA because interoperability is not an issue for me, all my systems are up-to-date and compatible. Both are strong options. Both are not RSA.

ssh-keygen -t ed25519-sk -O resident -O verify-required \
    -O "application=ssh:$(hostname -f)" \
    -C "$(hostname -f)"

Enter the PIN, touch the Yubikey then set a password to protect the SSH key.

Finally copy the public key to the remote hosts (file ~/.ssh/id_ed25519_sk.pub).

SSH agent

The main problem is that we need to pass 3 security tests for every single SSH connection:

  1. Enter the password of the SSH key
  2. Enter the FIDO2 PIN of the Yubikey
  3. Touch the Yubikey

I know this is security but it’s not very user-friendly. So I’ve tried to use an SSH agent to “cache” the private key for a limited amount of time, like I do for regular keys and even with PIV.

Let’s try to add the identities to the agent:

$ ssh-add -K
Enter PIN for authenticator: 
Resident identity added: ED25519-SK SHA256:***
$ ssh-add -l
256 SHA256:***  (ED25519-SK)

Then connect:

$ pilote 
sign_and_send_pubkey: signing failed for ED25519-SK "/home/jriou/.ssh/id_ed25519_sk" from agent: agent refused operation

Unfortunately, the SSH agent doesn’t seem to load the FIDO keys properly. To fix this issue, you can disable the agent in file ~/.ssh/config:

Host *
    IdentityAgent none

You still need to pass the 3 security checks but, at least, you can connect.

I use Ansible to manage most of my personal infrastructure. With FIDO2, you can multiply the number of security checks by the number of managed host. This is a nightmare. Unfortunately, I’ve chosen to create a regular ed25519 key pair and use it only for Ansible from one host that will never leave my secure house.

FIDO U2F to replace OTPs

This open authentication standard enables you add an additional security check based on a hardware key, to log on a website for example. Traditionally, you have to enter your user name and your password. You can add two-factor authentication with an application generating one-time passwords (“OTP”). They are generated for a limited amount of time. I have way too many OTPs registered in my FreeOTP application. FIDO U2F is a way to replace your OTP by touching your Yubikey. As simple as that.

It’s supported by plenty of platforms including (but not limited) Mastodon, Github and Linkedin. The workflow to setup a hardware key goes often like this:

  1. Go to Settings / Security or Account
  2. Look for 2FA or MFA
  3. Setup OTP (even if you won’t use it)
  4. Search for hardware key
  5. Enter the FIDO PIN
  6. Touch the Yubikey

The interface will be different for each platform but the workflow should be the same.

PGP

Nowadays, communication systems provide transparent end-to-end encryption. But it’s not always enabled by default (I see you Telegram). Do you trust those providers claiming they are not able to decrypt your messages? Where are my keys? Can I bring my own? It’s encrypted, trust me bro.

Technically, Pretty Good Privacy (“PGP”) solves this issue. I can generate my own set of keys to encrypt or authenticate data using public key cryptography. ECC is supported. The private key can be protected by the Yubikey.

The only problem is to find people like me, believing in privacy, able to use PGP to encrypt communications. In our daily lives, we talk mostly to non-technical people that don’t know a word about how cryptography works or even how computers work. E-mails are less and less used. Which means I don’t use PGP to encrypt my messages very often, except at work.

Another usage of PGP is to encrypt sensible files. In that case, storing the private key on a Yubikey is a good way to make it more secure. But at the same time, if you loose the key, you will never be able to decrypt the original files. You can still export the private key and store it somewhere secure. But how secure is it? Here it comes the endless loop of paranoia to encrypt the key of the key of the key of the… You get it. I’ve chosen to protect the exported private key by a password stored on my password manager which has its own password that I know in my head. The security stops if I forget the password manager’s password, and it’s ok.

I also use PGP to sign my git commits.

Access

There are two differents PINs for the PGP application on the Yubikey:

The PIN is specific to the PGP application. If you have already set up a FIDO PIN, please chose a different one for PGP.

Unlike FIDO, default PINs are set from factory. Run the “reset” operation only if you don’t know the current PIN. Be careful, this is a destructive operation. Any existing PGP key will be removed.

ykman openpgp reset

Change the Admin PIN:

ykman openpgp access change-admin-pin --admin-pin 12345678

Change the PIN:

ykman openpgp access change-pin --pin 123456

Generate key

Let’s generate the key pair locally, then move the private key to the Yubikey afterwards.

gpg --expert --full-gen-key
Please select what kind of key you want:
   (9) ECC (sign and encrypt) *default*
Your selection? 9
Please select which elliptic curve you want:
   (1) Curve 25519 *default*
Your selection? 1
Please specify how long the key should be valid.
Key is valid for? (0) 1y
Is this correct? (y/N) y

Enter real name, e-mail address and eventually a comment.

Validate:

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O

Then move key to the Yubikey:

gpg --edit-key 0x1234
gpg> keytocard 

Enter the PGP password, the one to protect the private key itself, then the Admin PIN to store it on the Yubikey.

Publish to keyserver

How to discover public keys from other people?

  1. Participate to a key signing party (at FOSDEM for example)
  2. Use a keyserver, because we cannot meet everybody in person

The keys.openpgp.org server seems to be a popular option to publish your public key. It verifies your identity by sending an e-mail to every PGP identity (= e-mail) included in the published key.

gpg --export 0x1234 | curl -T - https://keys.openpgp.org 

Info on the card

The gpg binary enables you to store more information like your full name and the URL of your public key, directly on the Yubikey. Feel free to set them up or not. Anyone grabbing your Yubikey will be able to retreive such information.

gpg --card-edit
gpg/card> admin
gpg/card> name

Enter your last name, first name, then the Admin PIN to save the modification.

For the URL:

gpg/card> url

Enter the public key URL (ex: https://keys.openpgp.org/vks/v1/by-fingerprint/1234), then the Admin PIN to save the modification.

Verify:

gpg --card-status

This commands tells you if your Yubikey has been detected by the gpg application by showing the card details. Sometimes, you could have the following error:

gpg: OpenPGP card not available: General error

In that case, you should unplug then plug your Yubikey.

The IT Crowd (TV show) meme saying 'Have you tried to turning it off and on again?'

Renew an expired key

It’s important to set an expiration duration because if you lose control over your key, it will expire automatically. I’ve performed this operation a couple of times over the years.

Edit the main key:

gpg --edit-key 0x1234
gpg> expire
Key is valid for? (0) 1y
Is this correct? (y/N) y

Enter the PIN to unlock the Yubikey.

Then edit the sub key:

gpg> key 1
gpg> expire
Key is valid for? (0) 1y
Is this correct? (y/N) y

Enter the PIN to unlock the Yubikey.

Save and publish:

gpg> save
gpg --keyserver keys.openpgp.org --send-keys 0x1234

This doesn’t prevent from compromision. In that case, you should revoke the key.

Sign git commits

Git is a very popular version control software used by open-source projects to distribute code and accept contributions. Show the world that you own your git commits! With PGP, you can sign your commits so everybody can verify that you are effectively the one and true author of the commit, or your private key has been compromised but that’s another topic.

File ~/.gitconfig:

[user]
	email = first@name.email
	name = First Name
	signingkey = 0x1234

Every time you will try to commit a change using git, you will have to unlock your PGP private key on the Yubikey by entering the PIN.

Finally, add your public key to your Github account so everyone will see the check mark on your commits.

Screenshot from Github with 'jouir committed 2 weeks ago' + 'Verified'

Sudo

The Yubikey can be used to secure sudo, the software used to execute commands with root privileges. You can decide to replace your password by a touch on the Yubikey (“passwordless”), or add a touch on the Yubikey after the password (“2FA”). This blog post describes the procedure to do both.

I’ve tried to setup a passwordless authentication for sudo but I’m still asked for my password from time to time. And when the Yubikey requires to be touched, sudo doesn’t print any instruction on the terminal, the Yubikey starts to blink. The sudo command is not stuck, you just have to touch the Yubikey.

Conclusion

Is my digital life more secure now? Yes, probably. But security comes at a cost. The cost of entering two passwords and touch the Yubikey for every single SSH connection (maybe using SSH with PIV is easier after all). The cost of encrypting messages with PGP to non tech-savvy people. The cost of monitoring the expiration date of your PGP keys. The cost of the Yubikey itself. On the bright side, replacing vicious OTPs that regenerate too quickly by a simple touch is very nice! Same for sudo. In the end, it was a fun project and that’s what matters.