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