Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2018 Yubico AB. All rights reserved. |
3 | | * Use of this source code is governed by a BSD-style |
4 | | * license that can be found in the LICENSE file. |
5 | | */ |
6 | | |
7 | | #include <openssl/sha.h> |
8 | | #include <openssl/x509.h> |
9 | | |
10 | | #ifdef HAVE_UNISTD_H |
11 | | #include <unistd.h> |
12 | | #endif |
13 | | |
14 | | #include "fido.h" |
15 | | #include "fido/es256.h" |
16 | | |
17 | | #if defined(_MSC_VER) |
18 | | static int |
19 | | usleep(unsigned int usec) |
20 | | { |
21 | | Sleep(usec / 1000); |
22 | | |
23 | | return (0); |
24 | | } |
25 | | #endif |
26 | | |
27 | | static int |
28 | | sig_get(fido_blob_t *sig, const unsigned char **buf, size_t *len) |
29 | 172 | { |
30 | 172 | sig->len = *len; /* consume the whole buffer */ |
31 | 172 | if ((sig->ptr = calloc(1, sig->len)) == NULL || |
32 | 172 | fido_buf_read(buf, len, sig->ptr, sig->len) < 0) { |
33 | 2 | fido_log_debug("%s: fido_buf_read", __func__); |
34 | 2 | fido_blob_reset(sig); |
35 | 2 | return (-1); |
36 | 2 | } |
37 | 170 | |
38 | 170 | return (0); |
39 | 170 | } |
40 | | |
41 | | static int |
42 | | x5c_get(fido_blob_t *x5c, const unsigned char **buf, size_t *len) |
43 | 63 | { |
44 | 63 | X509 *cert = NULL; |
45 | 63 | int ok = -1; |
46 | 63 | |
47 | 63 | if (*len > LONG_MAX) { |
48 | 0 | fido_log_debug("%s: invalid len %zu", __func__, *len); |
49 | 0 | goto fail; |
50 | 0 | } |
51 | 63 | |
52 | 63 | /* find out the certificate's length */ |
53 | 63 | const unsigned char *end = *buf; |
54 | 63 | if ((cert = d2i_X509(NULL, &end, (long)*len)) == NULL || end <= *buf || |
55 | 63 | (x5c->len = (size_t)(end - *buf)) >= *len) { |
56 | 10 | fido_log_debug("%s: d2i_X509", __func__); |
57 | 10 | goto fail; |
58 | 10 | } |
59 | 53 | |
60 | 53 | /* read accordingly */ |
61 | 53 | if ((x5c->ptr = calloc(1, x5c->len)) == NULL || |
62 | 53 | fido_buf_read(buf, len, x5c->ptr, x5c->len) < 0) { |
63 | 1 | fido_log_debug("%s: fido_buf_read", __func__); |
64 | 1 | goto fail; |
65 | 1 | } |
66 | 52 | |
67 | 52 | ok = 0; |
68 | 63 | fail: |
69 | 63 | if (cert != NULL) |
70 | 63 | X509_free(cert); |
71 | 63 | |
72 | 63 | if (ok < 0) |
73 | 11 | fido_blob_reset(x5c); |
74 | 63 | |
75 | 63 | return (ok); |
76 | 52 | } |
77 | | |
78 | | static int |
79 | | authdata_fake(const char *rp_id, uint8_t flags, uint32_t sigcount, |
80 | | fido_blob_t *fake_cbor_ad) |
81 | 119 | { |
82 | 119 | fido_authdata_t ad; |
83 | 119 | cbor_item_t *item = NULL; |
84 | 119 | size_t alloc_len; |
85 | 119 | |
86 | 119 | memset(&ad, 0, sizeof(ad)); |
87 | 119 | |
88 | 119 | if (SHA256((const void *)rp_id, strlen(rp_id), |
89 | 119 | ad.rp_id_hash) != ad.rp_id_hash) { |
90 | 1 | fido_log_debug("%s: sha256", __func__); |
91 | 1 | return (-1); |
92 | 1 | } |
93 | 118 | |
94 | 118 | ad.flags = flags; /* XXX translate? */ |
95 | 118 | ad.sigcount = sigcount; |
96 | 118 | |
97 | 118 | if ((item = cbor_build_bytestring((const unsigned char *)&ad, |
98 | 118 | sizeof(ad))) == NULL) { |
99 | 1 | fido_log_debug("%s: cbor_build_bytestring", __func__); |
100 | 1 | return (-1); |
101 | 1 | } |
102 | 117 | |
103 | 117 | if (fake_cbor_ad->ptr != NULL || |
104 | 117 | (fake_cbor_ad->len = cbor_serialize_alloc(item, &fake_cbor_ad->ptr, |
105 | 117 | &alloc_len)) == 0) { |
106 | 1 | fido_log_debug("%s: cbor_serialize_alloc", __func__); |
107 | 1 | cbor_decref(&item); |
108 | 1 | return (-1); |
109 | 1 | } |
110 | 116 | |
111 | 116 | cbor_decref(&item); |
112 | 116 | |
113 | 116 | return (0); |
114 | 116 | } |
115 | | |
116 | | /* TODO: use u2f_get_touch_begin & u2f_get_touch_status instead */ |
117 | | static int |
118 | | send_dummy_register(fido_dev_t *dev, int ms) |
119 | 18 | { |
120 | 18 | iso7816_apdu_t *apdu = NULL; |
121 | 18 | unsigned char challenge[SHA256_DIGEST_LENGTH]; |
122 | 18 | unsigned char application[SHA256_DIGEST_LENGTH]; |
123 | 18 | unsigned char reply[FIDO_MAXMSG]; |
124 | 18 | int r; |
125 | 18 | |
126 | 18 | #ifdef FIDO_FUZZ |
127 | 18 | ms = 0; /* XXX */ |
128 | 18 | #endif |
129 | 18 | |
130 | 18 | /* dummy challenge & application */ |
131 | 18 | memset(&challenge, 0xff, sizeof(challenge)); |
132 | 18 | memset(&application, 0xff, sizeof(application)); |
133 | 18 | |
134 | 18 | if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 * |
135 | 18 | SHA256_DIGEST_LENGTH)) == NULL || |
136 | 18 | iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 || |
137 | 18 | iso7816_add(apdu, &application, sizeof(application)) < 0) { |
138 | 1 | fido_log_debug("%s: iso7816", __func__); |
139 | 1 | r = FIDO_ERR_INTERNAL; |
140 | 1 | goto fail; |
141 | 1 | } |
142 | 17 | |
143 | 54 | do { |
144 | 54 | if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), |
145 | 54 | iso7816_len(apdu)) < 0) { |
146 | 1 | fido_log_debug("%s: fido_tx", __func__); |
147 | 1 | r = FIDO_ERR_TX; |
148 | 1 | goto fail; |
149 | 1 | } |
150 | 53 | if (fido_rx(dev, CTAP_CMD_MSG, &reply, sizeof(reply), ms) < 2) { |
151 | 7 | fido_log_debug("%s: fido_rx", __func__); |
152 | 7 | r = FIDO_ERR_RX; |
153 | 7 | goto fail; |
154 | 7 | } |
155 | 46 | if (usleep((unsigned)(ms == -1 ? 100 : ms) * 1000) < 0) { |
156 | 1 | fido_log_debug("%s: usleep", __func__); |
157 | 1 | r = FIDO_ERR_RX; |
158 | 1 | goto fail; |
159 | 1 | } |
160 | 45 | } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED); |
161 | 17 | |
162 | 17 | r = FIDO_OK; |
163 | 18 | fail: |
164 | 18 | iso7816_free(&apdu); |
165 | 18 | |
166 | 18 | return (r); |
167 | 8 | } |
168 | | |
169 | | static int |
170 | | key_lookup(fido_dev_t *dev, const char *rp_id, const fido_blob_t *key_id, |
171 | | int *found, int ms) |
172 | 810 | { |
173 | 810 | iso7816_apdu_t *apdu = NULL; |
174 | 810 | unsigned char challenge[SHA256_DIGEST_LENGTH]; |
175 | 810 | unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; |
176 | 810 | unsigned char reply[FIDO_MAXMSG]; |
177 | 810 | uint8_t key_id_len; |
178 | 810 | int r; |
179 | 810 | |
180 | 810 | if (key_id->len > UINT8_MAX || rp_id == NULL) { |
181 | 9 | fido_log_debug("%s: key_id->len=%zu, rp_id=%p", __func__, |
182 | 9 | key_id->len, (const void *)rp_id); |
183 | 9 | r = FIDO_ERR_INVALID_ARGUMENT; |
184 | 9 | goto fail; |
185 | 9 | } |
186 | 801 | |
187 | 801 | memset(&challenge, 0xff, sizeof(challenge)); |
188 | 801 | memset(&rp_id_hash, 0, sizeof(rp_id_hash)); |
189 | 801 | |
190 | 801 | if (SHA256((const void *)rp_id, strlen(rp_id), |
191 | 801 | rp_id_hash) != rp_id_hash) { |
192 | 3 | fido_log_debug("%s: sha256", __func__); |
193 | 3 | r = FIDO_ERR_INTERNAL; |
194 | 3 | goto fail; |
195 | 3 | } |
196 | 798 | |
197 | 798 | key_id_len = (uint8_t)key_id->len; |
198 | 798 | |
199 | 798 | if ((apdu = iso7816_new(0, U2F_CMD_AUTH, U2F_AUTH_CHECK, (uint16_t)(2 * |
200 | 798 | SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len))) == NULL || |
201 | 798 | iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 || |
202 | 798 | iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 || |
203 | 798 | iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 || |
204 | 798 | iso7816_add(apdu, key_id->ptr, key_id_len) < 0) { |
205 | 3 | fido_log_debug("%s: iso7816", __func__); |
206 | 3 | r = FIDO_ERR_INTERNAL; |
207 | 3 | goto fail; |
208 | 3 | } |
209 | 795 | |
210 | 795 | if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), |
211 | 795 | iso7816_len(apdu)) < 0) { |
212 | 49 | fido_log_debug("%s: fido_tx", __func__); |
213 | 49 | r = FIDO_ERR_TX; |
214 | 49 | goto fail; |
215 | 49 | } |
216 | 746 | if (fido_rx(dev, CTAP_CMD_MSG, &reply, sizeof(reply), ms) != 2) { |
217 | 349 | fido_log_debug("%s: fido_rx", __func__); |
218 | 349 | r = FIDO_ERR_RX; |
219 | 349 | goto fail; |
220 | 349 | } |
221 | 397 | |
222 | 397 | switch ((reply[0] << 8) | reply[1]) { |
223 | 276 | case SW_CONDITIONS_NOT_SATISFIED: |
224 | 276 | *found = 1; /* key exists */ |
225 | 276 | break; |
226 | 17 | case SW_WRONG_DATA: |
227 | 17 | *found = 0; /* key does not exist */ |
228 | 17 | break; |
229 | 104 | default: |
230 | 104 | /* unexpected sw */ |
231 | 104 | r = FIDO_ERR_INTERNAL; |
232 | 104 | goto fail; |
233 | 293 | } |
234 | 293 | |
235 | 293 | r = FIDO_OK; |
236 | 810 | fail: |
237 | 810 | iso7816_free(&apdu); |
238 | 810 | |
239 | 810 | return (r); |
240 | 293 | } |
241 | | |
242 | | static int |
243 | | parse_auth_reply(fido_blob_t *sig, fido_blob_t *ad, const char *rp_id, |
244 | | const unsigned char *reply, size_t len) |
245 | 160 | { |
246 | 160 | uint8_t flags; |
247 | 160 | uint32_t sigcount; |
248 | 160 | |
249 | 160 | if (len < 2 || ((reply[len - 2] << 8) | reply[len - 1]) != SW_NO_ERROR) { |
250 | 38 | fido_log_debug("%s: unexpected sw", __func__); |
251 | 38 | return (FIDO_ERR_RX); |
252 | 38 | } |
253 | 122 | |
254 | 122 | len -= 2; |
255 | 122 | |
256 | 122 | if (fido_buf_read(&reply, &len, &flags, sizeof(flags)) < 0 || |
257 | 122 | fido_buf_read(&reply, &len, &sigcount, sizeof(sigcount)) < 0) { |
258 | 2 | fido_log_debug("%s: fido_buf_read", __func__); |
259 | 2 | return (FIDO_ERR_RX); |
260 | 2 | } |
261 | 120 | |
262 | 120 | if (sig_get(sig, &reply, &len) < 0) { |
263 | 1 | fido_log_debug("%s: sig_get", __func__); |
264 | 1 | return (FIDO_ERR_RX); |
265 | 1 | } |
266 | 119 | |
267 | 119 | if (authdata_fake(rp_id, flags, sigcount, ad) < 0) { |
268 | 3 | fido_log_debug("%s; authdata_fake", __func__); |
269 | 3 | return (FIDO_ERR_RX); |
270 | 3 | } |
271 | 116 | |
272 | 116 | return (FIDO_OK); |
273 | 116 | } |
274 | | |
275 | | static int |
276 | | do_auth(fido_dev_t *dev, const fido_blob_t *cdh, const char *rp_id, |
277 | | const fido_blob_t *key_id, fido_blob_t *sig, fido_blob_t *ad, int ms) |
278 | 196 | { |
279 | 196 | iso7816_apdu_t *apdu = NULL; |
280 | 196 | unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; |
281 | 196 | unsigned char reply[FIDO_MAXMSG]; |
282 | 196 | int reply_len; |
283 | 196 | uint8_t key_id_len; |
284 | 196 | int r; |
285 | 196 | |
286 | 196 | #ifdef FIDO_FUZZ |
287 | 196 | ms = 0; /* XXX */ |
288 | 196 | #endif |
289 | 196 | |
290 | 196 | if (cdh->len != SHA256_DIGEST_LENGTH || key_id->len > UINT8_MAX || |
291 | 196 | rp_id == NULL) { |
292 | 14 | r = FIDO_ERR_INVALID_ARGUMENT; |
293 | 14 | goto fail; |
294 | 14 | } |
295 | 182 | |
296 | 182 | memset(&rp_id_hash, 0, sizeof(rp_id_hash)); |
297 | 182 | |
298 | 182 | if (SHA256((const void *)rp_id, strlen(rp_id), |
299 | 182 | rp_id_hash) != rp_id_hash) { |
300 | 1 | fido_log_debug("%s: sha256", __func__); |
301 | 1 | r = FIDO_ERR_INTERNAL; |
302 | 1 | goto fail; |
303 | 1 | } |
304 | 181 | |
305 | 181 | key_id_len = (uint8_t)key_id->len; |
306 | 181 | |
307 | 181 | if ((apdu = iso7816_new(0, U2F_CMD_AUTH, U2F_AUTH_SIGN, (uint16_t)(2 * |
308 | 181 | SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len))) == NULL || |
309 | 181 | iso7816_add(apdu, cdh->ptr, cdh->len) < 0 || |
310 | 181 | iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 || |
311 | 181 | iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 || |
312 | 181 | iso7816_add(apdu, key_id->ptr, key_id_len) < 0) { |
313 | 1 | fido_log_debug("%s: iso7816", __func__); |
314 | 1 | r = FIDO_ERR_INTERNAL; |
315 | 1 | goto fail; |
316 | 1 | } |
317 | 180 | |
318 | 333 | do { |
319 | 333 | if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), |
320 | 333 | iso7816_len(apdu)) < 0) { |
321 | 3 | fido_log_debug("%s: fido_tx", __func__); |
322 | 3 | r = FIDO_ERR_TX; |
323 | 3 | goto fail; |
324 | 3 | } |
325 | 330 | if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, &reply, |
326 | 330 | sizeof(reply), ms)) < 2) { |
327 | 16 | fido_log_debug("%s: fido_rx", __func__); |
328 | 16 | r = FIDO_ERR_RX; |
329 | 16 | goto fail; |
330 | 16 | } |
331 | 314 | if (usleep((unsigned)(ms == -1 ? 100 : ms) * 1000) < 0) { |
332 | 1 | fido_log_debug("%s: usleep", __func__); |
333 | 1 | r = FIDO_ERR_RX; |
334 | 1 | goto fail; |
335 | 1 | } |
336 | 313 | } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED); |
337 | 180 | |
338 | 180 | if ((r = parse_auth_reply(sig, ad, rp_id, reply, |
339 | 160 | (size_t)reply_len)) != FIDO_OK) { |
340 | 44 | fido_log_debug("%s: parse_auth_reply", __func__); |
341 | 44 | goto fail; |
342 | 44 | } |
343 | 196 | |
344 | 196 | fail: |
345 | 196 | iso7816_free(&apdu); |
346 | 196 | |
347 | 196 | return (r); |
348 | 160 | } |
349 | | |
350 | | static int |
351 | | cbor_blob_from_ec_point(const uint8_t *ec_point, size_t ec_point_len, |
352 | | fido_blob_t *cbor_blob) |
353 | 51 | { |
354 | 51 | es256_pk_t *pk = NULL; |
355 | 51 | cbor_item_t *pk_cbor = NULL; |
356 | 51 | size_t alloc_len; |
357 | 51 | int ok = -1; |
358 | 51 | |
359 | 51 | /* only handle uncompressed points */ |
360 | 51 | if (ec_point_len != 65 || ec_point[0] != 0x04) { |
361 | 8 | fido_log_debug("%s: unexpected format", __func__); |
362 | 8 | goto fail; |
363 | 8 | } |
364 | 43 | |
365 | 43 | if ((pk = es256_pk_new()) == NULL || |
366 | 43 | es256_pk_set_x(pk, &ec_point[1]) < 0 || |
367 | 43 | es256_pk_set_y(pk, &ec_point[33]) < 0) { |
368 | 1 | fido_log_debug("%s: es256_pk_set", __func__); |
369 | 1 | goto fail; |
370 | 1 | } |
371 | 42 | |
372 | 42 | if ((pk_cbor = es256_pk_encode(pk, 0)) == NULL) { |
373 | 1 | fido_log_debug("%s: es256_pk_encode", __func__); |
374 | 1 | goto fail; |
375 | 1 | } |
376 | 41 | |
377 | 41 | if ((cbor_blob->len = cbor_serialize_alloc(pk_cbor, &cbor_blob->ptr, |
378 | 41 | &alloc_len)) != 77) { |
379 | 1 | fido_log_debug("%s: cbor_serialize_alloc", __func__); |
380 | 1 | goto fail; |
381 | 1 | } |
382 | 40 | |
383 | 40 | ok = 0; |
384 | 51 | fail: |
385 | 51 | es256_pk_free(&pk); |
386 | 51 | |
387 | 51 | if (pk_cbor) |
388 | 41 | cbor_decref(&pk_cbor); |
389 | 51 | |
390 | 51 | return (ok); |
391 | 40 | } |
392 | | |
393 | | static int |
394 | | encode_cred_authdata(const char *rp_id, const uint8_t *kh, uint8_t kh_len, |
395 | | const uint8_t *pubkey, size_t pubkey_len, fido_blob_t *out) |
396 | 51 | { |
397 | 51 | fido_authdata_t authdata; |
398 | 51 | fido_attcred_raw_t attcred_raw; |
399 | 51 | fido_blob_t pk_blob; |
400 | 51 | fido_blob_t authdata_blob; |
401 | 51 | cbor_item_t *authdata_cbor = NULL; |
402 | 51 | unsigned char *ptr; |
403 | 51 | size_t len; |
404 | 51 | size_t alloc_len; |
405 | 51 | int ok = -1; |
406 | 51 | |
407 | 51 | memset(&pk_blob, 0, sizeof(pk_blob)); |
408 | 51 | memset(&authdata, 0, sizeof(authdata)); |
409 | 51 | memset(&authdata_blob, 0, sizeof(authdata_blob)); |
410 | 51 | memset(out, 0, sizeof(*out)); |
411 | 51 | |
412 | 51 | if (rp_id == NULL) { |
413 | 0 | fido_log_debug("%s: NULL rp_id", __func__); |
414 | 0 | goto fail; |
415 | 0 | } |
416 | 51 | |
417 | 51 | if (cbor_blob_from_ec_point(pubkey, pubkey_len, &pk_blob) < 0) { |
418 | 11 | fido_log_debug("%s: cbor_blob_from_ec_point", __func__); |
419 | 11 | goto fail; |
420 | 11 | } |
421 | 40 | |
422 | 40 | if (SHA256((const void *)rp_id, strlen(rp_id), |
423 | 40 | authdata.rp_id_hash) != authdata.rp_id_hash) { |
424 | 1 | fido_log_debug("%s: sha256", __func__); |
425 | 1 | goto fail; |
426 | 1 | } |
427 | 39 | |
428 | 39 | authdata.flags = (CTAP_AUTHDATA_ATT_CRED | CTAP_AUTHDATA_USER_PRESENT); |
429 | 39 | authdata.sigcount = 0; |
430 | 39 | |
431 | 39 | memset(&attcred_raw.aaguid, 0, sizeof(attcred_raw.aaguid)); |
432 | 39 | attcred_raw.id_len = htobe16(kh_len); |
433 | 39 | |
434 | 39 | len = authdata_blob.len = sizeof(authdata) + sizeof(attcred_raw) + |
435 | 39 | kh_len + pk_blob.len; |
436 | 39 | ptr = authdata_blob.ptr = calloc(1, authdata_blob.len); |
437 | 39 | |
438 | 39 | fido_log_debug("%s: ptr=%p, len=%zu", __func__, (void *)ptr, len); |
439 | 39 | |
440 | 39 | if (authdata_blob.ptr == NULL) |
441 | 39 | goto fail; |
442 | 38 | |
443 | 38 | if (fido_buf_write(&ptr, &len, &authdata, sizeof(authdata)) < 0 || |
444 | 38 | fido_buf_write(&ptr, &len, &attcred_raw, sizeof(attcred_raw)) < 0 || |
445 | 38 | fido_buf_write(&ptr, &len, kh, kh_len) < 0 || |
446 | 38 | fido_buf_write(&ptr, &len, pk_blob.ptr, pk_blob.len) < 0) { |
447 | 0 | fido_log_debug("%s: fido_buf_write", __func__); |
448 | 0 | goto fail; |
449 | 0 | } |
450 | 38 | |
451 | 38 | if ((authdata_cbor = fido_blob_encode(&authdata_blob)) == NULL) { |
452 | 1 | fido_log_debug("%s: fido_blob_encode", __func__); |
453 | 1 | goto fail; |
454 | 1 | } |
455 | 37 | |
456 | 37 | if ((out->len = cbor_serialize_alloc(authdata_cbor, &out->ptr, |
457 | 37 | &alloc_len)) == 0) { |
458 | 1 | fido_log_debug("%s: cbor_serialize_alloc", __func__); |
459 | 1 | goto fail; |
460 | 1 | } |
461 | 36 | |
462 | 36 | ok = 0; |
463 | 51 | fail: |
464 | 51 | if (authdata_cbor) |
465 | 37 | cbor_decref(&authdata_cbor); |
466 | 51 | |
467 | 51 | fido_blob_reset(&pk_blob); |
468 | 51 | fido_blob_reset(&authdata_blob); |
469 | 51 | |
470 | 51 | return (ok); |
471 | 36 | } |
472 | | |
473 | | static int |
474 | | parse_register_reply(fido_cred_t *cred, const unsigned char *reply, size_t len) |
475 | 106 | { |
476 | 106 | fido_blob_t x5c; |
477 | 106 | fido_blob_t sig; |
478 | 106 | fido_blob_t ad; |
479 | 106 | uint8_t dummy; |
480 | 106 | uint8_t pubkey[65]; |
481 | 106 | uint8_t kh_len = 0; |
482 | 106 | uint8_t *kh = NULL; |
483 | 106 | int r; |
484 | 106 | |
485 | 106 | memset(&x5c, 0, sizeof(x5c)); |
486 | 106 | memset(&sig, 0, sizeof(sig)); |
487 | 106 | memset(&ad, 0, sizeof(ad)); |
488 | 106 | r = FIDO_ERR_RX; |
489 | 106 | |
490 | 106 | /* status word */ |
491 | 106 | if (len < 2 || ((reply[len - 2] << 8) | reply[len - 1]) != SW_NO_ERROR) { |
492 | 33 | fido_log_debug("%s: unexpected sw", __func__); |
493 | 33 | goto fail; |
494 | 33 | } |
495 | 73 | |
496 | 73 | len -= 2; |
497 | 73 | |
498 | 73 | /* reserved byte */ |
499 | 73 | if (fido_buf_read(&reply, &len, &dummy, sizeof(dummy)) < 0 || |
500 | 73 | dummy != 0x05) { |
501 | 9 | fido_log_debug("%s: reserved byte", __func__); |
502 | 9 | goto fail; |
503 | 9 | } |
504 | 64 | |
505 | 64 | /* pubkey + key handle */ |
506 | 64 | if (fido_buf_read(&reply, &len, &pubkey, sizeof(pubkey)) < 0 || |
507 | 64 | fido_buf_read(&reply, &len, &kh_len, sizeof(kh_len)) < 0 || |
508 | 64 | (kh = calloc(1, kh_len)) == NULL || |
509 | 64 | fido_buf_read(&reply, &len, kh, kh_len) < 0) { |
510 | 1 | fido_log_debug("%s: fido_buf_read", __func__); |
511 | 1 | goto fail; |
512 | 1 | } |
513 | 63 | |
514 | 63 | /* x5c + sig */ |
515 | 63 | if (x5c_get(&x5c, &reply, &len) < 0 || |
516 | 63 | sig_get(&sig, &reply, &len) < 0) { |
517 | 12 | fido_log_debug("%s: x5c || sig", __func__); |
518 | 12 | goto fail; |
519 | 12 | } |
520 | 51 | |
521 | 51 | /* authdata */ |
522 | 51 | if (encode_cred_authdata(cred->rp.id, kh, kh_len, pubkey, |
523 | 51 | sizeof(pubkey), &ad) < 0) { |
524 | 15 | fido_log_debug("%s: encode_cred_authdata", __func__); |
525 | 15 | goto fail; |
526 | 15 | } |
527 | 36 | |
528 | 36 | if (fido_cred_set_fmt(cred, "fido-u2f") != FIDO_OK || |
529 | 36 | fido_cred_set_authdata(cred, ad.ptr, ad.len) != FIDO_OK || |
530 | 36 | fido_cred_set_x509(cred, x5c.ptr, x5c.len) != FIDO_OK || |
531 | 36 | fido_cred_set_sig(cred, sig.ptr, sig.len) != FIDO_OK) { |
532 | 5 | fido_log_debug("%s: fido_cred_set", __func__); |
533 | 5 | r = FIDO_ERR_INTERNAL; |
534 | 5 | goto fail; |
535 | 5 | } |
536 | 31 | |
537 | 31 | r = FIDO_OK; |
538 | 106 | fail: |
539 | 106 | freezero(kh, kh_len); |
540 | 106 | fido_blob_reset(&x5c); |
541 | 106 | fido_blob_reset(&sig); |
542 | 106 | fido_blob_reset(&ad); |
543 | 106 | |
544 | 106 | return (r); |
545 | 31 | } |
546 | | |
547 | | int |
548 | | u2f_register(fido_dev_t *dev, fido_cred_t *cred, int ms) |
549 | 306 | { |
550 | 306 | iso7816_apdu_t *apdu = NULL; |
551 | 306 | unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; |
552 | 306 | unsigned char reply[FIDO_MAXMSG]; |
553 | 306 | int reply_len; |
554 | 306 | int found; |
555 | 306 | int r; |
556 | 306 | |
557 | 306 | #ifdef FIDO_FUZZ |
558 | 306 | ms = 0; /* XXX */ |
559 | 306 | #endif |
560 | 306 | |
561 | 306 | if (cred->rk == FIDO_OPT_TRUE || cred->uv == FIDO_OPT_TRUE) { |
562 | 7 | fido_log_debug("%s: rk=%d, uv=%d", __func__, cred->rk, |
563 | 7 | cred->uv); |
564 | 7 | return (FIDO_ERR_UNSUPPORTED_OPTION); |
565 | 7 | } |
566 | 299 | |
567 | 299 | if (cred->type != COSE_ES256 || cred->cdh.ptr == NULL || |
568 | 299 | cred->rp.id == NULL || cred->cdh.len != SHA256_DIGEST_LENGTH) { |
569 | 28 | fido_log_debug("%s: type=%d, cdh=(%p,%zu)" , __func__, |
570 | 28 | cred->type, (void *)cred->cdh.ptr, cred->cdh.len); |
571 | 28 | return (FIDO_ERR_INVALID_ARGUMENT); |
572 | 28 | } |
573 | 271 | |
574 | 280 | for (size_t i = 0; i < cred->excl.len; i++) { |
575 | 164 | if ((r = key_lookup(dev, cred->rp.id, &cred->excl.ptr[i], |
576 | 164 | &found, ms)) != FIDO_OK) { |
577 | 137 | fido_log_debug("%s: key_lookup", __func__); |
578 | 137 | return (r); |
579 | 137 | } |
580 | 27 | if (found) { |
581 | 18 | if ((r = send_dummy_register(dev, ms)) != FIDO_OK) { |
582 | 10 | fido_log_debug("%s: send_dummy_register", |
583 | 10 | __func__); |
584 | 10 | return (r); |
585 | 10 | } |
586 | 8 | return (FIDO_ERR_CREDENTIAL_EXCLUDED); |
587 | 8 | } |
588 | 27 | } |
589 | 271 | |
590 | 271 | memset(&rp_id_hash, 0, sizeof(rp_id_hash)); |
591 | 116 | |
592 | 116 | if (SHA256((const void *)cred->rp.id, strlen(cred->rp.id), |
593 | 116 | rp_id_hash) != rp_id_hash) { |
594 | 1 | fido_log_debug("%s: sha256", __func__); |
595 | 1 | return (FIDO_ERR_INTERNAL); |
596 | 1 | } |
597 | 115 | |
598 | 115 | if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 * |
599 | 115 | SHA256_DIGEST_LENGTH)) == NULL || |
600 | 115 | iso7816_add(apdu, cred->cdh.ptr, cred->cdh.len) < 0 || |
601 | 115 | iso7816_add(apdu, rp_id_hash, sizeof(rp_id_hash)) < 0) { |
602 | 1 | fido_log_debug("%s: iso7816", __func__); |
603 | 1 | r = FIDO_ERR_INTERNAL; |
604 | 1 | goto fail; |
605 | 1 | } |
606 | 114 | |
607 | 350 | do { |
608 | 350 | if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), |
609 | 350 | iso7816_len(apdu)) < 0) { |
610 | 1 | fido_log_debug("%s: fido_tx", __func__); |
611 | 1 | r = FIDO_ERR_TX; |
612 | 1 | goto fail; |
613 | 1 | } |
614 | 349 | if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, &reply, |
615 | 349 | sizeof(reply), ms)) < 2) { |
616 | 6 | fido_log_debug("%s: fido_rx", __func__); |
617 | 6 | r = FIDO_ERR_RX; |
618 | 6 | goto fail; |
619 | 6 | } |
620 | 343 | if (usleep((unsigned)(ms == -1 ? 100 : ms) * 1000) < 0) { |
621 | 1 | fido_log_debug("%s: usleep", __func__); |
622 | 1 | r = FIDO_ERR_RX; |
623 | 1 | goto fail; |
624 | 1 | } |
625 | 342 | } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED); |
626 | 114 | |
627 | 114 | if ((r = parse_register_reply(cred, reply, |
628 | 106 | (size_t)reply_len)) != FIDO_OK) { |
629 | 75 | fido_log_debug("%s: parse_register_reply", __func__); |
630 | 75 | goto fail; |
631 | 75 | } |
632 | 115 | fail: |
633 | 115 | iso7816_free(&apdu); |
634 | 115 | |
635 | 115 | return (r); |
636 | 106 | } |
637 | | |
638 | | static int |
639 | | u2f_authenticate_single(fido_dev_t *dev, const fido_blob_t *key_id, |
640 | | fido_assert_t *fa, size_t idx, int ms) |
641 | 646 | { |
642 | 646 | fido_blob_t sig; |
643 | 646 | fido_blob_t ad; |
644 | 646 | int found; |
645 | 646 | int r; |
646 | 646 | |
647 | 646 | memset(&sig, 0, sizeof(sig)); |
648 | 646 | memset(&ad, 0, sizeof(ad)); |
649 | 646 | |
650 | 646 | if ((r = key_lookup(dev, fa->rp_id, key_id, &found, ms)) != FIDO_OK) { |
651 | 380 | fido_log_debug("%s: key_lookup", __func__); |
652 | 380 | goto fail; |
653 | 380 | } |
654 | 266 | |
655 | 266 | if (!found) { |
656 | 8 | fido_log_debug("%s: not found", __func__); |
657 | 8 | r = FIDO_ERR_CREDENTIAL_EXCLUDED; |
658 | 8 | goto fail; |
659 | 8 | } |
660 | 258 | |
661 | 258 | if (fido_blob_set(&fa->stmt[idx].id, key_id->ptr, key_id->len) < 0) { |
662 | 1 | fido_log_debug("%s: fido_blob_set", __func__); |
663 | 1 | r = FIDO_ERR_INTERNAL; |
664 | 1 | goto fail; |
665 | 1 | } |
666 | 257 | |
667 | 257 | if (fa->up == FIDO_OPT_FALSE) { |
668 | 61 | fido_log_debug("%s: checking for key existence only", __func__); |
669 | 61 | r = FIDO_ERR_USER_PRESENCE_REQUIRED; |
670 | 61 | goto fail; |
671 | 61 | } |
672 | 196 | |
673 | 196 | if ((r = do_auth(dev, &fa->cdh, fa->rp_id, key_id, &sig, &ad, |
674 | 196 | ms)) != FIDO_OK) { |
675 | 80 | fido_log_debug("%s: do_auth", __func__); |
676 | 80 | goto fail; |
677 | 80 | } |
678 | 116 | |
679 | 116 | if (fido_assert_set_authdata(fa, idx, ad.ptr, ad.len) != FIDO_OK || |
680 | 116 | fido_assert_set_sig(fa, idx, sig.ptr, sig.len) != FIDO_OK) { |
681 | 2 | fido_log_debug("%s: fido_assert_set", __func__); |
682 | 2 | r = FIDO_ERR_INTERNAL; |
683 | 2 | goto fail; |
684 | 2 | } |
685 | 114 | |
686 | 114 | r = FIDO_OK; |
687 | 646 | fail: |
688 | 646 | fido_blob_reset(&sig); |
689 | 646 | fido_blob_reset(&ad); |
690 | 646 | |
691 | 646 | return (r); |
692 | 114 | } |
693 | | |
694 | | int |
695 | | u2f_authenticate(fido_dev_t *dev, fido_assert_t *fa, int ms) |
696 | 520 | { |
697 | 520 | size_t nfound = 0; |
698 | 520 | size_t nauth_ok = 0; |
699 | 520 | int r; |
700 | 520 | |
701 | 520 | if (fa->uv == FIDO_OPT_TRUE || fa->allow_list.ptr == NULL) { |
702 | 40 | fido_log_debug("%s: uv=%d, allow_list=%p", __func__, fa->uv, |
703 | 40 | (void *)fa->allow_list.ptr); |
704 | 40 | return (FIDO_ERR_UNSUPPORTED_OPTION); |
705 | 40 | } |
706 | 480 | |
707 | 480 | if ((r = fido_assert_set_count(fa, fa->allow_list.len)) != FIDO_OK) { |
708 | 1 | fido_log_debug("%s: fido_assert_set_count", __func__); |
709 | 1 | return (r); |
710 | 1 | } |
711 | 479 | |
712 | 662 | for (size_t i = 0; i < fa->allow_list.len; i++) { |
713 | 646 | switch ((r = u2f_authenticate_single(dev, |
714 | 646 | &fa->allow_list.ptr[i], fa, nfound, ms))) { |
715 | 114 | case FIDO_OK: |
716 | 114 | nauth_ok++; |
717 | 114 | /* FALLTHROUGH */ |
718 | 175 | case FIDO_ERR_USER_PRESENCE_REQUIRED: |
719 | 175 | nfound++; |
720 | 175 | break; |
721 | 471 | default: |
722 | 471 | if (r != FIDO_ERR_CREDENTIAL_EXCLUDED) { |
723 | 463 | fido_log_debug("%s: u2f_authenticate_single", |
724 | 463 | __func__); |
725 | 463 | return (r); |
726 | 463 | } |
727 | 646 | /* ignore credentials that don't exist */ |
728 | 646 | } |
729 | 646 | } |
730 | 479 | |
731 | 479 | fa->stmt_len = nfound; |
732 | 16 | |
733 | 16 | if (nfound == 0) |
734 | 1 | return (FIDO_ERR_NO_CREDENTIALS); |
735 | 15 | if (nauth_ok == 0) |
736 | 2 | return (FIDO_ERR_USER_PRESENCE_REQUIRED); |
737 | 13 | |
738 | 13 | return (FIDO_OK); |
739 | 13 | } |
740 | | |
741 | | int |
742 | | u2f_get_touch_begin(fido_dev_t *dev) |
743 | 1.59k | { |
744 | 1.59k | iso7816_apdu_t *apdu = NULL; |
745 | 1.59k | const char *clientdata = FIDO_DUMMY_CLIENTDATA; |
746 | 1.59k | const char *rp_id = FIDO_DUMMY_RP_ID; |
747 | 1.59k | unsigned char clientdata_hash[SHA256_DIGEST_LENGTH]; |
748 | 1.59k | unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; |
749 | 1.59k | unsigned char reply[FIDO_MAXMSG]; |
750 | 1.59k | int r; |
751 | 1.59k | |
752 | 1.59k | memset(&clientdata_hash, 0, sizeof(clientdata_hash)); |
753 | 1.59k | memset(&rp_id_hash, 0, sizeof(rp_id_hash)); |
754 | 1.59k | |
755 | 1.59k | if (SHA256((const void *)clientdata, strlen(clientdata), |
756 | 1.59k | clientdata_hash) != clientdata_hash || SHA256((const void *)rp_id, |
757 | 1.59k | strlen(rp_id), rp_id_hash) != rp_id_hash) { |
758 | 22 | fido_log_debug("%s: sha256", __func__); |
759 | 22 | return (FIDO_ERR_INTERNAL); |
760 | 22 | } |
761 | 1.57k | |
762 | 1.57k | if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 * |
763 | 1.57k | SHA256_DIGEST_LENGTH)) == NULL || |
764 | 1.57k | iso7816_add(apdu, clientdata_hash, sizeof(clientdata_hash)) < 0 || |
765 | 1.57k | iso7816_add(apdu, rp_id_hash, sizeof(rp_id_hash)) < 0) { |
766 | 8 | fido_log_debug("%s: iso7816", __func__); |
767 | 8 | r = FIDO_ERR_INTERNAL; |
768 | 8 | goto fail; |
769 | 8 | } |
770 | 1.56k | |
771 | 1.56k | if (dev->attr.flags & FIDO_CAP_WINK) { |
772 | 1.02k | fido_tx(dev, CTAP_CMD_WINK, NULL, 0); |
773 | 1.02k | fido_rx(dev, CTAP_CMD_WINK, &reply, sizeof(reply), 200); |
774 | 1.02k | } |
775 | 1.56k | |
776 | 1.56k | if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), |
777 | 1.56k | iso7816_len(apdu)) < 0) { |
778 | 26 | fido_log_debug("%s: fido_tx", __func__); |
779 | 26 | r = FIDO_ERR_TX; |
780 | 26 | goto fail; |
781 | 26 | } |
782 | 1.54k | |
783 | 1.54k | r = FIDO_OK; |
784 | 1.57k | fail: |
785 | 1.57k | iso7816_free(&apdu); |
786 | 1.57k | |
787 | 1.57k | return (r); |
788 | 1.54k | } |
789 | | |
790 | | int |
791 | | u2f_get_touch_status(fido_dev_t *dev, int *touched, int ms) |
792 | 1.57k | { |
793 | 1.57k | unsigned char reply[FIDO_MAXMSG]; |
794 | 1.57k | int reply_len; |
795 | 1.57k | int r; |
796 | 1.57k | |
797 | 1.57k | if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, &reply, sizeof(reply), |
798 | 1.57k | ms)) < 2) { |
799 | 1.38k | fido_log_debug("%s: fido_rx", __func__); |
800 | 1.38k | return (FIDO_OK); /* ignore */ |
801 | 1.38k | } |
802 | 195 | |
803 | 195 | switch ((reply[reply_len - 2] << 8) | reply[reply_len - 1]) { |
804 | 24 | case SW_CONDITIONS_NOT_SATISFIED: |
805 | 24 | if ((r = u2f_get_touch_begin(dev)) != FIDO_OK) { |
806 | 9 | fido_log_debug("%s: u2f_get_touch_begin", __func__); |
807 | 9 | return (r); |
808 | 9 | } |
809 | 15 | *touched = 0; |
810 | 15 | break; |
811 | 15 | case SW_NO_ERROR: |
812 | 1 | *touched = 1; |
813 | 1 | break; |
814 | 170 | default: |
815 | 170 | fido_log_debug("%s: unexpected sw", __func__); |
816 | 170 | return (FIDO_ERR_RX); |
817 | 16 | } |
818 | 16 | |
819 | 16 | return (FIDO_OK); |
820 | 16 | } |