Path: blob/main/contrib/libfido2/tools/largeblob.c
39507 views
/*1* Copyright (c) 2020-2022 Yubico AB. All rights reserved.2* Use of this source code is governed by a BSD-style3* license that can be found in the LICENSE file.4* SPDX-License-Identifier: BSD-2-Clause5*/67#include <sys/types.h>8#include <sys/stat.h>910#include <fido.h>11#include <fido/credman.h>1213#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_H20#include <unistd.h>21#endif22#include <zlib.h>2324#include "../openbsd-compat/openbsd-compat.h"25#include "extern.h"2627#define BOUND (1024UL * 1024UL)2829struct rkmap {30fido_credman_rp_t *rp; /* known rps */31fido_credman_rk_t **rk; /* rk per rp */32};3334static void35free_rkmap(struct rkmap *map)36{37if (map->rp != NULL) {38for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++)39fido_credman_rk_free(&map->rk[i]);40fido_credman_rp_free(&map->rp);41}42free(map->rk);43}4445static int46map_known_rps(fido_dev_t *dev, const char *path, struct rkmap *map)47{48const char *rp_id;49char *pin = NULL;50size_t n;51int r, ok = -1;5253if ((map->rp = fido_credman_rp_new()) == NULL) {54warnx("%s: fido_credman_rp_new", __func__);55goto out;56}57if ((pin = get_pin(path)) == NULL)58goto out;59if ((r = fido_credman_get_dev_rp(dev, map->rp, pin)) != FIDO_OK) {60warnx("fido_credman_get_dev_rp: %s", fido_strerr(r));61goto out;62}63if ((n = fido_credman_rp_count(map->rp)) > UINT8_MAX) {64warnx("%s: fido_credman_rp_count > UINT8_MAX", __func__);65goto out;66}67if ((map->rk = calloc(n, sizeof(*map->rk))) == NULL) {68warnx("%s: calloc", __func__);69goto out;70}71for (size_t i = 0; i < n; i++) {72if ((rp_id = fido_credman_rp_id(map->rp, i)) == NULL) {73warnx("%s: fido_credman_rp_id %zu", __func__, i);74goto out;75}76if ((map->rk[i] = fido_credman_rk_new()) == NULL) {77warnx("%s: fido_credman_rk_new", __func__);78goto out;79}80if ((r = fido_credman_get_dev_rk(dev, rp_id, map->rk[i],81pin)) != FIDO_OK) {82warnx("%s: fido_credman_get_dev_rk %s: %s", __func__,83rp_id, fido_strerr(r));84goto out;85}86}8788ok = 0;89out:90freezero(pin, PINBUF_LEN);9192return ok;93}9495static int96lookup_key(const char *path, fido_dev_t *dev, const char *rp_id,97const struct blob *cred_id, char **pin, struct blob *key)98{99fido_credman_rk_t *rk = NULL;100const fido_cred_t *cred = NULL;101size_t i, n;102int r, ok = -1;103104if ((rk = fido_credman_rk_new()) == NULL) {105warnx("%s: fido_credman_rk_new", __func__);106goto out;107}108if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin)) != FIDO_OK &&109*pin == NULL && should_retry_with_pin(dev, r)) {110if ((*pin = get_pin(path)) == NULL)111goto out;112r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin);113}114if (r != FIDO_OK) {115warnx("%s: fido_credman_get_dev_rk: %s", __func__,116fido_strerr(r));117goto out;118}119if ((n = fido_credman_rk_count(rk)) == 0) {120warnx("%s: rp id not found", __func__);121goto out;122}123if (n == 1 && cred_id->len == 0) {124/* use the credential we found */125cred = fido_credman_rk(rk, 0);126} else {127if (cred_id->len == 0) {128warnx("%s: multiple credentials found", __func__);129goto out;130}131for (i = 0; i < n; i++) {132const fido_cred_t *x = fido_credman_rk(rk, i);133if (fido_cred_id_len(x) <= cred_id->len &&134!memcmp(fido_cred_id_ptr(x), cred_id->ptr,135fido_cred_id_len(x))) {136cred = x;137break;138}139}140}141if (cred == NULL) {142warnx("%s: credential not found", __func__);143goto out;144}145if (fido_cred_largeblob_key_ptr(cred) == NULL) {146warnx("%s: no associated blob key", __func__);147goto out;148}149key->len = fido_cred_largeblob_key_len(cred);150if ((key->ptr = malloc(key->len)) == NULL) {151warnx("%s: malloc", __func__);152goto out;153}154memcpy(key->ptr, fido_cred_largeblob_key_ptr(cred), key->len);155156ok = 0;157out:158fido_credman_rk_free(&rk);159160return ok;161}162163static int164load_key(const char *keyf, const char *cred_id64, const char *rp_id,165const char *path, fido_dev_t *dev, char **pin, struct blob *key)166{167struct blob cred_id;168FILE *fp;169int r;170171memset(&cred_id, 0, sizeof(cred_id));172173if (keyf != NULL) {174if (rp_id != NULL || cred_id64 != NULL)175usage();176fp = open_read(keyf);177if ((r = base64_read(fp, key)) < 0)178warnx("%s: base64_read %s", __func__, keyf);179fclose(fp);180return r;181}182if (rp_id == NULL)183usage();184if (cred_id64 != NULL && base64_decode(cred_id64, (void *)&cred_id.ptr,185&cred_id.len) < 0) {186warnx("%s: base64_decode %s", __func__, cred_id64);187return -1;188}189r = lookup_key(path, dev, rp_id, &cred_id, pin, key);190free(cred_id.ptr);191192return r;193}194195int196blob_set(const char *path, const char *keyf, const char *rp_id,197const char *cred_id64, const char *blobf)198{199fido_dev_t *dev;200struct blob key, blob;201char *pin = NULL;202int r, ok = 1;203204dev = open_dev(path);205memset(&key, 0, sizeof(key));206memset(&blob, 0, sizeof(blob));207208if (read_file(blobf, &blob.ptr, &blob.len) < 0 ||209load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)210goto out;211if ((r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,212blob.len, pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {213if ((pin = get_pin(path)) == NULL)214goto out;215r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,216blob.len, pin);217}218if (r != FIDO_OK) {219warnx("fido_dev_largeblob_set: %s", fido_strerr(r));220goto out;221}222223ok = 0; /* success */224out:225freezero(key.ptr, key.len);226freezero(blob.ptr, blob.len);227freezero(pin, PINBUF_LEN);228229fido_dev_close(dev);230fido_dev_free(&dev);231232exit(ok);233}234235int236blob_get(const char *path, const char *keyf, const char *rp_id,237const char *cred_id64, const char *blobf)238{239fido_dev_t *dev;240struct blob key, blob;241char *pin = NULL;242int r, ok = 1;243244dev = open_dev(path);245memset(&key, 0, sizeof(key));246memset(&blob, 0, sizeof(blob));247248if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)249goto out;250if ((r = fido_dev_largeblob_get(dev, key.ptr, key.len, &blob.ptr,251&blob.len)) != FIDO_OK) {252warnx("fido_dev_largeblob_get: %s", fido_strerr(r));253goto out;254}255if (write_file(blobf, blob.ptr, blob.len) < 0)256goto out;257258ok = 0; /* success */259out:260freezero(key.ptr, key.len);261freezero(blob.ptr, blob.len);262freezero(pin, PINBUF_LEN);263264fido_dev_close(dev);265fido_dev_free(&dev);266267exit(ok);268}269270int271blob_delete(const char *path, const char *keyf, const char *rp_id,272const char *cred_id64)273{274fido_dev_t *dev;275struct blob key;276char *pin = NULL;277int r, ok = 1;278279dev = open_dev(path);280memset(&key, 0, sizeof(key));281282if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)283goto out;284if ((r = fido_dev_largeblob_remove(dev, key.ptr, key.len,285pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {286if ((pin = get_pin(path)) == NULL)287goto out;288r = fido_dev_largeblob_remove(dev, key.ptr, key.len, pin);289}290if (r != FIDO_OK) {291warnx("fido_dev_largeblob_remove: %s", fido_strerr(r));292goto out;293}294295ok = 0; /* success */296out:297freezero(key.ptr, key.len);298freezero(pin, PINBUF_LEN);299300fido_dev_close(dev);301fido_dev_free(&dev);302303exit(ok);304}305306static int307try_decompress(const struct blob *in, uint64_t origsiz, int wbits)308{309struct blob out;310z_stream zs;311u_int ilen, olen;312int ok = -1;313314memset(&zs, 0, sizeof(zs));315memset(&out, 0, sizeof(out));316317if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND)318return -1;319if (origsiz > SIZE_MAX || origsiz > UINT_MAX ||320(olen = (u_int)origsiz) > BOUND)321return -1;322if (inflateInit2(&zs, wbits) != Z_OK)323return -1;324325if ((out.ptr = calloc(1, olen)) == NULL)326goto fail;327328out.len = olen;329zs.next_in = in->ptr;330zs.avail_in = ilen;331zs.next_out = out.ptr;332zs.avail_out = olen;333334if (inflate(&zs, Z_FINISH) != Z_STREAM_END)335goto fail;336if (zs.avail_out != 0)337goto fail;338339ok = 0;340fail:341if (inflateEnd(&zs) != Z_OK)342ok = -1;343344freezero(out.ptr, out.len);345346return ok;347}348349static int350decompress(const struct blob *plaintext, uint64_t origsiz)351{352if (try_decompress(plaintext, origsiz, MAX_WBITS) == 0) /* rfc1950 */353return 0;354return try_decompress(plaintext, origsiz, -MAX_WBITS); /* rfc1951 */355}356357static int358decode(const struct blob *ciphertext, const struct blob *nonce,359uint64_t origsiz, const fido_cred_t *cred)360{361uint8_t aad[4 + sizeof(uint64_t)];362EVP_CIPHER_CTX *ctx = NULL;363const EVP_CIPHER *cipher;364struct blob plaintext;365uint64_t tmp;366int ok = -1;367368memset(&plaintext, 0, sizeof(plaintext));369370if (nonce->len != 12)371return -1;372if (cred == NULL ||373fido_cred_largeblob_key_ptr(cred) == NULL ||374fido_cred_largeblob_key_len(cred) != 32)375return -1;376if (ciphertext->len > UINT_MAX ||377ciphertext->len > SIZE_MAX - 16 ||378ciphertext->len < 16)379return -1;380plaintext.len = ciphertext->len - 16;381if ((plaintext.ptr = calloc(1, plaintext.len)) == NULL)382return -1;383if ((ctx = EVP_CIPHER_CTX_new()) == NULL ||384(cipher = EVP_aes_256_gcm()) == NULL ||385EVP_CipherInit(ctx, cipher, fido_cred_largeblob_key_ptr(cred),386nonce->ptr, 0) == 0)387goto out;388if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,389ciphertext->ptr + ciphertext->len - 16) == 0)390goto out;391aad[0] = 0x62; /* b */392aad[1] = 0x6c; /* l */393aad[2] = 0x6f; /* o */394aad[3] = 0x62; /* b */395tmp = htole64(origsiz);396memcpy(&aad[4], &tmp, sizeof(uint64_t));397if (EVP_Cipher(ctx, NULL, aad, (u_int)sizeof(aad)) < 0 ||398EVP_Cipher(ctx, plaintext.ptr, ciphertext->ptr,399(u_int)plaintext.len) < 0 ||400EVP_Cipher(ctx, NULL, NULL, 0) < 0)401goto out;402if (decompress(&plaintext, origsiz) < 0)403goto out;404405ok = 0;406out:407freezero(plaintext.ptr, plaintext.len);408409if (ctx != NULL)410EVP_CIPHER_CTX_free(ctx);411412return ok;413}414415static const fido_cred_t *416try_rp(const fido_credman_rk_t *rk, const struct blob *ciphertext,417const struct blob *nonce, uint64_t origsiz)418{419const fido_cred_t *cred;420421for (size_t i = 0; i < fido_credman_rk_count(rk); i++)422if ((cred = fido_credman_rk(rk, i)) != NULL &&423decode(ciphertext, nonce, origsiz, cred) == 0)424return cred;425426return NULL;427}428429static int430decode_cbor_blob(struct blob *out, const cbor_item_t *item)431{432if (out->ptr != NULL ||433cbor_isa_bytestring(item) == false ||434cbor_bytestring_is_definite(item) == false)435return -1;436out->len = cbor_bytestring_length(item);437if ((out->ptr = malloc(out->len)) == NULL)438return -1;439memcpy(out->ptr, cbor_bytestring_handle(item), out->len);440441return 0;442}443444static int445decode_blob_entry(const cbor_item_t *item, struct blob *ciphertext,446struct blob *nonce, uint64_t *origsiz)447{448struct cbor_pair *v;449450if (item == NULL)451return -1;452if (cbor_isa_map(item) == false ||453cbor_map_is_definite(item) == false ||454(v = cbor_map_handle(item)) == NULL)455return -1;456if (cbor_map_size(item) > UINT8_MAX)457return -1;458459for (size_t i = 0; i < cbor_map_size(item); i++) {460if (cbor_isa_uint(v[i].key) == false ||461cbor_int_get_width(v[i].key) != CBOR_INT_8)462continue; /* ignore */463switch (cbor_get_uint8(v[i].key)) {464case 1: /* ciphertext */465if (decode_cbor_blob(ciphertext, v[i].value) < 0)466return -1;467break;468case 2: /* nonce */469if (decode_cbor_blob(nonce, v[i].value) < 0)470return -1;471break;472case 3: /* origSize */473if (*origsiz != 0 ||474cbor_isa_uint(v[i].value) == false ||475(*origsiz = cbor_get_int(v[i].value)) > SIZE_MAX)476return -1;477}478}479if (ciphertext->ptr == NULL || nonce->ptr == NULL || *origsiz == 0)480return -1;481482return 0;483}484485static void486print_blob_entry(size_t idx, const cbor_item_t *item, const struct rkmap *map)487{488struct blob ciphertext, nonce;489const fido_cred_t *cred = NULL;490const char *rp_id = NULL;491char *cred_id = NULL;492uint64_t origsiz = 0;493494memset(&ciphertext, 0, sizeof(ciphertext));495memset(&nonce, 0, sizeof(nonce));496497if (decode_blob_entry(item, &ciphertext, &nonce, &origsiz) < 0) {498printf("%02zu: <skipped: bad cbor>\n", idx);499goto out;500}501for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++) {502if ((cred = try_rp(map->rk[i], &ciphertext, &nonce,503origsiz)) != NULL) {504rp_id = fido_credman_rp_id(map->rp, i);505break;506}507}508if (cred == NULL) {509if ((cred_id = strdup("<unknown>")) == NULL) {510printf("%02zu: <skipped: strdup failed>\n", idx);511goto out;512}513} else {514if (base64_encode(fido_cred_id_ptr(cred),515fido_cred_id_len(cred), &cred_id) < 0) {516printf("%02zu: <skipped: base64_encode failed>\n", idx);517goto out;518}519}520if (rp_id == NULL)521rp_id = "<unknown>";522523printf("%02zu: %4zu %4zu %s %s\n", idx, ciphertext.len,524(size_t)origsiz, cred_id, rp_id);525out:526free(ciphertext.ptr);527free(nonce.ptr);528free(cred_id);529}530531static cbor_item_t *532get_cbor_array(fido_dev_t *dev)533{534struct cbor_load_result cbor_result;535cbor_item_t *item = NULL;536u_char *cbor_ptr = NULL;537size_t cbor_len;538int r, ok = -1;539540if ((r = fido_dev_largeblob_get_array(dev, &cbor_ptr,541&cbor_len)) != FIDO_OK) {542warnx("%s: fido_dev_largeblob_get_array: %s", __func__,543fido_strerr(r));544goto out;545}546if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) {547warnx("%s: cbor_load", __func__);548goto out;549}550if (cbor_result.read != cbor_len) {551warnx("%s: cbor_result.read (%zu) != cbor_len (%zu)", __func__,552cbor_result.read, cbor_len);553/* continue */554}555if (cbor_isa_array(item) == false ||556cbor_array_is_definite(item) == false) {557warnx("%s: cbor type", __func__);558goto out;559}560if (cbor_array_size(item) > UINT8_MAX) {561warnx("%s: cbor_array_size > UINT8_MAX", __func__);562goto out;563}564if (cbor_array_size(item) == 0) {565ok = 0; /* nothing to do */566goto out;567}568569printf("total map size: %zu byte%s\n", cbor_len, plural(cbor_len));570571ok = 0;572out:573if (ok < 0 && item != NULL) {574cbor_decref(&item);575item = NULL;576}577free(cbor_ptr);578579return item;580}581582int583blob_list(const char *path)584{585struct rkmap map;586fido_dev_t *dev = NULL;587cbor_item_t *item = NULL, **v;588int ok = 1;589590memset(&map, 0, sizeof(map));591dev = open_dev(path);592if (map_known_rps(dev, path, &map) < 0 ||593(item = get_cbor_array(dev)) == NULL)594goto out;595if (cbor_array_size(item) == 0) {596ok = 0; /* nothing to do */597goto out;598}599if ((v = cbor_array_handle(item)) == NULL) {600warnx("%s: cbor_array_handle", __func__);601goto out;602}603for (size_t i = 0; i < cbor_array_size(item); i++)604print_blob_entry(i, v[i], &map);605606ok = 0; /* success */607out:608free_rkmap(&map);609610if (item != NULL)611cbor_decref(&item);612613fido_dev_close(dev);614fido_dev_free(&dev);615616exit(ok);617}618619620