Smart card support was introduced around 2010 with OpenSSH 5.4. The inital scope was restricted to the RSA keys — the only supported key type at that time in OpenSSH — other than legacy DSA keys. Previously, users needed to specify the PKCS#11 driver for the smart card. Additionally, the OpenSSH client had to query the server with all the stored keys in the card, until an acceptable key was found. This slowed down authentication, and reveals public keys to the server that might not be necessary (e.g., if we have a single card with keys for distinct servers).

Over the years, OpenSSH gained support for additional authentication keys, such as ECDSA and later EdDSA. However, the smart card subsystem has not changed much since the early days. Cards with ECDSA keys are not yet supported, and there is no option for the user to specify the key to use when connecting to a server. Fedora 28 addresses these limitations. This article describes these improvements, the background behind them, and how they can be used.

Support for ECDSA keys

OpenSSH has supported ECDSA keys OpenSSH 5.7 (released in 2011). This includes widespread support for smart cards such as Yubikey and Nitrokey. Nevertheless, ECDSA support was not reflected in the OpenSSH PKCS#11 subsystem, which is still RSA-only. A patch to support the ECDSA keys in PKCS#11 was submitted to the OpenSSH project in 2015, but despite a long history of revisions it is still not incorporated. The motivation to use ECDSA keys can be either to avoid hardware RSA key vulnerabilities (ROCA: CVE-2017-15361) or to use shorter keys for faster connection times.

In Fedora 28 we include that patch set to allow you to use ECDSA keys from your security tokens. You can list them with ssh-keygen as any other keys:

$ ssh-keygen -D /usr/lib64/pkcs11/opensc-pkcs11.so [...] ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHA[...]k3J0hkYnnsM=

Or use them for authentication, if the server is configured to accept this key:

$ ssh -vvv -I /usr/lib64/pkcs11/opensc-pkcs11.so example.com [...] debug1: Offering public key: ECDSA SHA256:5BrE5wevULd5wipj2bXYAr4gXQIICiywfV+kF5hA9X8 ECDSA jjelen@example.com [...] debug1: Offering public key: ECDSA SHA256:q4zxb5Woucr1HlUSh9Fq66sKdv3r5hlxIIqQQtaQKy4 pkcs11:id=%01?module-path=/usr/lib64/pkcs11/opensc-pkcs11.so [...] debug1: Server accepts key: pkalg ecdsa-sha2-nistp256 blen 104 debug2: input_userauth_pk_ok: fp SHA256:q4zxb5Woucr1HlUSh9Fq66sKdv3r5hlxIIqQQtaQKy4 debug3: sign_and_send_pubkey: ECDSA SHA256:q4zxb5Woucr1HlUSh9Fq66sKdv3r5hlxIIqQQtaQKy4 Enter PIN for 'PIV Card Holder pin (PIV_II)': [...] debug1: Authentication succeeded (publickey).

Specify the key to use

Historically, applications were using various ways how to reference keys in PKCS#11 modules, because there was no standard way to do so. Alternatively some applications were using just everything that was made available by a PKCS#11 module.

This works fine if the smart card is a company-issued with few keys. But this does not work well if one has several private keys to access different services or there are more security tokens aggregated in a single system-wide PKCS#11 module. In such cases, you need to map private keys to services, rather than to leave it on the tool to try all of them sequentially. Selecting an explicit key will always be faster, and prevents exceeding maximum of authentication tries. Furthermore, it allows to the use of different identities, for example on Github.

PKCS#11 URIs defined in RFC7512 provide a standard way to identify a specific object/key on PKCS#11 module according to their attributes. That, when supported universally, enables you to configure all the system and applications with the same configuration string in form of URI.

Additionally, as Fedora 28 provides p11-kit proxy which acts as a wrapper over the registered smart card drivers in the system, we took advantage of it and it allows you to avoid the path to shared object altogether. The unique URI scheme allows you to specify the PKCS#11 URI in every place, where the path to a local private key file would be, including the configuration file, ssh-add or command-line ssh.

Examples

You can list all the keys provided by OpenSC PKCS#11 module (including their PKCS#11 URIs):

$ ssh-keygen -D /usr/lib64/pkcs11/opensc-pkcs11.so ssh-rsa AAAAB3NzaC1yc2E...KKZMzcQZzx pkcs11:id=%02;object=SIGN%20pubkey;token=SSH%20key;manufacturer=piv_II?module-path=/usr/lib64/pkcs11/opensc-pkcs11.so ecdsa-sha2-nistp256 AAA...J0hkYnnsM= pkcs11:id=%01;object=PIV%20AUTH%20pubkey;token=SSH%20key;manufacturer=piv_II?module-path=/usr/lib64/pkcs11/opensc-pkcs11.so

To connect to the server example.com using the ECDSA key from above (referenced by ID), you can use just a subset of the URI, which uniquely references our key:

$ ssh -i pkcs11:id=%01?module-path=/usr/lib64/pkcs11/opensc-pkcs11.so example.com Enter PIN for 'SSH key': [example.com] $

You can use the same URI string in ~/.ssh/config to make the configuration permanent:

$ cat ~/.ssh/config IdentityFile pkcs11:id=%01?module-path=/usr/lib64/pkcs11/opensc-pkcs11.so $ ssh example.com Enter PIN for 'SSH key': [example.com] $

Since the OpenSC PKCS#11 module is registered by default to p11-kit in Fedora, which is via p11-kit-proxy, used by OpenSSH, you can simplify the above commands:

$ ssh -i pkcs11:id=%01 example.com Enter PIN for 'SSH key': [example.com] $

The ssh-agent interface accepts the same URIs. For example, you can add only the ECDSA key to the agent and connect to example.com (this was not previously possible):

$ ssh-add pkcs11:id=%01 Enter passphrase for PKCS#11: Card added: pkcs11:id=%01 $ ssh example.com [example.com] $

If you skip the ID, OpenSSH will load all the keys that are available in the proxy module, which usually matches the previous behavior, but is much less typing:

$ ssh -i pkcs11: example.com Enter PIN for 'SSH key': [example.com] $

Known issues

While the smart cards work generally fine, there are some corner cases, that are not handled ideally yet. Most of them are tracked upstream bugzilla.

From the most painful ones, we can note that the keys stored in ssh-agent do not support reauthentication. Therefore once you physically remove the token, you also need to remove and re-add it in the ssh-agent. Its interface does not provide a functionality to this automatically, so there is still some work to be done.

The ssh-agent also does not allow you to use keys that require PIN verification before signature (ALWAYS_AUTHENTICATE flag). This is very common in PIV cards to ensure non-repudiation of digital signatures. In this case, the easy workaround is to use different key, that does not enforce this policy.

Summary

The new OpenSSH comes with few improvements for private keys stored in smart cards and security tokens. These changes make the usage of security tokens easier for new users, allow them to take advantage of secure storage, in comparison to keys on disk. They also integrate OpenSSH into Fedora which already supports the RFC7512 identifiers for objects stored in tokens and uses p11-kit for smart card driver registration.

If you found an issue with the above functionality, or you have and idea what could be improved, feel free to comment, open a bug or start a discussion on Fedora or OpenSSH mailing lists.

The aforementioned features are available in Fedora, but not yet in the upstream OpenSSH releases. We include these changes in the hope they will be useful for users and it can make the upstream adoption of them faster.