This document describes OpenSSH's support for U2F/FIDO security keys. Background ---------- U2F is an open standard for two-factor authentication hardware, widely used for user authentication to websites. U2F tokens are ubiquitous, available from a number of manufacturers and are currently by far the cheapest way for users to achieve hardware-backed credential storage. The U2F protocol however cannot be trivially used as an SSH protocol key type as both the inputs to the signature operation and the resultant signature differ from those specified for SSH. For similar reasons, integration of U2F devices cannot be achieved via the PKCS#11 API. U2F also offers a number of features that are attractive in the context of SSH authentication. They can be configured to require indication of "user presence" for each signature operation (typically achieved by requiring the user touch the key). They also offer an attestation mechanism at key enrollment time that can be used to prove that a given key is backed by hardware. Finally the signature format includes a monotonic signature counter that can be used (at scale) to detect concurrent use of a private key, should it be extracted from hardware. U2F private keys are generated through an enrollment operation, which takes an application ID - a URL-like string, typically "ssh:" in this case, but a HTTP origin for the case of web authentication, and a challenge string (typically randomly generated). The enrollment operation returns a public key, a key handle that must be used to invoke the hardware-backed private key, some flags and signed attestation information that may be used to verify that a private key is hosted on a particular hardware instance. It is common for U2F hardware to derive private keys from the key handle in conjunction with a small per-device secret that is unique to the hardware, thus requiring little on-device storage for an effectively unlimited number of supported keys. This drives the requirement that the key handle be supplied for each signature operation. U2F tokens primarily use ECDSA signatures in the NIST-P256 field, though the FIDO2 standard specified additional key types include one based on Ed25519. SSH U2F Key formats ------------------- OpenSSH integrates U2F as new key and corresponding certificate types: sk-ecdsa-sha2-nistp256@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com sk-ssh-ed25519@openssh.com sk-ssh-ed25519-cert-v01@openssh.com These key types are supported only for user authentication with the "publickey" method. They are not used for host-based user authentication or server host key authentication. While each uses ecdsa-sha256-nistp256 as the underlying signature primitive, keys require extra information in the public and private keys, and in the signature object itself. As such they cannot be made compatible with the existing ecdsa-sha2-nistp* key types. The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is: string "sk-ecdsa-sha2-nistp256@openssh.com" string curve name ec_point Q string application (user-specified, but typically "ssh:") The corresponding private key contains: string "sk-ecdsa-sha2-nistp256@openssh.com" string curve name ec_point Q string application (user-specified, but typically "ssh:") uint8 flags string key_handle string reserved The format of a sk-ssh-ed25519@openssh.com public key is: string "sk-ssh-ed25519@openssh.com" string public key string application (user-specified, but typically "ssh:") With a private half consisting of: string "sk-ssh-ed25519@openssh.com" string public key string application (user-specified, but typically "ssh:") uint32 flags string key_handle string reserved The certificate form for SSH U2F keys appends the usual certificate information to the public key: string "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com" string nonce string curve name ec_point Q string application uint64 serial uint32 type string key id string valid principals uint64 valid after uint64 valid before string critical options string extensions string reserved string signature key string signature string "sk-ssh-ed25519-cert-v01@openssh.com" string nonce string public key string application uint64 serial uint32 type string key id string valid principals uint64 valid after uint64 valid before string critical options string extensions string reserved string signature key string signature During key generation, the hardware also returns attestation information that may be used to cryptographically prove that a given key is hardware-backed. Unfortunately, the protocol required for this proof is not privacy-preserving and may be used to identify U2F tokens with at least manufacturer and batch number granularity. For this reason, we choose not to include this information in the public key or save it by default. Attestation information is very useful however in an organisational context, where it may be used by a CA as part of certificate issuance. In this case, exposure to the CA of hardware identity is desirable. To support this case, OpenSSH optionally allows retaining the attestation information at the time of key generation. It will take the following format: string "sk-attest-v00" uint32 version (1 for U2F, 2 for FIDO2 in future) string attestation certificate string enrollment signature SSH U2F signatures ------------------ In addition to the message to be signed, the U2F signature operation requires a few additional parameters: byte control bits (e.g. "user presence required" flag) byte[32] SHA256(message) byte[32] SHA256(application) byte key_handle length byte[] key_handle This signature is signed over a blob that consists of: byte[32] SHA256(application) byte flags (including "user present", extensions present) uint32 counter byte[] extensions byte[32] SHA256(message) The signature returned from U2F hardware takes the following format: byte flags (including "user present") uint32 counter byte[32] ecdsa_signature (in X9.62 format). For use in the SSH protocol, we wish to avoid server-side parsing of ASN.1 format data in the pre-authentication attack surface. Therefore, the signature format used on the wire in SSH2_USERAUTH_REQUEST packets will be reformatted to better match the existing signature encoding: string "sk-ecdsa-sha2-nistp256@openssh.com" string ecdsa_signature byte flags uint32 counter Where the "ecdsa_signature" field follows the RFC5656 ECDSA signature encoding: mpint r mpint s For Ed25519 keys the signature is encoded as: string "sk-ssh-ed25519@openssh.com" string signature byte flags uint32 counter ssh-agent protocol extensions ----------------------------- ssh-agent requires a protocol extension to support U2F keys. At present the closest analogue to Security Keys in ssh-agent are PKCS#11 tokens, insofar as they require a middleware library to communicate with the device that holds the keys. Unfortunately, the protocol message used to add PKCS#11 keys to ssh-agent does not include any way to send the key handle to the agent as U2F keys require. To avoid this, without having to add wholly new messages to the agent protocol, we will use the existing SSH2_AGENTC_ADD_ID_CONSTRAINED message with a new key constraint extension to encode a path to the middleware library for the key. The format of this constraint extension would be: byte SSH_AGENT_CONSTRAIN_EXTENSION string sk@openssh.com string middleware path This constraint-based approach does not present any compatibility problems. OpenSSH integration ------------------- U2F tokens may be attached via a number of means, including USB and NFC. The USB interface is standardised around a HID protocol, but we want to be able to support other transports as well as dummy implementations for regress testing. For this reason, OpenSSH shall support a dynamically- loaded middleware libraries to communicate with security keys, but offer support for the common case of USB HID security keys internally. The middleware library need only expose a handful of functions: /* Flags */ #define SSH_SK_USER_PRESENCE_REQD 0x01 /* Algs */ #define SSH_SK_ECDSA 0x00 #define SSH_SK_ED25519 0x01 struct sk_enroll_response { uint8_t *public_key; size_t public_key_len; uint8_t *key_handle; size_t key_handle_len; uint8_t *signature; size_t signature_len; uint8_t *attestation_cert; size_t attestation_cert_len; }; struct sk_sign_response { uint8_t flags; uint32_t counter; uint8_t *sig_r; size_t sig_r_len; uint8_t *sig_s; size_t sig_s_len; }; /* Return the version of the middleware API */ uint32_t sk_api_version(void); /* Enroll a U2F key (private key generation) */ int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, const char *application, uint8_t flags, struct sk_enroll_response **enroll_response); /* Sign a challenge */ int sk_sign(int alg, const uint8_t *message, size_t message_len, const char *application, const uint8_t *key_handle, size_t key_handle_len, uint8_t flags, struct sk_sign_response **sign_response); In OpenSSH, these will be invoked by using a similar mechanism to ssh-pkcs11-helper to provide address-space containment of the middleware from ssh-agent.