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