Annotation of src/usr.bin/ssh/PROTOCOL.u2f, Revision 1.24
1.1 djm 1: This document describes OpenSSH's support for U2F/FIDO security keys.
2:
3: Background
4: ----------
5:
6: U2F is an open standard for two-factor authentication hardware, widely
7: used for user authentication to websites. U2F tokens are ubiquitous,
8: available from a number of manufacturers and are currently by far the
9: cheapest way for users to achieve hardware-backed credential storage.
10:
11: The U2F protocol however cannot be trivially used as an SSH protocol key
12: type as both the inputs to the signature operation and the resultant
13: signature differ from those specified for SSH. For similar reasons,
14: integration of U2F devices cannot be achieved via the PKCS#11 API.
15:
16: U2F also offers a number of features that are attractive in the context
17: of SSH authentication. They can be configured to require indication
18: of "user presence" for each signature operation (typically achieved
19: by requiring the user touch the key). They also offer an attestation
20: mechanism at key enrollment time that can be used to prove that a
21: given key is backed by hardware. Finally the signature format includes
22: a monotonic signature counter that can be used (at scale) to detect
23: concurrent use of a private key, should it be extracted from hardware.
24:
1.2 naddy 25: U2F private keys are generated through an enrollment operation,
1.1 djm 26: which takes an application ID - a URL-like string, typically "ssh:"
27: in this case, but a HTTP origin for the case of web authentication,
28: and a challenge string (typically randomly generated). The enrollment
29: operation returns a public key, a key handle that must be used to invoke
30: the hardware-backed private key, some flags and signed attestation
1.2 naddy 31: information that may be used to verify that a private key is hosted on a
1.1 djm 32: particular hardware instance.
33:
34: It is common for U2F hardware to derive private keys from the key handle
35: in conjunction with a small per-device secret that is unique to the
36: hardware, thus requiring little on-device storage for an effectively
37: unlimited number of supported keys. This drives the requirement that
38: the key handle be supplied for each signature operation. U2F tokens
1.7 djm 39: primarily use ECDSA signatures in the NIST-P256 field, though the FIDO2
1.14 naddy 40: standard specifies additional key types, including one based on Ed25519.
1.1 djm 41:
1.22 djm 42: Use of U2F security keys does not automatically imply multi-factor
1.23 djm 43: authentication. From sshd's perspective, a security key constitutes a
1.22 djm 44: single factor of authentication, even if protected by a PIN or biometric
45: authentication. To enable multi-factor authentication in ssh, please
46: refer to the AuthenticationMethods option in sshd_config(5).
47:
48:
1.1 djm 49: SSH U2F Key formats
50: -------------------
51:
1.7 djm 52: OpenSSH integrates U2F as new key and corresponding certificate types:
1.1 djm 53:
54: sk-ecdsa-sha2-nistp256@openssh.com
55: sk-ecdsa-sha2-nistp256-cert-v01@openssh.com
1.7 djm 56: sk-ssh-ed25519@openssh.com
57: sk-ssh-ed25519-cert-v01@openssh.com
1.1 djm 58:
59: While each uses ecdsa-sha256-nistp256 as the underlying signature primitive,
60: keys require extra information in the public and private keys, and in
61: the signature object itself. As such they cannot be made compatible with
62: the existing ecdsa-sha2-nistp* key types.
63:
64: The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is:
65:
66: string "sk-ecdsa-sha2-nistp256@openssh.com"
1.5 djm 67: string curve name
1.1 djm 68: ec_point Q
69: string application (user-specified, but typically "ssh:")
70:
71: The corresponding private key contains:
72:
73: string "sk-ecdsa-sha2-nistp256@openssh.com"
1.5 djm 74: string curve name
1.1 djm 75: ec_point Q
76: string application (user-specified, but typically "ssh:")
1.6 djm 77: uint8 flags
1.1 djm 78: string key_handle
79: string reserved
80:
1.7 djm 81: The format of a sk-ssh-ed25519@openssh.com public key is:
82:
83: string "sk-ssh-ed25519@openssh.com"
84: string public key
85: string application (user-specified, but typically "ssh:")
86:
87: With a private half consisting of:
88:
89: string "sk-ssh-ed25519@openssh.com"
90: string public key
91: string application (user-specified, but typically "ssh:")
1.12 djm 92: uint8 flags
1.7 djm 93: string key_handle
94: string reserved
95:
96: The certificate form for SSH U2F keys appends the usual certificate
1.1 djm 97: information to the public key:
98:
1.2 naddy 99: string "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"
1.1 djm 100: string nonce
1.5 djm 101: string curve name
1.1 djm 102: ec_point Q
103: string application
104: uint64 serial
105: uint32 type
106: string key id
107: string valid principals
108: uint64 valid after
109: uint64 valid before
110: string critical options
111: string extensions
112: string reserved
113: string signature key
114: string signature
115:
1.12 djm 116: and for security key ed25519 certificates:
117:
1.7 djm 118: string "sk-ssh-ed25519-cert-v01@openssh.com"
119: string nonce
120: string public key
121: string application
122: uint64 serial
123: uint32 type
124: string key id
125: string valid principals
126: uint64 valid after
127: uint64 valid before
128: string critical options
129: string extensions
130: string reserved
131: string signature key
132: string signature
133:
1.12 djm 134: Both security key certificates use the following encoding for private keys:
135:
136: string type (e.g. "sk-ssh-ed25519-cert-v01@openssh.com")
137: string pubkey (the above key/cert structure)
138: string application
139: uint8 flags
140: string key_handle
141: string reserved
142:
1.1 djm 143: During key generation, the hardware also returns attestation information
144: that may be used to cryptographically prove that a given key is
145: hardware-backed. Unfortunately, the protocol required for this proof is
146: not privacy-preserving and may be used to identify U2F tokens with at
147: least manufacturer and batch number granularity. For this reason, we
148: choose not to include this information in the public key or save it by
149: default.
150:
1.19 djm 151: Attestation information is useful for out-of-band key and certificate
1.20 dtucker 152: registration workflows, e.g. proving to a CA that a key is backed
1.19 djm 153: by trusted hardware before it will issue a certificate. To support this
154: case, OpenSSH optionally allows retaining the attestation information
155: at the time of key generation. It will take the following format:
1.1 djm 156:
1.19 djm 157: string "ssh-sk-attest-v00"
1.1 djm 158: string attestation certificate
159: string enrollment signature
1.19 djm 160: uint32 reserved flags
161: string reserved string
162:
163: OpenSSH treats the attestation certificate and enrollment signatures as
164: opaque objects and does no interpretation of them itself.
1.1 djm 165:
166: SSH U2F signatures
167: ------------------
168:
1.9 djm 169: In addition to the message to be signed, the U2F signature operation
1.10 djm 170: requires the key handle and a few additional parameters. The signature
171: is signed over a blob that consists of:
1.1 djm 172:
173: byte[32] SHA256(application)
174: byte flags (including "user present", extensions present)
175: uint32 counter
176: byte[] extensions
177: byte[32] SHA256(message)
178:
1.20 dtucker 179: No extensions are yet defined for SSH use. If any are defined in the future,
1.13 djm 180: it will be possible to infer their presence from the contents of the "flags"
181: value.
182:
1.1 djm 183: The signature returned from U2F hardware takes the following format:
184:
185: byte flags (including "user present")
186: uint32 counter
1.10 djm 187: byte[] ecdsa_signature (in X9.62 format).
1.1 djm 188:
189: For use in the SSH protocol, we wish to avoid server-side parsing of ASN.1
190: format data in the pre-authentication attack surface. Therefore, the
191: signature format used on the wire in SSH2_USERAUTH_REQUEST packets will
1.8 djm 192: be reformatted to better match the existing signature encoding:
1.1 djm 193:
1.8 djm 194: string "sk-ecdsa-sha2-nistp256@openssh.com"
195: string ecdsa_signature
1.1 djm 196: byte flags
197: uint32 counter
198:
1.8 djm 199: Where the "ecdsa_signature" field follows the RFC5656 ECDSA signature
200: encoding:
201:
202: mpint r
203: mpint s
1.1 djm 204:
1.4 markus 205: For Ed25519 keys the signature is encoded as:
206:
207: string "sk-ssh-ed25519@openssh.com"
208: string signature
209: byte flags
210: uint32 counter
211:
1.24 ! djm 212: webauthn signatures
! 213: -------------------
! 214:
! 215: The W3C/FIDO webauthn[1] standard defines a mechanism for a web browser to
! 216: interact with FIDO authentication tokens. This standard builds upon the
! 217: FIDO standards, but requires different signature contents to raw FIDO
! 218: messages. OpenSSH supports ECDSA/p256 webauthn signatures through the
! 219: "webauthn-sk-ecdsa-sha2-nistp256@openssh.com" signature algorithm.
! 220:
! 221: The wire encoding for a webauthn-sk-ecdsa-sha2-nistp256@openssh.com
! 222: signature is similar to the sk-ecdsa-sha2-nistp256@openssh.com format:
! 223:
! 224: string "webauthn-sk-ecdsa-sha2-nistp256@openssh.com"
! 225: string ecdsa_signature
! 226: byte flags
! 227: uint32 counter
! 228: string origin
! 229: string clientData
! 230: string extensions
! 231:
! 232: Where "origin" is the HTTP origin making the signature, "clientData" is
! 233: the JSON-like structure signed by the browser and "extensions" are any
! 234: extensions used in making the signature.
! 235:
! 236: [1] https://www.w3.org/TR/webauthn-2/
! 237:
1.1 djm 238: ssh-agent protocol extensions
239: -----------------------------
240:
1.2 naddy 241: ssh-agent requires a protocol extension to support U2F keys. At
1.1 djm 242: present the closest analogue to Security Keys in ssh-agent are PKCS#11
243: tokens, insofar as they require a middleware library to communicate with
244: the device that holds the keys. Unfortunately, the protocol message used
245: to add PKCS#11 keys to ssh-agent does not include any way to send the
246: key handle to the agent as U2F keys require.
247:
1.2 naddy 248: To avoid this, without having to add wholly new messages to the agent
249: protocol, we will use the existing SSH2_AGENTC_ADD_ID_CONSTRAINED message
250: with a new key constraint extension to encode a path to the middleware
1.1 djm 251: library for the key. The format of this constraint extension would be:
252:
253: byte SSH_AGENT_CONSTRAIN_EXTENSION
1.11 djm 254: string sk-provider@openssh.com
1.1 djm 255: string middleware path
256:
257: This constraint-based approach does not present any compatibility
258: problems.
259:
260: OpenSSH integration
261: -------------------
262:
263: U2F tokens may be attached via a number of means, including USB and NFC.
264: The USB interface is standardised around a HID protocol, but we want to
265: be able to support other transports as well as dummy implementations for
1.7 djm 266: regress testing. For this reason, OpenSSH shall support a dynamically-
267: loaded middleware libraries to communicate with security keys, but offer
268: support for the common case of USB HID security keys internally.
1.1 djm 269:
270: The middleware library need only expose a handful of functions:
271:
1.21 djm 272: #define SSH_SK_VERSION_MAJOR 0x00050000 /* API version */
1.16 djm 273: #define SSH_SK_VERSION_MAJOR_MASK 0xffff0000
274:
1.1 djm 275: /* Flags */
276: #define SSH_SK_USER_PRESENCE_REQD 0x01
1.15 djm 277: #define SSH_SK_USER_VERIFICATION_REQD 0x04
278: #define SSH_SK_RESIDENT_KEY 0x20
1.1 djm 279:
1.3 markus 280: /* Algs */
281: #define SSH_SK_ECDSA 0x00
282: #define SSH_SK_ED25519 0x01
283:
1.17 djm 284: /* Error codes */
285: #define SSH_SK_ERR_GENERAL -1
286: #define SSH_SK_ERR_UNSUPPORTED -2
287: #define SSH_SK_ERR_PIN_REQUIRED -3
1.18 djm 288: #define SSH_SK_ERR_DEVICE_NOT_FOUND -4
1.17 djm 289:
1.1 djm 290: struct sk_enroll_response {
291: uint8_t *public_key;
292: size_t public_key_len;
293: uint8_t *key_handle;
294: size_t key_handle_len;
295: uint8_t *signature;
296: size_t signature_len;
297: uint8_t *attestation_cert;
298: size_t attestation_cert_len;
299: };
300:
301: struct sk_sign_response {
302: uint8_t flags;
303: uint32_t counter;
304: uint8_t *sig_r;
305: size_t sig_r_len;
306: uint8_t *sig_s;
307: size_t sig_s_len;
308: };
309:
1.16 djm 310: struct sk_resident_key {
1.17 djm 311: uint32_t alg;
1.16 djm 312: size_t slot;
313: char *application;
314: struct sk_enroll_response key;
315: };
316:
1.17 djm 317: struct sk_option {
318: char *name;
319: char *value;
320: uint8_t important;
321: };
322:
1.1 djm 323: /* Return the version of the middleware API */
324: uint32_t sk_api_version(void);
325:
326: /* Enroll a U2F key (private key generation) */
1.17 djm 327: int sk_enroll(uint32_t alg,
328: const uint8_t *challenge, size_t challenge_len,
1.16 djm 329: const char *application, uint8_t flags, const char *pin,
1.17 djm 330: struct sk_option **options,
1.1 djm 331: struct sk_enroll_response **enroll_response);
332:
333: /* Sign a challenge */
1.17 djm 334: int sk_sign(uint32_t alg, const uint8_t *message, size_t message_len,
1.1 djm 335: const char *application,
336: const uint8_t *key_handle, size_t key_handle_len,
1.17 djm 337: uint8_t flags, const char *pin, struct sk_option **options,
1.16 djm 338: struct sk_sign_response **sign_response);
339:
340: /* Enumerate all resident keys */
1.17 djm 341: int sk_load_resident_keys(const char *pin, struct sk_option **options,
1.16 djm 342: struct sk_resident_key ***rks, size_t *nrks);
343:
344: The SSH_SK_VERSION_MAJOR should be incremented for each incompatible
345: API change.
1.1 djm 346:
1.17 djm 347: The options may be used to pass miscellaneous options to the middleware
348: as a NULL-terminated array of pointers to struct sk_option. The middleware
349: may ignore unsupported or unknown options unless the "important" flag is
350: set, in which case it should return failure if an unsupported option is
351: requested.
352:
353: At present the following options names are supported:
354:
355: "device"
356:
357: Specifies a specific FIDO device on which to perform the
358: operation. The value in this field is interpreted by the
359: middleware but it would be typical to specify a path to
360: a /dev node for the device in question.
361:
362: "user"
363:
364: Specifies the FIDO2 username used when enrolling a key,
365: overriding OpenSSH's default of using an all-zero username.
366:
367: In OpenSSH, the middleware will be invoked by using a similar mechanism to
1.9 djm 368: ssh-pkcs11-helper to provide address-space containment of the
369: middleware from ssh-agent.
1.1 djm 370: