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