Home | History | Annotate | Line # | Download | only in tools
      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
     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
     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
     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
    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
    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
    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
    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
    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
    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
    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 *
    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
    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
    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
    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 *
    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
    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