diff options
author | djm@openbsd.org <djm@openbsd.org> | 2019-10-31 21:14:17 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2019-11-01 08:36:34 +1100 |
commit | 57ecc10628b04c384cbba2fbc87d38b74cd1199d (patch) | |
tree | a302fd0e014dac1b99be48b9e66474e81a0921cb /PROTOCOL.u2f | |
parent | f4fdcd2b7a2bbf5d8770d44565173ca5158d4dcb (diff) |
upstream: Protocol documentation for U2F/FIDO keys in OpenSSH
OpenBSD-Commit-ID: 8f3247317c2909870593aeb306dff848bc427915
Diffstat (limited to 'PROTOCOL.u2f')
-rw-r--r-- | PROTOCOL.u2f | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/PROTOCOL.u2f b/PROTOCOL.u2f new file mode 100644 index 000000000..ab9e3e333 --- /dev/null +++ b/PROTOCOL.u2f | |||
@@ -0,0 +1,224 @@ | |||
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 | |||
25 | U2F private keys are generatted through an enrollment operation, | ||
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 | ||
31 | information that may be used to verify that private key is hosted on a | ||
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 | ||
39 | primarily use ECDSA signatures in the NIST-P256 field. | ||
40 | |||
41 | SSH U2F Key formats | ||
42 | ------------------- | ||
43 | |||
44 | OpenSSH integrates U2F as a new key and corresponding certificate type: | ||
45 | |||
46 | sk-ecdsa-sha2-nistp256@openssh.com | ||
47 | sk-ecdsa-sha2-nistp256-cert-v01@openssh.com | ||
48 | |||
49 | These key types are supported only for user authentication with the | ||
50 | "publickey" method. They are not used for host-based user authentication | ||
51 | or server host key authentication. | ||
52 | |||
53 | While each uses ecdsa-sha256-nistp256 as the underlying signature primitive, | ||
54 | keys require extra information in the public and private keys, and in | ||
55 | the signature object itself. As such they cannot be made compatible with | ||
56 | the existing ecdsa-sha2-nistp* key types. | ||
57 | |||
58 | The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is: | ||
59 | |||
60 | string "sk-ecdsa-sha2-nistp256@openssh.com" | ||
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" | ||
67 | ec_point Q | ||
68 | string application (user-specified, but typically "ssh:") | ||
69 | string key_handle | ||
70 | uint32 flags | ||
71 | string reserved | ||
72 | |||
73 | The certificate form of a SSH U2F key appends the usual certificate | ||
74 | information to the public key: | ||
75 | |||
76 | string "sk-ecdsa-sha2-nistp256@openssh.com" | ||
77 | string nonce | ||
78 | ec_point Q | ||
79 | string application | ||
80 | uint64 serial | ||
81 | uint32 type | ||
82 | string key id | ||
83 | string valid principals | ||
84 | uint64 valid after | ||
85 | uint64 valid before | ||
86 | string critical options | ||
87 | string extensions | ||
88 | string reserved | ||
89 | string signature key | ||
90 | string signature | ||
91 | |||
92 | During key generation, the hardware also returns attestation information | ||
93 | that may be used to cryptographically prove that a given key is | ||
94 | hardware-backed. Unfortunately, the protocol required for this proof is | ||
95 | not privacy-preserving and may be used to identify U2F tokens with at | ||
96 | least manufacturer and batch number granularity. For this reason, we | ||
97 | choose not to include this information in the public key or save it by | ||
98 | default. | ||
99 | |||
100 | Attestation information is very useful however in an organisational | ||
101 | context, where it may be used by an CA as part of certificate | ||
102 | issuance. In this case, exposure to the CA of hardware identity is | ||
103 | desirable. To support this case, OpenSSH optionally allows retaining the | ||
104 | attestation information at the time of key generation. It will take the | ||
105 | following format: | ||
106 | |||
107 | string "sk-attest-v00" | ||
108 | uint32 version (1 for U2F, 2 for FIDO2 in future) | ||
109 | string attestation certificate | ||
110 | string enrollment signature | ||
111 | |||
112 | SSH U2F signatures | ||
113 | ------------------ | ||
114 | |||
115 | In addition to the message to be signed, the U2F signature operation | ||
116 | requires a few additional parameters: | ||
117 | |||
118 | byte control bits (e.g. "user presence required" flag) | ||
119 | byte[32] SHA256(message) | ||
120 | byte[32] SHA256(application) | ||
121 | byte key_handle length | ||
122 | byte[] key_handle | ||
123 | |||
124 | This signature is signed over a blob that consists of: | ||
125 | |||
126 | byte[32] SHA256(application) | ||
127 | byte flags (including "user present", extensions present) | ||
128 | uint32 counter | ||
129 | byte[] extensions | ||
130 | byte[32] SHA256(message) | ||
131 | |||
132 | The signature returned from U2F hardware takes the following format: | ||
133 | |||
134 | byte flags (including "user present") | ||
135 | uint32 counter | ||
136 | byte[32] ecdsa_signature (in X9.62 format). | ||
137 | |||
138 | For use in the SSH protocol, we wish to avoid server-side parsing of ASN.1 | ||
139 | format data in the pre-authentication attack surface. Therefore, the | ||
140 | signature format used on the wire in SSH2_USERAUTH_REQUEST packets will | ||
141 | be reformatted slightly: | ||
142 | |||
143 | mpint r | ||
144 | mpint s | ||
145 | byte flags | ||
146 | uint32 counter | ||
147 | |||
148 | Where 'r' and 's' are extracted by the client or token middleware from the | ||
149 | ecdsa_signature field returned from the hardware. | ||
150 | |||
151 | ssh-agent protocol extensions | ||
152 | ----------------------------- | ||
153 | |||
154 | ssh-agent requires some protocol extension to support U2F keys. At | ||
155 | present the closest analogue to Security Keys in ssh-agent are PKCS#11 | ||
156 | tokens, insofar as they require a middleware library to communicate with | ||
157 | the device that holds the keys. Unfortunately, the protocol message used | ||
158 | to add PKCS#11 keys to ssh-agent does not include any way to send the | ||
159 | key handle to the agent as U2F keys require. | ||
160 | |||
161 | To avoid this, without having to add wholy new messages to the agent | ||
162 | protocol we will use the existing SSH2_AGENTC_ADD_ID_CONSTRAINED message | ||
163 | with a new a key constraint extension to encode a path to the middleware | ||
164 | library for the key. The format of this constraint extension would be: | ||
165 | |||
166 | byte SSH_AGENT_CONSTRAIN_EXTENSION | ||
167 | string sk@openssh.com | ||
168 | string middleware path | ||
169 | |||
170 | This constraint-based approach does not present any compatibility | ||
171 | problems. | ||
172 | |||
173 | OpenSSH integration | ||
174 | ------------------- | ||
175 | |||
176 | U2F tokens may be attached via a number of means, including USB and NFC. | ||
177 | The USB interface is standardised around a HID protocol, but we want to | ||
178 | be able to support other transports as well as dummy implementations for | ||
179 | regress testing. For this reason, OpenSSH shall perform all U2F operations | ||
180 | via a dynamically-loaded middleware library. | ||
181 | |||
182 | The middleware library need only expose a handful of functions: | ||
183 | |||
184 | /* Flags */ | ||
185 | #define SSH_SK_USER_PRESENCE_REQD 0x01 | ||
186 | |||
187 | struct sk_enroll_response { | ||
188 | uint8_t *public_key; | ||
189 | size_t public_key_len; | ||
190 | uint8_t *key_handle; | ||
191 | size_t key_handle_len; | ||
192 | uint8_t *signature; | ||
193 | size_t signature_len; | ||
194 | uint8_t *attestation_cert; | ||
195 | size_t attestation_cert_len; | ||
196 | }; | ||
197 | |||
198 | struct sk_sign_response { | ||
199 | uint8_t flags; | ||
200 | uint32_t counter; | ||
201 | uint8_t *sig_r; | ||
202 | size_t sig_r_len; | ||
203 | uint8_t *sig_s; | ||
204 | size_t sig_s_len; | ||
205 | }; | ||
206 | |||
207 | /* Return the version of the middleware API */ | ||
208 | uint32_t sk_api_version(void); | ||
209 | |||
210 | /* Enroll a U2F key (private key generation) */ | ||
211 | int sk_enroll(const uint8_t *challenge, size_t challenge_len, | ||
212 | const char *application, uint8_t flags, | ||
213 | struct sk_enroll_response **enroll_response); | ||
214 | |||
215 | /* Sign a challenge */ | ||
216 | int sk_sign(const uint8_t *message, size_t message_len, | ||
217 | const char *application, | ||
218 | const uint8_t *key_handle, size_t key_handle_len, | ||
219 | uint8_t flags, struct sk_sign_response **sign_response); | ||
220 | |||
221 | In OpenSSH, these will be invoked by generalising the existing | ||
222 | ssh-pkcs11-helper mechanism to provide containment of the middleware from | ||
223 | ssh-agent. | ||
224 | |||