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