% Yubikey setup % SUNET % 2023-03-23
This guide steps you through the process of setting up a YubiKey for use with openssh and gpg.
Keys are generated outside the Yubikey for two reasons:
- to facilitate backups of the secret key (think loosing your Yubikey)
- who knows how good the RNG in the Yubikey is? http://smartfacts.cr.yp.to/
- YubiKey 5 Series
- brew
Make sure that /opt/homebrew/bin
(if using an ARM Mac) or /usr/local/bin
(x86) is before /bin
in your $PATH
. This should be the case with a standard Homebrew installation.
$ brew install ykman
$ brew install pinentry-mac
$ brew install gnupg
Create a gpg.conf in key_space
containing:
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
personal-digest-preferences SHA512
cert-digest-algo SHA512
- Install the latest release of Tugpgp
- Run the application and follow the guide at the wiki in Github for the next steps.
# sudo apt-get install gnupg2 gnupg-agent gpgsm pcscd scdaemon libfuse2
Add the following to $HOME/.gnupg/gpg-agent.conf. Don't forget to check the path to scdaemon.
scdaemon-program /usr/lib/gnupg/scdaemon
enable-ssh-support
# Enable debug logging if needed
#debug guru
#log-file /dev/shm/gpg-agent.log
Add the following to $HOME/.bashrc or equivalent
GPG_TTY=$(tty)
export GPG_TTY
# Set up ssh-agent socket as specified in gpg-agent(1)
unset SSH_AGENT_PID
if [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
fi
For gpg >= 2.4.4 (ex. Ubuntu 24.04) you need to add the following to $HOME/.gnupg/scdaemon.conf
disable-ccid
- Download the latest AppImage
- Follow along the guide at the wiki in Github for the steps.
Mac instructions
- YubiKey 5 Series
- brew
- a usb-memory
Make sure that /opt/homebrew/bin
(if using an ARM Mac) or /usr/local/bin
(x86) is before /bin
in your $PATH
. This should be the case with a standard Homebrew installation.
$ brew install ykman
$ brew install pinentry-mac
$ brew install gnupg
$ umask 077
$ diskutil erasevolume HFS+ 'RAMDisk' $(hdiutil attach -nomount ram://8388608)
$ cd /Volumes/RAMDisk
$ mkdir key_space
$ cd key_space
$ chown -R $(whoami) .
$ chmod 700 .
Create a gpg.conf in key_space
containing:
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
personal-digest-preferences SHA512
cert-digest-algo SHA512
$ export GNUPGHOME=$PWD
Kill any running gpg-agent
process.
$ gpg --full-gen-key
(1) RSA and RSA (default)
- 4096
- 1y
- Add
Real name
andEmail
but leaveComment
empty
Keep the returning key ID handy.
$ gpg --edit-key --expert <ID>
gpg> adduuid
# until done
gpg> addkey
(8) RSA (set your own capabilities)
- toggle S & E & A which should result in only "Authenticate".
- 4096
- 1y
gpg> list
sec rsa4096/0487C6AE38A74C12
created: 2021-05-28 expires: 2022-05-28 usage: SC
trust: ultimate validity: ultimate
ssb rsa4096/2E180F6B22DFF0D0
created: 2021-05-28 expires: 2022-05-28 usage: E
ssb rsa4096/E641CAE29209F416
created: 2021-05-28 expires: 2022-05-28 usage: A
[ultimate] (1). Magnus Svensson <masv@sunet.se>
gpg> save
When writing the keys to your card, gpg2 will delete the existing keys on your keyring and replace them with a stub. If you want a backup of the keys to store offline in a safe place, then now is the time to make a backup:
$ gpg --export-secret-key --armor > /Volumes/<USB_NAME>/secretkey.backup
$ gpg --output /Volumes/<USB_NAME>/revoke.asc.backup --gen-revoke <ID>
You will also need to save your public key somewhere, so you can import it in your real GPG keyring. Something like
$ gpg --armor --export <ID> > ~/my-public-key.asc
Type | Default code |
---|---|
PIN | 123456 |
Admin PIN | 12345678 |
$ gpg --edit-card
admin
passwd
Follow the instructions...
$ gpg --edit-card
name
Follow the instructions...
Place each key (capability) on card. This is a general instruction.
$ gpg --edit-key <ID>
gpg> keytocard
# primary key to card (Sign)
gpg> key 1
gpg> keytocard
gpg> key 1
gpg> key 2
gpg> keytocard
gpg> key 2
gpg> save
$ unset GNUPGHOME
Kill gpg-agent
.
$ gpg --import ~/my-public-key.asc
$ gpg --edit-key <ID>
gpg> trust
set 5
gpg> save
$ gpg -K
/Users/masv/.gnupg/pubring.kbx
------------------------------
sec> rsa4096 2021-05-28 [SC] [expires: 2022-05-28]
D0B2D9FF86A6B3A6314E632C5BA4BA8D2A3E5C61
Card serial no. = 0006 10124562
uid [ultimate] Magnus Svensson <masv@sunet.se>
ssb> rsa4096 2021-05-28 [E] [expires: 2022-05-28]
ssb> rsa4096 2021-05-28 [A] [expires: 2022-05-28]
$ echo "enable-ssh-support" >> ~/.gnupg/gpg-agent.conf
$ echo "pinentry-program $(which pinentry-mac)" >> ~/.gnupg/gpg-agent.conf
$ echo "export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)" >> ~/.zshrc
$ echo "gpgconf --launch gpg-agent" >> ~/.zshrc
$ gpg --export-ssh-key <ID> > ~/.ssh/id_rsa_yubikey.pub
Logout/login (Is this the only way of doing it?)
ssh-add -L
lists ssh public-keys
$ rm -Pr private-keys-v1.d/
$ rm -P pubring.kbx*
$ cd .. && rm -rf key_space/
$ sudo diskutil unmount force RAMDisk/
A touch policy takes advantage of the fact that the Yubikey is a HSM that can be restricted by touch activation.
$ ykman openpgp keys set-touch <AUT|SIG|ENC|ATT> ON
Note that attestation (ATT) is only available with YubiKey hardware version 4.3 and above.
$ ykman config usb --disable OTP
$ gpg --output /tmp/test_sign.sig --sign /tmp/test_sign
$ gpg --verify /tmp/test_sign.sig
Compare the returning key fingerprint with the sub key fingerprint with Sign capabilities, it should be equal.
$ gpg --list-keys --with-subkey-fingerprints
$ gpg -e -r <YOUR_EMAIL> /tmp/test_encrypt.txt
$ gpg --decrypt /tmp/test_encrypt.txt.gpg
should return:
gpg: encrypted with rsa4096 key, ID <ENCRYPT_SUBKEY_ID>, created <DATE>
"<NAME> <<EMAIL>>"
Linux instructions
# sudo apt-get install gnupg2 gnupg-agent gpgsm pcscd scdaemon
Add the following to $HOME/.gnupg/gpg-agent.conf. Don't forget to check the path to scdaemon.
scdaemon-program /usr/lib/gnupg/scdaemon
enable-ssh-support
# Enable debug logging if needed
#debug guru
#log-file /dev/shm/gpg-agent.log
Add the following to $HOME/.bashrc or equivalent
GPG_TTY=$(tty)
export GPG_TTY
# Set up ssh-agent socket as specified in gpg-agent(1)
unset SSH_AGENT_PID
if [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
fi
$ ykman config usb --disable OTP
From the factory the yubikey is setup only as a standard/classical OTP device. Run the following command to set it in dual HID (keyboard) and CCID mode. Resist the temptation to set it in CCID-only mode - you won't be able to use ykpersonalize again if you do.
# ykpersonalize -m82
If you failed to resist the temptation of CCID-mode you can expert-mode hack your NEO back to HID mode using this command:
$ opensc-tool -s '00 a4 04 00 07 a0 00 00 05 27 20 01 01' -s '00 01 11 00'
or, alledgedly, using the ykneomgr application found at http://opensource.yubico.com/libykneomgr/
If you want to use the token in a virtual machine (VMWare Fusion) when running OS X you can't use HID mode.
# ykpersonalize -m81
This step might not be needed in all cases.
/etc/udev/rules.d/69-yubikey.rules
ACTION!="add|change", GOTO="yubico_end"
# Yubico Yubikey 4
ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0010|0110|0111|0112|0114|0116|0401|0403|0405|0406|0407|0410", \
ENV{ID_SECURITY_TOKEN}="1", RUN+="/usr/sbin/service pcscd restart"
LABEL="yubico_end"
/etc/udev/rules.d/70-yubikey.rules
# Udev rules for letting the console user access the Yubikey USB
# device node, needed for challenge/response to work correctly.
ACTION=="add|change", SUBSYSTEM=="usb", \
ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0010|0110|0111|0112|0114|0116|0401|0403|0405|0406|0407|0410", \
TEST=="/var/run/ConsoleKit/database", \
RUN+="udev-acl --action=$env{ACTION} --device=$env{DEVNAME}"
If you are using GNOME 3 and experience problems such as
# gpg2 --card-status
# gpg: OpenPGP card not available: general error
Or if you notice that you have two gpg-agents running, then you can try to disable the GNOME Keyring GnuPG/SSH agent using:
# echo 'X-GNOME-Autostart-enabled=false' >> /etc/xdg/autostart/gnome-keyring-gpg.desktop
# echo 'X-GNOME-Autostart-enabled=false' >> /etc/xdg/autostart/gnome-keyring-ssh.desktop
# echo 'X-GNOME-Autostart-enabled=false' >> /etc/xdg/autostart/gnome-keyring-pkcs10.desktop
# echo manual > /etc/xdg/systemd/gnome-keyring.override
Create some space in memory you can easily clean out later...
# umask 077
# cd /dev/shm
Create a tempdir for gnupg stuff
# mkdir foo
# cd foo
Create a gpg.conf in the foo directory containing:
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
personal-digest-preferences SHA512
cert-digest-algo SHA512
Make sure gpg2 uses this directory and generate a key using the defaults (a 4096 bit RSA key) and set a 1 year validity.
# export GNUPGHOME=$PWD
# gpg2 --full-gen-key
[...]
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
Your selection? 1 [enter]
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (4096) [enter]
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) 1y [enter]
Key expires at ons 25 maj 2016 15:45:52 CEST
Is this correct? (y/N) y [enter]
[...]
Real name: Test Testsson [enter]
Email address: test@example.com [enter]
Comment: [enter] <--- Leave this field empty
You selected this USER-ID:
"Test Testsson <test@example.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.
Choose a strong password that is unique for this key and is made up of a random combination
of both upper and lower letters, as well as numbers.
Next edit the key in expert mode - $id is the id of the key you just created. Add your email addresses with adduid.
# gpg2 --edit-key --expert $id
gpg> adduid # until done
Now add 1 subkey to be used with SSH. Pick RSA (set your own capabilities) and toggle S & E & A which should result in only "Authenticate".
gpg> addkey
[...]
Please select what kind of key you want:
(3) DSA (sign only)
(4) RSA (sign only)
(5) Elgamal (encrypt only)
(6) RSA (encrypt only)
(7) DSA (set your own capabilities)
(8) RSA (set your own capabilities)
Your selection? 8 [enter]
Possible actions for a RSA key: Sign Encrypt Authenticate
Current allowed actions: Sign Encrypt
(S) Toggle the sign capability
(E) Toggle the encrypt capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection? S [enter], E [enter], A [enter], Q [enter]
[...]
What keysize do you want? (4096) [enter]
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) 1y [enter]
Key expires at ons 25 maj 2016 16:34:06 CEST
Is this correct? (y/N) y [enter]
Really create? (y/N) y [enter]
This is what the result should look like.
gpg> list
pub 4096R/662D4043 created: 2013-10-01 expires: 2014-10-01 usage: SC
trust: ultimate validity: ultimate
sub 4096R/FED803A7 created: 2013-10-01 expires: 2014-10-01 usage: E
sub 4096R/4334FEF8 created: 2013-10-01 expires: 2014-10-01 usage: A
[ unknown] (1) Leif Johansson <leifj@nordu.net>
[ unknown] (2) Leif Johansson <leifj@mnt.se>
[ unknown] (3). Leif Johansson <leifj@sunet.se>
Remember to save the changes when you exit edit mode using the command save.
gpg> save
When writing the keys to your card, gpg2 will delete the existing keys on your keyring and replace them with a stub. If you want a backup of the keys to store offline in a safe place, then now is the time to make a backup using either:
$ cp secring.gpg secring.gpg.backup
$ cp pubring.gpg pubring.gpg.backup
$ cp secring.gpg.backup pubring.gpg.backup /path/to/backup/media
Or by only exporting the secret key, including subkeys, to the offline backup:
$ gpg2 --export-secret-key --armor > /path/to/backup/media/secretkey.backup
You will also need to save your public key somewhere, so you can import it in your real GPG keyring. Something like
$ gpg2 --armor --export $id > ~/my-NEO-public-key.asc
Note that gpg2 might ask for the YubiKey admin pin (default 12345678) when moving the primary key.
# gpg2 --edit-key $id
gpg> toggle
gpg> keytocard
gpg> key 1
gpg> keytocard
gpg> key 1
gpg> key 2
gpg> keytocard
Do not forget to change the YubiKey admin pin (default 12345678), as well as the user pin (default 123456) to a strong password.
# gpg2 --card-edit
[...]
gpg/card> admin
Admin commands are allowed
gpg/card> passwd
[...]
1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit
Your selection? 1 [enter], 3 [enter], 4 [enter], Q [enter]
During this stage it is very important that only a gpg-agent with version >=2.0.22 is running. If you get errors like
gpg: error writing key to card: Not supported
it is probably not the right gpg-agent that is currently running. Kill any gpg-agent process and look in
/etc/X11/Xsession.d/90gpg-agent
for how to restart it with a gpg-agent binary that you are sure is >=2.0.22.
# cd /dev/shm/foo
# shred -zun 12 secring.gpg
# cd ..; rm foo -rf
A touch policy takes advantage of the fact that the Yubikey is a HSM that can be restricted by touch activation.
$ ykman openpgp keys set-touch <AUT|SIG|ENC|ATT> ON
Note that attestation (ATT) is only available with YubiKey hardware version 4.3 and above.
Lets see what we have...
# gpg2 --card-status
gpg: WARNING: unsafe permissions on homedir `/dev/shm/foo'
gpg: detected reader `Yubico Yubikey NEO OTP+CCID 00 00'
Application ID ...: D2760001240102000000000000010000
Version ..........: 2.0
Manufacturer .....: test card
Serial number ....: 00000001
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: forced
Key attributes ...: 2048R 2048R 2048R
Max. PIN lengths .: 127 127 127
PIN retry counter : 3 3 3
Signature counter : 0
Signature key ....: F95F 1654 911B 8B21 68A1 EF18 6D13 20C7 662D 4043
Encryption key....: 609A 2FC8 5606 421D B848 14DF 3EC4 D983 FED8 03A7
Authentication key: E2A3 D430 532E F05B B37F 1822 966F 771E 4B6F EF82
General key info..: pub 2048R/662D4043 2013-10-01 Leif Johansson <leifj@sunet.se>
sec 2048R/662D4043 created: 2013-10-01 expires: 2014-10-01
ssb 2048R/FED803A7 created: 2013-10-01 expires: 2014-10-01
ssb 2048R/4B6FEF82 created: 2013-10-01 expires: 2014-10-01
You can use the revoked key to verify old signatures, or decrypt data if you have not lost the private key, but it cannot be used to encrypt new messages to you.
gpg2 --output revocation-certificate.asc --gen-revoke '<fingerprint>'
[...]
Create a revocation certificate for this key? (y/N) y [enter]
Please select the reason for the revocation:
0 = No reason specified
1 = Key has been compromised
2 = Key is superseded
3 = Key is no longer used
Q = Cancel
(Probably you want to select 1 here)
Your decision? 2 [enter]
[...]
gpg2 --import revocation-certificate.asc
gpg2 --send '<fingerprint>'