Using a Yubikey with pass(1)

ljrk

2021-12-26

Inspired by Filippo Valsorda’s blogpost Touch-to-operate Password-store with Yubikey 4 I recently got an Yubikey myself, trying to build a similar setup. While I’d have preferred getting a solokeyV2/Nitrokey3 these seem to be still in production and not yet available. So for now I will test the YubiKey 5 NFC.

Reproducing the Setup

When I run the same commands as Filippo, I get:

$ ykman mode FIDO+CCID                  
WARNING: The use of this command is deprecated and will be removed!
Replace with: ykman config mode FIDO+CCID

Set mode of YubiKey to FIDO+CCID? [y/N]:

And indeed the newer version works without the warning. However, me being curious by nature, I’d like to see which of the USB modes listed on the ArchLinux wiki page 1 are currently enabled:

$ ykman config mode -h       
Usage: ykman config mode [OPTIONS] MODE

  Manage connection modes (USB Interfaces).

  This command is generaly used with YubiKeys prior to the 5 series. Use "ykman config usb" for more granular
  control on YubiKey 5 and later.

  Get the current connection mode of the YubiKey, or set it to MODE.

  MODE can be a string, such as "OTP+FIDO+CCID", or a shortened form: "o+f+c". It can also be a mode number.

  Examples:

    Set the OTP and FIDO mode:
    $ ykman config mode OTP+FIDO

    Set the CCID only mode and use touch to eject the smart card:
    $ ykman config mode CCID --touch-eject

Options:
  --touch-eject                When set, the button toggles the state of the smartcard between ejected and inserted
                               (CCID mode only).
  --autoeject-timeout SECONDS  When set, the smartcard will automatically eject after the given time. Implies
                               --touch-eject (CCID mode only).
  --chalresp-timeout SECONDS   Sets the timeout when waiting for touch for challenge response.
  -f, --force                  Confirm the action without prompting.
  -h, --help                   Show this message and exit.

Hm, this is maximally unhelpful. It says “Get the current connection mode” but doesn’t tell me how. A quick glance into the source code 2 shows that this feature seemes to have been (re)moved. This starts out to be a promising journey… While back then (tm) config mode changed the USB mode (FIDO, CCID or HID), we can now change the actual “applications” that are hidden behind these USB modes, as listed in the ArchLinux wiki page linked above.

We now have ykman config usb (and, they don’t tell you that, ykman config nfc for NFC enabled keys…):

$ ykman config usb -l
OTP
FIDO U2F
FIDO2
OATH
PIV
OpenPGP
YubiHSM Auth

(The output for nfc is the same, by default.)

So let’s disable all modes we don’t need using the new syntax:

$ for mode in OTP OATH HSMAUTH; do
    ykman config usb --force --disable "$mode";
    ykman config nfc --force --disable "$mode";
done

I disabled OTP based on my understanding that this requires the YubiKey key validation server 3, which goes strongly against my sense of security and secrecy.

I also disable OATH since I won’t store the credentials directly on the YubiKey but using pass(1), but encrypted using the YubiKey.

Further, I have no use (yet) for HSM authentication.

I leave FIDO/FIDO2 enabled. PIV and OPENPGP smartcard functionality shall stay enabled as well, although we will use OPENPGP mode only for now:

$ gpg --card-edit
[... snip ...]
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?

Change the PIN, Admin PIN (default each “12345678”) and optionally set a Reset Code. I also set the key-attr to use ECC instead of RSA.

Now enabling touch-to-operate:

$ ykman openpgp touch
Usage: ykman openpgp [OPTIONS] COMMAND [ARGS]...
Try 'ykman openpgp -h' for help.

Error: No such command 'touch'.

Hm, so apparently one can set the policy per signing/encryption/authentication or attestation key:

$ for key in SIG ENC AUT ATT; do
    ykman openpgp keys set-touch "$key" Fixed;
done
Error: No YubiKey found with the given interface(s)
Error: No YubiKey found with the given interface(s)
Error: No YubiKey found with the given interface(s)
Error: No YubiKey found with the given interface(s)

Uhm. So I went thinking to the bathroom and tried again, and – behold – now the command worked. Except:

Enter Admin PIN: 
WARNING: This touch policy cannot be changed without deleting the corresponding key slot!
Set touch policy of signature key to fixed? [y/N]: y^M^M

Okay, so sometimes, when the tool fails/errors, it doesn’t properly reset the terminal settings, so you might need to run

$ stty icrnl

in order for the enter key (carriage return / ^M) to be translated to LF that the linux console expects. Anyhow, you can check whether everything worked using:

$ ykman openpgp info
OpenPGP version: 3.4
Application version: 5.4.3

PIN tries remaining: 3
Reset code tries remaining: 3
Admin PIN tries remaining: 3

Touch policies
Signature key           On (fixed)
Encryption key          On (fixed)
Authentication key      On (fixed)
Attestation key         On (fixed)

You can now generate the keys:

$ gpg --card-edit
gpg/card> generate

as described in the original blog post.

You can now use the list subcommand to list the available keys:

gpg/card> list
[... snip ...]
Manufacturer .....: Yubico
[... snip ...]
Key attributes ...: ed25519 cv25519 ed25519
[... snip ...]
Signature key ....: 1234 1234 1234 1234 1234  1234 0123 4567 89AB CDEF
      created ....: 1970-01-01 00:00:00
Encryption key....: 1234 1234 1234 1234 1234  1234 ABCD EF01 2345 6789
      created ....: 1970-01-01 00:00:00
Authentication key: 1234 1234 1234 1234 1234  1234 9876 5432 10FE DCBA
      created ....: 1970-01-01 00:00:00
General key info..: 
pub  ed25519/0123456789ABCDEF 1990-01-01 John Doe (pass PGP key) <john.doe@example.com>
sec>  ed25519/0123456789ABCDEF  created: 1990-01-01  expires: never     
                                card-no: 0000 00000000
ssb>  ed25519/ABCDEF0123456789  created: 1990-01-01  expires: never     
                                card-no: 0000 00000000
ssb>  cv25519/9876543210FEDCBA  created: 1990-01-01  expires: never     
                                card-no: 0000 00000000

While the 0123456789ABCDEF is the main secret key also used for signatures, the ABCDEF0123456789 is the secret subkey used for encryption and 9876543210FEDCBA the key for authentication.

In order to use pass(1) we will now run:

$ pass init ABCDEF0123456789
$ pass insert passtestpassword
Enter password for passtestpassword: foobar
Retype password for passtestpassword: foobar
$ pass show passtestpassword
<touch yubikey>
foobar

It works!

Android pass(1) with OpenKeychain

We can now export the public key for import in OpenKeychain on Android:

gpg --export --armor 0123456789ABCDEF > pubkey.armor

Send it over to your Android phone and import the key. Then, in the “Keys” menu, open the 3-dot menu, select “Manage my keys” and “Use Security Token”. Hold your security token to your NFC enabled phone (or connect it via USB) and it will now pair the previously imported public key with the private key stored on the Yubikey.

You can now either create a new local repository for storing credentials or use git to synchronize your computers passwords with your Androids.

A Note on OTPs

You can use pass-otp to generate and store OTPs. However, if migrating from another application, this might have stored the TOTP secret as a text key and not as a OTP URI. In that case, unfortunately the best way forward seems to be to re-enroll using the new password manager application.

Further Work

Ideally, we could remove the need for GPG in pass(1). This is worked on with passage 4 which combines pass with age 5. Using the Rust implementation of age, rage, and the linked age-plugin-yubikey, the age key can be stored on the YubiKey as well and thus used for encryption.

Similarly, the Solokey V2 doesn’t support PGP at all, but instead provides only a PKCS PIV smartcard interface instead of an OpenPGP backed on. This can be used for encrypting the password credentials as well, and since YubiKeys also provide such an interface, this would work for both types of keys.

On the desktop side, the former design is already feasable, for Android this is highly WIP 6. I’m looking forward to phasing out the GPG encryption key for encrypting my passwords in favor of age, although this is a separate key anyway, whiches only purpose is the use for pass(1).

Since I need GPG for work, this would then free up my YubiKey to store that GPG key on it instead, or, I’d simply encrypt the GPG key using the age/PKCS from the YubiKey and store it as a separate passwordstore credential.


  1. https://wiki.archlinux.org/title/YubiKey#USB_connection_modes↩︎

  2. https://github.com/Yubico/yubikey-manager/blob/main/ykman/cli/config.py#L514↩︎

  3. https://wiki.archlinux.org/title/YubiKey#Yubico_OTP↩︎

  4. https://github.com/FiloSottile/passage↩︎

  5. https://github.com/FiloSottile/age↩︎

  6. https://github.com/android-password-store/Android-Password-Store/issues/1486↩︎