Yubikey for Git

Security keys such as Yubikey, Nitrokey and Google’s Titan Security Key are great for for keeping cryptographic material off of your device and improving authentication to cryptographic actions. Unfortunately, these keys are so versatile that it can be unclear exactly how you should use them for each application. For Yubikey for instance, you might find yourself trying to choose between:

The problem then typically becomes how you match that cryptographic interface (say PKCS#11 in the case of the PIV Module) to the application in question. It’s usually possible to figure that out and there is an article for every possible permutation, but the easiest path isn’t always clear.

This guide explains the most straight forward approach to using security keys with git. I promise – no PGP keys will be generated in this process 🙏.

There are two cryptographic operations we need to be concerned with when using git:

  • Authentication (pushing and pulling changes)
  • Signing commits

We can use SSH keys for both operations so lets take a look at how to create and use those with security keys.

Authentication

Authentication to push and pull changes to a remote git repository such as Github, Gitlab or Bitbucket typically use SSH keys.

In my experience, the simplest way to use SSH Keys with a hardware security key is via FIDO2 SSH keys. Generation is as simple as selecting the special -sk key types (e.g ecdsa-sk or ed25519-sk) when generating a new key:

ssh-keygen -t ed25519-sk -f "$HOME/.ssh/git-auth"

Ensure this key is used for your git host as follows (using Github as an example):

# ~/.ssh/config
Host github.com
  IdentityFile ~/.ssh/git-auth
  User git

That’s really it. You can copy the contents of e.g $HOME/.ssh/git-auth.pub into your git host as just like any other SSH public key. When you push or pull code you’ll be prompted to touch your security key to authenticate.

There is no need to set a passphrase on this SSH key either. The main reason to set a passphrase is to authenticate access to the SSH private key on the filesystem. These keys are already authenticated via the security key so a passphrase is redundant.

It’s important to note that, by default, the SSH private key for these FIDO2 SSH keys are sort of split in two like a horcrux. One part of the private key is on file (in $HOME/.ssh/git-auth in our example) and the other part is on the security key. As a consequence, you lose either the security key or your device the SSH key will no longer work. A good security measure, but something to think about if you’re trying to use that same SSH key across multiple devices.

Commit Signing

First, its important to mention that git commit signing is mostly security theatre. I think Harley Watson has described this situation well so I won’t add more to that conversation. That said, sometimes you gotta jump through hoops for work or maybe you just like the little green Github badges and that’s OK too.

Thankfully, we can use FIDO2 SSH keys to sign git commits as well! I suggest making a different SSH key for this purpose:

ssh-keygen -t ed25519-sk -f "$HOME/.ssh/git-signing"

To use the git for signing, modify your git config (globally or on a per repository basis):

# .gitconfig
[user]
  signingkey = /path/to/.ssh/git-signing.pub
[gpg]
  format = ssh
[commit]
  gpgsign = true

Why use a different key for signing and authentication?

In short, because Github forces you to upload a different key for each purpose, but they have a good reason! Its important to use a different cryptographic key for each distinct protocol or context. In both the git commit signing and SSH authentication protocols we’re producing signatures over content. What if we can trick someone into signing content in one text that is valid in the other? For instance, is it possible for you to structure a commit such that if its signed it would also be possible to interpret as part of SSH authentication? If these protocols are well designed, hopefully the answer is no, but we can remove the risk entirely by using separate keys for each context (this is called cryptographic domain separation).