Annotation of src/usr.bin/ssh/PROTOCOL.u2f, Revision 1.14
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:
42: SSH U2F Key formats
43: -------------------
44:
1.7 djm 45: OpenSSH integrates U2F as new key and corresponding certificate types:
1.1 djm 46:
47: sk-ecdsa-sha2-nistp256@openssh.com
48: sk-ecdsa-sha2-nistp256-cert-v01@openssh.com
1.7 djm 49: sk-ssh-ed25519@openssh.com
50: sk-ssh-ed25519-cert-v01@openssh.com
1.1 djm 51:
52: While each uses ecdsa-sha256-nistp256 as the underlying signature primitive,
53: keys require extra information in the public and private keys, and in
54: the signature object itself. As such they cannot be made compatible with
55: the existing ecdsa-sha2-nistp* key types.
56:
57: The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is:
58:
59: string "sk-ecdsa-sha2-nistp256@openssh.com"
1.5 djm 60: string curve name
1.1 djm 61: ec_point Q
62: string application (user-specified, but typically "ssh:")
63:
64: The corresponding private key contains:
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:")
1.6 djm 70: uint8 flags
1.1 djm 71: string key_handle
72: string reserved
73:
1.7 djm 74: The format of a sk-ssh-ed25519@openssh.com public key is:
75:
76: string "sk-ssh-ed25519@openssh.com"
77: string public key
78: string application (user-specified, but typically "ssh:")
79:
80: With a private half consisting of:
81:
82: string "sk-ssh-ed25519@openssh.com"
83: string public key
84: string application (user-specified, but typically "ssh:")
1.12 djm 85: uint8 flags
1.7 djm 86: string key_handle
87: string reserved
88:
89: The certificate form for SSH U2F keys appends the usual certificate
1.1 djm 90: information to the public key:
91:
1.2 naddy 92: string "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"
1.1 djm 93: string nonce
1.5 djm 94: string curve name
1.1 djm 95: ec_point Q
96: string application
97: uint64 serial
98: uint32 type
99: string key id
100: string valid principals
101: uint64 valid after
102: uint64 valid before
103: string critical options
104: string extensions
105: string reserved
106: string signature key
107: string signature
108:
1.12 djm 109: and for security key ed25519 certificates:
110:
1.7 djm 111: string "sk-ssh-ed25519-cert-v01@openssh.com"
112: string nonce
113: string public key
114: string application
115: uint64 serial
116: uint32 type
117: string key id
118: string valid principals
119: uint64 valid after
120: uint64 valid before
121: string critical options
122: string extensions
123: string reserved
124: string signature key
125: string signature
126:
1.12 djm 127: Both security key certificates use the following encoding for private keys:
128:
129: string type (e.g. "sk-ssh-ed25519-cert-v01@openssh.com")
130: string pubkey (the above key/cert structure)
131: string application
132: uint8 flags
133: string key_handle
134: string reserved
135:
1.1 djm 136: During key generation, the hardware also returns attestation information
137: that may be used to cryptographically prove that a given key is
138: hardware-backed. Unfortunately, the protocol required for this proof is
139: not privacy-preserving and may be used to identify U2F tokens with at
140: least manufacturer and batch number granularity. For this reason, we
141: choose not to include this information in the public key or save it by
142: default.
143:
144: Attestation information is very useful however in an organisational
1.2 naddy 145: context, where it may be used by a CA as part of certificate
1.1 djm 146: issuance. In this case, exposure to the CA of hardware identity is
147: desirable. To support this case, OpenSSH optionally allows retaining the
148: attestation information at the time of key generation. It will take the
149: following format:
150:
151: string "sk-attest-v00"
152: uint32 version (1 for U2F, 2 for FIDO2 in future)
153: string attestation certificate
154: string enrollment signature
155:
156: SSH U2F signatures
157: ------------------
158:
1.9 djm 159: In addition to the message to be signed, the U2F signature operation
1.10 djm 160: requires the key handle and a few additional parameters. The signature
161: is signed over a blob that consists of:
1.1 djm 162:
163: byte[32] SHA256(application)
164: byte flags (including "user present", extensions present)
165: uint32 counter
166: byte[] extensions
167: byte[32] SHA256(message)
168:
1.13 djm 169: No extensons are yet defined for SSH use. If any are defined in the future,
170: it will be possible to infer their presence from the contents of the "flags"
171: value.
172:
1.1 djm 173: The signature returned from U2F hardware takes the following format:
174:
175: byte flags (including "user present")
176: uint32 counter
1.10 djm 177: byte[] ecdsa_signature (in X9.62 format).
1.1 djm 178:
179: For use in the SSH protocol, we wish to avoid server-side parsing of ASN.1
180: format data in the pre-authentication attack surface. Therefore, the
181: signature format used on the wire in SSH2_USERAUTH_REQUEST packets will
1.8 djm 182: be reformatted to better match the existing signature encoding:
1.1 djm 183:
1.8 djm 184: string "sk-ecdsa-sha2-nistp256@openssh.com"
185: string ecdsa_signature
1.1 djm 186: byte flags
187: uint32 counter
188:
1.8 djm 189: Where the "ecdsa_signature" field follows the RFC5656 ECDSA signature
190: encoding:
191:
192: mpint r
193: mpint s
1.1 djm 194:
1.4 markus 195: For Ed25519 keys the signature is encoded as:
196:
197: string "sk-ssh-ed25519@openssh.com"
198: string signature
199: byte flags
200: uint32 counter
201:
1.1 djm 202: ssh-agent protocol extensions
203: -----------------------------
204:
1.2 naddy 205: ssh-agent requires a protocol extension to support U2F keys. At
1.1 djm 206: present the closest analogue to Security Keys in ssh-agent are PKCS#11
207: tokens, insofar as they require a middleware library to communicate with
208: the device that holds the keys. Unfortunately, the protocol message used
209: to add PKCS#11 keys to ssh-agent does not include any way to send the
210: key handle to the agent as U2F keys require.
211:
1.2 naddy 212: To avoid this, without having to add wholly new messages to the agent
213: protocol, we will use the existing SSH2_AGENTC_ADD_ID_CONSTRAINED message
214: with a new key constraint extension to encode a path to the middleware
1.1 djm 215: library for the key. The format of this constraint extension would be:
216:
217: byte SSH_AGENT_CONSTRAIN_EXTENSION
1.11 djm 218: string sk-provider@openssh.com
1.1 djm 219: string middleware path
220:
221: This constraint-based approach does not present any compatibility
222: problems.
223:
224: OpenSSH integration
225: -------------------
226:
227: U2F tokens may be attached via a number of means, including USB and NFC.
228: The USB interface is standardised around a HID protocol, but we want to
229: be able to support other transports as well as dummy implementations for
1.7 djm 230: regress testing. For this reason, OpenSSH shall support a dynamically-
231: loaded middleware libraries to communicate with security keys, but offer
232: support for the common case of USB HID security keys internally.
1.1 djm 233:
234: The middleware library need only expose a handful of functions:
235:
236: /* Flags */
237: #define SSH_SK_USER_PRESENCE_REQD 0x01
238:
1.3 markus 239: /* Algs */
240: #define SSH_SK_ECDSA 0x00
241: #define SSH_SK_ED25519 0x01
242:
1.1 djm 243: struct sk_enroll_response {
244: uint8_t *public_key;
245: size_t public_key_len;
246: uint8_t *key_handle;
247: size_t key_handle_len;
248: uint8_t *signature;
249: size_t signature_len;
250: uint8_t *attestation_cert;
251: size_t attestation_cert_len;
252: };
253:
254: struct sk_sign_response {
255: uint8_t flags;
256: uint32_t counter;
257: uint8_t *sig_r;
258: size_t sig_r_len;
259: uint8_t *sig_s;
260: size_t sig_s_len;
261: };
262:
263: /* Return the version of the middleware API */
264: uint32_t sk_api_version(void);
265:
266: /* Enroll a U2F key (private key generation) */
1.3 markus 267: int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len,
1.1 djm 268: const char *application, uint8_t flags,
269: struct sk_enroll_response **enroll_response);
270:
271: /* Sign a challenge */
1.3 markus 272: int sk_sign(int alg, const uint8_t *message, size_t message_len,
1.1 djm 273: const char *application,
274: const uint8_t *key_handle, size_t key_handle_len,
275: uint8_t flags, struct sk_sign_response **sign_response);
276:
1.9 djm 277: In OpenSSH, these will be invoked by using a similar mechanism to
278: ssh-pkcs11-helper to provide address-space containment of the
279: middleware from ssh-agent.
1.1 djm 280: