diff options
Diffstat (limited to 'src/cred.c')
-rw-r--r-- | src/cred.c | 1034 |
1 files changed, 1034 insertions, 0 deletions
diff --git a/src/cred.c b/src/cred.c new file mode 100644 index 0000000..c4e1edb --- /dev/null +++ b/src/cred.c | |||
@@ -0,0 +1,1034 @@ | |||
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/evp.h> | ||
9 | #include <openssl/sha.h> | ||
10 | #include <openssl/x509.h> | ||
11 | |||
12 | #include <string.h> | ||
13 | #include "fido.h" | ||
14 | #include "fido/es256.h" | ||
15 | |||
16 | static int | ||
17 | parse_makecred_reply(const cbor_item_t *key, const cbor_item_t *val, void *arg) | ||
18 | { | ||
19 | fido_cred_t *cred = arg; | ||
20 | |||
21 | if (cbor_isa_uint(key) == false || | ||
22 | cbor_int_get_width(key) != CBOR_INT_8) { | ||
23 | fido_log_debug("%s: cbor type", __func__); | ||
24 | return (0); /* ignore */ | ||
25 | } | ||
26 | |||
27 | switch (cbor_get_uint8(key)) { | ||
28 | case 1: /* fmt */ | ||
29 | return (cbor_decode_fmt(val, &cred->fmt)); | ||
30 | case 2: /* authdata */ | ||
31 | return (cbor_decode_cred_authdata(val, cred->type, | ||
32 | &cred->authdata_cbor, &cred->authdata, &cred->attcred, | ||
33 | &cred->authdata_ext)); | ||
34 | case 3: /* attestation statement */ | ||
35 | return (cbor_decode_attstmt(val, &cred->attstmt)); | ||
36 | default: /* ignore */ | ||
37 | fido_log_debug("%s: cbor type", __func__); | ||
38 | return (0); | ||
39 | } | ||
40 | } | ||
41 | |||
42 | static int | ||
43 | fido_dev_make_cred_tx(fido_dev_t *dev, fido_cred_t *cred, const char *pin) | ||
44 | { | ||
45 | fido_blob_t f; | ||
46 | fido_blob_t *ecdh = NULL; | ||
47 | es256_pk_t *pk = NULL; | ||
48 | cbor_item_t *argv[9]; | ||
49 | int r; | ||
50 | |||
51 | memset(&f, 0, sizeof(f)); | ||
52 | memset(argv, 0, sizeof(argv)); | ||
53 | |||
54 | if (cred->cdh.ptr == NULL || cred->type == 0) { | ||
55 | fido_log_debug("%s: cdh=%p, type=%d", __func__, | ||
56 | (void *)cred->cdh.ptr, cred->type); | ||
57 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
58 | goto fail; | ||
59 | } | ||
60 | |||
61 | if ((argv[0] = fido_blob_encode(&cred->cdh)) == NULL || | ||
62 | (argv[1] = cbor_encode_rp_entity(&cred->rp)) == NULL || | ||
63 | (argv[2] = cbor_encode_user_entity(&cred->user)) == NULL || | ||
64 | (argv[3] = cbor_encode_pubkey_param(cred->type)) == NULL) { | ||
65 | fido_log_debug("%s: cbor encode", __func__); | ||
66 | r = FIDO_ERR_INTERNAL; | ||
67 | goto fail; | ||
68 | } | ||
69 | |||
70 | /* excluded credentials */ | ||
71 | if (cred->excl.len) | ||
72 | if ((argv[4] = cbor_encode_pubkey_list(&cred->excl)) == NULL) { | ||
73 | fido_log_debug("%s: cbor_encode_pubkey_list", __func__); | ||
74 | r = FIDO_ERR_INTERNAL; | ||
75 | goto fail; | ||
76 | } | ||
77 | |||
78 | /* extensions */ | ||
79 | if (cred->ext) | ||
80 | if ((argv[5] = cbor_encode_extensions(cred->ext)) == NULL) { | ||
81 | fido_log_debug("%s: cbor_encode_extensions", __func__); | ||
82 | r = FIDO_ERR_INTERNAL; | ||
83 | goto fail; | ||
84 | } | ||
85 | |||
86 | /* options */ | ||
87 | if (cred->rk != FIDO_OPT_OMIT || cred->uv != FIDO_OPT_OMIT) | ||
88 | if ((argv[6] = cbor_encode_options(cred->rk, | ||
89 | cred->uv)) == NULL) { | ||
90 | fido_log_debug("%s: cbor_encode_options", __func__); | ||
91 | r = FIDO_ERR_INTERNAL; | ||
92 | goto fail; | ||
93 | } | ||
94 | |||
95 | /* pin authentication */ | ||
96 | if (pin) { | ||
97 | if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) { | ||
98 | fido_log_debug("%s: fido_do_ecdh", __func__); | ||
99 | goto fail; | ||
100 | } | ||
101 | if ((r = cbor_add_pin_params(dev, &cred->cdh, pk, ecdh, pin, | ||
102 | &argv[7], &argv[8])) != FIDO_OK) { | ||
103 | fido_log_debug("%s: cbor_add_pin_params", __func__); | ||
104 | goto fail; | ||
105 | } | ||
106 | } | ||
107 | |||
108 | /* framing and transmission */ | ||
109 | if (cbor_build_frame(CTAP_CBOR_MAKECRED, argv, 9, &f) < 0 || | ||
110 | fido_tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) { | ||
111 | fido_log_debug("%s: fido_tx", __func__); | ||
112 | r = FIDO_ERR_TX; | ||
113 | goto fail; | ||
114 | } | ||
115 | |||
116 | r = FIDO_OK; | ||
117 | fail: | ||
118 | es256_pk_free(&pk); | ||
119 | fido_blob_free(&ecdh); | ||
120 | cbor_vector_free(argv, nitems(argv)); | ||
121 | free(f.ptr); | ||
122 | |||
123 | return (r); | ||
124 | } | ||
125 | |||
126 | static int | ||
127 | fido_dev_make_cred_rx(fido_dev_t *dev, fido_cred_t *cred, int ms) | ||
128 | { | ||
129 | const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR; | ||
130 | unsigned char reply[2048]; | ||
131 | int reply_len; | ||
132 | int r; | ||
133 | |||
134 | fido_cred_reset_rx(cred); | ||
135 | |||
136 | if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) { | ||
137 | fido_log_debug("%s: fido_rx", __func__); | ||
138 | return (FIDO_ERR_RX); | ||
139 | } | ||
140 | |||
141 | if ((r = cbor_parse_reply(reply, (size_t)reply_len, cred, | ||
142 | parse_makecred_reply)) != FIDO_OK) { | ||
143 | fido_log_debug("%s: parse_makecred_reply", __func__); | ||
144 | return (r); | ||
145 | } | ||
146 | |||
147 | if (cred->fmt == NULL || fido_blob_is_empty(&cred->authdata_cbor) || | ||
148 | fido_blob_is_empty(&cred->attcred.id) || | ||
149 | fido_blob_is_empty(&cred->attstmt.sig)) { | ||
150 | fido_cred_reset_rx(cred); | ||
151 | return (FIDO_ERR_INVALID_CBOR); | ||
152 | } | ||
153 | |||
154 | return (FIDO_OK); | ||
155 | } | ||
156 | |||
157 | static int | ||
158 | fido_dev_make_cred_wait(fido_dev_t *dev, fido_cred_t *cred, const char *pin, int ms) | ||
159 | { | ||
160 | int r; | ||
161 | |||
162 | if ((r = fido_dev_make_cred_tx(dev, cred, pin)) != FIDO_OK || | ||
163 | (r = fido_dev_make_cred_rx(dev, cred, ms)) != FIDO_OK) | ||
164 | return (r); | ||
165 | |||
166 | return (FIDO_OK); | ||
167 | } | ||
168 | |||
169 | int | ||
170 | fido_dev_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin) | ||
171 | { | ||
172 | if (fido_dev_is_fido2(dev) == false) { | ||
173 | if (pin != NULL || cred->rk == FIDO_OPT_TRUE || cred->ext != 0) | ||
174 | return (FIDO_ERR_UNSUPPORTED_OPTION); | ||
175 | return (u2f_register(dev, cred, -1)); | ||
176 | } | ||
177 | |||
178 | return (fido_dev_make_cred_wait(dev, cred, pin, -1)); | ||
179 | } | ||
180 | |||
181 | static int | ||
182 | check_extensions(int authdata_ext, int ext) | ||
183 | { | ||
184 | if (authdata_ext != ext) { | ||
185 | fido_log_debug("%s: authdata_ext=0x%x != ext=0x%x", __func__, | ||
186 | authdata_ext, ext); | ||
187 | return (-1); | ||
188 | } | ||
189 | |||
190 | return (0); | ||
191 | } | ||
192 | |||
193 | int | ||
194 | fido_check_rp_id(const char *id, const unsigned char *obtained_hash) | ||
195 | { | ||
196 | unsigned char expected_hash[SHA256_DIGEST_LENGTH]; | ||
197 | |||
198 | explicit_bzero(expected_hash, sizeof(expected_hash)); | ||
199 | |||
200 | if (SHA256((const unsigned char *)id, strlen(id), | ||
201 | expected_hash) != expected_hash) { | ||
202 | fido_log_debug("%s: sha256", __func__); | ||
203 | return (-1); | ||
204 | } | ||
205 | |||
206 | return (timingsafe_bcmp(expected_hash, obtained_hash, | ||
207 | SHA256_DIGEST_LENGTH)); | ||
208 | } | ||
209 | |||
210 | static int | ||
211 | get_signed_hash_packed(fido_blob_t *dgst, const fido_blob_t *clientdata, | ||
212 | const fido_blob_t *authdata_cbor) | ||
213 | { | ||
214 | cbor_item_t *item = NULL; | ||
215 | unsigned char *authdata_ptr = NULL; | ||
216 | size_t authdata_len; | ||
217 | struct cbor_load_result cbor; | ||
218 | SHA256_CTX ctx; | ||
219 | int ok = -1; | ||
220 | |||
221 | if ((item = cbor_load(authdata_cbor->ptr, authdata_cbor->len, | ||
222 | &cbor)) == NULL) { | ||
223 | fido_log_debug("%s: cbor_load", __func__); | ||
224 | goto fail; | ||
225 | } | ||
226 | |||
227 | if (cbor_isa_bytestring(item) == false || | ||
228 | cbor_bytestring_is_definite(item) == false) { | ||
229 | fido_log_debug("%s: cbor type", __func__); | ||
230 | goto fail; | ||
231 | } | ||
232 | |||
233 | authdata_ptr = cbor_bytestring_handle(item); | ||
234 | authdata_len = cbor_bytestring_length(item); | ||
235 | |||
236 | if (dgst->len != SHA256_DIGEST_LENGTH || SHA256_Init(&ctx) == 0 || | ||
237 | SHA256_Update(&ctx, authdata_ptr, authdata_len) == 0 || | ||
238 | SHA256_Update(&ctx, clientdata->ptr, clientdata->len) == 0 || | ||
239 | SHA256_Final(dgst->ptr, &ctx) == 0) { | ||
240 | fido_log_debug("%s: sha256", __func__); | ||
241 | goto fail; | ||
242 | } | ||
243 | |||
244 | ok = 0; | ||
245 | fail: | ||
246 | if (item != NULL) | ||
247 | cbor_decref(&item); | ||
248 | |||
249 | return (ok); | ||
250 | } | ||
251 | |||
252 | static int | ||
253 | get_signed_hash_u2f(fido_blob_t *dgst, const unsigned char *rp_id, | ||
254 | size_t rp_id_len, const fido_blob_t *clientdata, const fido_blob_t *id, | ||
255 | const es256_pk_t *pk) | ||
256 | { | ||
257 | const uint8_t zero = 0; | ||
258 | const uint8_t four = 4; /* uncompressed point */ | ||
259 | SHA256_CTX ctx; | ||
260 | |||
261 | if (dgst->len != SHA256_DIGEST_LENGTH || SHA256_Init(&ctx) == 0 || | ||
262 | SHA256_Update(&ctx, &zero, sizeof(zero)) == 0 || | ||
263 | SHA256_Update(&ctx, rp_id, rp_id_len) == 0 || | ||
264 | SHA256_Update(&ctx, clientdata->ptr, clientdata->len) == 0 || | ||
265 | SHA256_Update(&ctx, id->ptr, id->len) == 0 || | ||
266 | SHA256_Update(&ctx, &four, sizeof(four)) == 0 || | ||
267 | SHA256_Update(&ctx, pk->x, sizeof(pk->x)) == 0 || | ||
268 | SHA256_Update(&ctx, pk->y, sizeof(pk->y)) == 0 || | ||
269 | SHA256_Final(dgst->ptr, &ctx) == 0) { | ||
270 | fido_log_debug("%s: sha256", __func__); | ||
271 | return (-1); | ||
272 | } | ||
273 | |||
274 | return (0); | ||
275 | } | ||
276 | |||
277 | static int | ||
278 | verify_sig(const fido_blob_t *dgst, const fido_blob_t *x5c, | ||
279 | const fido_blob_t *sig) | ||
280 | { | ||
281 | BIO *rawcert = NULL; | ||
282 | X509 *cert = NULL; | ||
283 | EVP_PKEY *pkey = NULL; | ||
284 | EC_KEY *ec; | ||
285 | int ok = -1; | ||
286 | |||
287 | /* openssl needs ints */ | ||
288 | if (dgst->len > INT_MAX || x5c->len > INT_MAX || sig->len > INT_MAX) { | ||
289 | fido_log_debug("%s: dgst->len=%zu, x5c->len=%zu, sig->len=%zu", | ||
290 | __func__, dgst->len, x5c->len, sig->len); | ||
291 | return (-1); | ||
292 | } | ||
293 | |||
294 | /* fetch key from x509 */ | ||
295 | if ((rawcert = BIO_new_mem_buf(x5c->ptr, (int)x5c->len)) == NULL || | ||
296 | (cert = d2i_X509_bio(rawcert, NULL)) == NULL || | ||
297 | (pkey = X509_get_pubkey(cert)) == NULL || | ||
298 | (ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL) { | ||
299 | fido_log_debug("%s: x509 key", __func__); | ||
300 | goto fail; | ||
301 | } | ||
302 | |||
303 | if (ECDSA_verify(0, dgst->ptr, (int)dgst->len, sig->ptr, | ||
304 | (int)sig->len, ec) != 1) { | ||
305 | fido_log_debug("%s: ECDSA_verify", __func__); | ||
306 | goto fail; | ||
307 | } | ||
308 | |||
309 | ok = 0; | ||
310 | fail: | ||
311 | if (rawcert != NULL) | ||
312 | BIO_free(rawcert); | ||
313 | if (cert != NULL) | ||
314 | X509_free(cert); | ||
315 | if (pkey != NULL) | ||
316 | EVP_PKEY_free(pkey); | ||
317 | |||
318 | return (ok); | ||
319 | } | ||
320 | |||
321 | int | ||
322 | fido_cred_verify(const fido_cred_t *cred) | ||
323 | { | ||
324 | unsigned char buf[SHA256_DIGEST_LENGTH]; | ||
325 | fido_blob_t dgst; | ||
326 | int r; | ||
327 | |||
328 | dgst.ptr = buf; | ||
329 | dgst.len = sizeof(buf); | ||
330 | |||
331 | /* do we have everything we need? */ | ||
332 | if (cred->cdh.ptr == NULL || cred->authdata_cbor.ptr == NULL || | ||
333 | cred->attstmt.x5c.ptr == NULL || cred->attstmt.sig.ptr == NULL || | ||
334 | cred->fmt == NULL || cred->attcred.id.ptr == NULL || | ||
335 | cred->rp.id == NULL) { | ||
336 | fido_log_debug("%s: cdh=%p, authdata=%p, x5c=%p, sig=%p, " | ||
337 | "fmt=%p id=%p, rp.id=%s", __func__, (void *)cred->cdh.ptr, | ||
338 | (void *)cred->authdata_cbor.ptr, | ||
339 | (void *)cred->attstmt.x5c.ptr, | ||
340 | (void *)cred->attstmt.sig.ptr, (void *)cred->fmt, | ||
341 | (void *)cred->attcred.id.ptr, cred->rp.id); | ||
342 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
343 | goto out; | ||
344 | } | ||
345 | |||
346 | if (fido_check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) { | ||
347 | fido_log_debug("%s: fido_check_rp_id", __func__); | ||
348 | r = FIDO_ERR_INVALID_PARAM; | ||
349 | goto out; | ||
350 | } | ||
351 | |||
352 | if (fido_check_flags(cred->authdata.flags, FIDO_OPT_TRUE, | ||
353 | cred->uv) < 0) { | ||
354 | fido_log_debug("%s: fido_check_flags", __func__); | ||
355 | r = FIDO_ERR_INVALID_PARAM; | ||
356 | goto out; | ||
357 | } | ||
358 | |||
359 | if (check_extensions(cred->authdata_ext, cred->ext) < 0) { | ||
360 | fido_log_debug("%s: check_extensions", __func__); | ||
361 | r = FIDO_ERR_INVALID_PARAM; | ||
362 | goto out; | ||
363 | } | ||
364 | |||
365 | if (!strcmp(cred->fmt, "packed")) { | ||
366 | if (get_signed_hash_packed(&dgst, &cred->cdh, | ||
367 | &cred->authdata_cbor) < 0) { | ||
368 | fido_log_debug("%s: get_signed_hash_packed", __func__); | ||
369 | r = FIDO_ERR_INTERNAL; | ||
370 | goto out; | ||
371 | } | ||
372 | } else { | ||
373 | if (get_signed_hash_u2f(&dgst, cred->authdata.rp_id_hash, | ||
374 | sizeof(cred->authdata.rp_id_hash), &cred->cdh, | ||
375 | &cred->attcred.id, &cred->attcred.pubkey.es256) < 0) { | ||
376 | fido_log_debug("%s: get_signed_hash_u2f", __func__); | ||
377 | r = FIDO_ERR_INTERNAL; | ||
378 | goto out; | ||
379 | } | ||
380 | } | ||
381 | |||
382 | if (verify_sig(&dgst, &cred->attstmt.x5c, &cred->attstmt.sig) < 0) { | ||
383 | fido_log_debug("%s: verify_sig", __func__); | ||
384 | r = FIDO_ERR_INVALID_SIG; | ||
385 | goto out; | ||
386 | } | ||
387 | |||
388 | r = FIDO_OK; | ||
389 | out: | ||
390 | explicit_bzero(buf, sizeof(buf)); | ||
391 | |||
392 | return (r); | ||
393 | } | ||
394 | |||
395 | int | ||
396 | fido_cred_verify_self(const fido_cred_t *cred) | ||
397 | { | ||
398 | unsigned char buf[SHA256_DIGEST_LENGTH]; | ||
399 | fido_blob_t dgst; | ||
400 | int ok = -1; | ||
401 | int r; | ||
402 | |||
403 | dgst.ptr = buf; | ||
404 | dgst.len = sizeof(buf); | ||
405 | |||
406 | /* do we have everything we need? */ | ||
407 | if (cred->cdh.ptr == NULL || cred->authdata_cbor.ptr == NULL || | ||
408 | cred->attstmt.x5c.ptr != NULL || cred->attstmt.sig.ptr == NULL || | ||
409 | cred->fmt == NULL || cred->attcred.id.ptr == NULL || | ||
410 | cred->rp.id == NULL) { | ||
411 | fido_log_debug("%s: cdh=%p, authdata=%p, x5c=%p, sig=%p, " | ||
412 | "fmt=%p id=%p, rp.id=%s", __func__, (void *)cred->cdh.ptr, | ||
413 | (void *)cred->authdata_cbor.ptr, | ||
414 | (void *)cred->attstmt.x5c.ptr, | ||
415 | (void *)cred->attstmt.sig.ptr, (void *)cred->fmt, | ||
416 | (void *)cred->attcred.id.ptr, cred->rp.id); | ||
417 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
418 | goto out; | ||
419 | } | ||
420 | |||
421 | if (fido_check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) { | ||
422 | fido_log_debug("%s: fido_check_rp_id", __func__); | ||
423 | r = FIDO_ERR_INVALID_PARAM; | ||
424 | goto out; | ||
425 | } | ||
426 | |||
427 | if (fido_check_flags(cred->authdata.flags, FIDO_OPT_TRUE, | ||
428 | cred->uv) < 0) { | ||
429 | fido_log_debug("%s: fido_check_flags", __func__); | ||
430 | r = FIDO_ERR_INVALID_PARAM; | ||
431 | goto out; | ||
432 | } | ||
433 | |||
434 | if (check_extensions(cred->authdata_ext, cred->ext) < 0) { | ||
435 | fido_log_debug("%s: check_extensions", __func__); | ||
436 | r = FIDO_ERR_INVALID_PARAM; | ||
437 | goto out; | ||
438 | } | ||
439 | |||
440 | if (!strcmp(cred->fmt, "packed")) { | ||
441 | if (get_signed_hash_packed(&dgst, &cred->cdh, | ||
442 | &cred->authdata_cbor) < 0) { | ||
443 | fido_log_debug("%s: get_signed_hash_packed", __func__); | ||
444 | r = FIDO_ERR_INTERNAL; | ||
445 | goto out; | ||
446 | } | ||
447 | } else { | ||
448 | if (get_signed_hash_u2f(&dgst, cred->authdata.rp_id_hash, | ||
449 | sizeof(cred->authdata.rp_id_hash), &cred->cdh, | ||
450 | &cred->attcred.id, &cred->attcred.pubkey.es256) < 0) { | ||
451 | fido_log_debug("%s: get_signed_hash_u2f", __func__); | ||
452 | r = FIDO_ERR_INTERNAL; | ||
453 | goto out; | ||
454 | } | ||
455 | } | ||
456 | |||
457 | switch (cred->attcred.type) { | ||
458 | case COSE_ES256: | ||
459 | ok = fido_verify_sig_es256(&dgst, &cred->attcred.pubkey.es256, | ||
460 | &cred->attstmt.sig); | ||
461 | break; | ||
462 | case COSE_RS256: | ||
463 | ok = fido_verify_sig_rs256(&dgst, &cred->attcred.pubkey.rs256, | ||
464 | &cred->attstmt.sig); | ||
465 | break; | ||
466 | case COSE_EDDSA: | ||
467 | ok = fido_verify_sig_eddsa(&dgst, &cred->attcred.pubkey.eddsa, | ||
468 | &cred->attstmt.sig); | ||
469 | break; | ||
470 | default: | ||
471 | fido_log_debug("%s: unsupported cose_alg %d", __func__, | ||
472 | cred->attcred.type); | ||
473 | r = FIDO_ERR_UNSUPPORTED_OPTION; | ||
474 | goto out; | ||
475 | } | ||
476 | |||
477 | if (ok < 0) | ||
478 | r = FIDO_ERR_INVALID_SIG; | ||
479 | else | ||
480 | r = FIDO_OK; | ||
481 | |||
482 | out: | ||
483 | explicit_bzero(buf, sizeof(buf)); | ||
484 | |||
485 | return (r); | ||
486 | } | ||
487 | |||
488 | fido_cred_t * | ||
489 | fido_cred_new(void) | ||
490 | { | ||
491 | return (calloc(1, sizeof(fido_cred_t))); | ||
492 | } | ||
493 | |||
494 | static void | ||
495 | fido_cred_clean_authdata(fido_cred_t *cred) | ||
496 | { | ||
497 | free(cred->authdata_cbor.ptr); | ||
498 | free(cred->attcred.id.ptr); | ||
499 | |||
500 | memset(&cred->authdata_ext, 0, sizeof(cred->authdata_ext)); | ||
501 | memset(&cred->authdata_cbor, 0, sizeof(cred->authdata_cbor)); | ||
502 | memset(&cred->authdata, 0, sizeof(cred->authdata)); | ||
503 | memset(&cred->attcred, 0, sizeof(cred->attcred)); | ||
504 | } | ||
505 | |||
506 | void | ||
507 | fido_cred_reset_tx(fido_cred_t *cred) | ||
508 | { | ||
509 | free(cred->cdh.ptr); | ||
510 | free(cred->rp.id); | ||
511 | free(cred->rp.name); | ||
512 | free(cred->user.id.ptr); | ||
513 | free(cred->user.icon); | ||
514 | free(cred->user.name); | ||
515 | free(cred->user.display_name); | ||
516 | fido_free_blob_array(&cred->excl); | ||
517 | |||
518 | memset(&cred->cdh, 0, sizeof(cred->cdh)); | ||
519 | memset(&cred->rp, 0, sizeof(cred->rp)); | ||
520 | memset(&cred->user, 0, sizeof(cred->user)); | ||
521 | memset(&cred->excl, 0, sizeof(cred->excl)); | ||
522 | |||
523 | cred->type = 0; | ||
524 | cred->ext = 0; | ||
525 | cred->rk = FIDO_OPT_OMIT; | ||
526 | cred->uv = FIDO_OPT_OMIT; | ||
527 | } | ||
528 | |||
529 | static void | ||
530 | fido_cred_clean_x509(fido_cred_t *cred) | ||
531 | { | ||
532 | free(cred->attstmt.x5c.ptr); | ||
533 | cred->attstmt.x5c.ptr = NULL; | ||
534 | cred->attstmt.x5c.len = 0; | ||
535 | } | ||
536 | |||
537 | static void | ||
538 | fido_cred_clean_sig(fido_cred_t *cred) | ||
539 | { | ||
540 | free(cred->attstmt.sig.ptr); | ||
541 | cred->attstmt.sig.ptr = NULL; | ||
542 | cred->attstmt.sig.len = 0; | ||
543 | } | ||
544 | |||
545 | void | ||
546 | fido_cred_reset_rx(fido_cred_t *cred) | ||
547 | { | ||
548 | free(cred->fmt); | ||
549 | cred->fmt = NULL; | ||
550 | |||
551 | fido_cred_clean_authdata(cred); | ||
552 | fido_cred_clean_x509(cred); | ||
553 | fido_cred_clean_sig(cred); | ||
554 | } | ||
555 | |||
556 | void | ||
557 | fido_cred_free(fido_cred_t **cred_p) | ||
558 | { | ||
559 | fido_cred_t *cred; | ||
560 | |||
561 | if (cred_p == NULL || (cred = *cred_p) == NULL) | ||
562 | return; | ||
563 | |||
564 | fido_cred_reset_tx(cred); | ||
565 | fido_cred_reset_rx(cred); | ||
566 | |||
567 | free(cred); | ||
568 | |||
569 | *cred_p = NULL; | ||
570 | } | ||
571 | |||
572 | int | ||
573 | fido_cred_set_authdata(fido_cred_t *cred, const unsigned char *ptr, size_t len) | ||
574 | { | ||
575 | cbor_item_t *item = NULL; | ||
576 | struct cbor_load_result cbor; | ||
577 | int r; | ||
578 | |||
579 | fido_cred_clean_authdata(cred); | ||
580 | |||
581 | if (ptr == NULL || len == 0) { | ||
582 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
583 | goto fail; | ||
584 | } | ||
585 | |||
586 | if ((item = cbor_load(ptr, len, &cbor)) == NULL) { | ||
587 | fido_log_debug("%s: cbor_load", __func__); | ||
588 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
589 | goto fail; | ||
590 | } | ||
591 | |||
592 | if (cbor_decode_cred_authdata(item, cred->type, &cred->authdata_cbor, | ||
593 | &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) { | ||
594 | fido_log_debug("%s: cbor_decode_cred_authdata", __func__); | ||
595 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
596 | goto fail; | ||
597 | } | ||
598 | |||
599 | r = FIDO_OK; | ||
600 | fail: | ||
601 | if (item != NULL) | ||
602 | cbor_decref(&item); | ||
603 | |||
604 | if (r != FIDO_OK) | ||
605 | fido_cred_clean_authdata(cred); | ||
606 | |||
607 | return (r); | ||
608 | |||
609 | } | ||
610 | |||
611 | int | ||
612 | fido_cred_set_authdata_raw(fido_cred_t *cred, const unsigned char *ptr, | ||
613 | size_t len) | ||
614 | { | ||
615 | cbor_item_t *item = NULL; | ||
616 | int r; | ||
617 | |||
618 | fido_cred_clean_authdata(cred); | ||
619 | |||
620 | if (ptr == NULL || len == 0) { | ||
621 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
622 | goto fail; | ||
623 | } | ||
624 | |||
625 | if ((item = cbor_build_bytestring(ptr, len)) == NULL) { | ||
626 | fido_log_debug("%s: cbor_build_bytestring", __func__); | ||
627 | r = FIDO_ERR_INTERNAL; | ||
628 | goto fail; | ||
629 | } | ||
630 | |||
631 | if (cbor_decode_cred_authdata(item, cred->type, &cred->authdata_cbor, | ||
632 | &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) { | ||
633 | fido_log_debug("%s: cbor_decode_cred_authdata", __func__); | ||
634 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
635 | goto fail; | ||
636 | } | ||
637 | |||
638 | r = FIDO_OK; | ||
639 | fail: | ||
640 | if (item != NULL) | ||
641 | cbor_decref(&item); | ||
642 | |||
643 | if (r != FIDO_OK) | ||
644 | fido_cred_clean_authdata(cred); | ||
645 | |||
646 | return (r); | ||
647 | |||
648 | } | ||
649 | |||
650 | int | ||
651 | fido_cred_set_x509(fido_cred_t *cred, const unsigned char *ptr, size_t len) | ||
652 | { | ||
653 | unsigned char *x509; | ||
654 | |||
655 | fido_cred_clean_x509(cred); | ||
656 | |||
657 | if (ptr == NULL || len == 0) | ||
658 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
659 | if ((x509 = malloc(len)) == NULL) | ||
660 | return (FIDO_ERR_INTERNAL); | ||
661 | |||
662 | memcpy(x509, ptr, len); | ||
663 | cred->attstmt.x5c.ptr = x509; | ||
664 | cred->attstmt.x5c.len = len; | ||
665 | |||
666 | return (FIDO_OK); | ||
667 | } | ||
668 | |||
669 | int | ||
670 | fido_cred_set_sig(fido_cred_t *cred, const unsigned char *ptr, size_t len) | ||
671 | { | ||
672 | unsigned char *sig; | ||
673 | |||
674 | fido_cred_clean_sig(cred); | ||
675 | |||
676 | if (ptr == NULL || len == 0) | ||
677 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
678 | if ((sig = malloc(len)) == NULL) | ||
679 | return (FIDO_ERR_INTERNAL); | ||
680 | |||
681 | memcpy(sig, ptr, len); | ||
682 | cred->attstmt.sig.ptr = sig; | ||
683 | cred->attstmt.sig.len = len; | ||
684 | |||
685 | return (FIDO_OK); | ||
686 | } | ||
687 | |||
688 | int | ||
689 | fido_cred_exclude(fido_cred_t *cred, const unsigned char *id_ptr, size_t id_len) | ||
690 | { | ||
691 | fido_blob_t id_blob; | ||
692 | fido_blob_t *list_ptr; | ||
693 | |||
694 | memset(&id_blob, 0, sizeof(id_blob)); | ||
695 | |||
696 | if (fido_blob_set(&id_blob, id_ptr, id_len) < 0) | ||
697 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
698 | |||
699 | if (cred->excl.len == SIZE_MAX) { | ||
700 | free(id_blob.ptr); | ||
701 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
702 | } | ||
703 | |||
704 | if ((list_ptr = recallocarray(cred->excl.ptr, cred->excl.len, | ||
705 | cred->excl.len + 1, sizeof(fido_blob_t))) == NULL) { | ||
706 | free(id_blob.ptr); | ||
707 | return (FIDO_ERR_INTERNAL); | ||
708 | } | ||
709 | |||
710 | list_ptr[cred->excl.len++] = id_blob; | ||
711 | cred->excl.ptr = list_ptr; | ||
712 | |||
713 | return (FIDO_OK); | ||
714 | } | ||
715 | |||
716 | int | ||
717 | fido_cred_set_clientdata_hash(fido_cred_t *cred, const unsigned char *hash, | ||
718 | size_t hash_len) | ||
719 | { | ||
720 | if (fido_blob_set(&cred->cdh, hash, hash_len) < 0) | ||
721 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
722 | |||
723 | return (FIDO_OK); | ||
724 | } | ||
725 | |||
726 | int | ||
727 | fido_cred_set_rp(fido_cred_t *cred, const char *id, const char *name) | ||
728 | { | ||
729 | fido_rp_t *rp = &cred->rp; | ||
730 | |||
731 | if (rp->id != NULL) { | ||
732 | free(rp->id); | ||
733 | rp->id = NULL; | ||
734 | } | ||
735 | if (rp->name != NULL) { | ||
736 | free(rp->name); | ||
737 | rp->name = NULL; | ||
738 | } | ||
739 | |||
740 | if (id != NULL && (rp->id = strdup(id)) == NULL) | ||
741 | goto fail; | ||
742 | if (name != NULL && (rp->name = strdup(name)) == NULL) | ||
743 | goto fail; | ||
744 | |||
745 | return (FIDO_OK); | ||
746 | fail: | ||
747 | free(rp->id); | ||
748 | free(rp->name); | ||
749 | rp->id = NULL; | ||
750 | rp->name = NULL; | ||
751 | |||
752 | return (FIDO_ERR_INTERNAL); | ||
753 | } | ||
754 | |||
755 | int | ||
756 | fido_cred_set_user(fido_cred_t *cred, const unsigned char *user_id, | ||
757 | size_t user_id_len, const char *name, const char *display_name, | ||
758 | const char *icon) | ||
759 | { | ||
760 | fido_user_t *up = &cred->user; | ||
761 | |||
762 | if (up->id.ptr != NULL) { | ||
763 | free(up->id.ptr); | ||
764 | up->id.ptr = NULL; | ||
765 | up->id.len = 0; | ||
766 | } | ||
767 | if (up->name != NULL) { | ||
768 | free(up->name); | ||
769 | up->name = NULL; | ||
770 | } | ||
771 | if (up->display_name != NULL) { | ||
772 | free(up->display_name); | ||
773 | up->display_name = NULL; | ||
774 | } | ||
775 | if (up->icon != NULL) { | ||
776 | free(up->icon); | ||
777 | up->icon = NULL; | ||
778 | } | ||
779 | |||
780 | if (user_id != NULL) { | ||
781 | if ((up->id.ptr = malloc(user_id_len)) == NULL) | ||
782 | goto fail; | ||
783 | memcpy(up->id.ptr, user_id, user_id_len); | ||
784 | up->id.len = user_id_len; | ||
785 | } | ||
786 | if (name != NULL && (up->name = strdup(name)) == NULL) | ||
787 | goto fail; | ||
788 | if (display_name != NULL && | ||
789 | (up->display_name = strdup(display_name)) == NULL) | ||
790 | goto fail; | ||
791 | if (icon != NULL && (up->icon = strdup(icon)) == NULL) | ||
792 | goto fail; | ||
793 | |||
794 | return (FIDO_OK); | ||
795 | fail: | ||
796 | free(up->id.ptr); | ||
797 | free(up->name); | ||
798 | free(up->display_name); | ||
799 | free(up->icon); | ||
800 | |||
801 | up->id.ptr = NULL; | ||
802 | up->id.len = 0; | ||
803 | up->name = NULL; | ||
804 | up->display_name = NULL; | ||
805 | up->icon = NULL; | ||
806 | |||
807 | return (FIDO_ERR_INTERNAL); | ||
808 | } | ||
809 | |||
810 | int | ||
811 | fido_cred_set_extensions(fido_cred_t *cred, int ext) | ||
812 | { | ||
813 | if (ext != 0 && ext != FIDO_EXT_HMAC_SECRET) | ||
814 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
815 | |||
816 | cred->ext = ext; | ||
817 | |||
818 | return (FIDO_OK); | ||
819 | } | ||
820 | |||
821 | int | ||
822 | fido_cred_set_options(fido_cred_t *cred, bool rk, bool uv) | ||
823 | { | ||
824 | cred->rk = rk ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; | ||
825 | cred->uv = uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; | ||
826 | |||
827 | return (FIDO_OK); | ||
828 | } | ||
829 | |||
830 | int | ||
831 | fido_cred_set_rk(fido_cred_t *cred, fido_opt_t rk) | ||
832 | { | ||
833 | cred->rk = rk; | ||
834 | |||
835 | return (FIDO_OK); | ||
836 | } | ||
837 | |||
838 | int | ||
839 | fido_cred_set_uv(fido_cred_t *cred, fido_opt_t uv) | ||
840 | { | ||
841 | cred->uv = uv; | ||
842 | |||
843 | return (FIDO_OK); | ||
844 | } | ||
845 | |||
846 | int | ||
847 | fido_cred_set_fmt(fido_cred_t *cred, const char *fmt) | ||
848 | { | ||
849 | free(cred->fmt); | ||
850 | cred->fmt = NULL; | ||
851 | |||
852 | if (fmt == NULL) | ||
853 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
854 | |||
855 | if (strcmp(fmt, "packed") && strcmp(fmt, "fido-u2f")) | ||
856 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
857 | |||
858 | if ((cred->fmt = strdup(fmt)) == NULL) | ||
859 | return (FIDO_ERR_INTERNAL); | ||
860 | |||
861 | return (FIDO_OK); | ||
862 | } | ||
863 | |||
864 | int | ||
865 | fido_cred_set_type(fido_cred_t *cred, int cose_alg) | ||
866 | { | ||
867 | if ((cose_alg != COSE_ES256 && cose_alg != COSE_RS256 && | ||
868 | cose_alg != COSE_EDDSA) || cred->type != 0) | ||
869 | return (FIDO_ERR_INVALID_ARGUMENT); | ||
870 | |||
871 | cred->type = cose_alg; | ||
872 | |||
873 | return (FIDO_OK); | ||
874 | } | ||
875 | |||
876 | int | ||
877 | fido_cred_type(const fido_cred_t *cred) | ||
878 | { | ||
879 | return (cred->type); | ||
880 | } | ||
881 | |||
882 | uint8_t | ||
883 | fido_cred_flags(const fido_cred_t *cred) | ||
884 | { | ||
885 | return (cred->authdata.flags); | ||
886 | } | ||
887 | |||
888 | const unsigned char * | ||
889 | fido_cred_clientdata_hash_ptr(const fido_cred_t *cred) | ||
890 | { | ||
891 | return (cred->cdh.ptr); | ||
892 | } | ||
893 | |||
894 | size_t | ||
895 | fido_cred_clientdata_hash_len(const fido_cred_t *cred) | ||
896 | { | ||
897 | return (cred->cdh.len); | ||
898 | } | ||
899 | |||
900 | const unsigned char * | ||
901 | fido_cred_x5c_ptr(const fido_cred_t *cred) | ||
902 | { | ||
903 | return (cred->attstmt.x5c.ptr); | ||
904 | } | ||
905 | |||
906 | size_t | ||
907 | fido_cred_x5c_len(const fido_cred_t *cred) | ||
908 | { | ||
909 | return (cred->attstmt.x5c.len); | ||
910 | } | ||
911 | |||
912 | const unsigned char * | ||
913 | fido_cred_sig_ptr(const fido_cred_t *cred) | ||
914 | { | ||
915 | return (cred->attstmt.sig.ptr); | ||
916 | } | ||
917 | |||
918 | size_t | ||
919 | fido_cred_sig_len(const fido_cred_t *cred) | ||
920 | { | ||
921 | return (cred->attstmt.sig.len); | ||
922 | } | ||
923 | |||
924 | const unsigned char * | ||
925 | fido_cred_authdata_ptr(const fido_cred_t *cred) | ||
926 | { | ||
927 | return (cred->authdata_cbor.ptr); | ||
928 | } | ||
929 | |||
930 | size_t | ||
931 | fido_cred_authdata_len(const fido_cred_t *cred) | ||
932 | { | ||
933 | return (cred->authdata_cbor.len); | ||
934 | } | ||
935 | |||
936 | const unsigned char * | ||
937 | fido_cred_pubkey_ptr(const fido_cred_t *cred) | ||
938 | { | ||
939 | const void *ptr; | ||
940 | |||
941 | switch (cred->attcred.type) { | ||
942 | case COSE_ES256: | ||
943 | ptr = &cred->attcred.pubkey.es256; | ||
944 | break; | ||
945 | case COSE_RS256: | ||
946 | ptr = &cred->attcred.pubkey.rs256; | ||
947 | break; | ||
948 | case COSE_EDDSA: | ||
949 | ptr = &cred->attcred.pubkey.eddsa; | ||
950 | break; | ||
951 | default: | ||
952 | ptr = NULL; | ||
953 | break; | ||
954 | } | ||
955 | |||
956 | return (ptr); | ||
957 | } | ||
958 | |||
959 | size_t | ||
960 | fido_cred_pubkey_len(const fido_cred_t *cred) | ||
961 | { | ||
962 | size_t len; | ||
963 | |||
964 | switch (cred->attcred.type) { | ||
965 | case COSE_ES256: | ||
966 | len = sizeof(cred->attcred.pubkey.es256); | ||
967 | break; | ||
968 | case COSE_RS256: | ||
969 | len = sizeof(cred->attcred.pubkey.rs256); | ||
970 | break; | ||
971 | case COSE_EDDSA: | ||
972 | len = sizeof(cred->attcred.pubkey.eddsa); | ||
973 | break; | ||
974 | default: | ||
975 | len = 0; | ||
976 | break; | ||
977 | } | ||
978 | |||
979 | return (len); | ||
980 | } | ||
981 | |||
982 | const unsigned char * | ||
983 | fido_cred_id_ptr(const fido_cred_t *cred) | ||
984 | { | ||
985 | return (cred->attcred.id.ptr); | ||
986 | } | ||
987 | |||
988 | size_t | ||
989 | fido_cred_id_len(const fido_cred_t *cred) | ||
990 | { | ||
991 | return (cred->attcred.id.len); | ||
992 | } | ||
993 | |||
994 | const char * | ||
995 | fido_cred_fmt(const fido_cred_t *cred) | ||
996 | { | ||
997 | return (cred->fmt); | ||
998 | } | ||
999 | |||
1000 | const char * | ||
1001 | fido_cred_rp_id(const fido_cred_t *cred) | ||
1002 | { | ||
1003 | return (cred->rp.id); | ||
1004 | } | ||
1005 | |||
1006 | const char * | ||
1007 | fido_cred_rp_name(const fido_cred_t *cred) | ||
1008 | { | ||
1009 | return (cred->rp.name); | ||
1010 | } | ||
1011 | |||
1012 | const char * | ||
1013 | fido_cred_user_name(const fido_cred_t *cred) | ||
1014 | { | ||
1015 | return (cred->user.name); | ||
1016 | } | ||
1017 | |||
1018 | const char * | ||
1019 | fido_cred_display_name(const fido_cred_t *cred) | ||
1020 | { | ||
1021 | return (cred->user.display_name); | ||
1022 | } | ||
1023 | |||
1024 | const unsigned char * | ||
1025 | fido_cred_user_id_ptr(const fido_cred_t *cred) | ||
1026 | { | ||
1027 | return (cred->user.id.ptr); | ||
1028 | } | ||
1029 | |||
1030 | size_t | ||
1031 | fido_cred_user_id_len(const fido_cred_t *cred) | ||
1032 | { | ||
1033 | return (cred->user.id.len); | ||
1034 | } | ||