YubiKey - hpaluch/hpaluch.github.io GitHub Wiki

Yubico YuibiKey

Recently I bought Yubico's YubiKey 5 NFC - "USB key" that can be used for authentication.

I know nothing about it (there are SOO MANY shorctuts) so I decided to start with: https://docs.fedoraproject.org/en-US/quick-docs/using-yubikeys/

Test OS: Fedora 43 Workstation (GNOME/Wayland) Beta in VM (ISO: Fedora-Workstation-Live-43-20251004.n.0.x86_64.iso)

Local Login with U2F

I want to avoid Cloud of any kind so I will skip pam_yubico and rather use pam_u2f, where U2F stands for Universal 2nd Factor.

I started with install (from docs):

# for PAM authentication
sudo dnf install pam-u2f pamu2fcfg
# for Key management: (not required for U2F, because it uses unique private key
# that never leaves YubiKey)
sudo dnf install yubikey-manager

Next I created /etc/pam.d/u2f-required with contents:

#%PAM-1.0
auth       required     pam_u2f.so

And also /etc/pam.d/u2f-sufficient with contents:

#%PAM-1.0
auth       sufficient     pam_u2f.so

Now insert your Yubikey to USB. If you are using VM like me you have to do this in Virt-manager:

  • select menu Virtual Machine -> Redirect USB device
  • check on line with Yubico.com Yubikey 4/5 OTP+U2F+CCID
  • invoke lusb inside guest, in my case there is:
$ lsusb | grep Yubi

Bus 001 Device 003: ID 1050:0407 Yubico.com Yubikey 4/5 OTP+U2F+CCID

To list detected YubiKey(s) (redacted SN):

$ sudo ykman list | sed 's/[0-9]*$/X/'

YubiKey 5 NFC (5.7.4) [OTP+FIDO+CCID] Serial: X

More detailed info (redacted SN):

$ sudo ykman info | sed '/Serial number:/s/[0-9]/X/g'

Device type: YubiKey 5 NFC
Serial number: XXXXXXXX
Firmware version: 5.7.4
Form factor: Keychain (USB-A)
Enabled USB interfaces: OTP, FIDO, CCID
NFC transport is enabled

Applications    USB     NFC
Yubico OTP      Enabled Enabled
FIDO U2F        Enabled Enabled
FIDO2           Enabled Enabled
OATH            Enabled Enabled
PIV             Enabled Enabled
OpenPGP         Enabled Enabled
YubiHSM Auth    Enabled Enabled

Generating configuration for for PAM:

  • do this as your target user that you want to authenticate with YubiKey

  • run:

    mkdir -p ~/.config/Yubico
    pamu2fcfg > ~/.config/Yubico/u2f_keys
    # content is not sensitive, but it is better to tighten permissions
    chmod 600 ~/.config/Yubico/u2f_keys
    
  • WARNING! Later command seems to freeze, but it is only because it want you to touch sensor on key (to verify that you knowingly want to authenticate)

  • verify that sensor on your Yubico is flashing (green LED) and touch it

  • this should make pamu2cfg ... command to finish.

  • verify that your generated file is non-0 length with:

    $ ls -lo ~/.config/Yubico/u2f_keys
    
    -rw-r--r--. 1 X 196 Oct  7 16:27 /home/X/.config/Yubico/u2f_keys
    

As final thing we have to include our prepared PAM configuration to specific service.

We will use sufficient which means that in case of failure user can authenticate with another module (for example plain password).

I strongly suggest to backup your current /etc/ with trivial but really handy:

sudo tar -cvzf /root/etc-`date '+%s'`.tar.gz -C / etc

I will start with local console login:

  • make backup: sudo cp -v /etc/pam.d/login{,.orig}
  • edit your /etc/pam.d/login
  • insert auth include u2f-sufficient right BEFORE line: auth substack system-auth

How it works:

  • switch to local console with Ctrl-Alt-F4 (try other Fx key if there is no login: prompt)
  • login with your username (user that created ~/.config/Yubico/u2f_keys
  • once you press ENTER after specifying login name you should again see blinking green LED on you YubiKey. Touch sensor.
  • right after touching sensor you should be logged in
  • tip: if you login with user that has no valid file ~/.config/Yubico/u2f_keys this authentication will fail, but PAM will allow to specify plain user's password (it will try next PAM module in chain) in such case thanks to sufficient keyword.

Using YubiKey with GDM (GNOME Login Manager):

  • in my case I edited /etc/pam.d/gdm-password
  • again, insert auth include u2f-sufficient right BEFORE line: auth substack system-auth
  • and try login in GDM as your user - instead of asking for password you should see blank screen and your YubiKey should start blinking - touch sensor to login without password...

Changing PINs and PUK

Before using YubiKey to storea any kind of private key we should change its PINs and PUK as described on: https://docs.fedoraproject.org/en-US/quick-docs/using-yubikeys/#_update_the_pins_of_the_piv_module

When you try following command it will warng you that you are using trivial defaults:

# ykman piv info

PIV version:              5.7.4
PIN tries remaining:      3/3
PUK tries remaining:      3/3
Management key algorithm: AES192

WARNING: Using default PIN!
WARNING: Using default PUK!
WARNING: Using default Management key!

CHUID: No data available
CCC:   No data available

WARNING! Store PINs and especially PUK reliably! You will loose all private key access without them!

To change PIN: use:

$ ykman piv access change-pin

Enter the current PIN:  #ENTER: 123456 (will be not echoed)
Enter the new PIN:      #ENTER 8-digits
Repeat for confirmation:
New PIN set.

$ ykman piv info

(there should be no longer warning on "default PIN", but other remains)

Same for PUK (store reliably!!!):

$ ykman piv access change-puk
Enter the current PUK:  # ENTER 12345678
Enter the new PUK:      # use 8 digits
Repeat for confirmation:
New PUK set.

Finally management key:

$ ykman piv access change-management-key --generate --protect

Enter the current management key [blank to use default key]: # just press ENTER
Enter PIN:  # ENTER your New PIN from above
New management key set.

Verification: now there should be any WARNING! in info output:

$ ykman piv info

PIV version:              5.7.4
PIN tries remaining:      3/3
PUK tries remaining:      3/3
Management key algorithm: AES192
Management key is stored on the YubiKey, protected by PIN.
CHUID: No data available
CCC:   No data available

Finally we should also change PIN for FIDO (WARNING! There is typo in Fedora docs - replace piv with fido in command!).

$ ykman fido access change-pin

### specify pin that is easy to remember (at least 4 digits) you will need it often:
Enter your new PIN:
Repeat for confirmation:
FIDO PIN updated.

$ ykman fido info

AAGUID:                       d7781e5d-e353-46aa-afe2-3ca49f13332a
PIN:                          8 attempt(s) remaining
Minimum PIN length:           4
Always Require UV:            Off
Credential storage remaining: 100

Finally we should set OATH password, but beware!

  • at least on Fedora 43 Beta
  • in my case second command crashed (run as root):
# this is OK:
$ ykman oath access change

Enter the new password:  # used complex 20 char password generated by keepass
Repeat for confirmation:
Password updated.

$ ykman oath access remember

ERROR: An unexpected error has occurredTraceback (most recent call last):
  File "/usr/lib/python3.14/site-packages/ykman/_cli/__main__.py", line 639, in main
...
gi.repository.GLib.GError: g-dbus-error-quark: Object does not exist at path “/org/freedesktop/secrets/collection/login” (19)
  • fix is trivial: run later command from GUI, it will work, because now GNOME will create appropriate d-bus object:
# under GUI as non-root user:

$ ykman oath access remember

Enter the password:  # your OATH password from above
Password remembered.
Password remembered.

$ ykman oath info

OATH version:        5.7.4
Password protection: enabled
The password for this YubiKey is remembered by ykman.

Anyway I also found really interesting article on PC/SC (= Personal Computer/Smart Card, see https://en.wikipedia.org/wiki/PC/SC):

$ pcsc_scan -r

0: Yubico YubiKey OTP+FIDO+CCID 00 00

There is also mentioned scriptor but it is currently too difficult for me (I just want to make such damn card to work :-)

Storing SSH private key on YubiKey

There are two main ways:

  1. FIDO2 - integrated with OpenSSH 8.2+ - easy
  2. PIV using PKCS#11 container (some PKCS containers are very popular as binary format for X.509 certificates)

SSH + FIDO2

Using Fedora guide, SSH+FIDO2 is trivial:

$ ssh-keygen -t ed25519-sk -O resident -O application=ssh:fedora -O verify-required

Generating public/private ed25519-sk key pair.
You may need to touch your authenticator to authorize key generation.

Enter PIN for authenticator: ### your FIDO PIN
You may need to touch your authenticator again to authorize key generation. ### touch sensor
Enter file in which to save the key (/home/uu/.ssh/id_ed25519_sk): /home/uu/.ssh/id_ed25519_sk_fido2

### I used empty passphrase
Enter passphrase for "/home/uu/.ssh/id_ed25519_sk_fido2" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/uu/.ssh/id_ed25519_sk_fido2
Your public key has been saved in /home/uu/.ssh/id_ed25519_sk_fido2.pub
The key fingerprint is:
...

Now you have to use standard ssh-copy-id to copy .pub certificate to remote server (in my case localhost with another user called test2):

ssh-copy-id -i ~/.ssh/id_ed25519_sk_fido2.pub test2@localhost

And finally test login via private key + YubiKey:

$ ssh -i ~/.ssh/id_ed25519_sk_fido2 test2@localhost

### touch sensor
Confirm user presence for key ED25519-SK SHA256:FQD3Oxz1ssieTHtsf0aM0vE5CmA8TjO13dv1XoDpNnM

### or enter your FIDO PIN
Enter PIN for ED25519-SK key /home/uu/.ssh/id_ed25519_sk_fido2:
Confirm user presence for key ED25519-SK SHA256:FQD3Oxz1ssieTHtsf0aM0vE5CmA8TjO13dv1XoDpNnM
User presence confirmed

$ id

uid=1002(test2) gid=1002(test2)  ...

Note, it is easy to dump information on public key:

$ ssh-keygen -lf ~/.ssh/id_ed25519_sk_fido2.pub

256 SHA256:FQD3Oxz1ssieTHtsf0aM0vE5CmA8TjO13dv1XoDpNnM uu@fed43-ubi (ED25519-SK)

SSH + PIV

We will follow:

Before you start you should decide with "slot" you will use. Fedora guide uses 9a, but fortunately according to: https://gist.github.com/daemonhorn/bdd77a7bc0ff5842e5a31d999b96e1f1#yubikey-piv-slot-names-and-numbers

9a, 9c, 9d, 9e, 82-95 can all be used by OpenSSH pkcs11 support (and most other PIV consumers)

So if you need to store more than 1 SSH PIV keypair (very likely) you can use another slots.

Undocumented:

  • to have installed stable 'library.so' (without number) suffix we can cheat that wit installing development version of package (that is normally required for linking only) with:
sudo dnf install yubico-piv-tool-devel

Verify that you have generated CCC and CHUID:

$ ykman piv info

PIV version:              5.7.4
PIN tries remaining:      3/3
PUK tries remaining:      3/3
Management key algorithm: AES192
Management key is stored on the YubiKey, protected by PIN.
CHUID: xxx
CCC:   No data available

Note: I had missing CCC so I followed https://gist.github.com/daemonhorn/bdd77a7bc0ff5842e5a31d999b96e1f1#initial-yubikey-piv-key-generation-if-not-already-done to generate it:

$ ykman piv objects generate ccc

Enter PIN:  ### your PIV PIN
Object generated.

$ ykman piv info

PIV version:              5.7.4
PIN tries remaining:      3/3
PUK tries remaining:      3/3
Management key algorithm: AES192
Management key is stored on the YubiKey, protected by PIN.
CHUID: 30...
CCC:   f0...
Slot 9A (AUTHENTICATION):
  Private key type: ED25519
  Public key type:  ED25519
  Subject DN:       CN=OpenSSH
  Issuer DN:        CN=OpenSSH
...

So try (modified) instructions from Fedora Guide using slot 9a:

$ mkdir ~/.ssh/piv1
$ ykman piv keys generate --algorithm ED25519 \
   --pin-policy ONCE --touch-policy ALWAYS 9a ~/.ssh/piv1/public.pem

Enter PIN: ### your PIV PIN
Private key generated in slot 9A (AUTHENTICATION), public key written to /home/uu/.ssh/piv1/public.pem.

$ ykman piv certificates generate --subject "CN=OpenSSH" --hash-algorithm SHA384 9a ~/.ssh/piv1/public.pem

Enter PIN:  ### your PIV PIN
Touch your YubiKey... ### touch sensor
Certificate generated in slot AUTHENTICATION.

Listing PIV keys:

$ ykman piv info

PIV version:              5.7.4
PIN tries remaining:      3/3
PUK tries remaining:      3/3
Management key algorithm: AES192
Management key is stored on the YubiKey, protected by PIN.
CHUID: xxx
CCC:   No data available
Slot 9A (AUTHENTICATION):
  Private key type: ED25519
  Public key type:  ED25519
  Subject DN:       CN=OpenSSH
  Issuer DN:        CN=OpenSSH
  Serial:           54:...
  Fingerprint:      f7....
  Not before:       2025-10-10T18:31:10+00:00
  Not after:        2026-10-10T18:31:10+00:00

Now it looks better...

Exporting public key:

$ ssh-keygen -D /usr/lib64/libykcs11.so -e
skipping unsupported key type
failed to fetch key
unknown certificate key type
failed to fetch key
unknown certificate key type
failed to fetch key
ssh-rsa AAAAB... Public key for PIV Attestation pkcs11:id=%19;\
  object=Public%20key%20for%20PIV%20Attestation;\
  token=YubiKey%20PIV%20%23xxxxxx;\
  manufacturer=Yubico%20(www.yubico.com);serial=xxxxxx?module-path=/usr/lib64/libykcs11.so

(to see just key - redirect error messages to /dev/null):

$ ssh-keygen -D /usr/lib64/libykcs11.so -e 2> /dev/null

ssh-rsa AAAAB... Public key for PIV Attestation pkcs11:id=%19;\
  object=Public%20key%20for%20PIV%20Attestation;\
  token=YubiKey%20PIV%20%23xxxxxx;\
  manufacturer=Yubico%20(www.yubico.com);serial=xxxxxx?module-path=/usr/lib64/libykcs11.so

So here we can extract public key (to be copied to remote system):

$ ssh-keygen -D /usr/lib64/libykcs11.so -e 2> /dev/null  > ~/.ssh/piv1/yubi-piv1.pub
$ file ~/.ssh/piv1/yubi-piv1.pub
/home/uu/.ssh/piv1/yubi-piv1.pub: OpenSSH RSA public key

To copy extracted key we have to add (-f) options, because ssh-copy-id tries to also open private key (without .pub suffix), but that is actually on YubiKey only:

ssh-copy-id -f -i ~/.ssh/piv1/yubi-piv1.pub testpiv1@localhost

Finally we can login (should, but) with:

$ ssh -I /usr/lib64/libykcs11.so  testpiv1@localhost
skipping unsupported key type
failed to fetch key
unknown certificate key type
failed to fetch key
unknown certificate key type
failed to fetch key
Enter PIN for 'YubiKey PIV #xxxxxx':  # your PIV pin
C_Sign failed: 7
C_Sign failed: 7
sign_and_send_pubkey: signing failed for RSA "pkcs11:?module-path=/usr/lib64/libykcs11.so": error in libcrypto

So far only found this debug switch:

YKCS11_DBG=1 ssh -I /usr/lib64/libykcs11.so testpiv1@localhost

Trying create key in slot 82 from FreeBSD guide:

$ mkdir ~/.ssh/piv82
$ cd ~/.ssh/piv82
$ ykman piv keys generate -a eccp384 --pin-policy once --touch-policy cached 82 82_pub.pem

Enter PIN:
Private key generated in slot 82 (RETIRED1), public key written to 82_pub.pem.

$ ykman piv certificates generate --valid-days 730 --subject "Y5C_82" 82 82_pub.pem
Enter PIN:
Touch your YubiKey...
Certificate generated in slot RETIRED1.

$ ykman piv info
...
Slot 82 (RETIRED1):
  Private key type: ECCP384
  Public key type:  ECCP384
  Subject DN:       CN=Y5C_82
  Issuer DN:        CN=Y5C_82
  Not before:       2025-10-10T19:46:59+00:00
  Not after:        2027-10-10T19:46:59+00:00
...

Exporting key:


# note: 'grep Retired' is crude way to select right public key (now I have 2 keys in PIV and
# both are exported)

$ ssh-keygen -D /usr/lib64/libykcs11.so -e | grep Retired > exported.pub
$ scp exported.pub testpiv82@localhost:.ssh/authorized_keys
$ ssh -I /usr/lib64/libykcs11.so testpiv82@localhost

Testing:

$ ssh -I /usr/lib64/libykcs11.so testpiv82@localhost

skipping unsupported key type
failed to fetch key
unknown certificate key type
failed to fetch key
unknown certificate key type
failed to fetch key
Enter PIN for 'YubiKey PIV #33089891':  ### your PIV key

testpiv82@fed43-ubi:~$ id
...

Huh! It works...

Resources

TODO: Study material