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