xref: /freebsd/contrib/libfido2/src/largeblob.c (revision f126890ac5386406dadf7c4cfa9566cbb56537c5)
1 /*
2  * Copyright (c) 2020-2022 Yubico AB. All rights reserved.
3  * Use of this source code is governed by a BSD-style
4  * license that can be found in the LICENSE file.
5  * SPDX-License-Identifier: BSD-2-Clause
6  */
7 
8 #include <openssl/sha.h>
9 
10 #include "fido.h"
11 #include "fido/es256.h"
12 
13 #define LARGEBLOB_DIGEST_LENGTH	16
14 #define LARGEBLOB_NONCE_LENGTH	12
15 #define LARGEBLOB_TAG_LENGTH	16
16 
17 typedef struct largeblob {
18 	size_t origsiz;
19 	fido_blob_t ciphertext;
20 	fido_blob_t nonce;
21 } largeblob_t;
22 
23 static largeblob_t *
24 largeblob_new(void)
25 {
26 	return calloc(1, sizeof(largeblob_t));
27 }
28 
29 static void
30 largeblob_reset(largeblob_t *blob)
31 {
32 	fido_blob_reset(&blob->ciphertext);
33 	fido_blob_reset(&blob->nonce);
34 	blob->origsiz = 0;
35 }
36 
37 static void
38 largeblob_free(largeblob_t **blob_ptr)
39 {
40 	largeblob_t *blob;
41 
42 	if (blob_ptr == NULL || (blob = *blob_ptr) == NULL)
43 		return;
44 	largeblob_reset(blob);
45 	free(blob);
46 	*blob_ptr = NULL;
47 }
48 
49 static int
50 largeblob_aad(fido_blob_t *aad, uint64_t size)
51 {
52 	uint8_t buf[4 + sizeof(uint64_t)];
53 
54 	buf[0] = 0x62; /* b */
55 	buf[1] = 0x6c; /* l */
56 	buf[2] = 0x6f; /* o */
57 	buf[3] = 0x62; /* b */
58 	size = htole64(size);
59 	memcpy(&buf[4], &size, sizeof(uint64_t));
60 
61 	return fido_blob_set(aad, buf, sizeof(buf));
62 }
63 
64 static fido_blob_t *
65 largeblob_decrypt(const largeblob_t *blob, const fido_blob_t *key)
66 {
67 	fido_blob_t *plaintext = NULL, *aad = NULL;
68 	int ok = -1;
69 
70 	if ((plaintext = fido_blob_new()) == NULL ||
71 	    (aad = fido_blob_new()) == NULL) {
72 		fido_log_debug("%s: fido_blob_new", __func__);
73 		goto fail;
74 	}
75 	if (largeblob_aad(aad, blob->origsiz) < 0) {
76 		fido_log_debug("%s: largeblob_aad", __func__);
77 		goto fail;
78 	}
79 	if (aes256_gcm_dec(key, &blob->nonce, aad, &blob->ciphertext,
80 	    plaintext) < 0) {
81 		fido_log_debug("%s: aes256_gcm_dec", __func__);
82 		goto fail;
83 	}
84 
85 	ok = 0;
86 fail:
87 	fido_blob_free(&aad);
88 
89 	if (ok < 0)
90 		fido_blob_free(&plaintext);
91 
92 	return plaintext;
93 }
94 
95 static int
96 largeblob_get_nonce(largeblob_t *blob)
97 {
98 	uint8_t buf[LARGEBLOB_NONCE_LENGTH];
99 	int ok = -1;
100 
101 	if (fido_get_random(buf, sizeof(buf)) < 0) {
102 		fido_log_debug("%s: fido_get_random", __func__);
103 		goto fail;
104 	}
105 	if (fido_blob_set(&blob->nonce, buf, sizeof(buf)) < 0) {
106 		fido_log_debug("%s: fido_blob_set", __func__);
107 		goto fail;
108 	}
109 
110 	ok = 0;
111 fail:
112 	explicit_bzero(buf, sizeof(buf));
113 
114 	return ok;
115 }
116 
117 static int
118 largeblob_seal(largeblob_t *blob, const fido_blob_t *body,
119     const fido_blob_t *key)
120 {
121 	fido_blob_t *plaintext = NULL, *aad = NULL;
122 	int ok = -1;
123 
124 	if ((plaintext = fido_blob_new()) == NULL ||
125 	    (aad = fido_blob_new()) == NULL) {
126 		fido_log_debug("%s: fido_blob_new", __func__);
127 		goto fail;
128 	}
129 	if (fido_compress(plaintext, body) != FIDO_OK) {
130 		fido_log_debug("%s: fido_compress", __func__);
131 		goto fail;
132 	}
133 	if (largeblob_aad(aad, body->len) < 0) {
134 		fido_log_debug("%s: largeblob_aad", __func__);
135 		goto fail;
136 	}
137 	if (largeblob_get_nonce(blob) < 0) {
138 		fido_log_debug("%s: largeblob_get_nonce", __func__);
139 		goto fail;
140 	}
141 	if (aes256_gcm_enc(key, &blob->nonce, aad, plaintext,
142 	    &blob->ciphertext) < 0) {
143 		fido_log_debug("%s: aes256_gcm_enc", __func__);
144 		goto fail;
145 	}
146 	blob->origsiz = body->len;
147 
148 	ok = 0;
149 fail:
150 	fido_blob_free(&plaintext);
151 	fido_blob_free(&aad);
152 
153 	return ok;
154 }
155 
156 static int
157 largeblob_get_tx(fido_dev_t *dev, size_t offset, size_t count, int *ms)
158 {
159 	fido_blob_t f;
160 	cbor_item_t *argv[3];
161 	int r;
162 
163 	memset(argv, 0, sizeof(argv));
164 	memset(&f, 0, sizeof(f));
165 
166 	if ((argv[0] = cbor_build_uint(count)) == NULL ||
167 	    (argv[2] = cbor_build_uint(offset)) == NULL) {
168 		fido_log_debug("%s: cbor encode", __func__);
169 		r = FIDO_ERR_INTERNAL;
170 		goto fail;
171 	}
172 	if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 ||
173 	    fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
174 		fido_log_debug("%s: fido_tx", __func__);
175 		r = FIDO_ERR_TX;
176 		goto fail;
177 	}
178 
179 	r = FIDO_OK;
180 fail:
181 	cbor_vector_free(argv, nitems(argv));
182 	free(f.ptr);
183 
184 	return r;
185 }
186 
187 static int
188 parse_largeblob_reply(const cbor_item_t *key, const cbor_item_t *val,
189     void *arg)
190 {
191 	if (cbor_isa_uint(key) == false ||
192 	    cbor_int_get_width(key) != CBOR_INT_8 ||
193 	    cbor_get_uint8(key) != 1) {
194 		fido_log_debug("%s: cbor type", __func__);
195 		return 0; /* ignore */
196 	}
197 
198 	return fido_blob_decode(val, arg);
199 }
200 
201 static int
202 largeblob_get_rx(fido_dev_t *dev, fido_blob_t **chunk, int *ms)
203 {
204 	unsigned char *msg;
205 	int msglen, r;
206 
207 	*chunk = NULL;
208 	if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
209 		r = FIDO_ERR_INTERNAL;
210 		goto out;
211 	}
212 	if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
213 		fido_log_debug("%s: fido_rx", __func__);
214 		r = FIDO_ERR_RX;
215 		goto out;
216 	}
217 	if ((*chunk = fido_blob_new()) == NULL) {
218 		fido_log_debug("%s: fido_blob_new", __func__);
219 		r = FIDO_ERR_INTERNAL;
220 		goto out;
221 	}
222 	if ((r = cbor_parse_reply(msg, (size_t)msglen, *chunk,
223 	    parse_largeblob_reply)) != FIDO_OK) {
224 		fido_log_debug("%s: parse_largeblob_reply", __func__);
225 		goto out;
226 	}
227 
228 	r = FIDO_OK;
229 out:
230 	if (r != FIDO_OK)
231 		fido_blob_free(chunk);
232 
233 	freezero(msg, FIDO_MAXMSG);
234 
235 	return r;
236 }
237 
238 static cbor_item_t *
239 largeblob_array_load(const uint8_t *ptr, size_t len)
240 {
241 	struct cbor_load_result cbor;
242 	cbor_item_t *item;
243 
244 	if (len < LARGEBLOB_DIGEST_LENGTH) {
245 		fido_log_debug("%s: len", __func__);
246 		return NULL;
247 	}
248 	len -= LARGEBLOB_DIGEST_LENGTH;
249 	if ((item = cbor_load(ptr, len, &cbor)) == NULL) {
250 		fido_log_debug("%s: cbor_load", __func__);
251 		return NULL;
252 	}
253 	if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) {
254 		fido_log_debug("%s: cbor type", __func__);
255 		cbor_decref(&item);
256 		return NULL;
257 	}
258 
259 	return item;
260 }
261 
262 static size_t
263 get_chunklen(fido_dev_t *dev)
264 {
265 	uint64_t maxchunklen;
266 
267 	if ((maxchunklen = fido_dev_maxmsgsize(dev)) > SIZE_MAX)
268 		maxchunklen = SIZE_MAX;
269 	if (maxchunklen > FIDO_MAXMSG)
270 		maxchunklen = FIDO_MAXMSG;
271 	maxchunklen = maxchunklen > 64 ? maxchunklen - 64 : 0;
272 
273 	return (size_t)maxchunklen;
274 }
275 
276 static int
277 largeblob_do_decode(const cbor_item_t *key, const cbor_item_t *val, void *arg)
278 {
279 	largeblob_t *blob = arg;
280 	uint64_t origsiz;
281 
282 	if (cbor_isa_uint(key) == false ||
283 	    cbor_int_get_width(key) != CBOR_INT_8) {
284 		fido_log_debug("%s: cbor type", __func__);
285 		return 0; /* ignore */
286 	}
287 
288 	switch (cbor_get_uint8(key)) {
289 	case 1: /* ciphertext */
290 		if (fido_blob_decode(val, &blob->ciphertext) < 0 ||
291 		    blob->ciphertext.len < LARGEBLOB_TAG_LENGTH)
292 			return -1;
293 		return 0;
294 	case 2: /* nonce */
295 		if (fido_blob_decode(val, &blob->nonce) < 0 ||
296 		    blob->nonce.len != LARGEBLOB_NONCE_LENGTH)
297 			return -1;
298 		return 0;
299 	case 3: /* origSize */
300 		if (!cbor_isa_uint(val) ||
301 		    (origsiz = cbor_get_int(val)) > SIZE_MAX)
302 			return -1;
303 		blob->origsiz = (size_t)origsiz;
304 		return 0;
305 	default: /* ignore */
306 		fido_log_debug("%s: cbor type", __func__);
307 		return 0;
308 	}
309 }
310 
311 static int
312 largeblob_decode(largeblob_t *blob, const cbor_item_t *item)
313 {
314 	if (!cbor_isa_map(item) || !cbor_map_is_definite(item)) {
315 		fido_log_debug("%s: cbor type", __func__);
316 		return -1;
317 	}
318 	if (cbor_map_iter(item, blob, largeblob_do_decode) < 0) {
319 		fido_log_debug("%s: cbor_map_iter", __func__);
320 		return -1;
321 	}
322 	if (fido_blob_is_empty(&blob->ciphertext) ||
323 	    fido_blob_is_empty(&blob->nonce) || blob->origsiz == 0) {
324 		fido_log_debug("%s: incomplete blob", __func__);
325 		return -1;
326 	}
327 
328 	return 0;
329 }
330 
331 static cbor_item_t *
332 largeblob_encode(const fido_blob_t *body, const fido_blob_t *key)
333 {
334 	largeblob_t *blob;
335 	cbor_item_t *argv[3], *item = NULL;
336 
337 	memset(argv, 0, sizeof(argv));
338 	if ((blob = largeblob_new()) == NULL ||
339 	    largeblob_seal(blob, body, key) < 0) {
340 		fido_log_debug("%s: largeblob_seal", __func__);
341 		goto fail;
342 	}
343 	if ((argv[0] = fido_blob_encode(&blob->ciphertext)) == NULL ||
344 	    (argv[1] = fido_blob_encode(&blob->nonce)) == NULL ||
345 	    (argv[2] = cbor_build_uint(blob->origsiz)) == NULL) {
346 		fido_log_debug("%s: cbor encode", __func__);
347 		goto fail;
348 	}
349 	item = cbor_flatten_vector(argv, nitems(argv));
350 fail:
351 	cbor_vector_free(argv, nitems(argv));
352 	largeblob_free(&blob);
353 
354 	return item;
355 }
356 
357 static int
358 largeblob_array_lookup(fido_blob_t *out, size_t *idx, const cbor_item_t *item,
359     const fido_blob_t *key)
360 {
361 	cbor_item_t **v;
362 	fido_blob_t *plaintext = NULL;
363 	largeblob_t blob;
364 	int r;
365 
366 	memset(&blob, 0, sizeof(blob));
367 	if (idx != NULL)
368 		*idx = 0;
369 	if ((v = cbor_array_handle(item)) == NULL)
370 		return FIDO_ERR_INVALID_ARGUMENT;
371 	for (size_t i = 0; i < cbor_array_size(item); i++) {
372 		if (largeblob_decode(&blob, v[i]) < 0 ||
373 		    (plaintext = largeblob_decrypt(&blob, key)) == NULL) {
374 			fido_log_debug("%s: largeblob_decode", __func__);
375 			largeblob_reset(&blob);
376 			continue;
377 		}
378 		if (idx != NULL)
379 			*idx = i;
380 		break;
381 	}
382 	if (plaintext == NULL) {
383 		fido_log_debug("%s: not found", __func__);
384 		return FIDO_ERR_NOTFOUND;
385 	}
386 	if (out != NULL)
387 		r = fido_uncompress(out, plaintext, blob.origsiz);
388 	else
389 		r = FIDO_OK;
390 
391 	fido_blob_free(&plaintext);
392 	largeblob_reset(&blob);
393 
394 	return r;
395 }
396 
397 static int
398 largeblob_array_digest(u_char out[LARGEBLOB_DIGEST_LENGTH], const u_char *data,
399     size_t len)
400 {
401 	u_char dgst[SHA256_DIGEST_LENGTH];
402 
403 	if (data == NULL || len == 0)
404 		return -1;
405 	if (SHA256(data, len, dgst) != dgst)
406 		return -1;
407 	memcpy(out, dgst, LARGEBLOB_DIGEST_LENGTH);
408 
409 	return 0;
410 }
411 
412 static int
413 largeblob_array_check(const fido_blob_t *array)
414 {
415 	u_char expected_hash[LARGEBLOB_DIGEST_LENGTH];
416 	size_t body_len;
417 
418 	fido_log_xxd(array->ptr, array->len, __func__);
419 	if (array->len < sizeof(expected_hash)) {
420 		fido_log_debug("%s: len %zu", __func__, array->len);
421 		return -1;
422 	}
423 	body_len = array->len - sizeof(expected_hash);
424 	if (largeblob_array_digest(expected_hash, array->ptr, body_len) < 0) {
425 		fido_log_debug("%s: largeblob_array_digest", __func__);
426 		return -1;
427 	}
428 
429 	return timingsafe_bcmp(expected_hash, array->ptr + body_len,
430 	    sizeof(expected_hash));
431 }
432 
433 static int
434 largeblob_get_array(fido_dev_t *dev, cbor_item_t **item, int *ms)
435 {
436 	fido_blob_t *array, *chunk = NULL;
437 	size_t n;
438 	int r;
439 
440 	*item = NULL;
441 	if ((n = get_chunklen(dev)) == 0)
442 		return FIDO_ERR_INVALID_ARGUMENT;
443 	if ((array = fido_blob_new()) == NULL)
444 		return FIDO_ERR_INTERNAL;
445 	do {
446 		fido_blob_free(&chunk);
447 		if ((r = largeblob_get_tx(dev, array->len, n, ms)) != FIDO_OK ||
448 		    (r = largeblob_get_rx(dev, &chunk, ms)) != FIDO_OK) {
449 			fido_log_debug("%s: largeblob_get_wait %zu/%zu",
450 			    __func__, array->len, n);
451 			goto fail;
452 		}
453 		if (fido_blob_append(array, chunk->ptr, chunk->len) < 0) {
454 			fido_log_debug("%s: fido_blob_append", __func__);
455 			r = FIDO_ERR_INTERNAL;
456 			goto fail;
457 		}
458 	} while (chunk->len == n);
459 
460 	if (largeblob_array_check(array) != 0)
461 		*item = cbor_new_definite_array(0); /* per spec */
462 	else
463 		*item = largeblob_array_load(array->ptr, array->len);
464 	if (*item == NULL)
465 		r = FIDO_ERR_INTERNAL;
466 	else
467 		r = FIDO_OK;
468 fail:
469 	fido_blob_free(&array);
470 	fido_blob_free(&chunk);
471 
472 	return r;
473 }
474 
475 static int
476 prepare_hmac(size_t offset, const u_char *data, size_t len, fido_blob_t *hmac)
477 {
478 	uint8_t buf[32 + 2 + sizeof(uint32_t) + SHA256_DIGEST_LENGTH];
479 	uint32_t u32_offset;
480 
481 	if (data == NULL || len == 0) {
482 		fido_log_debug("%s: invalid data=%p, len=%zu", __func__,
483 		    (const void *)data, len);
484 		return -1;
485 	}
486 	if (offset > UINT32_MAX) {
487 		fido_log_debug("%s: invalid offset=%zu", __func__, offset);
488 		return -1;
489 	}
490 
491 	memset(buf, 0xff, 32);
492 	buf[32] = CTAP_CBOR_LARGEBLOB;
493 	buf[33] = 0x00;
494 	u32_offset = htole32((uint32_t)offset);
495 	memcpy(&buf[34], &u32_offset, sizeof(uint32_t));
496 	if (SHA256(data, len, &buf[38]) != &buf[38]) {
497 		fido_log_debug("%s: SHA256", __func__);
498 		return -1;
499 	}
500 
501 	return fido_blob_set(hmac, buf, sizeof(buf));
502 }
503 
504 static int
505 largeblob_set_tx(fido_dev_t *dev, const fido_blob_t *token, const u_char *chunk,
506     size_t chunk_len, size_t offset, size_t totalsiz, int *ms)
507 {
508 	fido_blob_t *hmac = NULL, f;
509 	cbor_item_t *argv[6];
510 	int r;
511 
512 	memset(argv, 0, sizeof(argv));
513 	memset(&f, 0, sizeof(f));
514 
515 	if ((argv[1] = cbor_build_bytestring(chunk, chunk_len)) == NULL ||
516 	    (argv[2] = cbor_build_uint(offset)) == NULL ||
517 	    (offset == 0 && (argv[3] = cbor_build_uint(totalsiz)) == NULL)) {
518 		fido_log_debug("%s: cbor encode", __func__);
519 		r = FIDO_ERR_INTERNAL;
520 		goto fail;
521 	}
522 	if (token != NULL) {
523 		if ((hmac = fido_blob_new()) == NULL ||
524 		    prepare_hmac(offset, chunk, chunk_len, hmac) < 0 ||
525 		    (argv[4] = cbor_encode_pin_auth(dev, token, hmac)) == NULL ||
526 		    (argv[5] = cbor_encode_pin_opt(dev)) == NULL) {
527 			fido_log_debug("%s: cbor_encode_pin_auth", __func__);
528 			r = FIDO_ERR_INTERNAL;
529 			goto fail;
530 		}
531 	}
532 	if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 ||
533 	    fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
534 		fido_log_debug("%s: fido_tx", __func__);
535 		r = FIDO_ERR_TX;
536 		goto fail;
537 	}
538 
539 	r = FIDO_OK;
540 fail:
541 	cbor_vector_free(argv, nitems(argv));
542 	fido_blob_free(&hmac);
543 	free(f.ptr);
544 
545 	return r;
546 }
547 
548 static int
549 largeblob_get_uv_token(fido_dev_t *dev, const char *pin, fido_blob_t **token,
550     int *ms)
551 {
552 	es256_pk_t *pk = NULL;
553 	fido_blob_t *ecdh = NULL;
554 	int r;
555 
556 	if ((*token = fido_blob_new()) == NULL)
557 		return FIDO_ERR_INTERNAL;
558 	if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
559 		fido_log_debug("%s: fido_do_ecdh", __func__);
560 		goto fail;
561 	}
562 	if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_LARGEBLOB, pin, ecdh, pk,
563 	    NULL, *token, ms)) != FIDO_OK) {
564 		fido_log_debug("%s: fido_dev_get_uv_token", __func__);
565 		goto fail;
566 	}
567 
568 	r = FIDO_OK;
569 fail:
570 	if (r != FIDO_OK)
571 		fido_blob_free(token);
572 
573 	fido_blob_free(&ecdh);
574 	es256_pk_free(&pk);
575 
576 	return r;
577 }
578 
579 static int
580 largeblob_set_array(fido_dev_t *dev, const cbor_item_t *item, const char *pin,
581     int *ms)
582 {
583 	unsigned char dgst[SHA256_DIGEST_LENGTH];
584 	fido_blob_t cbor, *token = NULL;
585 	size_t chunklen, maxchunklen, totalsize;
586 	int r;
587 
588 	memset(&cbor, 0, sizeof(cbor));
589 
590 	if ((maxchunklen = get_chunklen(dev)) == 0) {
591 		fido_log_debug("%s: maxchunklen=%zu", __func__, maxchunklen);
592 		r = FIDO_ERR_INVALID_ARGUMENT;
593 		goto fail;
594 	}
595 	if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) {
596 		fido_log_debug("%s: cbor type", __func__);
597 		r = FIDO_ERR_INVALID_ARGUMENT;
598 		goto fail;
599 	}
600 	if ((fido_blob_serialise(&cbor, item)) < 0) {
601 		fido_log_debug("%s: fido_blob_serialise", __func__);
602 		r = FIDO_ERR_INTERNAL;
603 		goto fail;
604 	}
605 	if (cbor.len > SIZE_MAX - sizeof(dgst)) {
606 		fido_log_debug("%s: cbor.len=%zu", __func__, cbor.len);
607 		r = FIDO_ERR_INVALID_ARGUMENT;
608 		goto fail;
609 	}
610 	if (SHA256(cbor.ptr, cbor.len, dgst) != dgst) {
611 		fido_log_debug("%s: SHA256", __func__);
612 		r = FIDO_ERR_INTERNAL;
613 		goto fail;
614 	}
615 	totalsize = cbor.len + sizeof(dgst) - 16; /* the first 16 bytes only */
616 	if (pin != NULL || fido_dev_supports_permissions(dev)) {
617 		if ((r = largeblob_get_uv_token(dev, pin, &token,
618 		    ms)) != FIDO_OK) {
619 			fido_log_debug("%s: largeblob_get_uv_token", __func__);
620 			goto fail;
621 		}
622 	}
623 	for (size_t offset = 0; offset < cbor.len; offset += chunklen) {
624 		if ((chunklen = cbor.len - offset) > maxchunklen)
625 			chunklen = maxchunklen;
626 		if ((r = largeblob_set_tx(dev, token, cbor.ptr + offset,
627 		    chunklen, offset, totalsize, ms)) != FIDO_OK ||
628 		    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
629 			fido_log_debug("%s: body", __func__);
630 			goto fail;
631 		}
632 	}
633 	if ((r = largeblob_set_tx(dev, token, dgst, sizeof(dgst) - 16, cbor.len,
634 	    totalsize, ms)) != FIDO_OK ||
635 	    (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
636 		fido_log_debug("%s: dgst", __func__);
637 		goto fail;
638 	}
639 
640 	r = FIDO_OK;
641 fail:
642 	fido_blob_free(&token);
643 	fido_blob_reset(&cbor);
644 
645 	return r;
646 }
647 
648 static int
649 largeblob_add(fido_dev_t *dev, const fido_blob_t *key, cbor_item_t *item,
650     const char *pin, int *ms)
651 {
652 	cbor_item_t *array = NULL;
653 	size_t idx;
654 	int r;
655 
656 	if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) {
657 		fido_log_debug("%s: largeblob_get_array", __func__);
658 		goto fail;
659 	}
660 
661 	switch (r = largeblob_array_lookup(NULL, &idx, array, key)) {
662 	case FIDO_OK:
663 		if (!cbor_array_replace(array, idx, item)) {
664 			r = FIDO_ERR_INTERNAL;
665 			goto fail;
666 		}
667 		break;
668 	case FIDO_ERR_NOTFOUND:
669 		if (cbor_array_append(&array, item) < 0) {
670 			r = FIDO_ERR_INTERNAL;
671 			goto fail;
672 		}
673 		break;
674 	default:
675 		fido_log_debug("%s: largeblob_array_lookup", __func__);
676 		goto fail;
677 	}
678 
679 	if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) {
680 		fido_log_debug("%s: largeblob_set_array", __func__);
681 		goto fail;
682 	}
683 
684 	r = FIDO_OK;
685 fail:
686 	if (array != NULL)
687 		cbor_decref(&array);
688 
689 	return r;
690 }
691 
692 static int
693 largeblob_drop(fido_dev_t *dev, const fido_blob_t *key, const char *pin,
694     int *ms)
695 {
696 	cbor_item_t *array = NULL;
697 	size_t idx;
698 	int r;
699 
700 	if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) {
701 		fido_log_debug("%s: largeblob_get_array", __func__);
702 		goto fail;
703 	}
704 	if ((r = largeblob_array_lookup(NULL, &idx, array, key)) != FIDO_OK) {
705 		fido_log_debug("%s: largeblob_array_lookup", __func__);
706 		goto fail;
707 	}
708 	if (cbor_array_drop(&array, idx) < 0) {
709 		fido_log_debug("%s: cbor_array_drop", __func__);
710 		r = FIDO_ERR_INTERNAL;
711 		goto fail;
712 	}
713 	if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) {
714 		fido_log_debug("%s: largeblob_set_array", __func__);
715 		goto fail;
716 	}
717 
718 	r = FIDO_OK;
719 fail:
720 	if (array != NULL)
721 		cbor_decref(&array);
722 
723 	return r;
724 }
725 
726 int
727 fido_dev_largeblob_get(fido_dev_t *dev, const unsigned char *key_ptr,
728     size_t key_len, unsigned char **blob_ptr, size_t *blob_len)
729 {
730 	cbor_item_t *item = NULL;
731 	fido_blob_t key, body;
732 	int ms = dev->timeout_ms;
733 	int r;
734 
735 	memset(&key, 0, sizeof(key));
736 	memset(&body, 0, sizeof(body));
737 
738 	if (key_len != 32) {
739 		fido_log_debug("%s: invalid key len %zu", __func__, key_len);
740 		return FIDO_ERR_INVALID_ARGUMENT;
741 	}
742 	if (blob_ptr == NULL || blob_len == NULL) {
743 		fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%p", __func__,
744 		    (const void *)blob_ptr, (const void *)blob_len);
745 		return FIDO_ERR_INVALID_ARGUMENT;
746 	}
747 	*blob_ptr = NULL;
748 	*blob_len = 0;
749 	if (fido_blob_set(&key, key_ptr, key_len) < 0) {
750 		fido_log_debug("%s: fido_blob_set", __func__);
751 		return FIDO_ERR_INTERNAL;
752 	}
753 	if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) {
754 		fido_log_debug("%s: largeblob_get_array", __func__);
755 		goto fail;
756 	}
757 	if ((r = largeblob_array_lookup(&body, NULL, item, &key)) != FIDO_OK)
758 		fido_log_debug("%s: largeblob_array_lookup", __func__);
759 	else {
760 		*blob_ptr = body.ptr;
761 		*blob_len = body.len;
762 	}
763 fail:
764 	if (item != NULL)
765 		cbor_decref(&item);
766 
767 	fido_blob_reset(&key);
768 
769 	return r;
770 }
771 
772 int
773 fido_dev_largeblob_set(fido_dev_t *dev, const unsigned char *key_ptr,
774     size_t key_len, const unsigned char *blob_ptr, size_t blob_len,
775     const char *pin)
776 {
777 	cbor_item_t *item = NULL;
778 	fido_blob_t key, body;
779 	int ms = dev->timeout_ms;
780 	int r;
781 
782 	memset(&key, 0, sizeof(key));
783 	memset(&body, 0, sizeof(body));
784 
785 	if (key_len != 32) {
786 		fido_log_debug("%s: invalid key len %zu", __func__, key_len);
787 		return FIDO_ERR_INVALID_ARGUMENT;
788 	}
789 	if (blob_ptr == NULL || blob_len == 0) {
790 		fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%zu", __func__,
791 		    (const void *)blob_ptr, blob_len);
792 		return FIDO_ERR_INVALID_ARGUMENT;
793 	}
794 	if (fido_blob_set(&key, key_ptr, key_len) < 0 ||
795 	    fido_blob_set(&body, blob_ptr, blob_len) < 0) {
796 		fido_log_debug("%s: fido_blob_set", __func__);
797 		r = FIDO_ERR_INTERNAL;
798 		goto fail;
799 	}
800 	if ((item = largeblob_encode(&body, &key)) == NULL) {
801 		fido_log_debug("%s: largeblob_encode", __func__);
802 		r = FIDO_ERR_INTERNAL;
803 		goto fail;
804 	}
805 	if ((r = largeblob_add(dev, &key, item, pin, &ms)) != FIDO_OK)
806 		fido_log_debug("%s: largeblob_add", __func__);
807 fail:
808 	if (item != NULL)
809 		cbor_decref(&item);
810 
811 	fido_blob_reset(&key);
812 	fido_blob_reset(&body);
813 
814 	return r;
815 }
816 
817 int
818 fido_dev_largeblob_remove(fido_dev_t *dev, const unsigned char *key_ptr,
819     size_t key_len, const char *pin)
820 {
821 	fido_blob_t key;
822 	int ms = dev->timeout_ms;
823 	int r;
824 
825 	memset(&key, 0, sizeof(key));
826 
827 	if (key_len != 32) {
828 		fido_log_debug("%s: invalid key len %zu", __func__, key_len);
829 		return FIDO_ERR_INVALID_ARGUMENT;
830 	}
831 	if (fido_blob_set(&key, key_ptr, key_len) < 0) {
832 		fido_log_debug("%s: fido_blob_set", __func__);
833 		return FIDO_ERR_INTERNAL;
834 	}
835 	if ((r = largeblob_drop(dev, &key, pin, &ms)) != FIDO_OK)
836 		fido_log_debug("%s: largeblob_drop", __func__);
837 
838 	fido_blob_reset(&key);
839 
840 	return r;
841 }
842 
843 int
844 fido_dev_largeblob_get_array(fido_dev_t *dev, unsigned char **cbor_ptr,
845     size_t *cbor_len)
846 {
847 	cbor_item_t *item = NULL;
848 	fido_blob_t cbor;
849 	int ms = dev->timeout_ms;
850 	int r;
851 
852 	memset(&cbor, 0, sizeof(cbor));
853 
854 	if (cbor_ptr == NULL || cbor_len == NULL) {
855 		fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%p", __func__,
856 		    (const void *)cbor_ptr, (const void *)cbor_len);
857 		return FIDO_ERR_INVALID_ARGUMENT;
858 	}
859 	*cbor_ptr = NULL;
860 	*cbor_len = 0;
861 	if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) {
862 		fido_log_debug("%s: largeblob_get_array", __func__);
863 		return r;
864 	}
865 	if (fido_blob_serialise(&cbor, item) < 0) {
866 		fido_log_debug("%s: fido_blob_serialise", __func__);
867 		r = FIDO_ERR_INTERNAL;
868 	} else {
869 		*cbor_ptr = cbor.ptr;
870 		*cbor_len = cbor.len;
871 	}
872 
873 	cbor_decref(&item);
874 
875 	return r;
876 }
877 
878 int
879 fido_dev_largeblob_set_array(fido_dev_t *dev, const unsigned char *cbor_ptr,
880     size_t cbor_len, const char *pin)
881 {
882 	cbor_item_t *item = NULL;
883 	struct cbor_load_result cbor_result;
884 	int ms = dev->timeout_ms;
885 	int r;
886 
887 	if (cbor_ptr == NULL || cbor_len == 0) {
888 		fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%zu", __func__,
889 		    (const void *)cbor_ptr, cbor_len);
890 		return FIDO_ERR_INVALID_ARGUMENT;
891 	}
892 	if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) {
893 		fido_log_debug("%s: cbor_load", __func__);
894 		return FIDO_ERR_INVALID_ARGUMENT;
895 	}
896 	if ((r = largeblob_set_array(dev, item, pin, &ms)) != FIDO_OK)
897 		fido_log_debug("%s: largeblob_set_array", __func__);
898 
899 	cbor_decref(&item);
900 
901 	return r;
902 }
903