diff options
Diffstat (limited to 'src/assert.c')
-rw-r--r-- | src/assert.c | 1090 |
1 files changed, 1090 insertions, 0 deletions
diff --git a/src/assert.c b/src/assert.c new file mode 100644 index 0000000..a21b308 --- /dev/null +++ b/src/assert.c | |||
@@ -0,0 +1,1090 @@ | |||
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/ec.h> | ||
8 | #include <openssl/ecdsa.h> | ||
9 | #include <openssl/evp.h> | ||
10 | #include <openssl/sha.h> | ||
11 | |||
12 | #include <string.h> | ||
13 | #include "fido.h" | ||
14 | #include "fido/es256.h" | ||
15 | #include "fido/rs256.h" | ||
16 | #include "fido/eddsa.h" | ||
17 | |||
18 | static int | ||
19 | adjust_assert_count(const cbor_item_t *key, const cbor_item_t *val, void *arg) | ||
20 | { | ||
21 | fido_assert_t *assert = arg; | ||
22 | uint64_t n; | ||
23 | |||
24 | /* numberOfCredentials; see section 6.2 */ | ||
25 | if (cbor_isa_uint(key) == false || | ||
26 | cbor_int_get_width(key) != CBOR_INT_8 || | ||
27 | cbor_get_uint8(key) != 5) { | ||
28 | fido_log_debug("%s: cbor_type", __func__); | ||
29 | return (0); /* ignore */ | ||
30 | } | ||
31 | |||
32 | if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { | ||
33 | fido_log_debug("%s: cbor_decode_uint64", __func__); | ||
34 | return (-1); | ||
35 | } | ||
36 | |||
37 | if (assert->stmt_len != 0 || assert->stmt_cnt != 1 || | ||
38 | (size_t)n < assert->stmt_cnt) { | ||
39 | fido_log_debug("%s: stmt_len=%zu, stmt_cnt=%zu, n=%zu", | ||
40 | __func__, assert->stmt_len, assert->stmt_cnt, (size_t)n); | ||
41 | return (-1); | ||
42 | } | ||
43 | |||
44 | if (fido_assert_set_count(assert, (size_t)n) != FIDO_OK) { | ||
45 | fido_log_debug("%s: fido_assert_set_count", __func__); | ||
46 | return (-1); | ||
47 | } | ||
48 | |||
49 | assert->stmt_len = 0; /* XXX */ | ||
50 | |||
51 | return (0); | ||
52 | } | ||
53 | |||
54 | static int | ||
55 | parse_assert_reply(const cbor_item_t *key, const cbor_item_t *val, void *arg) | ||
56 | { | ||
57 | fido_assert_stmt *stmt = arg; | ||
58 | |||
59 | if (cbor_isa_uint(key) == false || | ||
60 | cbor_int_get_width(key) != CBOR_INT_8) { | ||
61 | fido_log_debug("%s: cbor type", __func__); | ||
62 | return (0); /* ignore */ | ||
63 | } | ||
64 | |||
65 | switch (cbor_get_uint8(key)) { | ||
66 | case 1: /* credential id */ | ||
67 | return (cbor_decode_cred_id(val, &stmt->id)); | ||
68 | case 2: /* authdata */ | ||
69 | return (cbor_decode_assert_authdata(val, &stmt->authdata_cbor, | ||
70 | &stmt->authdata, &stmt->authdata_ext, | ||
71 | &stmt->hmac_secret_enc)); | ||
72 | case 3: /* signature */ | ||
73 | return (fido_blob_decode(val, &stmt->sig)); | ||
74 | case 4: /* user attributes */ | ||
75 | return (cbor_decode_user(val, &stmt->user)); | ||
76 | default: /* ignore */ | ||
77 | fido_log_debug("%s: cbor type", __func__); | ||
78 | return (0); | ||
79 | } | ||
80 | } | ||
81 | |||
82 | static int | ||
83 | fido_dev_get_assert_tx(fido_dev_t *dev, fido_assert_t *assert, | ||
84 | const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin) | ||
85 | { | ||
86 | fido_blob_t f; | ||
87 | cbor_item_t *argv[7]; | ||
88 | int r; | ||
89 | |||
90 | memset(argv, 0, sizeof(argv)); | ||
91 | memset(&f, 0, sizeof(f)); | ||
92 | |||
93 | /* do we have everything we need? */ | ||
94 | if (assert->rp_id == NULL || assert->cdh.ptr == NULL) { | ||
95 | fido_log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__, | ||
96 | (void *)assert->rp_id, (void *)assert->cdh.ptr); | ||
97 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
98 | goto fail; | ||
99 | } | ||
100 | |||
101 | if ((argv[0] = cbor_build_string(assert->rp_id)) == NULL || | ||
102 | (argv[1] = fido_blob_encode(&assert->cdh)) == NULL) { | ||
103 | fido_log_debug("%s: cbor encode", __func__); | ||
104 | r = FIDO_ERR_INTERNAL; | ||
105 | goto fail; | ||
106 | } | ||
107 | |||
108 | /* allowed credentials */ | ||
109 | if (assert->allow_list.len) { | ||
110 | const fido_blob_array_t *cl = &assert->allow_list; | ||
111 | if ((argv[2] = cbor_encode_pubkey_list(cl)) == NULL) { | ||
112 | fido_log_debug("%s: cbor_encode_pubkey_list", __func__); | ||
113 | r = FIDO_ERR_INTERNAL; | ||
114 | goto fail; | ||
115 | } | ||
116 | } | ||
117 | |||
118 | /* hmac-secret extension */ | ||
119 | if (assert->ext & FIDO_EXT_HMAC_SECRET) | ||
120 | if ((argv[3] = cbor_encode_hmac_secret_param(ecdh, pk, | ||
121 | &assert->hmac_salt)) == NULL) { | ||
122 | fido_log_debug("%s: cbor_encode_hmac_secret_param", | ||
123 | __func__); | ||
124 | r = FIDO_ERR_INTERNAL; | ||
125 | goto fail; | ||
126 | } | ||
127 | |||
128 | /* options */ | ||
129 | if (assert->up != FIDO_OPT_OMIT || assert->uv != FIDO_OPT_OMIT) | ||
130 | if ((argv[4] = cbor_encode_assert_options(assert->up, | ||
131 | assert->uv)) == NULL) { | ||
132 | fido_log_debug("%s: cbor_encode_assert_options", | ||
133 | __func__); | ||
134 | r = FIDO_ERR_INTERNAL; | ||
135 | goto fail; | ||
136 | } | ||
137 | |||
138 | /* pin authentication */ | ||
139 | if (pin) { | ||
140 | if (pk == NULL || ecdh == NULL) { | ||
141 | fido_log_debug("%s: pin=%p, pk=%p, ecdh=%p", __func__, | ||
142 | (const void *)pin, (const void *)pk, | ||
143 | (const void *)ecdh); | ||
144 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
145 | goto fail; | ||
146 | } | ||
147 | if ((r = cbor_add_pin_params(dev, &assert->cdh, pk, ecdh, pin, | ||
148 | &argv[5], &argv[6])) != FIDO_OK) { | ||
149 | fido_log_debug("%s: cbor_add_pin_params", __func__); | ||
150 | goto fail; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | /* frame and transmit */ | ||
155 | if (cbor_build_frame(CTAP_CBOR_ASSERT, argv, 7, &f) < 0 || | ||
156 | fido_tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) { | ||
157 | fido_log_debug("%s: fido_tx", __func__); | ||
158 | r = FIDO_ERR_TX; | ||
159 | goto fail; | ||
160 | } | ||
161 | |||
162 | r = FIDO_OK; | ||
163 | fail: | ||
164 | cbor_vector_free(argv, nitems(argv)); | ||
165 | free(f.ptr); | ||
166 | |||
167 | return (r); | ||
168 | } | ||
169 | |||
170 | static int | ||
171 | fido_dev_get_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int ms) | ||
172 | { | ||
173 | const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR; | ||
174 | unsigned char reply[2048]; | ||
175 | int reply_len; | ||
176 | int r; | ||
177 | |||
178 | fido_assert_reset_rx(assert); | ||
179 | |||
180 | if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) { | ||
181 | fido_log_debug("%s: fido_rx", __func__); | ||
182 | return (FIDO_ERR_RX); | ||
183 | } | ||
184 | |||
185 | /* start with room for a single assertion */ | ||
186 | if ((assert->stmt = calloc(1, sizeof(fido_assert_stmt))) == NULL) | ||
187 | return (FIDO_ERR_INTERNAL); | ||
188 | |||
189 | assert->stmt_len = 0; | ||
190 | assert->stmt_cnt = 1; | ||
191 | |||
192 | /* adjust as needed */ | ||
193 | if ((r = cbor_parse_reply(reply, (size_t)reply_len, assert, | ||
194 | adjust_assert_count)) != FIDO_OK) { | ||
195 | fido_log_debug("%s: adjust_assert_count", __func__); | ||
196 | return (r); | ||
197 | } | ||
198 | |||
199 | /* parse the first assertion */ | ||
200 | if ((r = cbor_parse_reply(reply, (size_t)reply_len, | ||
201 | &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) { | ||
202 | fido_log_debug("%s: parse_assert_reply", __func__); | ||
203 | return (r); | ||
204 | } | ||
205 | |||
206 | assert->stmt_len++; | ||
207 | |||
208 | return (FIDO_OK); | ||
209 | } | ||
210 | |||
211 | static int | ||
212 | fido_get_next_assert_tx(fido_dev_t *dev) | ||
213 | { | ||
214 | const unsigned char cbor[] = { CTAP_CBOR_NEXT_ASSERT }; | ||
215 | const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR; | ||
216 | |||
217 | if (fido_tx(dev, cmd, cbor, sizeof(cbor)) < 0) { | ||
218 | fido_log_debug("%s: fido_tx", __func__); | ||
219 | return (FIDO_ERR_TX); | ||
220 | } | ||
221 | |||
222 | return (FIDO_OK); | ||
223 | } | ||
224 | |||
225 | static int | ||
226 | fido_get_next_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int ms) | ||
227 | { | ||
228 | const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR; | ||
229 | unsigned char reply[2048]; | ||
230 | int reply_len; | ||
231 | int r; | ||
232 | |||
233 | if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) { | ||
234 | fido_log_debug("%s: fido_rx", __func__); | ||
235 | return (FIDO_ERR_RX); | ||
236 | } | ||
237 | |||
238 | /* sanity check */ | ||
239 | if (assert->stmt_len >= assert->stmt_cnt) { | ||
240 | fido_log_debug("%s: stmt_len=%zu, stmt_cnt=%zu", __func__, | ||
241 | assert->stmt_len, assert->stmt_cnt); | ||
242 | return (FIDO_ERR_INTERNAL); | ||
243 | } | ||
244 | |||
245 | if ((r = cbor_parse_reply(reply, (size_t)reply_len, | ||
246 | &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) { | ||
247 | fido_log_debug("%s: parse_assert_reply", __func__); | ||
248 | return (r); | ||
249 | } | ||
250 | |||
251 | return (FIDO_OK); | ||
252 | } | ||
253 | |||
254 | static int | ||
255 | fido_dev_get_assert_wait(fido_dev_t *dev, fido_assert_t *assert, | ||
256 | const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, int ms) | ||
257 | { | ||
258 | int r; | ||
259 | |||
260 | if ((r = fido_dev_get_assert_tx(dev, assert, pk, ecdh, pin)) != FIDO_OK || | ||
261 | (r = fido_dev_get_assert_rx(dev, assert, ms)) != FIDO_OK) | ||
262 | return (r); | ||
263 | |||
264 | while (assert->stmt_len < assert->stmt_cnt) { | ||
265 | if ((r = fido_get_next_assert_tx(dev)) != FIDO_OK || | ||
266 | (r = fido_get_next_assert_rx(dev, assert, ms)) != FIDO_OK) | ||
267 | return (r); | ||
268 | assert->stmt_len++; | ||
269 | } | ||
270 | |||
271 | return (FIDO_OK); | ||
272 | } | ||
273 | |||
274 | static int | ||
275 | decrypt_hmac_secrets(fido_assert_t *assert, const fido_blob_t *key) | ||
276 | { | ||
277 | for (size_t i = 0; i < assert->stmt_cnt; i++) { | ||
278 | fido_assert_stmt *stmt = &assert->stmt[i]; | ||
279 | if (stmt->hmac_secret_enc.ptr != NULL) { | ||
280 | if (aes256_cbc_dec(key, &stmt->hmac_secret_enc, | ||
281 | &stmt->hmac_secret) < 0) { | ||
282 | fido_log_debug("%s: aes256_cbc_dec %zu", | ||
283 | __func__, i); | ||
284 | return (-1); | ||
285 | } | ||
286 | } | ||
287 | } | ||
288 | |||
289 | return (0); | ||
290 | } | ||
291 | |||
292 | int | ||
293 | fido_dev_get_assert(fido_dev_t *dev, fido_assert_t *assert, const char *pin) | ||
294 | { | ||
295 | fido_blob_t *ecdh = NULL; | ||
296 | es256_pk_t *pk = NULL; | ||
297 | int r; | ||
298 | |||
299 | if (assert->rp_id == NULL || assert->cdh.ptr == NULL) { | ||
300 | fido_log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__, | ||
301 | (void *)assert->rp_id, (void *)assert->cdh.ptr); | ||
302 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
303 | } | ||
304 | |||
305 | if (fido_dev_is_fido2(dev) == false) { | ||
306 | if (pin != NULL || assert->ext != 0) | ||
307 | return (FIDO_ERR_UNSUPPORTED_OPTION); | ||
308 | return (u2f_authenticate(dev, assert, -1)); | ||
309 | } | ||
310 | |||
311 | if (pin != NULL || assert->ext != 0) { | ||
312 | if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) { | ||
313 | fido_log_debug("%s: fido_do_ecdh", __func__); | ||
314 | goto fail; | ||
315 | } | ||
316 | } | ||
317 | |||
318 | r = fido_dev_get_assert_wait(dev, assert, pk, ecdh, pin, -1); | ||
319 | if (r == FIDO_OK && assert->ext & FIDO_EXT_HMAC_SECRET) | ||
320 | if (decrypt_hmac_secrets(assert, ecdh) < 0) { | ||
321 | fido_log_debug("%s: decrypt_hmac_secrets", __func__); | ||
322 | r = FIDO_ERR_INTERNAL; | ||
323 | goto fail; | ||
324 | } | ||
325 | |||
326 | fail: | ||
327 | es256_pk_free(&pk); | ||
328 | fido_blob_free(&ecdh); | ||
329 | |||
330 | return (r); | ||
331 | } | ||
332 | |||
333 | int | ||
334 | fido_check_flags(uint8_t flags, fido_opt_t up, fido_opt_t uv) | ||
335 | { | ||
336 | fido_log_debug("%s: flags=%02x", __func__, flags); | ||
337 | fido_log_debug("%s: up=%d, uv=%d", __func__, up, uv); | ||
338 | |||
339 | if (up == FIDO_OPT_TRUE && | ||
340 | (flags & CTAP_AUTHDATA_USER_PRESENT) == 0) { | ||
341 | fido_log_debug("%s: CTAP_AUTHDATA_USER_PRESENT", __func__); | ||
342 | return (-1); /* user not present */ | ||
343 | } | ||
344 | |||
345 | if (uv == FIDO_OPT_TRUE && | ||
346 | (flags & CTAP_AUTHDATA_USER_VERIFIED) == 0) { | ||
347 | fido_log_debug("%s: CTAP_AUTHDATA_USER_VERIFIED", __func__); | ||
348 | return (-1); /* user not verified */ | ||
349 | } | ||
350 | |||
351 | return (0); | ||
352 | } | ||
353 | |||
354 | static int | ||
355 | check_extensions(int authdata_ext, int ext) | ||
356 | { | ||
357 | if (authdata_ext != ext) { | ||
358 | fido_log_debug("%s: authdata_ext=0x%x != ext=0x%x", __func__, | ||
359 | authdata_ext, ext); | ||
360 | return (-1); | ||
361 | } | ||
362 | |||
363 | return (0); | ||
364 | } | ||
365 | |||
366 | static int | ||
367 | get_signed_hash(int cose_alg, fido_blob_t *dgst, const fido_blob_t *clientdata, | ||
368 | const fido_blob_t *authdata_cbor) | ||
369 | { | ||
370 | cbor_item_t *item = NULL; | ||
371 | unsigned char *authdata_ptr = NULL; | ||
372 | size_t authdata_len; | ||
373 | struct cbor_load_result cbor; | ||
374 | SHA256_CTX ctx; | ||
375 | int ok = -1; | ||
376 | |||
377 | if ((item = cbor_load(authdata_cbor->ptr, authdata_cbor->len, | ||
378 | &cbor)) == NULL || cbor_isa_bytestring(item) == false || | ||
379 | cbor_bytestring_is_definite(item) == false) { | ||
380 | fido_log_debug("%s: authdata", __func__); | ||
381 | goto fail; | ||
382 | } | ||
383 | |||
384 | authdata_ptr = cbor_bytestring_handle(item); | ||
385 | authdata_len = cbor_bytestring_length(item); | ||
386 | |||
387 | if (cose_alg != COSE_EDDSA) { | ||
388 | if (dgst->len < SHA256_DIGEST_LENGTH || SHA256_Init(&ctx) == 0 || | ||
389 | SHA256_Update(&ctx, authdata_ptr, authdata_len) == 0 || | ||
390 | SHA256_Update(&ctx, clientdata->ptr, clientdata->len) == 0 || | ||
391 | SHA256_Final(dgst->ptr, &ctx) == 0) { | ||
392 | fido_log_debug("%s: sha256", __func__); | ||
393 | goto fail; | ||
394 | } | ||
395 | dgst->len = SHA256_DIGEST_LENGTH; | ||
396 | } else { | ||
397 | if (SIZE_MAX - authdata_len < clientdata->len || | ||
398 | dgst->len < authdata_len + clientdata->len) { | ||
399 | fido_log_debug("%s: memcpy", __func__); | ||
400 | goto fail; | ||
401 | } | ||
402 | memcpy(dgst->ptr, authdata_ptr, authdata_len); | ||
403 | memcpy(dgst->ptr + authdata_len, clientdata->ptr, | ||
404 | clientdata->len); | ||
405 | dgst->len = authdata_len + clientdata->len; | ||
406 | } | ||
407 | |||
408 | ok = 0; | ||
409 | fail: | ||
410 | if (item != NULL) | ||
411 | cbor_decref(&item); | ||
412 | |||
413 | return (ok); | ||
414 | } | ||
415 | |||
416 | int | ||
417 | fido_verify_sig_es256(const fido_blob_t *dgst, const es256_pk_t *pk, | ||
418 | const fido_blob_t *sig) | ||
419 | { | ||
420 | EVP_PKEY *pkey = NULL; | ||
421 | EC_KEY *ec = NULL; | ||
422 | int ok = -1; | ||
423 | |||
424 | /* ECDSA_verify needs ints */ | ||
425 | if (dgst->len > INT_MAX || sig->len > INT_MAX) { | ||
426 | fido_log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__, | ||
427 | dgst->len, sig->len); | ||
428 | return (-1); | ||
429 | } | ||
430 | |||
431 | if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL || | ||
432 | (ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL) { | ||
433 | fido_log_debug("%s: pk -> ec", __func__); | ||
434 | goto fail; | ||
435 | } | ||
436 | |||
437 | if (ECDSA_verify(0, dgst->ptr, (int)dgst->len, sig->ptr, | ||
438 | (int)sig->len, ec) != 1) { | ||
439 | fido_log_debug("%s: ECDSA_verify", __func__); | ||
440 | goto fail; | ||
441 | } | ||
442 | |||
443 | ok = 0; | ||
444 | fail: | ||
445 | if (pkey != NULL) | ||
446 | EVP_PKEY_free(pkey); | ||
447 | |||
448 | return (ok); | ||
449 | } | ||
450 | |||
451 | int | ||
452 | fido_verify_sig_rs256(const fido_blob_t *dgst, const rs256_pk_t *pk, | ||
453 | const fido_blob_t *sig) | ||
454 | { | ||
455 | EVP_PKEY *pkey = NULL; | ||
456 | RSA *rsa = NULL; | ||
457 | int ok = -1; | ||
458 | |||
459 | /* RSA_verify needs unsigned ints */ | ||
460 | if (dgst->len > UINT_MAX || sig->len > UINT_MAX) { | ||
461 | fido_log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__, | ||
462 | dgst->len, sig->len); | ||
463 | return (-1); | ||
464 | } | ||
465 | |||
466 | if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL || | ||
467 | (rsa = EVP_PKEY_get0_RSA(pkey)) == NULL) { | ||
468 | fido_log_debug("%s: pk -> ec", __func__); | ||
469 | goto fail; | ||
470 | } | ||
471 | |||
472 | if (RSA_verify(NID_sha256, dgst->ptr, (unsigned int)dgst->len, sig->ptr, | ||
473 | (unsigned int)sig->len, rsa) != 1) { | ||
474 | fido_log_debug("%s: RSA_verify", __func__); | ||
475 | goto fail; | ||
476 | } | ||
477 | |||
478 | ok = 0; | ||
479 | fail: | ||
480 | if (pkey != NULL) | ||
481 | EVP_PKEY_free(pkey); | ||
482 | |||
483 | return (ok); | ||
484 | } | ||
485 | |||
486 | int | ||
487 | fido_verify_sig_eddsa(const fido_blob_t *dgst, const eddsa_pk_t *pk, | ||
488 | const fido_blob_t *sig) | ||
489 | { | ||
490 | EVP_PKEY *pkey = NULL; | ||
491 | EVP_MD_CTX *mdctx = NULL; | ||
492 | int ok = -1; | ||
493 | |||
494 | /* EVP_DigestVerify needs ints */ | ||
495 | if (dgst->len > INT_MAX || sig->len > INT_MAX) { | ||
496 | fido_log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__, | ||
497 | dgst->len, sig->len); | ||
498 | return (-1); | ||
499 | } | ||
500 | |||
501 | if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) { | ||
502 | fido_log_debug("%s: pk -> pkey", __func__); | ||
503 | goto fail; | ||
504 | } | ||
505 | |||
506 | if ((mdctx = EVP_MD_CTX_new()) == NULL) { | ||
507 | fido_log_debug("%s: EVP_MD_CTX_new", __func__); | ||
508 | goto fail; | ||
509 | } | ||
510 | |||
511 | if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1) { | ||
512 | fido_log_debug("%s: EVP_DigestVerifyInit", __func__); | ||
513 | goto fail; | ||
514 | } | ||
515 | |||
516 | if (EVP_DigestVerify(mdctx, sig->ptr, sig->len, dgst->ptr, | ||
517 | dgst->len) != 1) { | ||
518 | fido_log_debug("%s: EVP_DigestVerify", __func__); | ||
519 | goto fail; | ||
520 | } | ||
521 | |||
522 | ok = 0; | ||
523 | fail: | ||
524 | if (mdctx != NULL) | ||
525 | EVP_MD_CTX_free(mdctx); | ||
526 | |||
527 | if (pkey != NULL) | ||
528 | EVP_PKEY_free(pkey); | ||
529 | |||
530 | return (ok); | ||
531 | } | ||
532 | |||
533 | int | ||
534 | fido_assert_verify(const fido_assert_t *assert, size_t idx, int cose_alg, | ||
535 | const void *pk) | ||
536 | { | ||
537 | unsigned char buf[1024]; | ||
538 | fido_blob_t dgst; | ||
539 | const fido_assert_stmt *stmt = NULL; | ||
540 | int ok = -1; | ||
541 | int r; | ||
542 | |||
543 | dgst.ptr = buf; | ||
544 | dgst.len = sizeof(buf); | ||
545 | |||
546 | if (idx >= assert->stmt_len || pk == NULL) { | ||
547 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
548 | goto out; | ||
549 | } | ||
550 | |||
551 | stmt = &assert->stmt[idx]; | ||
552 | |||
553 | /* do we have everything we need? */ | ||
554 | if (assert->cdh.ptr == NULL || assert->rp_id == NULL || | ||
555 | stmt->authdata_cbor.ptr == NULL || stmt->sig.ptr == NULL) { | ||
556 | fido_log_debug("%s: cdh=%p, rp_id=%s, authdata=%p, sig=%p", | ||
557 | __func__, (void *)assert->cdh.ptr, assert->rp_id, | ||
558 | (void *)stmt->authdata_cbor.ptr, (void *)stmt->sig.ptr); | ||
559 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
560 | goto out; | ||
561 | } | ||
562 | |||
563 | if (fido_check_flags(stmt->authdata.flags, assert->up, | ||
564 | assert->uv) < 0) { | ||
565 | fido_log_debug("%s: fido_check_flags", __func__); | ||
566 | r = FIDO_ERR_INVALID_PARAM; | ||
567 | goto out; | ||
568 | } | ||
569 | |||
570 | if (check_extensions(stmt->authdata_ext, assert->ext) < 0) { | ||
571 | fido_log_debug("%s: check_extensions", __func__); | ||
572 | r = FIDO_ERR_INVALID_PARAM; | ||
573 | goto out; | ||
574 | } | ||
575 | |||
576 | if (fido_check_rp_id(assert->rp_id, stmt->authdata.rp_id_hash) != 0) { | ||
577 | fido_log_debug("%s: fido_check_rp_id", __func__); | ||
578 | r = FIDO_ERR_INVALID_PARAM; | ||
579 | goto out; | ||
580 | } | ||
581 | |||
582 | if (get_signed_hash(cose_alg, &dgst, &assert->cdh, | ||
583 | &stmt->authdata_cbor) < 0) { | ||
584 | fido_log_debug("%s: get_signed_hash", __func__); | ||
585 | r = FIDO_ERR_INTERNAL; | ||
586 | goto out; | ||
587 | } | ||
588 | |||
589 | switch (cose_alg) { | ||
590 | case COSE_ES256: | ||
591 | ok = fido_verify_sig_es256(&dgst, pk, &stmt->sig); | ||
592 | break; | ||
593 | case COSE_RS256: | ||
594 | ok = fido_verify_sig_rs256(&dgst, pk, &stmt->sig); | ||
595 | break; | ||
596 | case COSE_EDDSA: | ||
597 | ok = fido_verify_sig_eddsa(&dgst, pk, &stmt->sig); | ||
598 | break; | ||
599 | default: | ||
600 | fido_log_debug("%s: unsupported cose_alg %d", __func__, | ||
601 | cose_alg); | ||
602 | r = FIDO_ERR_UNSUPPORTED_OPTION; | ||
603 | goto out; | ||
604 | } | ||
605 | |||
606 | if (ok < 0) | ||
607 | r = FIDO_ERR_INVALID_SIG; | ||
608 | else | ||
609 | r = FIDO_OK; | ||
610 | out: | ||
611 | explicit_bzero(buf, sizeof(buf)); | ||
612 | |||
613 | return (r); | ||
614 | } | ||
615 | |||
616 | int | ||
617 | fido_assert_set_clientdata_hash(fido_assert_t *assert, | ||
618 | const unsigned char *hash, size_t hash_len) | ||
619 | { | ||
620 | if (fido_blob_set(&assert->cdh, hash, hash_len) < 0) | ||
621 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
622 | |||
623 | return (FIDO_OK); | ||
624 | } | ||
625 | |||
626 | int | ||
627 | fido_assert_set_hmac_salt(fido_assert_t *assert, const unsigned char *salt, | ||
628 | size_t salt_len) | ||
629 | { | ||
630 | if ((salt_len != 32 && salt_len != 64) || | ||
631 | fido_blob_set(&assert->hmac_salt, salt, salt_len) < 0) | ||
632 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
633 | |||
634 | return (FIDO_OK); | ||
635 | } | ||
636 | |||
637 | int | ||
638 | fido_assert_set_rp(fido_assert_t *assert, const char *id) | ||
639 | { | ||
640 | if (assert->rp_id != NULL) { | ||
641 | free(assert->rp_id); | ||
642 | assert->rp_id = NULL; | ||
643 | } | ||
644 | |||
645 | if (id == NULL) | ||
646 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
647 | |||
648 | if ((assert->rp_id = strdup(id)) == NULL) | ||
649 | return (FIDO_ERR_INTERNAL); | ||
650 | |||
651 | return (FIDO_OK); | ||
652 | } | ||
653 | |||
654 | int | ||
655 | fido_assert_allow_cred(fido_assert_t *assert, const unsigned char *ptr, | ||
656 | size_t len) | ||
657 | { | ||
658 | fido_blob_t id; | ||
659 | fido_blob_t *list_ptr; | ||
660 | int r; | ||
661 | |||
662 | memset(&id, 0, sizeof(id)); | ||
663 | |||
664 | if (assert->allow_list.len == SIZE_MAX) { | ||
665 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
666 | goto fail; | ||
667 | } | ||
668 | |||
669 | if (fido_blob_set(&id, ptr, len) < 0 || (list_ptr = | ||
670 | recallocarray(assert->allow_list.ptr, assert->allow_list.len, | ||
671 | assert->allow_list.len + 1, sizeof(fido_blob_t))) == NULL) { | ||
672 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
673 | goto fail; | ||
674 | } | ||
675 | |||
676 | list_ptr[assert->allow_list.len++] = id; | ||
677 | assert->allow_list.ptr = list_ptr; | ||
678 | |||
679 | return (FIDO_OK); | ||
680 | fail: | ||
681 | free(id.ptr); | ||
682 | |||
683 | return (r); | ||
684 | |||
685 | } | ||
686 | |||
687 | int | ||
688 | fido_assert_set_extensions(fido_assert_t *assert, int ext) | ||
689 | { | ||
690 | if (ext != 0 && ext != FIDO_EXT_HMAC_SECRET) | ||
691 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
692 | |||
693 | assert->ext = ext; | ||
694 | |||
695 | return (FIDO_OK); | ||
696 | } | ||
697 | |||
698 | int | ||
699 | fido_assert_set_options(fido_assert_t *assert, bool up, bool uv) | ||
700 | { | ||
701 | assert->up = up ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; | ||
702 | assert->uv = uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; | ||
703 | |||
704 | return (FIDO_OK); | ||
705 | } | ||
706 | |||
707 | int | ||
708 | fido_assert_set_up(fido_assert_t *assert, fido_opt_t up) | ||
709 | { | ||
710 | assert->up = up; | ||
711 | |||
712 | return (FIDO_OK); | ||
713 | } | ||
714 | |||
715 | int | ||
716 | fido_assert_set_uv(fido_assert_t *assert, fido_opt_t uv) | ||
717 | { | ||
718 | assert->uv = uv; | ||
719 | |||
720 | return (FIDO_OK); | ||
721 | } | ||
722 | |||
723 | const unsigned char * | ||
724 | fido_assert_clientdata_hash_ptr(const fido_assert_t *assert) | ||
725 | { | ||
726 | return (assert->cdh.ptr); | ||
727 | } | ||
728 | |||
729 | size_t | ||
730 | fido_assert_clientdata_hash_len(const fido_assert_t *assert) | ||
731 | { | ||
732 | return (assert->cdh.len); | ||
733 | } | ||
734 | |||
735 | fido_assert_t * | ||
736 | fido_assert_new(void) | ||
737 | { | ||
738 | return (calloc(1, sizeof(fido_assert_t))); | ||
739 | } | ||
740 | |||
741 | void | ||
742 | fido_assert_reset_tx(fido_assert_t *assert) | ||
743 | { | ||
744 | free(assert->rp_id); | ||
745 | free(assert->cdh.ptr); | ||
746 | free(assert->hmac_salt.ptr); | ||
747 | fido_free_blob_array(&assert->allow_list); | ||
748 | |||
749 | memset(&assert->cdh, 0, sizeof(assert->cdh)); | ||
750 | memset(&assert->hmac_salt, 0, sizeof(assert->hmac_salt)); | ||
751 | memset(&assert->allow_list, 0, sizeof(assert->allow_list)); | ||
752 | |||
753 | assert->rp_id = NULL; | ||
754 | assert->up = FIDO_OPT_OMIT; | ||
755 | assert->uv = FIDO_OPT_OMIT; | ||
756 | assert->ext = 0; | ||
757 | } | ||
758 | |||
759 | void | ||
760 | fido_assert_reset_rx(fido_assert_t *assert) | ||
761 | { | ||
762 | for (size_t i = 0; i < assert->stmt_cnt; i++) { | ||
763 | free(assert->stmt[i].user.id.ptr); | ||
764 | free(assert->stmt[i].user.icon); | ||
765 | free(assert->stmt[i].user.name); | ||
766 | free(assert->stmt[i].user.display_name); | ||
767 | free(assert->stmt[i].id.ptr); | ||
768 | if (assert->stmt[i].hmac_secret.ptr != NULL) { | ||
769 | explicit_bzero(assert->stmt[i].hmac_secret.ptr, | ||
770 | assert->stmt[i].hmac_secret.len); | ||
771 | } | ||
772 | free(assert->stmt[i].hmac_secret.ptr); | ||
773 | free(assert->stmt[i].hmac_secret_enc.ptr); | ||
774 | free(assert->stmt[i].authdata_cbor.ptr); | ||
775 | free(assert->stmt[i].sig.ptr); | ||
776 | memset(&assert->stmt[i], 0, sizeof(assert->stmt[i])); | ||
777 | } | ||
778 | |||
779 | free(assert->stmt); | ||
780 | |||
781 | assert->stmt = NULL; | ||
782 | assert->stmt_len = 0; | ||
783 | assert->stmt_cnt = 0; | ||
784 | } | ||
785 | |||
786 | void | ||
787 | fido_assert_free(fido_assert_t **assert_p) | ||
788 | { | ||
789 | fido_assert_t *assert; | ||
790 | |||
791 | if (assert_p == NULL || (assert = *assert_p) == NULL) | ||
792 | return; | ||
793 | |||
794 | fido_assert_reset_tx(assert); | ||
795 | fido_assert_reset_rx(assert); | ||
796 | |||
797 | free(assert); | ||
798 | |||
799 | *assert_p = NULL; | ||
800 | } | ||
801 | |||
802 | size_t | ||
803 | fido_assert_count(const fido_assert_t *assert) | ||
804 | { | ||
805 | return (assert->stmt_len); | ||
806 | } | ||
807 | |||
808 | const char * | ||
809 | fido_assert_rp_id(const fido_assert_t *assert) | ||
810 | { | ||
811 | return (assert->rp_id); | ||
812 | } | ||
813 | |||
814 | uint8_t | ||
815 | fido_assert_flags(const fido_assert_t *assert, size_t idx) | ||
816 | { | ||
817 | if (idx >= assert->stmt_len) | ||
818 | return (0); | ||
819 | |||
820 | return (assert->stmt[idx].authdata.flags); | ||
821 | } | ||
822 | |||
823 | uint32_t | ||
824 | fido_assert_sigcount(const fido_assert_t *assert, size_t idx) | ||
825 | { | ||
826 | if (idx >= assert->stmt_len) | ||
827 | return (0); | ||
828 | |||
829 | return (assert->stmt[idx].authdata.sigcount); | ||
830 | } | ||
831 | |||
832 | const unsigned char * | ||
833 | fido_assert_authdata_ptr(const fido_assert_t *assert, size_t idx) | ||
834 | { | ||
835 | if (idx >= assert->stmt_len) | ||
836 | return (NULL); | ||
837 | |||
838 | return (assert->stmt[idx].authdata_cbor.ptr); | ||
839 | } | ||
840 | |||
841 | size_t | ||
842 | fido_assert_authdata_len(const fido_assert_t *assert, size_t idx) | ||
843 | { | ||
844 | if (idx >= assert->stmt_len) | ||
845 | return (0); | ||
846 | |||
847 | return (assert->stmt[idx].authdata_cbor.len); | ||
848 | } | ||
849 | |||
850 | const unsigned char * | ||
851 | fido_assert_sig_ptr(const fido_assert_t *assert, size_t idx) | ||
852 | { | ||
853 | if (idx >= assert->stmt_len) | ||
854 | return (NULL); | ||
855 | |||
856 | return (assert->stmt[idx].sig.ptr); | ||
857 | } | ||
858 | |||
859 | size_t | ||
860 | fido_assert_sig_len(const fido_assert_t *assert, size_t idx) | ||
861 | { | ||
862 | if (idx >= assert->stmt_len) | ||
863 | return (0); | ||
864 | |||
865 | return (assert->stmt[idx].sig.len); | ||
866 | } | ||
867 | |||
868 | const unsigned char * | ||
869 | fido_assert_id_ptr(const fido_assert_t *assert, size_t idx) | ||
870 | { | ||
871 | if (idx >= assert->stmt_len) | ||
872 | return (NULL); | ||
873 | |||
874 | return (assert->stmt[idx].id.ptr); | ||
875 | } | ||
876 | |||
877 | size_t | ||
878 | fido_assert_id_len(const fido_assert_t *assert, size_t idx) | ||
879 | { | ||
880 | if (idx >= assert->stmt_len) | ||
881 | return (0); | ||
882 | |||
883 | return (assert->stmt[idx].id.len); | ||
884 | } | ||
885 | |||
886 | const unsigned char * | ||
887 | fido_assert_user_id_ptr(const fido_assert_t *assert, size_t idx) | ||
888 | { | ||
889 | if (idx >= assert->stmt_len) | ||
890 | return (NULL); | ||
891 | |||
892 | return (assert->stmt[idx].user.id.ptr); | ||
893 | } | ||
894 | |||
895 | size_t | ||
896 | fido_assert_user_id_len(const fido_assert_t *assert, size_t idx) | ||
897 | { | ||
898 | if (idx >= assert->stmt_len) | ||
899 | return (0); | ||
900 | |||
901 | return (assert->stmt[idx].user.id.len); | ||
902 | } | ||
903 | |||
904 | const char * | ||
905 | fido_assert_user_icon(const fido_assert_t *assert, size_t idx) | ||
906 | { | ||
907 | if (idx >= assert->stmt_len) | ||
908 | return (NULL); | ||
909 | |||
910 | return (assert->stmt[idx].user.icon); | ||
911 | } | ||
912 | |||
913 | const char * | ||
914 | fido_assert_user_name(const fido_assert_t *assert, size_t idx) | ||
915 | { | ||
916 | if (idx >= assert->stmt_len) | ||
917 | return (NULL); | ||
918 | |||
919 | return (assert->stmt[idx].user.name); | ||
920 | } | ||
921 | |||
922 | const char * | ||
923 | fido_assert_user_display_name(const fido_assert_t *assert, size_t idx) | ||
924 | { | ||
925 | if (idx >= assert->stmt_len) | ||
926 | return (NULL); | ||
927 | |||
928 | return (assert->stmt[idx].user.display_name); | ||
929 | } | ||
930 | |||
931 | const unsigned char * | ||
932 | fido_assert_hmac_secret_ptr(const fido_assert_t *assert, size_t idx) | ||
933 | { | ||
934 | if (idx >= assert->stmt_len) | ||
935 | return (NULL); | ||
936 | |||
937 | return (assert->stmt[idx].hmac_secret.ptr); | ||
938 | } | ||
939 | |||
940 | size_t | ||
941 | fido_assert_hmac_secret_len(const fido_assert_t *assert, size_t idx) | ||
942 | { | ||
943 | if (idx >= assert->stmt_len) | ||
944 | return (0); | ||
945 | |||
946 | return (assert->stmt[idx].hmac_secret.len); | ||
947 | } | ||
948 | |||
949 | static void | ||
950 | fido_assert_clean_authdata(fido_assert_stmt *as) | ||
951 | { | ||
952 | free(as->authdata_cbor.ptr); | ||
953 | free(as->hmac_secret_enc.ptr); | ||
954 | |||
955 | memset(&as->authdata_ext, 0, sizeof(as->authdata_ext)); | ||
956 | memset(&as->authdata_cbor, 0, sizeof(as->authdata_cbor)); | ||
957 | memset(&as->authdata, 0, sizeof(as->authdata)); | ||
958 | memset(&as->hmac_secret_enc, 0, sizeof(as->hmac_secret_enc)); | ||
959 | } | ||
960 | |||
961 | int | ||
962 | fido_assert_set_authdata(fido_assert_t *assert, size_t idx, | ||
963 | const unsigned char *ptr, size_t len) | ||
964 | { | ||
965 | cbor_item_t *item = NULL; | ||
966 | fido_assert_stmt *stmt = NULL; | ||
967 | struct cbor_load_result cbor; | ||
968 | int r; | ||
969 | |||
970 | if (idx >= assert->stmt_len || ptr == NULL || len == 0) | ||
971 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
972 | |||
973 | stmt = &assert->stmt[idx]; | ||
974 | fido_assert_clean_authdata(stmt); | ||
975 | |||
976 | if ((item = cbor_load(ptr, len, &cbor)) == NULL) { | ||
977 | fido_log_debug("%s: cbor_load", __func__); | ||
978 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
979 | goto fail; | ||
980 | } | ||
981 | |||
982 | if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor, | ||
983 | &stmt->authdata, &stmt->authdata_ext, &stmt->hmac_secret_enc) < 0) { | ||
984 | fido_log_debug("%s: cbor_decode_assert_authdata", __func__); | ||
985 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
986 | goto fail; | ||
987 | } | ||
988 | |||
989 | r = FIDO_OK; | ||
990 | fail: | ||
991 | if (item != NULL) | ||
992 | cbor_decref(&item); | ||
993 | |||
994 | if (r != FIDO_OK) | ||
995 | fido_assert_clean_authdata(stmt); | ||
996 | |||
997 | return (r); | ||
998 | } | ||
999 | |||
1000 | int | ||
1001 | fido_assert_set_authdata_raw(fido_assert_t *assert, size_t idx, | ||
1002 | const unsigned char *ptr, size_t len) | ||
1003 | { | ||
1004 | cbor_item_t *item = NULL; | ||
1005 | fido_assert_stmt *stmt = NULL; | ||
1006 | int r; | ||
1007 | |||
1008 | if (idx >= assert->stmt_len || ptr == NULL || len == 0) | ||
1009 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
1010 | |||
1011 | stmt = &assert->stmt[idx]; | ||
1012 | fido_assert_clean_authdata(stmt); | ||
1013 | |||
1014 | if ((item = cbor_build_bytestring(ptr, len)) == NULL) { | ||
1015 | fido_log_debug("%s: cbor_build_bytestring", __func__); | ||
1016 | r = FIDO_ERR_INTERNAL; | ||
1017 | goto fail; | ||
1018 | } | ||
1019 | |||
1020 | if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor, | ||
1021 | &stmt->authdata, &stmt->authdata_ext, &stmt->hmac_secret_enc) < 0) { | ||
1022 | fido_log_debug("%s: cbor_decode_assert_authdata", __func__); | ||
1023 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
1024 | goto fail; | ||
1025 | } | ||
1026 | |||
1027 | r = FIDO_OK; | ||
1028 | fail: | ||
1029 | if (item != NULL) | ||
1030 | cbor_decref(&item); | ||
1031 | |||
1032 | if (r != FIDO_OK) | ||
1033 | fido_assert_clean_authdata(stmt); | ||
1034 | |||
1035 | return (r); | ||
1036 | } | ||
1037 | |||
1038 | static void | ||
1039 | fido_assert_clean_sig(fido_assert_stmt *as) | ||
1040 | { | ||
1041 | free(as->sig.ptr); | ||
1042 | as->sig.ptr = NULL; | ||
1043 | as->sig.len = 0; | ||
1044 | } | ||
1045 | |||
1046 | int | ||
1047 | fido_assert_set_sig(fido_assert_t *a, size_t idx, const unsigned char *ptr, | ||
1048 | size_t len) | ||
1049 | { | ||
1050 | unsigned char *sig; | ||
1051 | |||
1052 | if (idx >= a->stmt_len || ptr == NULL || len == 0) | ||
1053 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
1054 | |||
1055 | fido_assert_clean_sig(&a->stmt[idx]); | ||
1056 | |||
1057 | if ((sig = malloc(len)) == NULL) | ||
1058 | return (FIDO_ERR_INTERNAL); | ||
1059 | |||
1060 | memcpy(sig, ptr, len); | ||
1061 | a->stmt[idx].sig.ptr = sig; | ||
1062 | a->stmt[idx].sig.len = len; | ||
1063 | |||
1064 | return (FIDO_OK); | ||
1065 | } | ||
1066 | |||
1067 | /* XXX shrinking leaks memory; fortunately that shouldn't happen */ | ||
1068 | int | ||
1069 | fido_assert_set_count(fido_assert_t *assert, size_t n) | ||
1070 | { | ||
1071 | void *new_stmt; | ||
1072 | |||
1073 | #ifdef FIDO_FUZZ | ||
1074 | if (n > UINT8_MAX) { | ||
1075 | fido_log_debug("%s: n > UINT8_MAX", __func__); | ||
1076 | return (FIDO_ERR_INTERNAL); | ||
1077 | } | ||
1078 | #endif | ||
1079 | |||
1080 | new_stmt = recallocarray(assert->stmt, assert->stmt_cnt, n, | ||
1081 | sizeof(fido_assert_stmt)); | ||
1082 | if (new_stmt == NULL) | ||
1083 | return (FIDO_ERR_INTERNAL); | ||
1084 | |||
1085 | assert->stmt = new_stmt; | ||
1086 | assert->stmt_cnt = n; | ||
1087 | assert->stmt_len = n; | ||
1088 | |||
1089 | return (FIDO_OK); | ||
1090 | } | ||