- Summary
- Estimated burden
- Instructions
- VMware Fusion
- Docker Content Trust
- Why disable Yubikey OTP?
- Troubleshooting
- TODO
- Acknowledgements
- References
GPG is useful for authenticating yourself over SSH and / or GPG-signing your git commits / tags. However, without hardware like the Yubikey, you would typically keep your GPG private subkeys in "plain view" on your machine, even if encrypted. That is, attackers who personally target [1, 2, 3, 4] you can compromise your machine can exfiltrate your (encrypted) private key, and your passphrase, in order to pretend to be you.
Instead, this setup lets you store your private subkeys on your Yubikey. Actually, it gives you much stronger guarantees: you cannot authenticate over SSH and / or sign GPG commits / tags without: (1) your Yubikey plugged in and operational, (2) your Yubikey PIN, and (3) touching your Yubikey. So, even if there is malware trying to get you to sign, encrypt, or authenticate something, you would almost certainly notice, because your Yubikey will flash, asking for your attention. (There is the "time of check to time of use" issue, but that is out of our scope.)
About 2-3 hours.
-
Install Homebrew.
-
Install GPG and other preliminaries.
brew install gnupg hopenpgp-tools pinentry-mac ykman yubico-piv-tool
-
Check the card status.
-
Don’t worry even if it says it supports only 3 RSA 2048-bit subkeys. We found that we could actually install 3 RSA 4096-bit subkeys (depending on the Yubikey model).
-
https://github.com/drduh/YubiKey-Guide/tree/ed1c2fdfa6300bdd6143d7e1877749f2f2fcab8e#insert-yubikey
-
-
Change card PINs and metadata.
-
Since we won’t be using it here, turn off OTP. Disabling it is recommended: see the explanation here.
-
https://developers.yubico.com/PIV/Introduction/Admin_access.html
-
https://ruimarinho.gitbooks.io/yubikey-handbook/content/openpgp/editing-metadata.html
-
Optional: set management key (but not PIN and PUK) via yubico-piv-tool (https://developers.yubico.com/PIV/Guides/Device_setup.html)
-
Set the user and admin PINs.
-
Optional: You might want to also set the unblock and reset PINs.
-
You might want to force asking for the PIN every time you sign. See the
forcesig
option ingpg
. -
Useful reset in case things go wrong: https://developers.yubico.com/ykneo-openpgp/ResetApplet.html
-
Looks like anyone can reset your card if they have physical access, or can compromise your machine, which is undesirable. At least you can recover from your key backup later. (Ask Yubico about this.) In the meantime, I am told that standard industry practice is to use two Yubikeys, with one serving as backup in case the other fails.
-
Store these PINs in an offline safe.
-
-
Configure GPG.
-
See Table 1. (How future-proof are these recommendations?)
-
https://blog.eleven-labs.com/en/openpgp-almost-perfect-key-pair-part-1/#install-the-right-tools
-
# Some random stuff to check later use-agent charset utf-8 fixed-list-mode # Avoid information leaked no-emit-version no-comments export-options export-minimal # Displays the long format of the ID of the keys and their fingerprints keyid-format 0xlong with-fingerprint # Displays the validity of the keys list-options show-uid-validity verify-options show-uid-validity # Limits the algorithms used personal-cipher-preferences AES256 personal-digest-preferences SHA512 default-preference-list SHA512 SHA384 SHA256 RIPEMD160 AES256 TWOFISH BLOWFISH ZLIB BZIP2 ZIP Uncompressed cipher-algo AES256 digest-algo SHA512 cert-digest-algo SHA512 compress-algo ZLIB disable-cipher-algo 3DES weak-digest SHA1 s2k-cipher-algo AES256 s2k-digest-algo SHA512 s2k-mode 3 s2k-count 65011712
Table 1: ~/.gnupg/gpg.conf
.
-
Create temporary working directory for GPG.
-
Create the master key.
-
Make sure it has only the ability to Certify.
-
Make sure it expires (perhaps in a few years).
-
Set reminder to renew key.
-
Commit passphrase to memory and / or offline storage.
-
It might be much easier if we generate the keys on the Yubikey itself, and then export a backup. Upside: easier. Downside: key generation on hardware can be prone to bugs (see the RoCA vulnerability). Either way, I STRONGLY recommend making an offline backup of your private keys (Steps 14-16).] We should make this the default for its ease of use, unless you are importing existing keys.
-
Cache the key ID (for convenience).
-
Create subkeys.
-
Make sure you know which key size your Yubikey can accommodate (see Step 3).
-
Create a subkey for only each of the following capabilities: Sign, Encrypt, Authenticate.
-
The passphrase is the same as for the master key (see Step 7i).
-
Make sure it expires (perhaps in 1 year).
-
Set reminder(s) to renew subkeys.
-
-
Check your list of secret keys.
-
gpg --export $KEYID | hokey lint
-
The output will display any problems with your key in red text. If everything is green, your key passes each of the tests. If it is red, your key has failed one of the tests.
-
hokey may warn (orange text) about cross certification for the authentication key. GPG's Signing Subkey Cross-Certification documentation has more detail on cross certification, and gpg v2.2.1 notes "subkey does not sign and so does not need to be cross-certified".
-
https://github.com/drduh/YubiKey-Guide/tree/ed1c2fdfa6300bdd6143d7e1877749f2f2fcab8e#check-your-work
-
-
Create a revocation certificate (in case master key is compromised).
-
gpg --output $GNUPGHOME/openpgp-revocs.d/$KEYID.rev --gen-revoke $KEYID
-
less $GNUPGHOME/openpgp-revocs.d/$KEYID.rev
-
-
Export public key.
-
gpg --export --armor $KEYID > $GNUPGHOME/$KEYID.pub.asc
-
less $GNUPGHOME/$KEYID.pub.asc
-
-
Import public key.
-
gpg --import < $GNUPGHOME/$KEYID.pub.asc
-
Copy this public key elsewhere for future reference.
-
-
Export private master key.
-
gpg --export-secret-keys --armor $KEYID > $GNUPGHOME/$KEYID.priv.asc
-
less $GNUPGHOME/$KEYID.priv.asc
-
-
Export private subkeys.
-
gpg --export-secret-subkeys --armor $KEYID > $GNUPGHOME/$KEYID.sub_priv.asc
-
The passphrase is the same as for the master key (see Step 8a).
-
less $GNUPGHOME/$KEYID.sub_priv.asc
-
-
Backup everything unto offline encrypted image.
-
Choose a good password.
-
hdiutil create /tmp/encrypted-gpg-backup.dmg -encryption -volname "gpg-backup" -fs APFS -srcfolder $GNUPGHOME
-
Move encrypted image unto offline storage.
-
Commit password to memory and / or offline storage.
-
Securely delete
$GNUPGHOME
(which is not straightforward on SSDs).
-
-
Delete private keys from memory.
-
gpg --delete-secret-key $KEYID
-
gpg --list-secret-keys
-
-
Restore private subkeys to memory.
-
gpg --import $GNUPGHOME/$KEYID.sub_priv.asc
-
gpg --list-secret-keys
-
-
Move private subkeys onto Yubikey.
-
gpg --expert --edit-key $KEYID
-
https://blog.eleven-labs.com/en/openpgp-secret-keys-yubikey-part-2/?#export-the-keys-to-the-yubikey
-
If Yubikey complains about
"gpg: selecting openpgp failed: Operation not supported by device"
, just unplug and replug Yubikey, then try again. -
Use the ADMIN PIN from Step 4iii.
-
Make sure you put the right types of subkeys in the right slots!
-
gpg --keyid-format LONG --list-secret-keys
-
gpg --card-status
-
Reader ...........: Yubico Yubikey 4 OTP U2F CCID Application ID ...: [redacted] Version ..........: 2.1 Manufacturer .....: Yubico Serial number ....: [redacted] Name of cardholder: Trishank Karthik Kuppusamy Language prefs ...: en Sex ..............: male URL of public key : [not set] Login data .......: [redacted] Signature PIN ....: forced Key attributes ...: rsa4096 rsa4096 rsa4096 Max. PIN lengths .: 127 127 127 PIN retry counter : 3 3 3 Signature counter : 0 Signature key ....: 6E7A C369 F3FD 6B74 D89F 3EA5 B4AF 1C9C 7351 8187 created ....: 2017-11-05 21:47:07 Encryption key....: 4DFE E3D7 AF94 C6B4 DB96 CF0B 97BF BA5F 949E 66BB created ....: 2017-11-05 21:46:19 Authentication key: 02CB F034 EED6 99A6 6EB8 686A 6543 5A46 D929 EAB8 created ....: 2017-11-05 21:47:43 General key info..: sub rsa4096/B4AF1C9C73518187 2017-11-05 Trishank Karthik Kuppusamy [redacted] sec# rsa4096/B9D5EC8FD089F227 created: 2017-11-05 expires: 2021-11-04 ssb> rsa4096/97BFBA5F949E66BB created: 2017-11-05 expires: 2018-11-05 card-no: [redacted] ssb> rsa4096/B4AF1C9C73518187 created: 2017-11-05 expires: 2018-11-05 card-no: [redacted] ssb> rsa4096/65435A46D929EAB8 created: 2017-11-05 expires: 2018-11-05 card-no: [redacted]
Table 2: gpg --card-status
.
-
Set trust for the master key.
-
It looks like we have to import our public key here again for whatever reason (see Step 13). It also looks like this step needs to be done after reboot for permanent effect.
-
-
Enable touch protection.
-
Use the ADMIN PIN from Step 4iii.
-
ykman openpgp touch aut on
-
ykman openpgp touch enc on
-
ykman openpgp touch sig on
-
-
Set up SSH agent.
-
Configure
gpg-agent
as in Table 3. -
Add the lines in Table 4 to your
bash
profile.
-
# https://github.com/drduh/YubiKey-Guide/tree/ed1c2fdfa6300bdd6143d7e1877749f2f2fcab8e#update-configuration # https://ruimarinho.gitbooks.io/yubikey-handbook/content/openpgp/authenticating-ssh-with-gpg.html enable-ssh-support pinentry-program /usr/local/bin/pinentry-mac default-cache-ttl 600 max-cache-ttl 7200
Table 3: ~/.gnupg/gpg-agent.conf
.
export "GPG_TTY=$(tty)" export "SSH_AUTH_SOCK=${HOME}/.gnupg/S.gpg-agent.ssh"
Table 4: ~/.bash_profile
.
-
Test the keys.
-
Start a new
bash
shell. -
echo "$(uname -a)" | gpg --encrypt --sign --armor --default-key B9D5EC8FD089F227 --recipient B4AF1C9C73518187 | gpg --decrypt --armor
-
Use the USER PIN from Step 4iii.
-
Make sure to touch your Yubikey (see Step 21).
-
-
Kill running GPG agents and restart them.
gpgconf --kill all
-
Upload SSH public key to GitHub.
-
ssh-add -L | grep -iF 'cardno' | pbcopy
-
https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/
-
-
Test GitHub SSH.
-
ssh -T -vvv [email protected]
-
Use the USER PIN from Step 4iii.
-
Make sure to touch your Yubikey (see Step 21).
-
-
Upload GPG public key to GitHub.
-
gpg --armor --export B9D5EC8FD089F227 | pbcopy
-
https://help.github.com/articles/adding-a-new-gpg-key-to-your-github-account/
-
-
Configure and test Git signing.
-
git config --global user.signingkey B9D5EC8FD089F227
-
git config --global commit.gpgsign true
-
Use the USER PIN from Step 4iii.
-
Make sure to touch your Yubikey (see Step 21).
-
Optional: configure U2F for GitHub and Google.
-
https://help.github.com/articles/configuring-two-factor-authentication-via-fido-u2f/
-
Why is this optional? Because an evil maid attack gives you access to U2F-enabled services. Should not be required for people who travel with Yubikey in laptop. In any case, it's a race between the user and the attacker anyway.
-
-
Reboot.
Optional: using Yubikey inside GNU/Linux running on VMware Fusion.
-
Shut down your VM, find its .vmx file, edit the file to the add the following line, and then reboot it:
usb.generic.allowHID = "TRUE"
-
Connect your Yubikey to the VM once you have booted and logged in.
-
Install libraries for smart card:
-
Ubuntu 17.10:
apt install scdaemon
-
Fedora 27:
dnf install pcsc-lite pcsc-lite-ccid
-
-
Import your public key (see Step 13).
-
Set ultimate trust for your key (see Step 20).
-
Configure GPG (see Step 22).
-
Test the keys (see Step 23). On Fedora, make sure to replace
gpg
withgpg2
. -
Use the absolutely terrible kludge in Table 5 to make SSH work.
-
Spawn a new shell, and test GitHub SSH (see Step 26).
-
Test Git signing (see Step 28). On Fedora, make sure to replace
gpg
withgpg2
:git config --global gpg.program gpg2
# gpg-ssh hack gpg-connect-agent killagent /bye eval $(gpg-agent --daemon --enable-ssh-support --sh) ssh-add -l
Table 5: Add these lines to ~/.bashrc
.
Optional: using Yubikey to store the root role key for Docker Notary.
-
Assumption: you are running all of the following under Fedora 27.
-
Install prerequisites:
dnf install golang yubico-piv-tool
-
Set GOPATH (make sure to update PATH too), and spawn a new
bash
shell. -
Check out the Notary source code:
go get github.com/theupdateframework/notary
-
Patch source code to point to correct location of shared library on Fedora.
-
cd ~/go/src/go get github.com/theupdateframework/notary
-
git pull https://github.com/trishankatdatadog/notary.git trishank_kuppusamy/fedora-pkcs11
-
-
Build and install the Notary client:
go install -tags pkcs11 github.com/theupdateframework/notary/cmd/notary
-
Add the lines in Table 6 to your
bash
profile, and spawn a new shell. -
Try listing keys (there should be no signing keys as yet):
-
dockernotary key list -D
-
If you see the line
"DEBU[0000] Initialized PKCS11 library /usr/lib64/libykcs11.so.1 and started HSM session"
, then we are in business. -
Otherwise, if you see the line
"DEBU[0000] No yubikey found, using alternative key storage: found library /usr/lib64/libykcs11.so.1, but initialize error pkcs11: 0x6: CKR_FUNCTION_FAILED"
, then you probably need togpgconf --kill scdaemon
(see this issue), and try again.
-
-
Generate the root role key (can be reused across multiple Docker repositories), and export it to both Yubikey, and keep a copy on disk:
-
Choose a strong passphrase.
-
dockernotary key generate -D
-
Commit passphrase to memory and / or offline storage.
-
Try listing keys again, you should now see a copy of the same private key in two places (disk, and Yubikey).
-
Backup private key in
~/.docker/trust/private/KEYID.key
unto offline, encrypted, long-term storage. -
Securely delete this private key on disk.
-
Now if you list the keys again, you should see the private key only on Yubikey.
-
-
Link the yubikey library so that the prebuilt docker client can find it:
sudo ln -s /usr/lib64/libykcs11.so.1 /usr/local/lib/libykcs11.so
-
Later, when you want Docker to use the root role key on your Yubikey:
-
When you push an image, you may have to kill
scdaemon
(in a separate shell) right after Docker pushes, but right before Docker uses the root role key on your Yubikey, and generates a new targets key for the repository. -
Use
docker -D
to find out exactly when to do this. -
This is annoying, but it works.
-
# docker notary stuff alias dockernotary="notary -s https://notary.docker.io -d ~/.docker/trust" # always be using content trust export DOCKER_CONTENT_TRUST=1
Table 6: Add these lines to ~/.bashrc
.
Yubikey OTPs are vulnerable to replay attacks. To first understand the attack scenario, one must understand how the OTPs are generated Source and full explanation here.
-
When a OTP is generated by the Yubikey, it outputs a 44 character value.
-
Ex: cccjgjgkhcbbirdrfdnlnghhfgrtnnlgedjlftrbdeut
-
The first 12 characters represent the public ID of the Yubikey and remain constant. The remaining 32 characters represent a unique token that includes a counter.
- The counter part is the most important piece of why one should disable OTP.
-
-
When the user submits the OTP to an IDP:
-
The IdP validates the unique ID is associated with the user account.
-
The IdP decrypts the token with a pre-shared AES key, proving that the user is who they say they are.
-
The IdP then check the counter to ensure it is greater than the last token they are aware of.
-
The IdP updates the counter so that this token and all previous tokens cannot be replayed.
-
Attack Scenarios:
-
Scenario 1 - Fake IdP
-
An attacker sets up a fake IdP.
-
Attacker directs a benign user to this fake IdP.
-
The user uses the OTP to authenticate to the fake IdP.
-
The attacker can now replay this token because it’s counter will be greater than the one known to the legitimate IdP.
-
-
Scenario 2 - Malicious or Compromised IdP
-
Prerequisite: The IdP is compromised by an attacker or the IdP has malicious intentions:
-
The IdP can capture the most recently used token and replay it on any other IdP the Yubikey device is associated with. Furthermore, this token is valid on ALL other IdPs until a newer token has been used to increment the counter in the IdPs database.
-
-
Scenario 3 - Yubispam
-
It is not uncommon for the user to accidentally share an OTP token by pressing the Yubikey. This assumes the default short click is configured for OTP on Yubikey.
-
A malicious user sees the OTP and can use this OTP token on ALL associated IdPs until a newer token has been used to increment the counter in the IdPs database.
-
-
If you are blocked out of using GPG because you entered your PIN wrong too many times (3x by default), don’t panic: just follow the instructions here.
-
If you suddenly start getting
Permission denied (publickey)
, verify thatssh-agent
is not running. Ifssh-agent
is running, kill the process. If the error persists, use the kludge in Table 5. -
If you are having issues failing to make connections, you still need to have
ssh-agent
running along withgpg-agent
:eval $(ssh-agent -s)
-
Automate, automate, automate as much as possible (e.g., using
bash
andexpect
scripts). -
Instructions for revoking and / or replacing keys.
-
Solving the PGP Revocation Problem with OpenTimestamps for Git Commits.
-
Upload public key to Keybase.
-
Procedures for recovering from key compromise / theft / loss.
-
Setup NFC 2FA (downside: would not work out-of-the-box on iPhones as yet).
-
Setup PAM authentication (downside: can get locked out of laptop).
I developed this guide while working at Datadog, in order to use it in various product security efforts. Thanks to Jules Denardou (Datadog), Cara Marie (Datadog), Cody Lee (Datadog), and Santiago Torres-Arias (NYU) who helped me to test these instructions. Thanks to Justin Massey (Datadog) for contributing the section on disabling Yubikey OTP.
-
https://blog.eleven-labs.com/en/openpgp-almost-perfect-key-pair-part-1/
-
https://github.com/drduh/YubiKey-Guide/tree/ed1c2fdfa6300bdd6143d7e1877749f2f2fcab8e
-
https://medium.com/@ahawkins/securing-my-digital-life-gpg-yubikey-ssh-on-macos-5f115cb01266
-
https://ocramius.github.io/blog/yubikey-for-ssh-gpg-git-and-local-login/
-
https://blog.eleven-labs.com/en/openpgp-secret-keys-yubikey-part-2/
-
https://ruimarinho.gitbooks.io/yubikey-handbook/content/openpgp/
-
http://karl.kornel.us/2017/10/welp-there-go-my-git-signatures/
-
https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2014-May/005877.html