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