xref: /freebsd/contrib/libfido2/tools/largeblob.c (revision 18054d0220cfc8df9c9568c437bd6fbb59d53c3c)
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 <sys/types.h>
8 #include <sys/stat.h>
9 
10 #include <fido.h>
11 #include <fido/credman.h>
12 
13 #include <cbor.h>
14 #include <fcntl.h>
15 #include <limits.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #ifdef HAVE_UNISTD_H
20 #include <unistd.h>
21 #endif
22 #include <zlib.h>
23 
24 #include "../openbsd-compat/openbsd-compat.h"
25 #include "extern.h"
26 
27 struct rkmap {
28 	fido_credman_rp_t  *rp; /* known rps */
29 	fido_credman_rk_t **rk; /* rk per rp */
30 };
31 
32 static void
33 free_rkmap(struct rkmap *map)
34 {
35 	if (map->rp != NULL) {
36 		for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++)
37 			fido_credman_rk_free(&map->rk[i]);
38 		fido_credman_rp_free(&map->rp);
39 	}
40 	free(map->rk);
41 }
42 
43 static int
44 map_known_rps(fido_dev_t *dev, const char *path, struct rkmap *map)
45 {
46 	const char *rp_id;
47 	char *pin = NULL;
48 	size_t n;
49 	int r, ok = -1;
50 
51 	if ((map->rp = fido_credman_rp_new()) == NULL) {
52 		warnx("%s: fido_credman_rp_new", __func__);
53 		goto out;
54 	}
55 	if ((pin = get_pin(path)) == NULL)
56 		goto out;
57 	if ((r = fido_credman_get_dev_rp(dev, map->rp, pin)) != FIDO_OK) {
58 		warnx("fido_credman_get_dev_rp: %s", fido_strerr(r));
59 		goto out;
60 	}
61 	if ((n = fido_credman_rp_count(map->rp)) > UINT8_MAX) {
62 		warnx("%s: fido_credman_rp_count > UINT8_MAX", __func__);
63 		goto out;
64 	}
65 	if ((map->rk = calloc(n, sizeof(*map->rk))) == NULL) {
66 		warnx("%s: calloc", __func__);
67 		goto out;
68 	}
69 	for (size_t i = 0; i < n; i++) {
70 		if ((rp_id = fido_credman_rp_id(map->rp, i)) == NULL) {
71 			warnx("%s: fido_credman_rp_id %zu", __func__, i);
72 			goto out;
73 		}
74 		if ((map->rk[i] = fido_credman_rk_new()) == NULL) {
75 			warnx("%s: fido_credman_rk_new", __func__);
76 			goto out;
77 		}
78 		if ((r = fido_credman_get_dev_rk(dev, rp_id, map->rk[i],
79 		    pin)) != FIDO_OK) {
80 			warnx("%s: fido_credman_get_dev_rk %s: %s", __func__,
81 			    rp_id, fido_strerr(r));
82 			goto out;
83 		}
84 	}
85 
86 	ok = 0;
87 out:
88 	freezero(pin, PINBUF_LEN);
89 
90 	return ok;
91 }
92 
93 static int
94 lookup_key(const char *path, fido_dev_t *dev, const char *rp_id,
95     const struct blob *cred_id, char **pin, struct blob *key)
96 {
97 	fido_credman_rk_t *rk = NULL;
98 	const fido_cred_t *cred = NULL;
99 	size_t i, n;
100 	int r, ok = -1;
101 
102 	if ((rk = fido_credman_rk_new()) == NULL) {
103 		warnx("%s: fido_credman_rk_new", __func__);
104 		goto out;
105 	}
106 	if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin)) != FIDO_OK &&
107 	    *pin == NULL && should_retry_with_pin(dev, r)) {
108 		if ((*pin = get_pin(path)) == NULL)
109 			goto out;
110 		r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin);
111 	}
112 	if (r != FIDO_OK) {
113 		warnx("%s: fido_credman_get_dev_rk: %s", __func__,
114 		    fido_strerr(r));
115 		goto out;
116 	}
117 	if ((n = fido_credman_rk_count(rk)) == 0) {
118 		warnx("%s: rp id not found", __func__);
119 		goto out;
120 	}
121 	if (n == 1 && cred_id->len == 0) {
122 		/* use the credential we found */
123 		cred = fido_credman_rk(rk, 0);
124 	} else {
125 		if (cred_id->len == 0) {
126 			warnx("%s: multiple credentials found", __func__);
127 			goto out;
128 		}
129 		for (i = 0; i < n; i++) {
130 			const fido_cred_t *x = fido_credman_rk(rk, i);
131 			if (fido_cred_id_len(x) <= cred_id->len &&
132 			    !memcmp(fido_cred_id_ptr(x), cred_id->ptr,
133 			    fido_cred_id_len(x))) {
134 				cred = x;
135 				break;
136 			}
137 		}
138 	}
139 	if (cred == NULL) {
140 		warnx("%s: credential not found", __func__);
141 		goto out;
142 	}
143 	if (fido_cred_largeblob_key_ptr(cred) == NULL) {
144 		warnx("%s: no associated blob key", __func__);
145 		goto out;
146 	}
147 	key->len = fido_cred_largeblob_key_len(cred);
148 	if ((key->ptr = malloc(key->len)) == NULL) {
149 		warnx("%s: malloc", __func__);
150 		goto out;
151 	}
152 	memcpy(key->ptr, fido_cred_largeblob_key_ptr(cred), key->len);
153 
154 	ok = 0;
155 out:
156 	fido_credman_rk_free(&rk);
157 
158 	return ok;
159 }
160 
161 static int
162 load_key(const char *keyf, const char *cred_id64, const char *rp_id,
163     const char *path, fido_dev_t *dev, char **pin, struct blob *key)
164 {
165 	struct blob cred_id;
166 	FILE *fp;
167 	int r;
168 
169 	memset(&cred_id, 0, sizeof(cred_id));
170 
171 	if (keyf != NULL) {
172 		if (rp_id != NULL || cred_id64 != NULL)
173 			usage();
174 		fp = open_read(keyf);
175 		if ((r = base64_read(fp, key)) < 0)
176 			warnx("%s: base64_read %s", __func__, keyf);
177 		fclose(fp);
178 		return r;
179 	}
180 	if (rp_id == NULL)
181 		usage();
182 	if (cred_id64 != NULL && base64_decode(cred_id64, (void *)&cred_id.ptr,
183 	    &cred_id.len) < 0) {
184 		warnx("%s: base64_decode %s", __func__, cred_id64);
185 		return -1;
186 	}
187 	r = lookup_key(path, dev, rp_id, &cred_id, pin, key);
188 	free(cred_id.ptr);
189 
190 	return r;
191 }
192 
193 int
194 blob_set(const char *path, const char *keyf, const char *rp_id,
195     const char *cred_id64, const char *blobf)
196 {
197 	fido_dev_t *dev;
198 	struct blob key, blob;
199 	char *pin = NULL;
200 	int r, ok = 1;
201 
202 	dev = open_dev(path);
203 	memset(&key, 0, sizeof(key));
204 	memset(&blob, 0, sizeof(blob));
205 
206 	if (read_file(blobf, &blob.ptr, &blob.len) < 0 ||
207 	    load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
208 		goto out;
209 	if ((r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,
210 	    blob.len, pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {
211 		if ((pin = get_pin(path)) == NULL)
212 			goto out;
213 		r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,
214 		    blob.len, pin);
215 	}
216 	if (r != FIDO_OK) {
217 		warnx("fido_dev_largeblob_set: %s", fido_strerr(r));
218 		goto out;
219 	}
220 
221 	ok = 0; /* success */
222 out:
223 	freezero(key.ptr, key.len);
224 	freezero(blob.ptr, blob.len);
225 	freezero(pin, PINBUF_LEN);
226 
227 	fido_dev_close(dev);
228 	fido_dev_free(&dev);
229 
230 	exit(ok);
231 }
232 
233 int
234 blob_get(const char *path, const char *keyf, const char *rp_id,
235     const char *cred_id64, const char *blobf)
236 {
237 	fido_dev_t *dev;
238 	struct blob key, blob;
239 	char *pin = NULL;
240 	int r, ok = 1;
241 
242 	dev = open_dev(path);
243 	memset(&key, 0, sizeof(key));
244 	memset(&blob, 0, sizeof(blob));
245 
246 	if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
247 		goto out;
248 	if ((r = fido_dev_largeblob_get(dev, key.ptr, key.len, &blob.ptr,
249 	    &blob.len)) != FIDO_OK) {
250 		warnx("fido_dev_largeblob_get: %s", fido_strerr(r));
251 		goto out;
252 	}
253 	if (write_file(blobf, blob.ptr, blob.len) < 0)
254 		goto out;
255 
256 	ok = 0; /* success */
257 out:
258 	freezero(key.ptr, key.len);
259 	freezero(blob.ptr, blob.len);
260 	freezero(pin, PINBUF_LEN);
261 
262 	fido_dev_close(dev);
263 	fido_dev_free(&dev);
264 
265 	exit(ok);
266 }
267 
268 int
269 blob_delete(const char *path, const char *keyf, const char *rp_id,
270     const char *cred_id64)
271 {
272 	fido_dev_t *dev;
273 	struct blob key;
274 	char *pin = NULL;
275 	int r, ok = 1;
276 
277 	dev = open_dev(path);
278 	memset(&key, 0, sizeof(key));
279 
280 	if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
281 		goto out;
282 	if ((r = fido_dev_largeblob_remove(dev, key.ptr, key.len,
283 	    pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {
284 		if ((pin = get_pin(path)) == NULL)
285 			goto out;
286 		r = fido_dev_largeblob_remove(dev, key.ptr, key.len, pin);
287 	}
288 	if (r != FIDO_OK) {
289 		warnx("fido_dev_largeblob_remove: %s", fido_strerr(r));
290 		goto out;
291 	}
292 
293 	ok = 0; /* success */
294 out:
295 	freezero(key.ptr, key.len);
296 	freezero(pin, PINBUF_LEN);
297 
298 	fido_dev_close(dev);
299 	fido_dev_free(&dev);
300 
301 	exit(ok);
302 }
303 
304 static int
305 decompress(const struct blob *plaintext, uint64_t origsiz)
306 {
307 	struct blob inflated;
308 	u_long ilen, plen;
309 	int ok = -1;
310 
311 	memset(&inflated, 0, sizeof(inflated));
312 
313 	if (plaintext->len > ULONG_MAX)
314 		return -1;
315 	if (origsiz > ULONG_MAX || origsiz > SIZE_MAX)
316 		return -1;
317 	plen = (u_long)plaintext->len;
318 	ilen = (u_long)origsiz;
319 	inflated.len = (size_t)origsiz;
320 	if ((inflated.ptr = calloc(1, inflated.len)) == NULL)
321 		return -1;
322 	if (uncompress(inflated.ptr, &ilen, plaintext->ptr, plen) != Z_OK ||
323 	    ilen > SIZE_MAX || (size_t)ilen != (size_t)origsiz)
324 		goto out;
325 
326 	ok = 0; /* success */
327 out:
328 	freezero(inflated.ptr, inflated.len);
329 
330 	return ok;
331 }
332 
333 static int
334 decode(const struct blob *ciphertext, const struct blob *nonce,
335     uint64_t origsiz, const fido_cred_t *cred)
336 {
337 	uint8_t aad[4 + sizeof(uint64_t)];
338 	EVP_CIPHER_CTX *ctx = NULL;
339 	const EVP_CIPHER *cipher;
340 	struct blob plaintext;
341 	uint64_t tmp;
342 	int ok = -1;
343 
344 	memset(&plaintext, 0, sizeof(plaintext));
345 
346 	if (nonce->len != 12)
347 		return -1;
348 	if (cred == NULL ||
349 	    fido_cred_largeblob_key_ptr(cred) == NULL ||
350 	    fido_cred_largeblob_key_len(cred) != 32)
351 		return -1;
352 	if (ciphertext->len > UINT_MAX ||
353 	    ciphertext->len > SIZE_MAX - 16 ||
354 	    ciphertext->len < 16)
355 		return -1;
356 	plaintext.len = ciphertext->len - 16;
357 	if ((plaintext.ptr = calloc(1, plaintext.len)) == NULL)
358 		return -1;
359 	if ((ctx = EVP_CIPHER_CTX_new()) == NULL ||
360 	    (cipher = EVP_aes_256_gcm()) == NULL ||
361 	    EVP_CipherInit(ctx, cipher, fido_cred_largeblob_key_ptr(cred),
362 	    nonce->ptr, 0) == 0)
363 		goto out;
364 	if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
365 	    ciphertext->ptr + ciphertext->len - 16) == 0)
366 		goto out;
367 	aad[0] = 0x62; /* b */
368 	aad[1] = 0x6c; /* l */
369 	aad[2] = 0x6f; /* o */
370 	aad[3] = 0x62; /* b */
371 	tmp = htole64(origsiz);
372 	memcpy(&aad[4], &tmp, sizeof(uint64_t));
373 	if (EVP_Cipher(ctx, NULL, aad, (u_int)sizeof(aad)) < 0 ||
374 	    EVP_Cipher(ctx, plaintext.ptr, ciphertext->ptr,
375 	    (u_int)plaintext.len) < 0 ||
376 	    EVP_Cipher(ctx, NULL, NULL, 0) < 0)
377 		goto out;
378 	if (decompress(&plaintext, origsiz) < 0)
379 		goto out;
380 
381 	ok = 0;
382 out:
383 	freezero(plaintext.ptr, plaintext.len);
384 
385 	if (ctx != NULL)
386 		EVP_CIPHER_CTX_free(ctx);
387 
388 	return ok;
389 }
390 
391 static const fido_cred_t *
392 try_rp(const fido_credman_rk_t *rk, const struct blob *ciphertext,
393     const struct blob *nonce, uint64_t origsiz)
394 {
395 	const fido_cred_t *cred;
396 
397 	for (size_t i = 0; i < fido_credman_rk_count(rk); i++)
398 		if ((cred = fido_credman_rk(rk, i)) != NULL &&
399 		    decode(ciphertext, nonce, origsiz, cred) == 0)
400 			return cred;
401 
402 	return NULL;
403 }
404 
405 static int
406 decode_cbor_blob(struct blob *out, const cbor_item_t *item)
407 {
408 	if (out->ptr != NULL ||
409 	    cbor_isa_bytestring(item) == false ||
410 	    cbor_bytestring_is_definite(item) == false)
411 		return -1;
412 	out->len = cbor_bytestring_length(item);
413 	if ((out->ptr = malloc(out->len)) == NULL)
414 		return -1;
415 	memcpy(out->ptr, cbor_bytestring_handle(item), out->len);
416 
417 	return 0;
418 }
419 
420 static int
421 decode_blob_entry(const cbor_item_t *item, struct blob *ciphertext,
422     struct blob *nonce, uint64_t *origsiz)
423 {
424 	struct cbor_pair *v;
425 
426 	if (item == NULL)
427 		return -1;
428 	if (cbor_isa_map(item) == false ||
429 	    cbor_map_is_definite(item) == false ||
430 	    (v = cbor_map_handle(item)) == NULL)
431 		return -1;
432 	if (cbor_map_size(item) > UINT8_MAX)
433 		return -1;
434 
435 	for (size_t i = 0; i < cbor_map_size(item); i++) {
436 		if (cbor_isa_uint(v[i].key) == false ||
437 		    cbor_int_get_width(v[i].key) != CBOR_INT_8)
438 			continue; /* ignore */
439 		switch (cbor_get_uint8(v[i].key)) {
440 		case 1: /* ciphertext */
441 			if (decode_cbor_blob(ciphertext, v[i].value) < 0)
442 				return -1;
443 			break;
444 		case 2: /* nonce */
445 			if (decode_cbor_blob(nonce, v[i].value) < 0)
446 				return -1;
447 			break;
448 		case 3: /* origSize */
449 			if (*origsiz != 0 ||
450 			    cbor_isa_uint(v[i].value) == false ||
451 			    (*origsiz = cbor_get_int(v[i].value)) > SIZE_MAX)
452 				return -1;
453 		}
454 	}
455 	if (ciphertext->ptr == NULL || nonce->ptr == NULL || *origsiz == 0)
456 		return -1;
457 
458 	return 0;
459 }
460 
461 static void
462 print_blob_entry(size_t idx, const cbor_item_t *item, const struct rkmap *map)
463 {
464 	struct blob ciphertext, nonce;
465 	const fido_cred_t *cred = NULL;
466 	const char *rp_id = NULL;
467 	char *cred_id = NULL;
468 	uint64_t origsiz = 0;
469 
470 	memset(&ciphertext, 0, sizeof(ciphertext));
471 	memset(&nonce, 0, sizeof(nonce));
472 
473 	if (decode_blob_entry(item, &ciphertext, &nonce, &origsiz) < 0) {
474 		printf("%02zu: <skipped: bad cbor>\n", idx);
475 		goto out;
476 	}
477 	for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++) {
478 		if ((cred = try_rp(map->rk[i], &ciphertext, &nonce,
479 		    origsiz)) != NULL) {
480 			rp_id = fido_credman_rp_id(map->rp, i);
481 			break;
482 		}
483 	}
484 	if (cred == NULL) {
485 		if ((cred_id = strdup("<unknown>")) == NULL) {
486 			printf("%02zu: <skipped: strdup failed>\n", idx);
487 			goto out;
488 		}
489 	} else {
490 		if (base64_encode(fido_cred_id_ptr(cred),
491 		    fido_cred_id_len(cred), &cred_id) < 0) {
492 			printf("%02zu: <skipped: base64_encode failed>\n", idx);
493 			goto out;
494 		}
495 	}
496 	if (rp_id == NULL)
497 		rp_id = "<unknown>";
498 
499 	printf("%02zu: %4zu %4zu %s %s\n", idx, ciphertext.len,
500 	    (size_t)origsiz, cred_id, rp_id);
501 out:
502 	free(ciphertext.ptr);
503 	free(nonce.ptr);
504 	free(cred_id);
505 }
506 
507 static cbor_item_t *
508 get_cbor_array(fido_dev_t *dev)
509 {
510 	struct cbor_load_result cbor_result;
511 	cbor_item_t *item = NULL;
512 	u_char *cbor_ptr = NULL;
513 	size_t cbor_len;
514 	int r, ok = -1;
515 
516 	if ((r = fido_dev_largeblob_get_array(dev, &cbor_ptr,
517 	    &cbor_len)) != FIDO_OK) {
518 		warnx("%s: fido_dev_largeblob_get_array: %s", __func__,
519 		    fido_strerr(r));
520 		goto out;
521 	}
522 	if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) {
523 		warnx("%s: cbor_load", __func__);
524 		goto out;
525 	}
526 	if (cbor_result.read != cbor_len) {
527 		warnx("%s: cbor_result.read (%zu) != cbor_len (%zu)", __func__,
528 		    cbor_result.read, cbor_len);
529 		/* continue */
530 	}
531 	if (cbor_isa_array(item) == false ||
532 	    cbor_array_is_definite(item) == false) {
533 		warnx("%s: cbor type", __func__);
534 		goto out;
535 	}
536 	if (cbor_array_size(item) > UINT8_MAX) {
537 		warnx("%s: cbor_array_size > UINT8_MAX", __func__);
538 		goto out;
539 	}
540 	if (cbor_array_size(item) == 0) {
541 		ok = 0; /* nothing to do */
542 		goto out;
543 	}
544 
545 	printf("total map size: %zu byte%s\n", cbor_len, plural(cbor_len));
546 
547 	ok = 0;
548 out:
549 	if (ok < 0 && item != NULL) {
550 		cbor_decref(&item);
551 		item = NULL;
552 	}
553 	free(cbor_ptr);
554 
555 	return item;
556 }
557 
558 int
559 blob_list(const char *path)
560 {
561 	struct rkmap map;
562 	fido_dev_t *dev = NULL;
563 	cbor_item_t *item = NULL, **v;
564 	int ok = 1;
565 
566 	memset(&map, 0, sizeof(map));
567 	dev = open_dev(path);
568 	if (map_known_rps(dev, path, &map) < 0 ||
569 	    (item = get_cbor_array(dev)) == NULL)
570 		goto out;
571 	if (cbor_array_size(item) == 0) {
572 		ok = 0; /* nothing to do */
573 		goto out;
574 	}
575 	if ((v = cbor_array_handle(item)) == NULL) {
576 		warnx("%s: cbor_array_handle", __func__);
577 		goto out;
578 	}
579 	for (size_t i = 0; i < cbor_array_size(item); i++)
580 		print_blob_entry(i, v[i], &map);
581 
582 	ok = 0; /* success */
583 out:
584 	free_rkmap(&map);
585 
586 	if (item != NULL)
587 		cbor_decref(&item);
588 
589 	fido_dev_close(dev);
590 	fido_dev_free(&dev);
591 
592 	exit(ok);
593 }
594