GPG on Yubikey for git

OpenPGP and GNU Privacy Guard

https://www.howtogeek.com/816878/how-to-back-up-and-restore-gpg-keys-on-linux/

One of the advantages of electronic files over paper hard copies is you can encrypt electronic files so that they are only accessible by authorized people. If they fall into the wrong hands, it doesn’t matter. Only you and the intended recipient can access the contents of the files.

The OpenPGP standard describes a system of encryption called public-key encryption. The GNU Privacy Guard implementation of that standard resulted in gpg, a command-line tool for encrypting and decrypting in accordance with the standard.

The standard outlines a public-key encryption scheme. Although it is called “public-key”, there are two keys involved. Each person has a public key and a private key. Private keys, as the name suggests are never revealed nor transmitted to anyone else. Public keys can be safely shared. in fact, public keys must be shared for the scheme to work.

When a file is encrypted, the sender’s private key and the recipient’s public key are used in the encoding process. The file can then be delivered to the recipient. They use their private key and the sender’s public key to decrypt the file.

Public and private keys are generated as a matched pair and tied to a particular identity. Even if you don’t transmit sensitive material to other people, you may use them on your own computer to add an extra layer of protection to private documents.

Related: How to Encrypt and Decrypt Files With GPG on Linux

The encryption uses world-class algorithms and cryptographic functions. Without the appropriate public and private keys, you simply can’t get into encrypted files. And, should you lose your keys,  that goes for you too. Generating new keys won’t help. To decrypt your files you need the keys that were used in the encryption process.

Needless to say, backing up your keys is of paramount importance, as is knowing how to restore them. Here’s how to accomplish these tasks.

The .gnupg Directory

Your keys are stored in a directory called “.gnupg” in your home directory. This directory will also store the public keys of anyone that has sent encrypted files to you. When you import their public keys, they are added to an indexed database file in that directory.

Nothing in this directory is stored in plain text, of course. When you generate your GPG keys you’re prompted for a passphrase. Hopefully, you’ve remembered what that passphrase is. You’re going to need it. The entries in the “.gnugp” directory cannot be decrypted without it.

If we use the tree

utility to look at the directory, we’ll see this structure of subdirectories and files. You’ll find tree

in your distribution’s repositories if you don’t already have it on your computer. tree .gnupg

The directory structure of the .gnupg directorystru

The contents of the directory tree are:

  • openpgp-revocs.d: This subdirectory contains your revocation certificate. You’ll need this if your private key ever becomes common knowledge or otherwise compromised. Your revocation certificate is used in the process of retiring your old keys and adopting new keys.
  • private-keys-v1.d: This subdirectory stores your private keys.
  • pubring.kbx: An encrypted file. It contains public keys, including yours, and some metadata about them.
  • pubring.kbx~: This is a backup copy of “pubring.kbx.” It is updated just before changes are made to “pubring.kbx.”
  • trustdb.gpg: This holds the trust relationships you have established for your own keys and for any accepted public keys belonging to other people.

You should be making regular, frequent backups of your home directory anyway, including the hidden files and folders. That will back up the “.gnupg” directory as a matter of course.

But you may think that your GPG keys are important enough to warrant a periodic backup of their own, or perhaps you want to copy your keys from your desktop to your laptop so that you have them on both machines. You’re you on both machines, after all.

Determining Which Keys to Back Up

We can ask gpg to tell us which keys are in your GPG system. We’ll use the --list-secret-keys options and the --keyid-format LONG options. gpg –list-secret-keys –keyid-format LONG

Listing the GPG key details to the terminal window

We’re told that GPG is looking inside the “/home/dave/.gnupg/pubring.kbx” file.

None of what appears on screen is your actual secret key.

  • The “sec” (secret) line shows the number of bits in the encryption (4096 in this example), the key ID, the date the key was created, and “[SC].” The “S” means the key can be used for digital signatures and the “C” means it can be used for certification.
  • The next line is the key fingerprint.
  • The “uid” line holds the ID of the key’s owner.
  • The “ssb” line shows the secret subkey, when it was created, and “E.” The “E” indicates it can be used for encryption.

If you have created multiple key pairs for use with different identities, they’ll be listed too. There’s only one key pair to back up for this user. The backup will include any public keys belonging to other people that the owner of this key has collected and decided to trust.

Backing Up

We can either ask gpg to back up all keys for all identities, or to back up the keys associated with a single identity. We’ll back up the private key, the secret key, and the trust database file.

To back up the public keys, use the --export  option. We’re also going to use the --export-options backup options. This ensures all GPG-specific metadata is included to allow the files to be imported correctly on another computer.

We’ll specify an output file with the --output option. If we didn’t do that, the output would be sent to the terminal window. gpg --export --export-options backup --output public.gpg

Exporting the public GPG keys

If you only wanted to back up the keys for a single identity, add the email address associated with the keys to the command line. If you can’t remember which email address it is, use the --list-secret-keys option, as described above. gpg --export --export-options backup --output public.gpg [email protected]

Exporting the public GPG keys for a single identity

To back up our private keys, we need to use the --export-secret-keys option instead of the --export option. Make sure you save this to a different file. gpg --export-secret-keys --export-options backup --output private.gpg

Exporting the private GPG keys

Because this is your private key, you’ll need to authenticate with GPG before you can proceed.

Note that you’re not being asked for your password. What you need to enter is the passphrase you supplied when your first created your GPG keys. Good password managers let you hold information like that as secure notes. It’s a good place to store them.

Providing the GPG passphrase to export the private keys

If the passphrase is accepted, the export takes place.

To back up your trust relationships, we need to export the settings from your “trustdb.gpg” file. We’re sending the output to a file called “trust.gpg.” This is a text file. It can be viewed using cat. gpg --export-ownertrust > trust.gpg cat trust.gpg

Exporting the GPG trust relationships

Here are the three files we’ve created. ls -hl *.gpg

The three files created by the exporting commands

We’ll move these over to another computer, and restore them. This will establish our identity on that machine, and allow us to use our existing GPG keys.

If you’re not moving the keys to another computer and you’re just backing them up because you want to be doubly sure they’re safe, copy them to some other media and store them safely. Even if they fall into the wrong hands, your public key is public anyway, so there’s no harm there. And without your passphrase, your private key cannot be restored. But still, keep your backups safe and private.

We’ve copied the files to a Manjaro 21 computer. ls *.gpg

The exported files transferred to a Manjaro computer

By default, Manjaro 21 uses the Z shell, zsh, which is why it looks different. But this doesn’t matter, it won’t affect anything. What we’re doing is governed by the gpg program, not the shell.

To import our keys, we need to use the --import option. gpg --import public.gpg

Importing the public GPG keys

The details of the key are displayed as it is imported. The “trustdb.gpg” file is also created for us. To import the private key is just as easy. We use the --import option again. gpg --import private.gpg

Importing the private GPG keys

We’re prompted to enter the passphrase.

Entering the passphrase to import the private GPG keys

Type it into the “Passphrase” field, hit the “Tab” key, and hit “Enter.”

Confirmation of the imported private GPG keys

The details of the imported keys are displayed. In our case, we only have one key.

To import our trust database, type: gpg –import-ownertrust trust.gpg

Importing the GPG trust relationships

We can check everything has been imported properly by using the --list-secret-keys option once more. gpg --list-secret-keys --keyid-format LONG

Verifying the import has worked

This gives us exactly the same output we saw on our Ubuntu computer earlier.

Protect Your Privacy

Make sure your GPG keys are safe by backing them up. If you have a computer disaster or just upgrade to a newer model, make sure you know how to transfer your keys to the new machine.

Why Use a YubiKey?

A YubiKey is a hardware-based authentication device that can securely store secret keys. Once a private key is written to your YubiKey, it cannot be recovered. Keeping secrets off your computer is more secure than storing them on your computer’s hard drive—another application could read your SSH keys from the ~/.ssh folder.

Various YubiKeys from Yubico

Each type of YubiKey supports a variety of different “interfaces,” each with different use cases. Many people associate a YubiKey with generating long one-time passwords (OTP) that look like this:

cccjgjgkhcbbirdrfdnlnghhfgrtnnlgedjlftrbdeut

However, generating one-time passwords is just a small slice of what you can do with a YubiKey. In this post, I’ll be talking about the OpenPGP interface and how you can use it for authentication.

If you don’t own a YubiKey, you can still follow along and skip the YubiKey parts.

What Is OpenPGP?

OpenPGP is a specification (RFC-4880), which describes a protocol for using public-key cryptography for encryption, signing, and key exchange, based on the original Phil Zimmermann work of Pretty Good Privacy (PGP).

There is often confusion between PGP and Gnu Privacy Guard (GnuPG or GPG), probably because of the inverted acronym. Sometimes these terms are used interchangeably, but GPG is an implementation of the OpenPGP specification (and arguably the most popular one).

You may have seen “Verified” badges on GitHub commits that use OpenPGP to confirm an author’s identity.

GitHub Verified Badge

Set Up and Configure a GPG Key

First, you need to generate a GPG key. You could do this directly on a YubiKey. However, you can NOT back up the keys once they are on the device. So instead, I’ll generate a GPG key on my computer, and once I have everything working, I’ll permanently move it to my YubiKey.

Start by generating a new key using gpg. If you already have a key, you can skip this first step:

gpg --full-generate-key
Please select what kind of key you want:
   (1) RSA and RSA
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (9) ECC (sign and encrypt) *default*
  (10) ECC (sign only)
  (14) Existing key from card
Your selection? 1 
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 4096 
Requested keysize is 4096 bits
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) 2y 
Key expires at Sat Jun  3 15:08:09 2023 EDT
Is this correct? (y/N) y 

GnuPG needs to construct a user ID to identify your key.

Real name: Brian Demers 
Email address: [email protected] 
Comment: bdemers test key 
You selected this USER-ID:
    "Brian Demers (bdemers test key) <[email protected]>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o 
Create an RSA key.
Set the key size to 4096.
Expire in 2 years, you can extend the expiration later.
Enter your name.
Enter your email address.
Enter an optional comment.
You will be prompted for a secret passphrase.
Press `o’ to save and exit.

Now you have a key! You can view your secret keys at any time by running:

gpg --list-secret-keys
---------------------------------
sec   rsa4096 2021-06-03 [SC] [expires: 2023-06-03]
      4C40E4AD3A157D172ECB27C9B2EAA49E11DE8CBD 
uid           [ultimate] Brian Demers (bdemers test key) <[email protected]>
ssb   rsa4096 2021-06-03 [E] [expires: 2023-06-03]
make a note of the Key ID; you will need this for a few different steps below.

Add an authentication sub-key for use with SSH for authentication—more on that below.

gpg --quick-add-key {your-key-id} rsa4096 auth 2y

If you list the secret keys again, you can see the new key and capability:

gpg --list-secret-keys
---------------------------------
sec   rsa4096 2021-06-03 [SC] [expires: 2023-06-03] 
      4C40E4AD3A157D172ECB27C9B2EAA49E11DE8CBD
uid           [ultimate] Brian Demers (bdemers test key) <[email protected]>
ssb   rsa4096 2021-06-03 [E] [expires: 2023-06-03] 
ssb   rsa4096 2021-06-03 [A] [expires: 2023-06-03] 
The primary key, has the capabilities of signing [S] and certification [C].
The encryption [E] subkey.
The new authentication [A] subkey.

Now that you have your newly minted key, back them up!

Back Up Your GPG Keys

Backups of your GPG keys should be stored offline. You are going through the process of securely storing your keys on a YubiKey, don’t leave your backup hanging around on disk.

Pick a backup strategy that works for you, anything from storing the keys on a USB stick in a lock box, to a printed paper key, or you could go all out.

Run the following commands to export the keys and trust store.

gpg --armor --export > public-keys.asc 
gpg --armor --export-secret-keys > private-keys.asc 
gpg --export-ownertrust > ownertrust.asc 

# Create a revocation certificate, in case you need lose your key
gpg --armor --gen-revoke {your-key-id} > revocation.asc 
# Select 1 for "Key has been compromised"
Export all public keys.
Export all private keys.
Export the trust store.
Create a revocation certificate as well. Take a look at the GnuPG docs to learn more about key revocation.
The --armor argument outputs the key in a PEM format.

If you ever need to restore your keys from this backup, you can run:

# restore public keys
gpg --import public-keys.asc
# restore private keys
gpg --import private-keys.asc
# restore trust store
gpg --import-ownertrust ownertrust.asc

Enable Your GPG Key for SSH

There are a few moving parts needed to expose your new GPG key in a way that your SSH client will use them. Initially, this part confused me the most and left me jumping between blog posts and various Stack Overflow questions (many of which were out of date).

Working backward from the SSH client: – The SSH client reads the SSH_AUTH_SOCK environment variable; it contains the location of a Unix socket managed by an agent. – A gpg-agent running in the background controls this socket and allows your GPG key to be used for authentication.

gpg-agent can replace the need for ssh-agent.

Enable SSH support using standard sockets by updating the ~/.gnupg/gpg-agent.conf file:

echo "enable-ssh-support" >> ~/.gnupg/gpg-agent.conf
echo "use-standard-socket" >> ~/.gnupg/gpg-agent.conf

Next, you will need to find the “keygrip” for the authentication key; this is different from the key id, run:

gpg --list-secret-keys --with-keygrip
---------------------------------
sec   rsa4096 2021-06-03 [SC] [expires: 2023-06-03]
      4C40E4AD3A157D172ECB27C9B2EAA49E11DE8CBD 
      Keygrip = 78BCD171C2DD44E5D6054F0EC98B8C5D2A37D076
uid           [ultimate] Brian Demers (bdemers test key) <[email protected]>
ssb   rsa4096 2021-06-03 [E] [expires: 2023-06-03]
      Keygrip = 48B8049057AE142926CADB23A816DFF57DC85098
ssb   rsa4096 2021-06-03 [A] [expires: 2023-06-03]
      Keygrip = 28E05AC1DCFCB0C23EFD89A86C627B0959758813 
Don’t confuse the Key ID with the “keygrip”
The “keygrip” for the authentication [A] key.

Update ~/.gnupg/sshcontrol with the authentication “keygrip”; this allows the gpg-agent to use this key with SSH.

echo {keygrip} >> ~/.gnupg/sshcontrol

Configure your shell environment to use gpg-agent:

# configure SSH to use GPG
echo 'export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)' >> ~/.zshrc

# start gpg-agent, if it isn't started already
echo 'gpgconf --launch gpg-agent' >> ~/.zshrc
echo 'gpg-connect-agent /bye' >> ~/.zshrc
# the docs say to use: gpg-connect-agent /bye

# Set an environment variable to tell GPG the current terminal.
echo 'export GPG_TTY=$(tty)' >> ~/.zshrc
The gpg-agent is started automatically the first time it is used. However, to make sure it is running and available for SSH, it needs to be run when your shell starts.

Open a new terminal session and run ssh-add -L; if everything is working correctly, your public key in SSH format will be output to your console:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC66/kO8H70GENVLxdD6ZBaRKzj5iDmhUpjFw1WzQmFe+O/dW8FpIXtuZX7QxtV+fqCaK6zbMPfKcUTfogRPdUtzzy/1Ik5WOAfJRF/woL6rMpId0klLalAJ4etOq2X3izBY8RhdiBGPOBPWl9bVTMcvrxIJqcO61FUC0vfwcXX/0GnQ+CnnA2c3CoeZAJbVFWSjo3imii26DdcfL3S0++6yN1y8EFr6BXh7S50Wog/c3CjgyM9t8Hiew/6XpB4deHWEPKkjn/TquRrg1xoFlCkz8w4NJ+jjkhhn8zZ0pcL9fk6VlkzkGiA1ADaEYj+ji0yKvenjrMiiM2FxHEcnTyXsAJkw/3iSxkQ2CpnWjg+BMZnV0inCH9KGvgQcZ3NF6hLuCi1wWP9TA1pVIcLVsDXJrwAnKYyrngWF1O2eI60x2I6ySQUJd1bExYWt2M50V5SynqKWUiYcRecLrO3/wPKzdUsYSNgCcwRSE4pXabAzTsre/WOp7MPQZ9tqWp1tPjyg+wn5UeQ21j0Fm3pZ4EWhBDQmPjm6y9tLv0kzoR8gmqa1KfSqwWyCl3FrNkT1wixxjQL1DVhVy3Kqoy5HA/z30hhkd5BSaqqouykirS/fmFE+k5pwZ/TVwf7BlC1AFNH0AzlCqoWt8s7wFsMUKsVkhZmYaHU52EIvn5rwPcUQQ== (none)
If you don’t see any output, try restarting the agent with the following command: gpg-connect-agent reloadagent /bye

Test Your GPG Keys with GitHub

Now that I have GPG configured on my computer, the next thing is to make sure everything is working correctly.

Log in to GitHub and go the SettingsSSH and GPG page. Copy the output from ssh-add -L and add a new SSH key.

On the same page add your GPG key, copy the value from gpg --armor --export {your-key-id}.

On macOS, you can pipe the output directly to your clipboard using pbcopy, for example, ssh-add -L | pbcopy.

Once you have your key configured, you can open an SSH connection to GitHub:

ssh [email protected]

The session will close immediately but will print a message:

Hi bdemers! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.

Woot! Everything is working!

Use a Graphical Pin Entry Program

If you would rather use a graphical application to enter your passphrase, you can install an alternative “pinentry” program. For example, on macOS:

# install a GUI pin entry program
brew install pinentry-mac

# configure gpg-agent to use this pinentry application
echo "pinentry-program $(which pinentry-mac)" >>  ~/.gnupg/gpg-agent.conf

# Restart gpg-agent
gpg-connect-agent reloadagent /bye

Sign Git Commits

Signing your commits is the only way to prove you are the author. Without a signature, someone could easily impersonate you by setting the name and email on a commit to match your information.

Configure Git to sign commits and tags automatically takes a few global properties; you want that “Verified” label on GitHub, don’t you 😉:

git config --global commit.gpgsign true
git config --global tag.gpgSign true
git config --global user.signingkey {your-key-id}

Your next commit will be signed, and you can double-check this by running git log --show-signature:

commit 85e0174d961f44666d8ffc7000e81df22eea13c6
gpg: Signature made Tue Jun  8 12:19:14 2021 EDT
gpg:                using RSA key 4C40E4AD3A157D172ECB27C9B2EAA49E11DE8CBD
gpg: Good signature from "Brian Demers (bdemers test key) <[email protected]>" [ultimate]
Author: Brian Demers <[email protected]>
Date:   Tue Jun 8 12:19:13 2021 -0400

    Testing commit signing

Setting Up a YubiKey

You didn’t need a YubiKey to complete any of the above GPG setup. Without one, though, I don’t think I’d go through setting up GPG + SSH authentication. Using standard SSH keys will offer the same level of security with less complexity. As I mentioned above, the goal was to move keys off my computer, and into the secure storage of the YubiKey.

One of the first things I do when I get a new YubiKey is to disable the keyboard functions. Unfortunately, I found myself accidentally touching the device, only to have it spew out a long set of characters; this is an excellent feature if you use it, but if you don’t, it can easily be disabled.

Open up the YubiKey Manager Application, select the Interfaces tab, and disable “OTP,” “PIV,” and “OATH” interfaces, and press the Save Interfaces button; the result will look something like this:

Enabled YubiKey Interfaces

Open up a terminal and run gpg --card-status, to display information about your device.

GPG refers to devices as “smartcards”, so any time you see the term “card” it refers to your YubiKey.
Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: D2760001240103040006162602010000
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 16260201
Name of cardholder: [not set]
Language prefs ...: [not set]
Salutation .......:
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
KDF setting ......: off
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

If you see an “Operation not supported by device” error message, make sure you have a recent version of GPG installed and try again. I’m using version 2.2.27 in this post.

To configure the device with your settings, run:

gpg --card-edit

This command will open an interactive session; type admin to enable setting properties on the devices.

Run the following commands to update the card.

gpg/card> admin 
Admin commands are allowed

gpg/card> passwd 
gpg: OpenPGP card no. D2760001240103040006162602010000 detected

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 1 
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? 3 
PIN changed.

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

Your selection? q

gpg/card> name 
Cardholder's surname: Demers
Cardholder's given name: Brian

gpg/card> lang 
Language preferences: en

gpg/card> login 
Login data (account name): bdemers

gpg/card> url 
URL to retrieve public key: https://github.com/bdemers.gpg

gpg/card> quit 
The admin command enables additional commands.
Enter the passwd to enter the password/pin sub-menu.
The default PIN is 123456.
The default Admin PIN is 12345678.
Set your name, last name, then first name.
The two-letter shortcode for your primary language.
Your preferred login name.
The URL of where your public key is stored, GitHub serves them at https://github.com/<username>.gpg.
Exit the program.

If you run gpg --card-status again you will updated information stored on your card:

Name of cardholder: Brian Demers
Language prefs ...: en
URL of public key : https://github.com/bdemers.gpg
Login data .......: bdemers

Move Your GPG Keys to a YubiKey

Make sure you back up your keys before moving them; this is your last chance!

Each key will need to be individual, the signature, encryption, and authentication keys. Edit the key by running:

gpg --edit-key {your-key-id}

Follow along with the prompts:

gpg> keytocard 
Really move the primary key? (y/N) y
Please select where to store the key:
   (1) Signature key
   (3) Authentication key
Your selection? 1

...

gpg> key 1 

sec  rsa4096/B2EAA49E11DE8CBD
     created: 2021-06-03  expires: 2023-06-03  usage: SC
     trust: ultimate      validity: ultimate
ssb* rsa4096/E45F9D38B846EC9E 
     created: 2021-06-03  expires: 2023-06-03  usage: E
ssb  rsa4096/D81BDB63BB563819
     created: 2021-06-03  expires: 2023-06-03  usage: A
[ultimate] (1). Brian Demers (bdemers test key) <[email protected]>

gpg> keytocard 
Please select where to store the key:
   (2) Encryption key
Your selection? 2

...
gpg> key 1 
...

gpg> key 2 

...

gpg> keytocard 
Please select where to store the key:
   (2) Authentciation key
Your selection? 3 

...

gpg> q 
Save changes? (y/N) y
Move the primary key to the smartcard.
Switch to key 1, the encryption key.
The selected key is marked with a *. If you do not see a selected key that means the primary key 0 has been selected.
Run keytocard again.
Deselect key 1.
Repeat the process for key 2 the authentication key.
You know the drill keytocard
All done! Exit and save changes.
After moving your keys to smartcard like a YubiKey, running the gpg --list-secret-keys command will show a greater-than symbol > next to the sec and ssb listings:
---------------------------------
sec>  rsa4096 2021-06-03 [SC] [expires: 2023-06-03]
      4C40E4AD3A157D172ECB27C9B2EAA49E11DE8CBD
      Card serial no. = 0006 16260201
uid           [ultimate] Brian Demers (bdemers test key) <[email protected]>
ssb>  rsa4096 2021-06-03 [E] [expires: 2023-06-03]
ssb>  rsa4096 2021-06-03 [A] [expires: 2023-06-03]

The smart card does NOT store your public key, run the fetch sub command to make sure GPG can fetch your key from the GitHub URL specified above:

gpg --edit-card
gpg/card> fetch
gpg: requesting key from 'https://github.com/bdemers.gpg'
gpg: key B2EAA49E11DE8CBD: duplicated subkeys detected - merged
gpg: key B2EAA49E11DE8CBD: public key "Brian Demers (bdemers test key) <[email protected]>" imported
gpg: Total number processed: 1
gpg:               imported: 1

Use Your GPG Key on Multiple Computers

One of the great things about storing your GPG keys on a YubiKey is that you can easily bring the keys to a different device. Since the keys are stored on the smartcard, you simply need to “link” the device’s keys:

gpg --card-edit
gpg/card> fetch
gpg: requesting key from 'https://github.com/bdemers.gpg'
gpg: key B2EAA49E11DE8CBD: duplicated subkeys detected - merged
gpg: key B2EAA49E11DE8CBD: public key "Brian Demers (bdemers test key) <[email protected]>" imported
gpg: Total number processed: 1
gpg:               imported: 1

gpg/card> quit

Finally, you can confirm the keys have been linked by running gpg --list-secret-keys and look to see if the sec entry is marked with a >.

---------------------------------
sec>  rsa4096 2021-06-03 [SC] [expires: 2023-06-03]
      4C40E4AD3A157D172ECB27C9B2EAA49E11DE8CBD
      Card serial no. = 0006 16260201
uid           [ultimate] Brian Demers (bdemers test key) <[email protected]>
ssb>  rsa4096 2021-06-03 [E] [expires: 2023-06-03]
ssb>  rsa4096 2021-06-03 [A] [expires: 2023-06-03]

The last thing to do is update the trust store on the new computer:

gpg --edit-key
...
gpg> trust 
...
Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5 
Do you really want to set this key to ultimate trust? (y/N) y

gpg> q 
Run the trust subcommand.
Select 5 ultimately trust; ONLY do this for your key.
Finished! Press q to exit.

Your smartcard is now set up on multiple computers!

Changing the trust level of an imported GPG key

It took me quite a while to reach the solution, which is:

gpg --edit-key 'Pang'

which fires up GPG and shows a prompt.

gpg (GnuPG) 1.4.11; Copyright (C) 2010 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.

Secret key is available.

pub  2048R/2F67056A  created: 2013-07-13  expires: never       usage: SC
                     trust: never         validity: unknown
sub  2048R/          created: 2013-07-13  expires: never       usage: E
[ unknown] (1). Pang Yan Han
gpg >

At this point, I entered:

trust

which shows:

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision?

Since this is my own key, I entered:

5

which trusts it ultimately.

macOS

piv-agent requires Homebrew in order to install dependencies. So install that first.

Copy the piv-agent binary into your $PATH, and the launchd .plist files to the correct location:

sudo cp piv-agent /usr/local/bin/
cp deploy/launchd/com.github.smlx.piv-agent.plist ~/Library/LaunchAgents/

From what I can tell .plist files only support absolute file paths, even for user agents. So edit ~/Library/LaunchAgents/com.github.smlx.piv-agent.plist and update the path to $HOME/.gnupg/S.gpg-agent.

If you plan to use gpg, install it via brew install gnupg. If not, you still need a pinentry, so brew install pinentry.

If ~/.gnupg doesn’t already exist, create it.

mkdir ~/.gnupg
chmod 700 ~/.gnupg

Then enable the service:

launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.github.smlx.piv-agent.plist
launchctl enable gui/$UID/com.github.smlx.piv-agent

A socket should appear in ~/.gnupg/S.gpg-agent.

Disable ssh-agent to avoid SSH_AUTH_SOCK environment variable conflict.

launchctl disable gui/$UID/com.openssh.ssh-agent

Set launchd user path to include /usr/local/bin/ for pinentry.

sudo launchctl config user path $PATH

Reboot and log back in.

Socket activation

piv-agent relies on socket activation, and is currently tested with systemd on Linux, and launchd on macOS. It doesn’t listen to any sockets directly, and instead requires the init system to pass file descriptors to the piv-agent process after it is running. This requirement makes it possible to exit the process when not in use.

ssh-agent and gpg-agent functionality are enabled by default in the systemd and launchd configuration files.

On Linux, the index of the sockets listed in piv-agent.socket are indicated by the arguments to --agent-types.

https://smlx.github.io/piv-agent/docs/install/#macos

Using a Yubikey for SSH on macOS

SSH 8.2 introduced support for using any U2F key in place of a private key file. Using it on macOS with full support for ssh-agent is a bit more complex.

Generating the keys

  1. You must choose between ed25519-sk and ecdsa-sk. Try ed25519-sk (Options 1 or 3) first. If it does not work due to device incompatibilities, fall back on ecdsa-sk (Options 2 or 4)
  2. You must choose if you want to store the key handle as a resident key on the device. If you want to, use options 1 or 2. If not, use options 3 or 4.

A U2F attestation requires a key handle to be sent to the device. When generating the key, ssh-keygen will create private and public key files that look similar to normal ssh key. The private key file is actually a key handle that cannot be used without the hardware token, however, the hardware token can also not be used without the key handle.

A resident key solves this problem by storing the key handle on the device. However, your key may or may not support it and only a limited number of resident keys may be stored on a device. Additionally, it may reduce the security of your ssh key as they could use it if they steal the hardware device. For this reason, a good pin is important.

It is your choice whether to use a resident key. If you do, you can load it directly to the ssh-agent using ssh-add -K, or write the key handle and public key to disk using ssh-keygen -K

ssh-keygen -t ed25519-sk -O resident # 1
ssh-keygen -t ecdsa-sk -O resident   # 2
ssh-keygen -t ed25519-sk             # 3
ssh-keygen -t ecdsa-sk               # 4

Updating SSH

SSH v8.2 or higher is required to use a security key. Install it with brew.

brew install openssh opensc

You can specifiy the path to the private key handle in your ssh config. Otherwise, you can configure the ssh-agent. The configuration in this guide assumes brew installed ssh and opensc into /opt/homebrew/bin/ this might be different on your system and should be verified.

ssh-agent on macOS

To be used with a security key, the ssh-agent must be on v8.2, which the system default is not.

First, disable the macOS default ssh-agent for your user.

launchctl disable user/$UID/com.openssh.ssh-agent

Next, add a new launchd service for your ssh-agent. Add the following file to ~/Library/LaunchAgents/com.zerowidth.launched.ssh_agent.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.zerowidth.launched.ssh_agent</string>
  <key>ProgramArguments</key>
  <array>
    <string>sh</string>
    <string>-c</string>
    <string>/opt/homebrew/bin/ssh-agent -D -a ~/.ssh/agent -P /opt/homebrew/lib/*,/opt/homebrew/Cellar/opensc/*/lib/*.so</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
</dict>
</plist>

And load it with launchctl load -w ~/Library/LaunchAgents/com.zerowidth.launched.ssh_agent.plist.

In your .bashrc or .zshrc, set SSH_AUTH_SOCK="~/.ssh/agent"

This plist was created using the launchd plist generator over at zerowidth. It runs the command /opt/homebrew/bin/ssh-agent -D -a ~/.ssh/agent. -D prevents ssh-agent from forking, and -a ~/.ssh/agent directs the agent to create a socket file at that location that is referenced in $SSH_AUTH_SOCK.

If you are having any issues you can always unload the launchd service and manually run the ssh-agent with the following command and read the errors
/opt/homebrew/bin/ssh-agent -d -a ~/.ssh/agent -P /opt/homebrew/lib/*,/opt/homebrew/Cellar/opensc/*/lib/*.so

Storing keys in the keyring

The following stanza can be adapted and placed in ~/.ssh/config. It removes the need to manually ssh-add keys with nonstandard names and stores key passwords if set in the macOS keyring.

Host *
  IgnoreUnknown UseKeychain
  UseKeychain yes
  AddKeysToAgent yes
  IdentityFile ~/.ssh/id_ecdsa_sk
  IdentityFile ~/.ssh/id_ed25519_sk

The first two lines direct ssh to use the macOS keychain to store passwords. The third automatically adds keys that are used to the agent and the last two specify additional keys to use. All of this can also be configured on a per host basis.

Reloading the Yubikey if it times out and fails to sign

Sometimes you might see an error where the Yubikey will not work with SSH. In order to solve this you simply have to re-add it to the agent. I wrote a simple function that works with my setup. You may need to make minor changes for it to work for you.

I added the following to my .zshrc you can also add it to your .bashrc

function reload_ssh {
  # pkcs11_path="/usr/lib/ssh-keychain.dylib"
  # pkcs11_path="/usr/local/lib/opensc-pkcs11.so"
  pkcs11_path="/opt/homebrew/lib/pkcs11/opensc-pkcs11.so"
  ssh_add_path="ssh-add"
  ssh_agent_path="ssh-agent"

  $ssh_add_path -D
  if [ "$1" = "-f" ]; then
    echo "Forcibly killing off previous process"
    launchctl unload ~/Library/LaunchAgents/com.zerowidth.launched.ssh_agent.plist
    pkill -9 ssh-agent
    pkill -9 ssh-pkcs11-helper
    launchctl load -w ~/Library/LaunchAgents/com.zerowidth.launched.ssh_agent.plist
  else
    $ssh_add_path -e $pkcs11_path >> /dev/null
    if [ $? -gt 0 ]; then
      echo "Failed to remove previous card. Retry or run with -f option"
    fi
  fi

  $ssh_add_path -s $pkcs11_path
  $ssh_add_path
}

After adding this function you can run reload_ssh to re-add your Yubikey and fix the most common errors.

Copyright © 2018 tpmullan.com. All right reserved