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
30struct rkmap {
31	fido_credman_rp_t  *rp; /* known rps */
32	fido_credman_rk_t **rk; /* rk per rp */
33};
34
35static void
36free_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
46static int
47map_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;
90out:
91	freezero(pin, PINBUF_LEN);
92
93	return ok;
94}
95
96static int
97lookup_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;
158out:
159	fido_credman_rk_free(&rk);
160
161	return ok;
162}
163
164static int
165load_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
196int
197blob_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 */
225out:
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
236int
237blob_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 */
260out:
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
271int
272blob_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 */
297out:
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
307static int
308try_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;
341fail:
342	if (inflateEnd(&zs) != Z_OK)
343		ok = -1;
344
345	freezero(out.ptr, out.len);
346
347	return ok;
348}
349
350static int
351decompress(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
358static int
359decode(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;
407out:
408	freezero(plaintext.ptr, plaintext.len);
409
410	if (ctx != NULL)
411		EVP_CIPHER_CTX_free(ctx);
412
413	return ok;
414}
415
416static const fido_cred_t *
417try_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
430static int
431decode_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
445static int
446decode_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
486static void
487print_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);
526out:
527	free(ciphertext.ptr);
528	free(nonce.ptr);
529	free(cred_id);
530}
531
532static cbor_item_t *
533get_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;
573out:
574	if (ok < 0 && item != NULL) {
575		cbor_decref(&item);
576		item = NULL;
577	}
578	free(cbor_ptr);
579
580	return item;
581}
582
583int
584blob_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 */
608out:
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