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
free_rkmap(struct rkmap * map)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
map_known_rps(fido_dev_t * dev,const char * path,struct rkmap * map)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
lookup_key(const char * path,fido_dev_t * dev,const char * rp_id,const struct blob * cred_id,char ** pin,struct blob * key)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
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)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
blob_set(const char * path,const char * keyf,const char * rp_id,const char * cred_id64,const char * blobf)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
blob_get(const char * path,const char * keyf,const char * rp_id,const char * cred_id64,const char * blobf)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
blob_delete(const char * path,const char * keyf,const char * rp_id,const char * cred_id64)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
decompress(const struct blob * plaintext,uint64_t origsiz)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
decode(const struct blob * ciphertext,const struct blob * nonce,uint64_t origsiz,const fido_cred_t * cred)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 *
try_rp(const fido_credman_rk_t * rk,const struct blob * ciphertext,const struct blob * nonce,uint64_t origsiz)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
decode_cbor_blob(struct blob * out,const cbor_item_t * item)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
decode_blob_entry(const cbor_item_t * item,struct blob * ciphertext,struct blob * nonce,uint64_t * origsiz)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
print_blob_entry(size_t idx,const cbor_item_t * item,const struct rkmap * map)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 *
get_cbor_array(fido_dev_t * dev)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
blob_list(const char * path)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