195dbdf32Schristos /*
2*2d40c451Schristos * Copyright (c) 2020-2022 Yubico AB. All rights reserved.
395dbdf32Schristos * Use of this source code is governed by a BSD-style
495dbdf32Schristos * license that can be found in the LICENSE file.
5*2d40c451Schristos * SPDX-License-Identifier: BSD-2-Clause
695dbdf32Schristos */
795dbdf32Schristos
895dbdf32Schristos #include <sys/types.h>
995dbdf32Schristos #include <sys/stat.h>
1095dbdf32Schristos
1195dbdf32Schristos #include <fido.h>
1295dbdf32Schristos #include <fido/credman.h>
1395dbdf32Schristos
1495dbdf32Schristos #include <cbor.h>
1595dbdf32Schristos #include <fcntl.h>
1695dbdf32Schristos #include <limits.h>
1795dbdf32Schristos #include <stdio.h>
1895dbdf32Schristos #include <stdlib.h>
1995dbdf32Schristos #include <string.h>
2095dbdf32Schristos #ifdef HAVE_UNISTD_H
2195dbdf32Schristos #include <unistd.h>
2295dbdf32Schristos #endif
2395dbdf32Schristos #include <zlib.h>
2495dbdf32Schristos
2595dbdf32Schristos #include "../openbsd-compat/openbsd-compat.h"
2695dbdf32Schristos #include "extern.h"
2795dbdf32Schristos
28*2d40c451Schristos #define BOUND (1024UL * 1024UL)
29*2d40c451Schristos
3095dbdf32Schristos struct rkmap {
3195dbdf32Schristos fido_credman_rp_t *rp; /* known rps */
3295dbdf32Schristos fido_credman_rk_t **rk; /* rk per rp */
3395dbdf32Schristos };
3495dbdf32Schristos
3595dbdf32Schristos static void
free_rkmap(struct rkmap * map)3695dbdf32Schristos free_rkmap(struct rkmap *map)
3795dbdf32Schristos {
3895dbdf32Schristos if (map->rp != NULL) {
3995dbdf32Schristos for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++)
4095dbdf32Schristos fido_credman_rk_free(&map->rk[i]);
4195dbdf32Schristos fido_credman_rp_free(&map->rp);
4295dbdf32Schristos }
4395dbdf32Schristos free(map->rk);
4495dbdf32Schristos }
4595dbdf32Schristos
4695dbdf32Schristos static int
map_known_rps(fido_dev_t * dev,const char * path,struct rkmap * map)4795dbdf32Schristos map_known_rps(fido_dev_t *dev, const char *path, struct rkmap *map)
4895dbdf32Schristos {
4995dbdf32Schristos const char *rp_id;
5095dbdf32Schristos char *pin = NULL;
5195dbdf32Schristos size_t n;
5295dbdf32Schristos int r, ok = -1;
5395dbdf32Schristos
5495dbdf32Schristos if ((map->rp = fido_credman_rp_new()) == NULL) {
5595dbdf32Schristos warnx("%s: fido_credman_rp_new", __func__);
5695dbdf32Schristos goto out;
5795dbdf32Schristos }
5895dbdf32Schristos if ((pin = get_pin(path)) == NULL)
5995dbdf32Schristos goto out;
6095dbdf32Schristos if ((r = fido_credman_get_dev_rp(dev, map->rp, pin)) != FIDO_OK) {
6195dbdf32Schristos warnx("fido_credman_get_dev_rp: %s", fido_strerr(r));
6295dbdf32Schristos goto out;
6395dbdf32Schristos }
6495dbdf32Schristos if ((n = fido_credman_rp_count(map->rp)) > UINT8_MAX) {
6595dbdf32Schristos warnx("%s: fido_credman_rp_count > UINT8_MAX", __func__);
6695dbdf32Schristos goto out;
6795dbdf32Schristos }
6895dbdf32Schristos if ((map->rk = calloc(n, sizeof(*map->rk))) == NULL) {
6995dbdf32Schristos warnx("%s: calloc", __func__);
7095dbdf32Schristos goto out;
7195dbdf32Schristos }
7295dbdf32Schristos for (size_t i = 0; i < n; i++) {
7395dbdf32Schristos if ((rp_id = fido_credman_rp_id(map->rp, i)) == NULL) {
7495dbdf32Schristos warnx("%s: fido_credman_rp_id %zu", __func__, i);
7595dbdf32Schristos goto out;
7695dbdf32Schristos }
7795dbdf32Schristos if ((map->rk[i] = fido_credman_rk_new()) == NULL) {
7895dbdf32Schristos warnx("%s: fido_credman_rk_new", __func__);
7995dbdf32Schristos goto out;
8095dbdf32Schristos }
8195dbdf32Schristos if ((r = fido_credman_get_dev_rk(dev, rp_id, map->rk[i],
8295dbdf32Schristos pin)) != FIDO_OK) {
8395dbdf32Schristos warnx("%s: fido_credman_get_dev_rk %s: %s", __func__,
8495dbdf32Schristos rp_id, fido_strerr(r));
8595dbdf32Schristos goto out;
8695dbdf32Schristos }
8795dbdf32Schristos }
8895dbdf32Schristos
8995dbdf32Schristos ok = 0;
9095dbdf32Schristos out:
9195dbdf32Schristos freezero(pin, PINBUF_LEN);
9295dbdf32Schristos
9395dbdf32Schristos return ok;
9495dbdf32Schristos }
9595dbdf32Schristos
9695dbdf32Schristos 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)9795dbdf32Schristos lookup_key(const char *path, fido_dev_t *dev, const char *rp_id,
9895dbdf32Schristos const struct blob *cred_id, char **pin, struct blob *key)
9995dbdf32Schristos {
10095dbdf32Schristos fido_credman_rk_t *rk = NULL;
10195dbdf32Schristos const fido_cred_t *cred = NULL;
10295dbdf32Schristos size_t i, n;
10395dbdf32Schristos int r, ok = -1;
10495dbdf32Schristos
10595dbdf32Schristos if ((rk = fido_credman_rk_new()) == NULL) {
10695dbdf32Schristos warnx("%s: fido_credman_rk_new", __func__);
10795dbdf32Schristos goto out;
10895dbdf32Schristos }
10995dbdf32Schristos if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin)) != FIDO_OK &&
11095dbdf32Schristos *pin == NULL && should_retry_with_pin(dev, r)) {
11195dbdf32Schristos if ((*pin = get_pin(path)) == NULL)
11295dbdf32Schristos goto out;
11395dbdf32Schristos r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin);
11495dbdf32Schristos }
11595dbdf32Schristos if (r != FIDO_OK) {
11695dbdf32Schristos warnx("%s: fido_credman_get_dev_rk: %s", __func__,
11795dbdf32Schristos fido_strerr(r));
11895dbdf32Schristos goto out;
11995dbdf32Schristos }
12095dbdf32Schristos if ((n = fido_credman_rk_count(rk)) == 0) {
12195dbdf32Schristos warnx("%s: rp id not found", __func__);
12295dbdf32Schristos goto out;
12395dbdf32Schristos }
12495dbdf32Schristos if (n == 1 && cred_id->len == 0) {
12595dbdf32Schristos /* use the credential we found */
12695dbdf32Schristos cred = fido_credman_rk(rk, 0);
12795dbdf32Schristos } else {
12895dbdf32Schristos if (cred_id->len == 0) {
12995dbdf32Schristos warnx("%s: multiple credentials found", __func__);
13095dbdf32Schristos goto out;
13195dbdf32Schristos }
13295dbdf32Schristos for (i = 0; i < n; i++) {
13395dbdf32Schristos const fido_cred_t *x = fido_credman_rk(rk, i);
13495dbdf32Schristos if (fido_cred_id_len(x) <= cred_id->len &&
13595dbdf32Schristos !memcmp(fido_cred_id_ptr(x), cred_id->ptr,
13695dbdf32Schristos fido_cred_id_len(x))) {
13795dbdf32Schristos cred = x;
13895dbdf32Schristos break;
13995dbdf32Schristos }
14095dbdf32Schristos }
14195dbdf32Schristos }
14295dbdf32Schristos if (cred == NULL) {
14395dbdf32Schristos warnx("%s: credential not found", __func__);
14495dbdf32Schristos goto out;
14595dbdf32Schristos }
14695dbdf32Schristos if (fido_cred_largeblob_key_ptr(cred) == NULL) {
14795dbdf32Schristos warnx("%s: no associated blob key", __func__);
14895dbdf32Schristos goto out;
14995dbdf32Schristos }
15095dbdf32Schristos key->len = fido_cred_largeblob_key_len(cred);
15195dbdf32Schristos if ((key->ptr = malloc(key->len)) == NULL) {
15295dbdf32Schristos warnx("%s: malloc", __func__);
15395dbdf32Schristos goto out;
15495dbdf32Schristos }
15595dbdf32Schristos memcpy(key->ptr, fido_cred_largeblob_key_ptr(cred), key->len);
15695dbdf32Schristos
15795dbdf32Schristos ok = 0;
15895dbdf32Schristos out:
15995dbdf32Schristos fido_credman_rk_free(&rk);
16095dbdf32Schristos
16195dbdf32Schristos return ok;
16295dbdf32Schristos }
16395dbdf32Schristos
16495dbdf32Schristos 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)16595dbdf32Schristos load_key(const char *keyf, const char *cred_id64, const char *rp_id,
16695dbdf32Schristos const char *path, fido_dev_t *dev, char **pin, struct blob *key)
16795dbdf32Schristos {
16895dbdf32Schristos struct blob cred_id;
16995dbdf32Schristos FILE *fp;
17095dbdf32Schristos int r;
17195dbdf32Schristos
17295dbdf32Schristos memset(&cred_id, 0, sizeof(cred_id));
17395dbdf32Schristos
17495dbdf32Schristos if (keyf != NULL) {
17595dbdf32Schristos if (rp_id != NULL || cred_id64 != NULL)
17695dbdf32Schristos usage();
17795dbdf32Schristos fp = open_read(keyf);
17895dbdf32Schristos if ((r = base64_read(fp, key)) < 0)
17995dbdf32Schristos warnx("%s: base64_read %s", __func__, keyf);
18095dbdf32Schristos fclose(fp);
18195dbdf32Schristos return r;
18295dbdf32Schristos }
18395dbdf32Schristos if (rp_id == NULL)
18495dbdf32Schristos usage();
18595dbdf32Schristos if (cred_id64 != NULL && base64_decode(cred_id64, (void *)&cred_id.ptr,
18695dbdf32Schristos &cred_id.len) < 0) {
18795dbdf32Schristos warnx("%s: base64_decode %s", __func__, cred_id64);
18895dbdf32Schristos return -1;
18995dbdf32Schristos }
19095dbdf32Schristos r = lookup_key(path, dev, rp_id, &cred_id, pin, key);
19195dbdf32Schristos free(cred_id.ptr);
19295dbdf32Schristos
19395dbdf32Schristos return r;
19495dbdf32Schristos }
19595dbdf32Schristos
19695dbdf32Schristos int
blob_set(const char * path,const char * keyf,const char * rp_id,const char * cred_id64,const char * blobf)19795dbdf32Schristos blob_set(const char *path, const char *keyf, const char *rp_id,
19895dbdf32Schristos const char *cred_id64, const char *blobf)
19995dbdf32Schristos {
20095dbdf32Schristos fido_dev_t *dev;
20195dbdf32Schristos struct blob key, blob;
20295dbdf32Schristos char *pin = NULL;
20395dbdf32Schristos int r, ok = 1;
20495dbdf32Schristos
20595dbdf32Schristos dev = open_dev(path);
20695dbdf32Schristos memset(&key, 0, sizeof(key));
20795dbdf32Schristos memset(&blob, 0, sizeof(blob));
20895dbdf32Schristos
20995dbdf32Schristos if (read_file(blobf, &blob.ptr, &blob.len) < 0 ||
21095dbdf32Schristos load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
21195dbdf32Schristos goto out;
21295dbdf32Schristos if ((r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,
21395dbdf32Schristos blob.len, pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {
21495dbdf32Schristos if ((pin = get_pin(path)) == NULL)
21595dbdf32Schristos goto out;
21695dbdf32Schristos r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,
21795dbdf32Schristos blob.len, pin);
21895dbdf32Schristos }
21995dbdf32Schristos if (r != FIDO_OK) {
22095dbdf32Schristos warnx("fido_dev_largeblob_set: %s", fido_strerr(r));
22195dbdf32Schristos goto out;
22295dbdf32Schristos }
22395dbdf32Schristos
22495dbdf32Schristos ok = 0; /* success */
22595dbdf32Schristos out:
22695dbdf32Schristos freezero(key.ptr, key.len);
22795dbdf32Schristos freezero(blob.ptr, blob.len);
22895dbdf32Schristos freezero(pin, PINBUF_LEN);
22995dbdf32Schristos
23095dbdf32Schristos fido_dev_close(dev);
23195dbdf32Schristos fido_dev_free(&dev);
23295dbdf32Schristos
23395dbdf32Schristos exit(ok);
23495dbdf32Schristos }
23595dbdf32Schristos
23695dbdf32Schristos int
blob_get(const char * path,const char * keyf,const char * rp_id,const char * cred_id64,const char * blobf)23795dbdf32Schristos blob_get(const char *path, const char *keyf, const char *rp_id,
23895dbdf32Schristos const char *cred_id64, const char *blobf)
23995dbdf32Schristos {
24095dbdf32Schristos fido_dev_t *dev;
24195dbdf32Schristos struct blob key, blob;
24295dbdf32Schristos char *pin = NULL;
24395dbdf32Schristos int r, ok = 1;
24495dbdf32Schristos
24595dbdf32Schristos dev = open_dev(path);
24695dbdf32Schristos memset(&key, 0, sizeof(key));
24795dbdf32Schristos memset(&blob, 0, sizeof(blob));
24895dbdf32Schristos
24995dbdf32Schristos if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
25095dbdf32Schristos goto out;
25195dbdf32Schristos if ((r = fido_dev_largeblob_get(dev, key.ptr, key.len, &blob.ptr,
25295dbdf32Schristos &blob.len)) != FIDO_OK) {
25395dbdf32Schristos warnx("fido_dev_largeblob_get: %s", fido_strerr(r));
25495dbdf32Schristos goto out;
25595dbdf32Schristos }
25695dbdf32Schristos if (write_file(blobf, blob.ptr, blob.len) < 0)
25795dbdf32Schristos goto out;
25895dbdf32Schristos
25995dbdf32Schristos ok = 0; /* success */
26095dbdf32Schristos out:
26195dbdf32Schristos freezero(key.ptr, key.len);
26295dbdf32Schristos freezero(blob.ptr, blob.len);
26395dbdf32Schristos freezero(pin, PINBUF_LEN);
26495dbdf32Schristos
26595dbdf32Schristos fido_dev_close(dev);
26695dbdf32Schristos fido_dev_free(&dev);
26795dbdf32Schristos
26895dbdf32Schristos exit(ok);
26995dbdf32Schristos }
27095dbdf32Schristos
27195dbdf32Schristos int
blob_delete(const char * path,const char * keyf,const char * rp_id,const char * cred_id64)27295dbdf32Schristos blob_delete(const char *path, const char *keyf, const char *rp_id,
27395dbdf32Schristos const char *cred_id64)
27495dbdf32Schristos {
27595dbdf32Schristos fido_dev_t *dev;
27695dbdf32Schristos struct blob key;
27795dbdf32Schristos char *pin = NULL;
27895dbdf32Schristos int r, ok = 1;
27995dbdf32Schristos
28095dbdf32Schristos dev = open_dev(path);
28195dbdf32Schristos memset(&key, 0, sizeof(key));
28295dbdf32Schristos
28395dbdf32Schristos if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
28495dbdf32Schristos goto out;
28595dbdf32Schristos if ((r = fido_dev_largeblob_remove(dev, key.ptr, key.len,
28695dbdf32Schristos pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {
28795dbdf32Schristos if ((pin = get_pin(path)) == NULL)
28895dbdf32Schristos goto out;
28995dbdf32Schristos r = fido_dev_largeblob_remove(dev, key.ptr, key.len, pin);
29095dbdf32Schristos }
29195dbdf32Schristos if (r != FIDO_OK) {
29295dbdf32Schristos warnx("fido_dev_largeblob_remove: %s", fido_strerr(r));
29395dbdf32Schristos goto out;
29495dbdf32Schristos }
29595dbdf32Schristos
29695dbdf32Schristos ok = 0; /* success */
29795dbdf32Schristos out:
29895dbdf32Schristos freezero(key.ptr, key.len);
29995dbdf32Schristos freezero(pin, PINBUF_LEN);
30095dbdf32Schristos
30195dbdf32Schristos fido_dev_close(dev);
30295dbdf32Schristos fido_dev_free(&dev);
30395dbdf32Schristos
30495dbdf32Schristos exit(ok);
30595dbdf32Schristos }
30695dbdf32Schristos
30795dbdf32Schristos static int
try_decompress(const struct blob * in,uint64_t origsiz,int wbits)308*2d40c451Schristos try_decompress(const struct blob *in, uint64_t origsiz, int wbits)
30995dbdf32Schristos {
310*2d40c451Schristos struct blob out;
311*2d40c451Schristos z_stream zs;
312*2d40c451Schristos u_int ilen, olen;
31395dbdf32Schristos int ok = -1;
31495dbdf32Schristos
315*2d40c451Schristos memset(&zs, 0, sizeof(zs));
316*2d40c451Schristos memset(&out, 0, sizeof(out));
31795dbdf32Schristos
318*2d40c451Schristos if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND)
31995dbdf32Schristos return -1;
320*2d40c451Schristos if (origsiz > SIZE_MAX || origsiz > UINT_MAX ||
321*2d40c451Schristos (olen = (u_int)origsiz) > BOUND)
32295dbdf32Schristos return -1;
323*2d40c451Schristos if (inflateInit2(&zs, wbits) != Z_OK)
32495dbdf32Schristos return -1;
32595dbdf32Schristos
326*2d40c451Schristos if ((out.ptr = calloc(1, olen)) == NULL)
327*2d40c451Schristos goto fail;
328*2d40c451Schristos
329*2d40c451Schristos out.len = olen;
330*2d40c451Schristos zs.next_in = in->ptr;
331*2d40c451Schristos zs.avail_in = ilen;
332*2d40c451Schristos zs.next_out = out.ptr;
333*2d40c451Schristos zs.avail_out = olen;
334*2d40c451Schristos
335*2d40c451Schristos if (inflate(&zs, Z_FINISH) != Z_STREAM_END)
336*2d40c451Schristos goto fail;
337*2d40c451Schristos if (zs.avail_out != 0)
338*2d40c451Schristos goto fail;
339*2d40c451Schristos
340*2d40c451Schristos ok = 0;
341*2d40c451Schristos fail:
342*2d40c451Schristos if (inflateEnd(&zs) != Z_OK)
343*2d40c451Schristos ok = -1;
344*2d40c451Schristos
345*2d40c451Schristos freezero(out.ptr, out.len);
34695dbdf32Schristos
34795dbdf32Schristos return ok;
34895dbdf32Schristos }
34995dbdf32Schristos
35095dbdf32Schristos static int
decompress(const struct blob * plaintext,uint64_t origsiz)351*2d40c451Schristos decompress(const struct blob *plaintext, uint64_t origsiz)
352*2d40c451Schristos {
353*2d40c451Schristos if (try_decompress(plaintext, origsiz, MAX_WBITS) == 0) /* rfc1950 */
354*2d40c451Schristos return 0;
355*2d40c451Schristos return try_decompress(plaintext, origsiz, -MAX_WBITS); /* rfc1951 */
356*2d40c451Schristos }
357*2d40c451Schristos
358*2d40c451Schristos static int
decode(const struct blob * ciphertext,const struct blob * nonce,uint64_t origsiz,const fido_cred_t * cred)35995dbdf32Schristos decode(const struct blob *ciphertext, const struct blob *nonce,
36095dbdf32Schristos uint64_t origsiz, const fido_cred_t *cred)
36195dbdf32Schristos {
36295dbdf32Schristos uint8_t aad[4 + sizeof(uint64_t)];
36395dbdf32Schristos EVP_CIPHER_CTX *ctx = NULL;
36495dbdf32Schristos const EVP_CIPHER *cipher;
36595dbdf32Schristos struct blob plaintext;
36695dbdf32Schristos uint64_t tmp;
36795dbdf32Schristos int ok = -1;
36895dbdf32Schristos
36995dbdf32Schristos memset(&plaintext, 0, sizeof(plaintext));
37095dbdf32Schristos
37195dbdf32Schristos if (nonce->len != 12)
37295dbdf32Schristos return -1;
37395dbdf32Schristos if (cred == NULL ||
37495dbdf32Schristos fido_cred_largeblob_key_ptr(cred) == NULL ||
37595dbdf32Schristos fido_cred_largeblob_key_len(cred) != 32)
37695dbdf32Schristos return -1;
37795dbdf32Schristos if (ciphertext->len > UINT_MAX ||
37895dbdf32Schristos ciphertext->len > SIZE_MAX - 16 ||
37995dbdf32Schristos ciphertext->len < 16)
38095dbdf32Schristos return -1;
38195dbdf32Schristos plaintext.len = ciphertext->len - 16;
38295dbdf32Schristos if ((plaintext.ptr = calloc(1, plaintext.len)) == NULL)
38395dbdf32Schristos return -1;
38495dbdf32Schristos if ((ctx = EVP_CIPHER_CTX_new()) == NULL ||
38595dbdf32Schristos (cipher = EVP_aes_256_gcm()) == NULL ||
38695dbdf32Schristos EVP_CipherInit(ctx, cipher, fido_cred_largeblob_key_ptr(cred),
38795dbdf32Schristos nonce->ptr, 0) == 0)
38895dbdf32Schristos goto out;
38995dbdf32Schristos if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
39095dbdf32Schristos ciphertext->ptr + ciphertext->len - 16) == 0)
39195dbdf32Schristos goto out;
39295dbdf32Schristos aad[0] = 0x62; /* b */
39395dbdf32Schristos aad[1] = 0x6c; /* l */
39495dbdf32Schristos aad[2] = 0x6f; /* o */
39595dbdf32Schristos aad[3] = 0x62; /* b */
39695dbdf32Schristos tmp = htole64(origsiz);
39795dbdf32Schristos memcpy(&aad[4], &tmp, sizeof(uint64_t));
39895dbdf32Schristos if (EVP_Cipher(ctx, NULL, aad, (u_int)sizeof(aad)) < 0 ||
39995dbdf32Schristos EVP_Cipher(ctx, plaintext.ptr, ciphertext->ptr,
40095dbdf32Schristos (u_int)plaintext.len) < 0 ||
40195dbdf32Schristos EVP_Cipher(ctx, NULL, NULL, 0) < 0)
40295dbdf32Schristos goto out;
40395dbdf32Schristos if (decompress(&plaintext, origsiz) < 0)
40495dbdf32Schristos goto out;
40595dbdf32Schristos
40695dbdf32Schristos ok = 0;
40795dbdf32Schristos out:
40895dbdf32Schristos freezero(plaintext.ptr, plaintext.len);
40995dbdf32Schristos
41095dbdf32Schristos if (ctx != NULL)
41195dbdf32Schristos EVP_CIPHER_CTX_free(ctx);
41295dbdf32Schristos
41395dbdf32Schristos return ok;
41495dbdf32Schristos }
41595dbdf32Schristos
41695dbdf32Schristos static const fido_cred_t *
try_rp(const fido_credman_rk_t * rk,const struct blob * ciphertext,const struct blob * nonce,uint64_t origsiz)41795dbdf32Schristos try_rp(const fido_credman_rk_t *rk, const struct blob *ciphertext,
41895dbdf32Schristos const struct blob *nonce, uint64_t origsiz)
41995dbdf32Schristos {
42095dbdf32Schristos const fido_cred_t *cred;
42195dbdf32Schristos
42295dbdf32Schristos for (size_t i = 0; i < fido_credman_rk_count(rk); i++)
42395dbdf32Schristos if ((cred = fido_credman_rk(rk, i)) != NULL &&
42495dbdf32Schristos decode(ciphertext, nonce, origsiz, cred) == 0)
42595dbdf32Schristos return cred;
42695dbdf32Schristos
42795dbdf32Schristos return NULL;
42895dbdf32Schristos }
42995dbdf32Schristos
43095dbdf32Schristos static int
decode_cbor_blob(struct blob * out,const cbor_item_t * item)43195dbdf32Schristos decode_cbor_blob(struct blob *out, const cbor_item_t *item)
43295dbdf32Schristos {
43395dbdf32Schristos if (out->ptr != NULL ||
43495dbdf32Schristos cbor_isa_bytestring(item) == false ||
43595dbdf32Schristos cbor_bytestring_is_definite(item) == false)
43695dbdf32Schristos return -1;
43795dbdf32Schristos out->len = cbor_bytestring_length(item);
43895dbdf32Schristos if ((out->ptr = malloc(out->len)) == NULL)
43995dbdf32Schristos return -1;
44095dbdf32Schristos memcpy(out->ptr, cbor_bytestring_handle(item), out->len);
44195dbdf32Schristos
44295dbdf32Schristos return 0;
44395dbdf32Schristos }
44495dbdf32Schristos
44595dbdf32Schristos static int
decode_blob_entry(const cbor_item_t * item,struct blob * ciphertext,struct blob * nonce,uint64_t * origsiz)44695dbdf32Schristos decode_blob_entry(const cbor_item_t *item, struct blob *ciphertext,
44795dbdf32Schristos struct blob *nonce, uint64_t *origsiz)
44895dbdf32Schristos {
44995dbdf32Schristos struct cbor_pair *v;
45095dbdf32Schristos
45195dbdf32Schristos if (item == NULL)
45295dbdf32Schristos return -1;
45395dbdf32Schristos if (cbor_isa_map(item) == false ||
45495dbdf32Schristos cbor_map_is_definite(item) == false ||
45595dbdf32Schristos (v = cbor_map_handle(item)) == NULL)
45695dbdf32Schristos return -1;
45795dbdf32Schristos if (cbor_map_size(item) > UINT8_MAX)
45895dbdf32Schristos return -1;
45995dbdf32Schristos
46095dbdf32Schristos for (size_t i = 0; i < cbor_map_size(item); i++) {
46195dbdf32Schristos if (cbor_isa_uint(v[i].key) == false ||
46295dbdf32Schristos cbor_int_get_width(v[i].key) != CBOR_INT_8)
46395dbdf32Schristos continue; /* ignore */
46495dbdf32Schristos switch (cbor_get_uint8(v[i].key)) {
46595dbdf32Schristos case 1: /* ciphertext */
46695dbdf32Schristos if (decode_cbor_blob(ciphertext, v[i].value) < 0)
46795dbdf32Schristos return -1;
46895dbdf32Schristos break;
46995dbdf32Schristos case 2: /* nonce */
47095dbdf32Schristos if (decode_cbor_blob(nonce, v[i].value) < 0)
47195dbdf32Schristos return -1;
47295dbdf32Schristos break;
47395dbdf32Schristos case 3: /* origSize */
47495dbdf32Schristos if (*origsiz != 0 ||
47595dbdf32Schristos cbor_isa_uint(v[i].value) == false ||
47695dbdf32Schristos (*origsiz = cbor_get_int(v[i].value)) > SIZE_MAX)
47795dbdf32Schristos return -1;
47895dbdf32Schristos }
47995dbdf32Schristos }
48095dbdf32Schristos if (ciphertext->ptr == NULL || nonce->ptr == NULL || *origsiz == 0)
48195dbdf32Schristos return -1;
48295dbdf32Schristos
48395dbdf32Schristos return 0;
48495dbdf32Schristos }
48595dbdf32Schristos
48695dbdf32Schristos static void
print_blob_entry(size_t idx,const cbor_item_t * item,const struct rkmap * map)48795dbdf32Schristos print_blob_entry(size_t idx, const cbor_item_t *item, const struct rkmap *map)
48895dbdf32Schristos {
48995dbdf32Schristos struct blob ciphertext, nonce;
49095dbdf32Schristos const fido_cred_t *cred = NULL;
49195dbdf32Schristos const char *rp_id = NULL;
49295dbdf32Schristos char *cred_id = NULL;
49395dbdf32Schristos uint64_t origsiz = 0;
49495dbdf32Schristos
49595dbdf32Schristos memset(&ciphertext, 0, sizeof(ciphertext));
49695dbdf32Schristos memset(&nonce, 0, sizeof(nonce));
49795dbdf32Schristos
49895dbdf32Schristos if (decode_blob_entry(item, &ciphertext, &nonce, &origsiz) < 0) {
49995dbdf32Schristos printf("%02zu: <skipped: bad cbor>\n", idx);
50095dbdf32Schristos goto out;
50195dbdf32Schristos }
50295dbdf32Schristos for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++) {
50395dbdf32Schristos if ((cred = try_rp(map->rk[i], &ciphertext, &nonce,
50495dbdf32Schristos origsiz)) != NULL) {
50595dbdf32Schristos rp_id = fido_credman_rp_id(map->rp, i);
50695dbdf32Schristos break;
50795dbdf32Schristos }
50895dbdf32Schristos }
50995dbdf32Schristos if (cred == NULL) {
51095dbdf32Schristos if ((cred_id = strdup("<unknown>")) == NULL) {
51195dbdf32Schristos printf("%02zu: <skipped: strdup failed>\n", idx);
51295dbdf32Schristos goto out;
51395dbdf32Schristos }
51495dbdf32Schristos } else {
51595dbdf32Schristos if (base64_encode(fido_cred_id_ptr(cred),
51695dbdf32Schristos fido_cred_id_len(cred), &cred_id) < 0) {
51795dbdf32Schristos printf("%02zu: <skipped: base64_encode failed>\n", idx);
51895dbdf32Schristos goto out;
51995dbdf32Schristos }
52095dbdf32Schristos }
52195dbdf32Schristos if (rp_id == NULL)
52295dbdf32Schristos rp_id = "<unknown>";
52395dbdf32Schristos
52495dbdf32Schristos printf("%02zu: %4zu %4zu %s %s\n", idx, ciphertext.len,
52595dbdf32Schristos (size_t)origsiz, cred_id, rp_id);
52695dbdf32Schristos out:
52795dbdf32Schristos free(ciphertext.ptr);
52895dbdf32Schristos free(nonce.ptr);
52995dbdf32Schristos free(cred_id);
53095dbdf32Schristos }
53195dbdf32Schristos
53295dbdf32Schristos static cbor_item_t *
get_cbor_array(fido_dev_t * dev)53395dbdf32Schristos get_cbor_array(fido_dev_t *dev)
53495dbdf32Schristos {
53595dbdf32Schristos struct cbor_load_result cbor_result;
53695dbdf32Schristos cbor_item_t *item = NULL;
53795dbdf32Schristos u_char *cbor_ptr = NULL;
53895dbdf32Schristos size_t cbor_len;
53995dbdf32Schristos int r, ok = -1;
54095dbdf32Schristos
54195dbdf32Schristos if ((r = fido_dev_largeblob_get_array(dev, &cbor_ptr,
54295dbdf32Schristos &cbor_len)) != FIDO_OK) {
54395dbdf32Schristos warnx("%s: fido_dev_largeblob_get_array: %s", __func__,
54495dbdf32Schristos fido_strerr(r));
54595dbdf32Schristos goto out;
54695dbdf32Schristos }
54795dbdf32Schristos if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) {
54895dbdf32Schristos warnx("%s: cbor_load", __func__);
54995dbdf32Schristos goto out;
55095dbdf32Schristos }
55195dbdf32Schristos if (cbor_result.read != cbor_len) {
55295dbdf32Schristos warnx("%s: cbor_result.read (%zu) != cbor_len (%zu)", __func__,
55395dbdf32Schristos cbor_result.read, cbor_len);
55495dbdf32Schristos /* continue */
55595dbdf32Schristos }
55695dbdf32Schristos if (cbor_isa_array(item) == false ||
55795dbdf32Schristos cbor_array_is_definite(item) == false) {
55895dbdf32Schristos warnx("%s: cbor type", __func__);
55995dbdf32Schristos goto out;
56095dbdf32Schristos }
56195dbdf32Schristos if (cbor_array_size(item) > UINT8_MAX) {
56295dbdf32Schristos warnx("%s: cbor_array_size > UINT8_MAX", __func__);
56395dbdf32Schristos goto out;
56495dbdf32Schristos }
56595dbdf32Schristos if (cbor_array_size(item) == 0) {
56695dbdf32Schristos ok = 0; /* nothing to do */
56795dbdf32Schristos goto out;
56895dbdf32Schristos }
56995dbdf32Schristos
57095dbdf32Schristos printf("total map size: %zu byte%s\n", cbor_len, plural(cbor_len));
57195dbdf32Schristos
57295dbdf32Schristos ok = 0;
57395dbdf32Schristos out:
57495dbdf32Schristos if (ok < 0 && item != NULL) {
57595dbdf32Schristos cbor_decref(&item);
57695dbdf32Schristos item = NULL;
57795dbdf32Schristos }
57895dbdf32Schristos free(cbor_ptr);
57995dbdf32Schristos
58095dbdf32Schristos return item;
58195dbdf32Schristos }
58295dbdf32Schristos
58395dbdf32Schristos int
blob_list(const char * path)58495dbdf32Schristos blob_list(const char *path)
58595dbdf32Schristos {
58695dbdf32Schristos struct rkmap map;
58795dbdf32Schristos fido_dev_t *dev = NULL;
58895dbdf32Schristos cbor_item_t *item = NULL, **v;
58995dbdf32Schristos int ok = 1;
59095dbdf32Schristos
59195dbdf32Schristos memset(&map, 0, sizeof(map));
59295dbdf32Schristos dev = open_dev(path);
59395dbdf32Schristos if (map_known_rps(dev, path, &map) < 0 ||
59495dbdf32Schristos (item = get_cbor_array(dev)) == NULL)
59595dbdf32Schristos goto out;
59695dbdf32Schristos if (cbor_array_size(item) == 0) {
59795dbdf32Schristos ok = 0; /* nothing to do */
59895dbdf32Schristos goto out;
59995dbdf32Schristos }
60095dbdf32Schristos if ((v = cbor_array_handle(item)) == NULL) {
60195dbdf32Schristos warnx("%s: cbor_array_handle", __func__);
60295dbdf32Schristos goto out;
60395dbdf32Schristos }
60495dbdf32Schristos for (size_t i = 0; i < cbor_array_size(item); i++)
60595dbdf32Schristos print_blob_entry(i, v[i], &map);
60695dbdf32Schristos
60795dbdf32Schristos ok = 0; /* success */
60895dbdf32Schristos out:
60995dbdf32Schristos free_rkmap(&map);
61095dbdf32Schristos
61195dbdf32Schristos if (item != NULL)
61295dbdf32Schristos cbor_decref(&item);
61395dbdf32Schristos
61495dbdf32Schristos fido_dev_close(dev);
61595dbdf32Schristos fido_dev_free(&dev);
61695dbdf32Schristos
61795dbdf32Schristos exit(ok);
61895dbdf32Schristos }
619