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