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