Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/libfido2/tools/largeblob.c
39507 views
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
620