Signing Git Commits with Yubikey

Thieves are compromising software with malicious code that steals cryptocurrencies. If you build software, you need to protect your clients from these attackers. At TokenSoft, we take this threat seriously and we practice what we preach. This piece outlines one aspect of our best practices.

So far there have been several attempts to compromise software hosted by NPM, a popular package manager for the Javascript community. Attackers gain control of a commonly used software package with an innocent name like event-stream or electron-native-notify and then push malicious updates to NPM.

These compromised packages are then automatically pulled into your own software projects during the build process and eventually distributed to unsuspecting users. The NPM team has foiled some of these attacks, but attackers can also target your project’s source code directly.

The first obvious step is to host your code on a reputable platform like Github and require all code contributors to use multi-factor authentication. But what if that’s not good enough? The next step is securing your git repository with signed commits, ensuring that all code can be traced to a specific authorized developer.

How To

To start, we’ll assume you have a Yubikey 4 and an Apple computer and are committing code to a git repository stored on Github.

Prepare to roll up your sleeves: this will take some time.

Check Yubikey Firmware

First, plug in your Yubikey and look it up in System Report.

<pre><code>Apple → About this Mac → System Report → Hardware → USB</code></pre>

Verify that the Yubikey version is at least 3.1.8. This guide will assume you have a Yubikey 4. The instructions are not compatible with the older Yubikey FIDO U2F Security Key.

Set Yubikey Mode

Switch Yubikey to OTP/U2F/CCID mode (the Yubikey is probably already in this mode as a factory default, so this may only be necessary if it has been configured previously).

<pre><code>$ brew install yubikey-personalization$ ykpersonalize -m6</code></pre>
Firmware version 4.2.6 Touch level 516 Program sequence 1
The USB mode will be set to: 0x6Commit? (y/n) [n]: y

Enable Yubikey Touch Feature

This setting causes the Yubikey to flash when a signature is requested and only sign the message once the user touches the Yubikey.

Because we haven’t set up any custom PINs yet, the default Yubikey PIN (123456) and admin PIN(12345678) still work.

<pre><code>$ brew install ykman$ ykman openpgp touch sig on</code></pre>
Current touch policy of AUTHENTICATE key is OFF.
Set touch policy of AUTHENTICATE key to ON? [y/N]: y
Enter admin PIN: 12345678
Touch policy successfully set.

Install GPG tools and Create GPG Key

Install GPG tools (and install Brew if you don’t have it already).

<pre><code>$ brew cask install gpg-suite</code></pre>

Use gpg2 to configure your Yubikey “card” for a 4096 bit RSA key.

<pre><code>$ gpg2 --card-editReader ...........: Yubico Yubikey 4 OTP U2F CCID</code></pre>
Application ID ...: E1881231250102010005075345670000
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 09421669
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]gpg/card> adminAdmin commands are allowedgpg/card> key-attrChanging card key attribute for: Signature key
Please select what kind of key you want:
(1) RSA
(2) ECCYour selection? 1What keysize do you want? (2048) 4096The card will now be re-configured to generate a key of 4096 bits
Changing card key attribute for: Encryption key
Please select what kind of key you want:
(1) RSA
(2) ECCYour selection? 1What keysize do you want? (2048) 4096
The card will now be re-configured to generate a key of 4096 bits
Changing card key attribute for: Authentication key
Please select what kind of key you want:
(1) RSA
(2) ECCYour selection? 1What keysize do you want? (2048) 4096

Generate the new RSA private and public key on the Yubikey (do not store the key locally). Note that the email address you will provide below in the next command must match your Github account email address if you configure Github to accept only verified git commits.

This command takes a long time (several minutes) to generate keys.

<pre><code>$ gpg2 --card-editReader ...........: Yubico Yubikey 4 OTP U2F CCID</code></pre>
Application ID ...: E1881231250102010005075345670000
Version ..........: 2.1
Manufacturer .....: Yubico
Serial number ....: 09421669
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 ...: rsa4096 rsa4096 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]gpg/card> adminAdmin commands are allowedgpg/card> generateMake 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
Please specify how long the key should be valid.
     0 = key does not expire
<n> = key expires in n days</n>
<n>w = key expires in n weeks</n>
<n>m = key expires in n months</n>
<n>y = key expires in n yearsKey is valid </n>for? (0)Key does not expire at allIs this correct? (y/N) yGnuPG needs to construct a user ID to identify your key.Real name: Chris WalkerEmail address: <email address="" for="" github="" account="">Comment: tokensoftYou selected </email>this USER-ID:
"Chris Walker (tokensoft) <email address="" for="" github="" account="">"Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O</email>

The Yubikey device lights should flicker while the keys are being generated.

Change Card PINs

The Yubikey PIN and Admin PIN should be changed from their factory defaults: make sure to save these PINs safely.

The PIN must be 6 digits and the Admin PIN must be 8 digits: this command will fail without a helpful error message if the PINs are not the correct length.

<pre><code>$ gpg2 --card-edit gpg/card> adminAdmin commands are allowedgpg/card> passwdgpg: OpenPGP card no. E1881231250102010005075345670000 detected</code></pre>
1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quitYour selection? 1(enter the default PIN 123456)
(enter the new pin)
PIN changed.Your selection? 3
(enter the default Admin PIN 12345678)
(enter the new Admin PIN)
PIN changed.Your selection? qgpg/card> q

Back It Up and Do It Again

That’s right! You weren’t planning on using a single Yubikey, were you?

Securing crypto assets and software supply chains both require forethought and personal responsibility. Losing a single Yubikey must not shut down your software development process, so repeat the entire process above for two or more Yubikeys.

Get RSA Key IDs

Next, we need to list all keys held on Yubikey devices so that we can tell git to use the correct key for signing commits.

<pre><code>$ gpg2 --list-</code></pre>public-keys --keyid-format LONGgpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   3  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 3u
gpg: next trustdb check due at 2021-04-01
/Users/cw/.gnupg/pubring.kbx
----------------------------
pub   dsa2048/76D78F4567D026C4 2010-08-19 [SC] [expires: 2020-06-15]
     85E38F69046B44C1FBFBFB07B76D78F4567D026C4
uid                 [ unknown] GPGTools Team <team@gpgtools.org></team@gpgtools.org>
sub   elg2048/07EFF49ADBCBE671 2010-08-19 [E] [expires: 2020-06-15]
sub   rsa4096/E8A678480D9E43F5 2014-04-08 [S] [expires: 2024-01-02]pub   rsa2048/5F5F11700099D0E6 2019-04-02 [SC] [expires: 2021-04-01]
     289FDB94C4BAFD1F9BE5555C5F5F11700099D0E6
uid                 [ultimate] chris <email address=""></email>
sub   rsa2048/2EF25E8DF1C5F49D 2019-04-02 [E] [expires: 2021-04-01]pub   rsa4096/8614429225103F77 2019-04-02 [SC]@
     5ECB4A51E85D1695CD3A714D8614429225103F77
uid                 [ultimate] Chris Walker (tokensoft) <email address=""></email>
sub   rsa2048/37CFFB1842DCC41E 2019-04-02 [A]
sub   rsa4096/A2A37EF4F95B689D 2019-04-02 [E]pub   rsa4096/4012AA0013C6724A 2019-04-02 [SC]
     C5F64E5B7494A0A5233D83E74012AA0013C6724A
uid                 [ultimate] Chris Walker (tokensoft) <email address=""></email>
sub   rsa4096/78A3D72DB3F8F0F0 2019-04-02 [A]
sub   rsa4096/1D5F782EBBF2EF81 2019-04-02 [E]

Find the key IDs for the keys with these characteristics:

  • RSA 4096 used for public key
  • The key is used for signatures / certs (SC)
  • The key was generated today
  • UID matches the description you provided when generating the key.

In this case, my public keys from two Yubikeys are 8614429225103F77 and 4012AA0013C6724A.

Connecting Git to Yubikey

Tell git to use the key ID from the Yubikey, automatically sign each git commit, and use the gpg2 program for signing. Unfortunately, I don’t know of a way to configure git to accept multiple signing keys, so this configuration must be updated if you switch between the Yubikeys that were set up earlier.

<pre><code>$ git config --global user.signingkey 8614429225103F77 $ git config --global commit.gpgsign </code></pre>true$ git config --global gpg.program gpg2]

Your git config should now look something like this.

<pre><code>$ cat ~/.gitconfig</code></pre>
[user]
     email = <an email=""></an>
     name = Chris Walker
     signingkey = 8614429225103F77
[core]
     editor = nano
[gpg]
     program = gpg2
[commit]
     gpgsign = true]

Verifying Functionality

Make some changes to a git repo and then try to commit them, e.g.

<pre><code>$ git commit -am 'testing gpg'</code></pre>

If you haven’t already entered the PIN recently to unlock the Yubikey, a modal dialog should pop up asking you to enter the PIN.

Once this is done, the Yubikey will flash. Press the Yubikey button to sign the commit. Because it’s easy to forget the signing step and wonder why the git commit is hanging, I added a precommit hook to notify the user that their signature is required after passing typical linting and tests.

<pre><code>$ git commit -am 'testing yubikey'husky > npm run -s precommit (node v10.20.1)</code></pre>
lerna info version 2.16.0
lerna success run Ran npm script 'precommit' in packages:
lerna success - foo
lerna success - bar
WAITING FOR GIT SIGNATURE[e372dfc9] testing yubikey1 file changed, 2 insertions(+), 1 deletion(-)

The git log command can check the RSA signatures on git commits for validity.

<pre><code>$ git log --show-signaturecommit 5d954a6371a63003756591a6c7f07a80efd497ea (HEAD -> master)</code></pre>
gpg: Signature made Tue Apr  2 12:42:53 2019 PDT
gpg:                using RSA key 6ACB4A51E85D1695CD3A714D8614429225103F77
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: next trustdb check due at 2021-04-01
gpg: Good signature from "Chris Walker (tokensoft) <email address="">" [ultimate]</email>
Author: Chris Walker <email address=""></email>
Date:   Tue Apr 2 12:42:53 2019 -0700
...

Configuring Your Personal Github Account

Export the correct public key from each configured Yubikey.

<pre><code>gpg2 --armor --export 4012AA0013C6724A-----BEGIN PGP PUBLIC KEY BLOCK-----</code></pre>
mRQ3KLdsT...
...
...
...
...
=eFKq
-----END PGP PUBLIC KEY BLOCK-----


Navigate to your settings: Github → Settings → SSH and GPG Keys

Click the “New GPG Key” button

Paste the exported public key. After setting up both keys the GPG key section should look like the image below.

Configuring Your Project Github Account

To lock down your software supply chain further, configure protected branches to require verified commits.

It Works!

This process may be complex, but the assurance it provides to your software team is worthwhile, especially in the cryptocurrency industry where clients depend on your software to secure their business and finances.

Fixes (You Will Mess This Up)

During this process, I misconfigured or locked myself out of Yubikeys several times.

Resetting Yubikey Applet

If you enter the incorrect PIN or admin PIN too many times the Yubikey locks you out. This is easy to fix but you will lose all saved keys.

Install the Yubikey manager.

<pre><code>$ brew install ykman</code></pre>

Reset the applet.

<pre><code>$ ykman openpgp resetWARNING! This will delete all stored OpenPGP keys and data and restore factory settings? [y/N]: yResetting OpenPGP data, don't remove your YubiKey...</code></pre>
Success! All data has been cleared and default PINs are set.
PIN:         123456
Reset code:  NOT SET
Admin PIN:   12345678

After resetting the applet you will need to go through this setup process again.

Deleting unused GPG keys

During the setup process, you may create incorrectly configured keys. These can be deleted from your gpg key ring by first deleting the secret key and then the public key as follows (do ensure the key being deleted is not necessary).

<pre><code>$ gpg2 --delete-secret-keys 5F5F11700099D0E6gpg (GnuPG/MacGPG2) 2.2.10; Copyright (C) 2018 Free Software Foundation, Inc.</code></pre>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.sec  rsa2048/5F5F1170009D0E6 2019-04-02 chris <email>Delete </email>this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y$  gpg2 --delete-key 5F5F11700099D0E6gpg (GnuPG/MacGPG2) 2.2.10; Copyright (C) 2018 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.pub  rsa2048/5F5F11700099D0E6 2019-04-02 chris <email>Delete </email>this key from the keyring? (y/N) y

Further References

The first article below describes the minimum viable security practices. If you haven’t read this article yet and aren’t sure if you are following good practices, stop what you are doing, read the article, and implement everything on it right now.

Minimum Viable Security

The least you can do to frustrate would-be hackers.

The following articles were helpful source material for figuring out how to set up these Yubikeys.