SSH supports FIDO2/U2F since 8.2, so you can generate a FIDO2 key with a trivial ssh-keygen -t ecdsa-sk. However, neither the default SSH in Windows 10 nor macOS is new enough, so I’ll guide you through the smartcard way (aka gpg ssh).

Setting up the key

To make sure you’re starting with a clean state, reset the OpenPGP applet on your yubikey. Beware, this will wipe the existing keys forever (but if you have some keys in there I suppose you don’t even need this guide).

$ ./ykman openpgp reset
WARNING! This will delete all stored OpenPGP keys and data and restore factory settings? [y/N]: y
Resetting OpenPGP data, don't remove your YubiKey...
Success! All data has been cleared and default PINs are set.
PIN:         123456
Reset code:  NOT SET
Admin PIN:   12345678

Generating the secrets

Make sure you got gpg (or gpg4win) installed.

$ gpg --edit-card --expert
- gpg-agent[13052]: card has S/N: ...
Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: ...
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: ...
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
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 : 10 10 10
Signature counter : 0
KDF setting ......: on
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Notice that the key attributes are set to rsa2048 for all the keys; this will be the key type to generate. Continue on if you’re happy with the defaults, otherwise you can change them to something else. I like ECC:

gpg/card> admin
Admin commands are allowed

gpg/card> key-attr
Changing card key attribute for: Signature key
Please select what kind of key you want:
   (1) RSA
   (2) ECC
Your selection? 2
Please select which elliptic curve you want:
   (1) Curve 25519
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
Your selection? 1
The card will now be re-configured to generate a key of type: ed25519
Note: There is no guarantee that the card supports the requested size.
      If the key generation does not succeed, please check the
      documentation of your card to see what sizes are allowed.
Changing card key attribute for: Encryption key
Please select what kind of key you want:
   (1) RSA
   (2) ECC
Your selection? 2
Please select which elliptic curve you want:
   (1) Curve 25519
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
Your selection? 1
The card will now be re-configured to generate a key of type: cv25519
Changing card key attribute for: Authentication key
Please select what kind of key you want:
   (1) RSA
   (2) ECC
Your selection? 2
Please select which elliptic curve you want:
   (1) Curve 25519
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
Your selection? 1
The card will now be re-configured to generate a key of type: ed25519

gpg/card> info

Invalid command  (try "help")

gpg/card> list

gpg-agent[13052]: card has S/N: ...
Reader ...........: Yubico YubiKey OTP FIDO CCID
Application ID ...: ...
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: ...
Name of cardholder: [not set]
Language prefs ...: [not set]
Sex ..............: unspecified
URL of public key : [not set]
Login data .......: [not set]
Signature PIN ....: not forced
Key attributes ...: ed25519 cv25519 ed25519
Max. PIN lengths .: 127 127 127
PIN retry counter : 10 10 10
Signature counter : 0
KDF setting ......: on
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

Different keys support different algorithms, check up the support page for your yubikey.

Now’s a good time to change the default passwords:

gpg/card> passwd
gpg-agent[13052]: card has S/N: ...
gpg: OpenPGP card no. ... 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? 4
Reset Code set.

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

Your selection? q

Finally, generate the keys:

gpg/card> generate
Make off-card backup of encryption key? (Y/n) n
- Please note that the factory settings of the PINs are
 PIN = '123456'     Admin PIN = '12345678'
You should change them using the command --change-pin
- 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)
Key does not expire at all
Is this correct? (y/N) y
- GnuPG needs to construct a user ID to identify your key.
- Real name: Hello World
Email address:
Comment:
You selected this USER-ID:
  "Hello World"
- Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
gpg: revocation certificate stored
public and secret key created and signed.

Make sure you answer NO to the “Make off-card backup of encryption key” prompt. You do not want the secret key to leak from your hardware, and then, the encryption key is practically useless (for ssh) as you’ll use the authentication key.

Now you can run list again and you’ll see your “General key info” info populated.

Better auth defaults

Typically, I raise the number of the pin retries from the default 3:

$ ./ykman openpgp set-pin-retries 10 10 10
Enter admin PIN: [12345678]
Set PIN retry counters to: 10 10 10? [y/N]: y

and enable the touch-to-use for all the key types:

$ ./ykman openpgp set-touch sig on
Enter admin PIN: [12345678]
Set touch policy of signature key to on? [y/N]: y
$ ./ykman openpgp set-touch enc on
Enter admin PIN: [12345678]
Set touch policy of encryption key to on? [y/N]: y
$ ./ykman openpgp set-touch aut on
Enter admin PIN: [12345678]
Set touch policy of authentication key to on? [y/N]: y
$ ./ykman openpgp set-touch att on
Enter admin PIN: [12345678]
Set touch policy of attestation key to on? [y/N]: y

This way you will guarantee the proof of presence – you’ll have to physically touch your key to ssh; thus making some attacks impossible. It works best with the ssh control masters to prevent you from actively rubbing your key all the time.

Usage

From here on, if you got your gpg-agent configured properly, you can see and use the key in your ssh sessions:

$ env SSH_AUTH_SOCK=$HOME/.gnupg/S.gpg-agent.ssh ssh-add -l
256 SHA256:... cardno:... (ED25519)

For Windows 10 native openssh support you’ll need to get wsl-ssh-pageant installed. It works natively with gpg4win and doesn’t require putty anymore.