summaryrefslogtreecommitdiffstats
path: root/lib/libfido2/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libfido2/src')
-rw-r--r--lib/libfido2/src/aes256.c98
-rw-r--r--lib/libfido2/src/assert.c1085
-rw-r--r--lib/libfido2/src/authkey.c98
-rw-r--r--lib/libfido2/src/bio.c844
-rw-r--r--lib/libfido2/src/blob.c102
-rw-r--r--lib/libfido2/src/blob.h28
-rw-r--r--lib/libfido2/src/buf.c34
-rw-r--r--lib/libfido2/src/cbor.c1514
-rw-r--r--lib/libfido2/src/cred.c1031
-rw-r--r--lib/libfido2/src/credman.c736
-rw-r--r--lib/libfido2/src/dev.c291
-rw-r--r--lib/libfido2/src/ecdh.c121
-rw-r--r--lib/libfido2/src/eddsa.c169
-rw-r--r--lib/libfido2/src/err.c122
-rw-r--r--lib/libfido2/src/es256.c433
-rw-r--r--lib/libfido2/src/export.llvm178
-rw-r--r--lib/libfido2/src/extern.h131
-rw-r--r--lib/libfido2/src/fido.h196
-rw-r--r--lib/libfido2/src/fido/bio.h95
-rw-r--r--lib/libfido2/src/fido/credman.h74
-rw-r--r--lib/libfido2/src/fido/eddsa.h40
-rw-r--r--lib/libfido2/src/fido/err.h69
-rw-r--r--lib/libfido2/src/fido/es256.h34
-rw-r--r--lib/libfido2/src/fido/param.h84
-rw-r--r--lib/libfido2/src/fido/rs256.h22
-rw-r--r--lib/libfido2/src/hid.c70
-rw-r--r--lib/libfido2/src/hid_openbsd.c277
-rw-r--r--lib/libfido2/src/info.c410
-rw-r--r--lib/libfido2/src/io.c256
-rw-r--r--lib/libfido2/src/iso7816.c70
-rw-r--r--lib/libfido2/src/iso7816.h38
-rw-r--r--lib/libfido2/src/log.c63
-rw-r--r--lib/libfido2/src/packed.h22
-rw-r--r--lib/libfido2/src/pin.c428
-rw-r--r--lib/libfido2/src/reset.c40
-rw-r--r--lib/libfido2/src/rs256.c204
-rw-r--r--lib/libfido2/src/types.h171
-rw-r--r--lib/libfido2/src/u2f.c751
38 files changed, 10429 insertions, 0 deletions
diff --git a/lib/libfido2/src/aes256.c b/lib/libfido2/src/aes256.c
new file mode 100644
index 00000000000..478fad90eeb
--- /dev/null
+++ b/lib/libfido2/src/aes256.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/evp.h>
+#include <string.h>
+
+#include "fido.h"
+
+int
+aes256_cbc_enc(const fido_blob_t *key, const fido_blob_t *in, fido_blob_t *out)
+{
+ EVP_CIPHER_CTX *ctx = NULL;
+ unsigned char iv[32];
+ int len;
+ int ok = -1;
+
+ memset(iv, 0, sizeof(iv));
+ out->ptr = NULL;
+ out->len = 0;
+
+ /* sanity check */
+ if (in->len > INT_MAX || (in->len % 16) != 0 ||
+ (out->ptr = calloc(1, in->len)) == NULL) {
+ log_debug("%s: in->len=%zu", __func__, in->len);
+ goto fail;
+ }
+
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL || key->len != 32 ||
+ !EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key->ptr, iv) ||
+ !EVP_CIPHER_CTX_set_padding(ctx, 0) ||
+ !EVP_EncryptUpdate(ctx, out->ptr, &len, in->ptr, (int)in->len) ||
+ len < 0 || (size_t)len != in->len) {
+ log_debug("%s: EVP_Encrypt", __func__);
+ goto fail;
+ }
+
+ out->len = (size_t)len;
+
+ ok = 0;
+fail:
+ if (ctx != NULL)
+ EVP_CIPHER_CTX_free(ctx);
+
+ if (ok < 0) {
+ free(out->ptr);
+ out->ptr = NULL;
+ out->len = 0;
+ }
+
+ return (ok);
+}
+
+int
+aes256_cbc_dec(const fido_blob_t *key, const fido_blob_t *in, fido_blob_t *out)
+{
+ EVP_CIPHER_CTX *ctx = NULL;
+ unsigned char iv[32];
+ int len;
+ int ok = -1;
+
+ memset(iv, 0, sizeof(iv));
+ out->ptr = NULL;
+ out->len = 0;
+
+ /* sanity check */
+ if (in->len > INT_MAX || (in->len % 16) != 0 ||
+ (out->ptr = calloc(1, in->len)) == NULL) {
+ log_debug("%s: in->len=%zu", __func__, in->len);
+ goto fail;
+ }
+
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL || key->len != 32 ||
+ !EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key->ptr, iv) ||
+ !EVP_CIPHER_CTX_set_padding(ctx, 0) ||
+ !EVP_DecryptUpdate(ctx, out->ptr, &len, in->ptr, (int)in->len) ||
+ len < 0 || (size_t)len > in->len + 32) {
+ log_debug("%s: EVP_Decrypt", __func__);
+ goto fail;
+ }
+
+ out->len = (size_t)len;
+
+ ok = 0;
+fail:
+ if (ctx != NULL)
+ EVP_CIPHER_CTX_free(ctx);
+
+ if (ok < 0) {
+ free(out->ptr);
+ out->ptr = NULL;
+ out->len = 0;
+ }
+
+ return (ok);
+}
diff --git a/lib/libfido2/src/assert.c b/lib/libfido2/src/assert.c
new file mode 100644
index 00000000000..76a9dfe4ff2
--- /dev/null
+++ b/lib/libfido2/src/assert.c
@@ -0,0 +1,1085 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+
+#include <string.h>
+#include "fido.h"
+#include "fido/es256.h"
+#include "fido/rs256.h"
+#include "fido/eddsa.h"
+
+static int
+adjust_assert_count(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_assert_t *assert = arg;
+ uint64_t n;
+
+ /* numberOfCredentials; see section 6.2 */
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 5) {
+ log_debug("%s: cbor_type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
+ log_debug("%s: decode_uint64", __func__);
+ return (-1);
+ }
+
+ if (assert->stmt_len != 0 || assert->stmt_cnt != 1 ||
+ (size_t)n < assert->stmt_cnt) {
+ log_debug("%s: stmt_len=%zu, stmt_cnt=%zu, n=%zu", __func__,
+ assert->stmt_len, assert->stmt_cnt, (size_t)n);
+ return (-1);
+ }
+
+ if (fido_assert_set_count(assert, (size_t)n) != FIDO_OK) {
+ log_debug("%s: fido_assert_set_count", __func__);
+ return (-1);
+ }
+
+ assert->stmt_len = 0; /* XXX */
+
+ return (0);
+}
+
+static int
+parse_assert_reply(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_assert_stmt *stmt = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* credential id */
+ return (decode_cred_id(val, &stmt->id));
+ case 2: /* authdata */
+ return (decode_assert_authdata(val, &stmt->authdata_cbor,
+ &stmt->authdata, &stmt->authdata_ext,
+ &stmt->hmac_secret_enc));
+ case 3: /* signature */
+ return (fido_blob_decode(val, &stmt->sig));
+ case 4: /* user attributes */
+ return (decode_user(val, &stmt->user));
+ default: /* ignore */
+ log_debug("%s: cbor type", __func__);
+ return (0);
+ }
+}
+
+static int
+fido_dev_get_assert_tx(fido_dev_t *dev, fido_assert_t *assert,
+ const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin)
+{
+ fido_blob_t f;
+ cbor_item_t *argv[7];
+ int r;
+
+ memset(argv, 0, sizeof(argv));
+ memset(&f, 0, sizeof(f));
+
+ /* do we have everything we need? */
+ if (assert->rp_id == NULL || assert->cdh.ptr == NULL) {
+ log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__,
+ (void *)assert->rp_id, (void *)assert->cdh.ptr);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((argv[0] = cbor_build_string(assert->rp_id)) == NULL ||
+ (argv[1] = fido_blob_encode(&assert->cdh)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* allowed credentials */
+ if (assert->allow_list.len) {
+ const fido_blob_array_t *cl = &assert->allow_list;
+ if ((argv[2] = encode_pubkey_list(cl)) == NULL) {
+ log_debug("%s: encode_pubkey_list", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ }
+
+ /* hmac-secret extension */
+ if (assert->ext & FIDO_EXT_HMAC_SECRET)
+ if ((argv[3] = encode_hmac_secret_param(ecdh, pk,
+ &assert->hmac_salt)) == NULL) {
+ log_debug("%s: encode_hmac_secret_param", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* options */
+ if (assert->up != FIDO_OPT_OMIT || assert->uv != FIDO_OPT_OMIT)
+ if ((argv[4] = encode_assert_options(assert->up,
+ assert->uv)) == NULL) {
+ log_debug("%s: encode_assert_options", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* pin authentication */
+ if (pin) {
+ if (pk == NULL || ecdh == NULL) {
+ log_debug("%s: pin=%p, pk=%p, ecdh=%p", __func__,
+ (const void *)pin, (const void *)pk,
+ (const void *)ecdh);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+ if ((r = add_cbor_pin_params(dev, &assert->cdh, pk, ecdh, pin,
+ &argv[5], &argv[6])) != FIDO_OK) {
+ log_debug("%s: add_cbor_pin_params", __func__);
+ goto fail;
+ }
+ }
+
+ /* frame and transmit */
+ if (cbor_build_frame(CTAP_CBOR_ASSERT, argv, 7, &f) < 0 ||
+ tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_get_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ fido_assert_reset_rx(assert);
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ /* start with room for a single assertion */
+ if ((assert->stmt = calloc(1, sizeof(fido_assert_stmt))) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ assert->stmt_len = 0;
+ assert->stmt_cnt = 1;
+
+ /* adjust as needed */
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, assert,
+ adjust_assert_count)) != FIDO_OK) {
+ log_debug("%s: adjust_assert_count", __func__);
+ return (r);
+ }
+
+ /* parse the first assertion */
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len,
+ &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) {
+ log_debug("%s: parse_assert_reply", __func__);
+ return (r);
+ }
+
+ assert->stmt_len++;
+
+ return (FIDO_OK);
+}
+
+static int
+fido_get_next_assert_tx(fido_dev_t *dev)
+{
+ const unsigned char cbor[] = { CTAP_CBOR_NEXT_ASSERT };
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+
+ if (tx(dev, cmd, cbor, sizeof(cbor)) < 0) {
+ log_debug("%s: tx", __func__);
+ return (FIDO_ERR_TX);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_get_next_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ /* sanity check */
+ if (assert->stmt_len >= assert->stmt_cnt) {
+ log_debug("%s: stmt_len=%zu, stmt_cnt=%zu", __func__,
+ assert->stmt_len, assert->stmt_cnt);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len,
+ &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) {
+ log_debug("%s: parse_assert_reply", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_dev_get_assert_wait(fido_dev_t *dev, fido_assert_t *assert,
+ const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, int ms)
+{
+ int r;
+
+ if ((r = fido_dev_get_assert_tx(dev, assert, pk, ecdh, pin)) != FIDO_OK ||
+ (r = fido_dev_get_assert_rx(dev, assert, ms)) != FIDO_OK)
+ return (r);
+
+ while (assert->stmt_len < assert->stmt_cnt) {
+ if ((r = fido_get_next_assert_tx(dev)) != FIDO_OK ||
+ (r = fido_get_next_assert_rx(dev, assert, ms)) != FIDO_OK)
+ return (r);
+ assert->stmt_len++;
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+decrypt_hmac_secrets(fido_assert_t *assert, const fido_blob_t *key)
+{
+ for (size_t i = 0; i < assert->stmt_cnt; i++) {
+ fido_assert_stmt *stmt = &assert->stmt[i];
+ if (stmt->hmac_secret_enc.ptr != NULL) {
+ if (aes256_cbc_dec(key, &stmt->hmac_secret_enc,
+ &stmt->hmac_secret) < 0) {
+ log_debug("%s: aes256_cbc_dec %zu", __func__, i);
+ return (-1);
+ }
+ }
+ }
+
+ return (0);
+}
+
+int
+fido_dev_get_assert(fido_dev_t *dev, fido_assert_t *assert, const char *pin)
+{
+ fido_blob_t *ecdh = NULL;
+ es256_pk_t *pk = NULL;
+ int r;
+
+ if (assert->rp_id == NULL || assert->cdh.ptr == NULL) {
+ log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__,
+ (void *)assert->rp_id, (void *)assert->cdh.ptr);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if (fido_dev_is_fido2(dev) == false) {
+ if (pin != NULL || assert->ext != 0)
+ return (FIDO_ERR_UNSUPPORTED_OPTION);
+ return (u2f_authenticate(dev, assert, -1));
+ }
+
+ if (pin != NULL || assert->ext != 0) {
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
+ log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ }
+
+ r = fido_dev_get_assert_wait(dev, assert, pk, ecdh, pin, -1);
+ if (r == FIDO_OK && assert->ext & FIDO_EXT_HMAC_SECRET)
+ if (decrypt_hmac_secrets(assert, ecdh) < 0) {
+ log_debug("%s: decrypt_hmac_secrets", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+fail:
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+
+ return (r);
+}
+
+int
+check_flags(uint8_t flags, fido_opt_t up, fido_opt_t uv)
+{
+ log_debug("%s: flags=%02x", __func__, flags);
+ log_debug("%s: up=%d, uv=%d", __func__, up, uv);
+
+ if (up == FIDO_OPT_TRUE &&
+ (flags & CTAP_AUTHDATA_USER_PRESENT) == 0) {
+ log_debug("%s: CTAP_AUTHDATA_USER_PRESENT", __func__);
+ return (-1); /* user not present */
+ }
+
+ if (uv == FIDO_OPT_TRUE &&
+ (flags & CTAP_AUTHDATA_USER_VERIFIED) == 0) {
+ log_debug("%s: CTAP_AUTHDATA_USER_VERIFIED", __func__);
+ return (-1); /* user not verified */
+ }
+
+ return (0);
+}
+
+static int
+check_extensions(int authdata_ext, int ext)
+{
+ if (authdata_ext != ext) {
+ log_debug("%s: authdata_ext=0x%x != ext=0x%x", __func__,
+ authdata_ext, ext);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+get_signed_hash(int cose_alg, fido_blob_t *dgst, const fido_blob_t *clientdata,
+ const fido_blob_t *authdata_cbor)
+{
+ cbor_item_t *item = NULL;
+ unsigned char *authdata_ptr = NULL;
+ size_t authdata_len;
+ struct cbor_load_result cbor;
+ SHA256_CTX ctx;
+ int ok = -1;
+
+ if ((item = cbor_load(authdata_cbor->ptr, authdata_cbor->len,
+ &cbor)) == NULL || cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false) {
+ log_debug("%s: authdata", __func__);
+ goto fail;
+ }
+
+ authdata_ptr = cbor_bytestring_handle(item);
+ authdata_len = cbor_bytestring_length(item);
+
+ if (cose_alg != COSE_EDDSA) {
+ if (dgst->len < SHA256_DIGEST_LENGTH || SHA256_Init(&ctx) == 0 ||
+ SHA256_Update(&ctx, authdata_ptr, authdata_len) == 0 ||
+ SHA256_Update(&ctx, clientdata->ptr, clientdata->len) == 0 ||
+ SHA256_Final(dgst->ptr, &ctx) == 0) {
+ log_debug("%s: sha256", __func__);
+ goto fail;
+ }
+ dgst->len = SHA256_DIGEST_LENGTH;
+ } else {
+ if (SIZE_MAX - authdata_len < clientdata->len ||
+ dgst->len < authdata_len + clientdata->len) {
+ log_debug("%s: memcpy", __func__);
+ goto fail;
+ }
+ memcpy(dgst->ptr, authdata_ptr, authdata_len);
+ memcpy(dgst->ptr + authdata_len, clientdata->ptr,
+ clientdata->len);
+ dgst->len = authdata_len + clientdata->len;
+ }
+
+ ok = 0;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (ok);
+}
+
+int
+verify_sig_es256(const fido_blob_t *dgst, const es256_pk_t *pk,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY *pkey = NULL;
+ EC_KEY *ec = NULL;
+ int ok = -1;
+
+ /* ECDSA_verify needs ints */
+ if (dgst->len > INT_MAX || sig->len > INT_MAX) {
+ log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__,
+ dgst->len, sig->len);
+ return (-1);
+ }
+
+ if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL ||
+ (ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL) {
+ log_debug("%s: pk -> ec", __func__);
+ goto fail;
+ }
+
+ if (ECDSA_verify(0, dgst->ptr, (int)dgst->len, sig->ptr,
+ (int)sig->len, ec) != 1) {
+ log_debug("%s: ECDSA_verify", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pkey != NULL)
+ EVP_PKEY_free(pkey);
+
+ return (ok);
+}
+
+int
+verify_sig_rs256(const fido_blob_t *dgst, const rs256_pk_t *pk,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY *pkey = NULL;
+ RSA *rsa = NULL;
+ int ok = -1;
+
+ /* RSA_verify needs unsigned ints */
+ if (dgst->len > UINT_MAX || sig->len > UINT_MAX) {
+ log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__,
+ dgst->len, sig->len);
+ return (-1);
+ }
+
+ if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL ||
+ (rsa = EVP_PKEY_get0_RSA(pkey)) == NULL) {
+ log_debug("%s: pk -> ec", __func__);
+ goto fail;
+ }
+
+ if (RSA_verify(NID_sha256, dgst->ptr, (unsigned int)dgst->len, sig->ptr,
+ (unsigned int)sig->len, rsa) != 1) {
+ log_debug("%s: RSA_verify", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pkey != NULL)
+ EVP_PKEY_free(pkey);
+
+ return (ok);
+}
+
+int
+verify_sig_eddsa(const fido_blob_t *dgst, const eddsa_pk_t *pk,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY *pkey = NULL;
+ EVP_MD_CTX *mdctx = NULL;
+ int ok = -1;
+
+ /* EVP_DigestVerify needs ints */
+ if (dgst->len > INT_MAX || sig->len > INT_MAX) {
+ log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__,
+ dgst->len, sig->len);
+ return (-1);
+ }
+
+ if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) {
+ log_debug("%s: pk -> pkey", __func__);
+ goto fail;
+ }
+
+ if ((mdctx = EVP_MD_CTX_new()) == NULL) {
+ log_debug("%s: EVP_MD_CTX_new", __func__);
+ goto fail;
+ }
+
+ if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1) {
+ log_debug("%s: EVP_DigestVerifyInit", __func__);
+ goto fail;
+ }
+
+ if (EVP_DigestVerify(mdctx, sig->ptr, sig->len, dgst->ptr,
+ dgst->len) != 1) {
+ log_debug("%s: EVP_DigestVerify", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (mdctx != NULL)
+ EVP_MD_CTX_free(mdctx);
+
+ if (pkey != NULL)
+ EVP_PKEY_free(pkey);
+
+ return (ok);
+}
+
+int
+fido_assert_verify(const fido_assert_t *assert, size_t idx, int cose_alg,
+ const void *pk)
+{
+ unsigned char buf[1024];
+ fido_blob_t dgst;
+ const fido_assert_stmt *stmt = NULL;
+ int ok = -1;
+ int r;
+
+ dgst.ptr = buf;
+ dgst.len = sizeof(buf);
+
+ if (idx >= assert->stmt_len || pk == NULL) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ stmt = &assert->stmt[idx];
+
+ /* do we have everything we need? */
+ if (assert->cdh.ptr == NULL || assert->rp_id == NULL ||
+ stmt->authdata_cbor.ptr == NULL || stmt->sig.ptr == NULL) {
+ log_debug("%s: cdh=%p, rp_id=%s, authdata=%p, sig=%p", __func__,
+ (void *)assert->cdh.ptr, assert->rp_id,
+ (void *)stmt->authdata_cbor.ptr, (void *)stmt->sig.ptr);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ if (check_flags(stmt->authdata.flags, assert->up, assert->uv) < 0) {
+ log_debug("%s: check_flags", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (check_extensions(stmt->authdata_ext, assert->ext) < 0) {
+ log_debug("%s: check_extensions", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (check_rp_id(assert->rp_id, stmt->authdata.rp_id_hash) != 0) {
+ log_debug("%s: check_rp_id", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (get_signed_hash(cose_alg, &dgst, &assert->cdh,
+ &stmt->authdata_cbor) < 0) {
+ log_debug("%s: get_signed_hash", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ switch (cose_alg) {
+ case COSE_ES256:
+ ok = verify_sig_es256(&dgst, pk, &stmt->sig);
+ break;
+ case COSE_RS256:
+ ok = verify_sig_rs256(&dgst, pk, &stmt->sig);
+ break;
+ case COSE_EDDSA:
+ ok = verify_sig_eddsa(&dgst, pk, &stmt->sig);
+ break;
+ default:
+ log_debug("%s: unsupported cose_alg %d", __func__, cose_alg);
+ r = FIDO_ERR_UNSUPPORTED_OPTION;
+ goto out;
+ }
+
+ if (ok < 0)
+ r = FIDO_ERR_INVALID_SIG;
+ else
+ r = FIDO_OK;
+out:
+ explicit_bzero(buf, sizeof(buf));
+
+ return (r);
+}
+
+int
+fido_assert_set_clientdata_hash(fido_assert_t *assert,
+ const unsigned char *hash, size_t hash_len)
+{
+ if (fido_blob_set(&assert->cdh, hash, hash_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_hmac_salt(fido_assert_t *assert, const unsigned char *salt,
+ size_t salt_len)
+{
+ if ((salt_len != 32 && salt_len != 64) ||
+ fido_blob_set(&assert->hmac_salt, salt, salt_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_rp(fido_assert_t *assert, const char *id)
+{
+ if (assert->rp_id != NULL) {
+ free(assert->rp_id);
+ assert->rp_id = NULL;
+ }
+
+ if (id == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((assert->rp_id = strdup(id)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_allow_cred(fido_assert_t *assert, const unsigned char *ptr,
+ size_t len)
+{
+ fido_blob_t id;
+ fido_blob_t *list_ptr;
+ int r;
+
+ memset(&id, 0, sizeof(id));
+
+ if (assert->allow_list.len == SIZE_MAX) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if (fido_blob_set(&id, ptr, len) < 0 || (list_ptr =
+ recallocarray(assert->allow_list.ptr, assert->allow_list.len,
+ assert->allow_list.len + 1, sizeof(fido_blob_t))) == NULL) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ list_ptr[assert->allow_list.len++] = id;
+ assert->allow_list.ptr = list_ptr;
+
+ return (FIDO_OK);
+fail:
+ free(id.ptr);
+
+ return (r);
+
+}
+
+int
+fido_assert_set_extensions(fido_assert_t *assert, int ext)
+{
+ if (ext != 0 && ext != FIDO_EXT_HMAC_SECRET)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ assert->ext = ext;
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_options(fido_assert_t *assert, bool up, bool uv)
+{
+ assert->up = up ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
+ assert->uv = uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_up(fido_assert_t *assert, fido_opt_t up)
+{
+ assert->up = up;
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_uv(fido_assert_t *assert, fido_opt_t uv)
+{
+ assert->uv = uv;
+
+ return (FIDO_OK);
+}
+
+const unsigned char *
+fido_assert_clientdata_hash_ptr(const fido_assert_t *assert)
+{
+ return (assert->cdh.ptr);
+}
+
+size_t
+fido_assert_clientdata_hash_len(const fido_assert_t *assert)
+{
+ return (assert->cdh.len);
+}
+
+fido_assert_t *
+fido_assert_new(void)
+{
+ return (calloc(1, sizeof(fido_assert_t)));
+}
+
+void
+fido_assert_reset_tx(fido_assert_t *assert)
+{
+ free(assert->rp_id);
+ free(assert->cdh.ptr);
+ free(assert->hmac_salt.ptr);
+ free_blob_array(&assert->allow_list);
+
+ memset(&assert->cdh, 0, sizeof(assert->cdh));
+ memset(&assert->hmac_salt, 0, sizeof(assert->hmac_salt));
+ memset(&assert->allow_list, 0, sizeof(assert->allow_list));
+
+ assert->rp_id = NULL;
+ assert->up = FIDO_OPT_OMIT;
+ assert->uv = FIDO_OPT_OMIT;
+ assert->ext = 0;
+}
+
+void
+fido_assert_reset_rx(fido_assert_t *assert)
+{
+ for (size_t i = 0; i < assert->stmt_cnt; i++) {
+ free(assert->stmt[i].user.id.ptr);
+ free(assert->stmt[i].user.icon);
+ free(assert->stmt[i].user.name);
+ free(assert->stmt[i].user.display_name);
+ free(assert->stmt[i].id.ptr);
+ if (assert->stmt[i].hmac_secret.ptr != NULL) {
+ explicit_bzero(assert->stmt[i].hmac_secret.ptr,
+ assert->stmt[i].hmac_secret.len);
+ }
+ free(assert->stmt[i].hmac_secret.ptr);
+ free(assert->stmt[i].hmac_secret_enc.ptr);
+ free(assert->stmt[i].authdata_cbor.ptr);
+ free(assert->stmt[i].sig.ptr);
+ memset(&assert->stmt[i], 0, sizeof(assert->stmt[i]));
+ }
+
+ free(assert->stmt);
+
+ assert->stmt = NULL;
+ assert->stmt_len = 0;
+ assert->stmt_cnt = 0;
+}
+
+void
+fido_assert_free(fido_assert_t **assert_p)
+{
+ fido_assert_t *assert;
+
+ if (assert_p == NULL || (assert = *assert_p) == NULL)
+ return;
+
+ fido_assert_reset_tx(assert);
+ fido_assert_reset_rx(assert);
+
+ free(assert);
+
+ *assert_p = NULL;
+}
+
+size_t
+fido_assert_count(const fido_assert_t *assert)
+{
+ return (assert->stmt_len);
+}
+
+const char *
+fido_assert_rp_id(const fido_assert_t *assert)
+{
+ return (assert->rp_id);
+}
+
+uint8_t
+fido_assert_flags(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].authdata.flags);
+}
+
+uint32_t
+fido_assert_sigcount(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].authdata.sigcount);
+}
+
+const unsigned char *
+fido_assert_authdata_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].authdata_cbor.ptr);
+}
+
+size_t
+fido_assert_authdata_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].authdata_cbor.len);
+}
+
+const unsigned char *
+fido_assert_sig_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].sig.ptr);
+}
+
+size_t
+fido_assert_sig_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].sig.len);
+}
+
+const unsigned char *
+fido_assert_id_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].id.ptr);
+}
+
+size_t
+fido_assert_id_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].id.len);
+}
+
+const unsigned char *
+fido_assert_user_id_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].user.id.ptr);
+}
+
+size_t
+fido_assert_user_id_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].user.id.len);
+}
+
+const char *
+fido_assert_user_icon(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].user.icon);
+}
+
+const char *
+fido_assert_user_name(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].user.name);
+}
+
+const char *
+fido_assert_user_display_name(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].user.display_name);
+}
+
+const unsigned char *
+fido_assert_hmac_secret_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].hmac_secret.ptr);
+}
+
+size_t
+fido_assert_hmac_secret_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].hmac_secret.len);
+}
+
+static void
+fido_assert_clean_authdata(fido_assert_stmt *as)
+{
+ free(as->authdata_cbor.ptr);
+ free(as->hmac_secret_enc.ptr);
+
+ memset(&as->authdata_ext, 0, sizeof(as->authdata_ext));
+ memset(&as->authdata_cbor, 0, sizeof(as->authdata_cbor));
+ memset(&as->authdata, 0, sizeof(as->authdata));
+ memset(&as->hmac_secret_enc, 0, sizeof(as->hmac_secret_enc));
+}
+
+int
+fido_assert_set_authdata(fido_assert_t *assert, size_t idx,
+ const unsigned char *ptr, size_t len)
+{
+ cbor_item_t *item = NULL;
+ fido_assert_stmt *stmt = NULL;
+ struct cbor_load_result cbor;
+ int r;
+
+ if (idx >= assert->stmt_len || ptr == NULL || len == 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ stmt = &assert->stmt[idx];
+ fido_assert_clean_authdata(stmt);
+
+ if ((item = cbor_load(ptr, len, &cbor)) == NULL) {
+ log_debug("%s: cbor_load", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if (decode_assert_authdata(item, &stmt->authdata_cbor, &stmt->authdata,
+ &stmt->authdata_ext, &stmt->hmac_secret_enc) < 0) {
+ log_debug("%s: decode_assert_authdata", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ if (r != FIDO_OK)
+ fido_assert_clean_authdata(stmt);
+
+ return (r);
+}
+
+int
+fido_assert_set_authdata_raw(fido_assert_t *assert, size_t idx,
+ const unsigned char *ptr, size_t len)
+{
+ cbor_item_t *item = NULL;
+ fido_assert_stmt *stmt = NULL;
+ int r;
+
+ if (idx >= assert->stmt_len || ptr == NULL || len == 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ stmt = &assert->stmt[idx];
+ fido_assert_clean_authdata(stmt);
+
+ if ((item = cbor_build_bytestring(ptr, len)) == NULL) {
+ log_debug("%s: cbor_build_bytestring", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (decode_assert_authdata(item, &stmt->authdata_cbor, &stmt->authdata,
+ &stmt->authdata_ext, &stmt->hmac_secret_enc) < 0) {
+ log_debug("%s: decode_assert_authdata", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ if (r != FIDO_OK)
+ fido_assert_clean_authdata(stmt);
+
+ return (r);
+}
+
+static void
+fido_assert_clean_sig(fido_assert_stmt *as)
+{
+ free(as->sig.ptr);
+ as->sig.ptr = NULL;
+ as->sig.len = 0;
+}
+
+int
+fido_assert_set_sig(fido_assert_t *a, size_t idx, const unsigned char *ptr,
+ size_t len)
+{
+ unsigned char *sig;
+
+ if (idx >= a->stmt_len || ptr == NULL || len == 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ fido_assert_clean_sig(&a->stmt[idx]);
+
+ if ((sig = malloc(len)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ memcpy(sig, ptr, len);
+ a->stmt[idx].sig.ptr = sig;
+ a->stmt[idx].sig.len = len;
+
+ return (FIDO_OK);
+}
+
+/* XXX shrinking leaks memory; fortunately that shouldn't happen */
+int
+fido_assert_set_count(fido_assert_t *assert, size_t n)
+{
+ void *new_stmt;
+
+#ifdef FIDO_FUZZ
+ if (n > UINT8_MAX) {
+ log_debug("%s: n > UINT8_MAX", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+#endif
+
+ new_stmt = recallocarray(assert->stmt, assert->stmt_cnt, n,
+ sizeof(fido_assert_stmt));
+ if (new_stmt == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ assert->stmt = new_stmt;
+ assert->stmt_cnt = n;
+ assert->stmt_len = n;
+
+ return (FIDO_OK);
+}
diff --git a/lib/libfido2/src/authkey.c b/lib/libfido2/src/authkey.c
new file mode 100644
index 00000000000..eec797d28b3
--- /dev/null
+++ b/lib/libfido2/src/authkey.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <string.h>
+#include "fido.h"
+
+static int
+parse_authkey(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ es256_pk_t *authkey = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 1) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ return (es256_pk_decode(val, authkey));
+}
+
+static int
+fido_dev_authkey_tx(fido_dev_t *dev)
+{
+ fido_blob_t f;
+ cbor_item_t *argv[2];
+ int r;
+
+ log_debug("%s: dev=%p", __func__, (void *)dev);
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ /* add command parameters */
+ if ((argv[0] = cbor_build_uint8(1)) == NULL ||
+ (argv[1] = cbor_build_uint8(2)) == NULL) {
+ log_debug("%s: cbor_build", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* frame and transmit */
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, 2, &f) < 0 ||
+ tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_authkey_rx(fido_dev_t *dev, es256_pk_t *authkey, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+
+ log_debug("%s: dev=%p, authkey=%p, ms=%d", __func__, (void *)dev,
+ (void *)authkey, ms);
+
+ memset(authkey, 0, sizeof(*authkey));
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ return (parse_cbor_reply(reply, (size_t)reply_len, authkey,
+ parse_authkey));
+}
+
+static int
+fido_dev_authkey_wait(fido_dev_t *dev, es256_pk_t *authkey, int ms)
+{
+ int r;
+
+ if ((r = fido_dev_authkey_tx(dev)) != FIDO_OK ||
+ (r = fido_dev_authkey_rx(dev, authkey, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_authkey(fido_dev_t *dev, es256_pk_t *authkey)
+{
+ return (fido_dev_authkey_wait(dev, authkey, -1));
+}
diff --git a/lib/libfido2/src/bio.c b/lib/libfido2/src/bio.c
new file mode 100644
index 00000000000..a5cfade1f23
--- /dev/null
+++ b/lib/libfido2/src/bio.c
@@ -0,0 +1,844 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <string.h>
+
+#include "fido.h"
+#include "fido/bio.h"
+#include "fido/es256.h"
+
+#define CMD_ENROLL_BEGIN 0x01
+#define CMD_ENROLL_NEXT 0x02
+#define CMD_ENROLL_CANCEL 0x03
+#define CMD_ENUM 0x04
+#define CMD_SET_NAME 0x05
+#define CMD_ENROLL_REMOVE 0x06
+#define CMD_GET_INFO 0x07
+
+static int
+bio_prepare_hmac(uint8_t cmd, cbor_item_t **argv, size_t argc,
+ cbor_item_t **param, fido_blob_t *hmac_data)
+{
+ const uint8_t prefix[2] = { 0x01 /* modality */, cmd };
+ int ok = -1;
+ size_t cbor_alloc_len;
+ size_t cbor_len;
+ unsigned char *cbor = NULL;
+
+ if (argv == NULL || param == NULL)
+ return (fido_blob_set(hmac_data, prefix, sizeof(prefix)));
+
+ if ((*param = cbor_flatten_vector(argv, argc)) == NULL) {
+ log_debug("%s: cbor_flatten_vector", __func__);
+ goto fail;
+ }
+
+ if ((cbor_len = cbor_serialize_alloc(*param, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > SIZE_MAX - sizeof(prefix)) {
+ log_debug("%s: cbor_serialize_alloc", __func__);
+ goto fail;
+ }
+
+ if ((hmac_data->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) {
+ log_debug("%s: malloc", __func__);
+ goto fail;
+ }
+
+ memcpy(hmac_data->ptr, prefix, sizeof(prefix));
+ memcpy(hmac_data->ptr + sizeof(prefix), cbor, cbor_len);
+ hmac_data->len = cbor_len + sizeof(prefix);
+
+ ok = 0;
+fail:
+ free(cbor);
+
+ return (ok);
+}
+
+static int
+bio_tx(fido_dev_t *dev, uint8_t cmd, cbor_item_t **sub_argv, size_t sub_argc,
+ const char *pin, const fido_blob_t *token)
+{
+ cbor_item_t *argv[5];
+ es256_pk_t *pk = NULL;
+ fido_blob_t *ecdh = NULL;
+ fido_blob_t f;
+ fido_blob_t hmac;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&f, 0, sizeof(f));
+ memset(&hmac, 0, sizeof(hmac));
+ memset(&argv, 0, sizeof(argv));
+
+ /* modality, subCommand */
+ if ((argv[0] = cbor_build_uint8(1)) == NULL ||
+ (argv[1] = cbor_build_uint8(cmd)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ /* subParams */
+ if (pin || token) {
+ if (bio_prepare_hmac(cmd, sub_argv, sub_argc, &argv[2],
+ &hmac) < 0) {
+ log_debug("%s: bio_prepare_hmac", __func__);
+ goto fail;
+ }
+ }
+
+ /* pinProtocol, pinAuth */
+ if (pin) {
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
+ log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ if ((r = add_cbor_pin_params(dev, &hmac, pk, ecdh, pin,
+ &argv[4], &argv[3])) != FIDO_OK) {
+ log_debug("%s: add_cbor_pin_params", __func__);
+ goto fail;
+ }
+ } else if (token) {
+ if ((argv[3] = encode_pin_opt()) == NULL ||
+ (argv[4] = encode_pin_auth(token, &hmac)) == NULL) {
+ log_debug("%s: encode pin", __func__);
+ goto fail;
+ }
+ }
+
+ /* framing and transmission */
+ if (cbor_build_frame(CTAP_CBOR_BIO_ENROLL_PRE, argv, 5, &f) < 0 ||
+ tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+ free(f.ptr);
+ free(hmac.ptr);
+
+ return (r);
+}
+
+static void
+bio_reset_template(fido_bio_template_t *t)
+{
+ free(t->name);
+ free(t->id.ptr);
+ t->name = NULL;
+ memset(&t->id, 0, sizeof(t->id));
+}
+
+static void
+bio_reset_template_array(fido_bio_template_array_t *ta)
+{
+ for (size_t i = 0; i < ta->n_alloc; i++)
+ bio_reset_template(&ta->ptr[i]);
+
+ free(ta->ptr);
+ ta->ptr = NULL;
+ memset(ta, 0, sizeof(*ta));
+}
+
+static int
+decode_template(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_bio_template_t *t = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* id */
+ return (fido_blob_decode(val, &t->id));
+ case 2: /* name */
+ return (cbor_string_copy(val, &t->name));
+ }
+
+ return (0); /* ignore */
+}
+
+static int
+decode_template_array(const cbor_item_t *item, void *arg)
+{
+ fido_bio_template_array_t *ta = arg;
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (ta->n_rx >= ta->n_alloc) {
+ log_debug("%s: n_rx >= n_alloc", __func__);
+ return (-1);
+ }
+
+ if (cbor_map_iter(item, &ta->ptr[ta->n_rx], decode_template) < 0) {
+ log_debug("%s: decode_template", __func__);
+ return (-1);
+ }
+
+ ta->n_rx++;
+
+ return (0);
+}
+
+static int
+bio_parse_template_array(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_bio_template_array_t *ta = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 7) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (cbor_isa_array(val) == false ||
+ cbor_array_is_definite(val) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (ta->ptr != NULL || ta->n_alloc != 0 || ta->n_rx != 0) {
+ log_debug("%s: ptr != NULL || n_alloc != 0 || n_rx != 0",
+ __func__);
+ return (-1);
+ }
+
+ if ((ta->ptr = calloc(cbor_array_size(val), sizeof(*ta->ptr))) == NULL)
+ return (-1);
+
+ ta->n_alloc = cbor_array_size(val);
+
+ if (cbor_array_iter(val, ta, decode_template_array) < 0) {
+ log_debug("%s: decode_template_array", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+bio_rx_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ bio_reset_template_array(ta);
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, ta,
+ bio_parse_template_array)) != FIDO_OK) {
+ log_debug("%s: bio_parse_template_array" , __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+bio_get_template_array_wait(fido_dev_t *dev, fido_bio_template_array_t *ta,
+ const char *pin, int ms)
+{
+ int r;
+
+ if ((r = bio_tx(dev, CMD_ENUM, NULL, 0, pin, NULL)) != FIDO_OK ||
+ (r = bio_rx_template_array(dev, ta, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_bio_dev_get_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta,
+ const char *pin)
+{
+ if (pin == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (bio_get_template_array_wait(dev, ta, pin, -1));
+}
+
+static int
+bio_set_template_name_wait(fido_dev_t *dev, const fido_bio_template_t *t,
+ const char *pin, int ms)
+{
+ cbor_item_t *argv[2];
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&argv, 0, sizeof(argv));
+
+ if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
+ (argv[1] = cbor_build_string(t->name)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if ((r = bio_tx(dev, CMD_SET_NAME, argv, 2, pin, NULL)) != FIDO_OK ||
+ (r = rx_cbor_status(dev, ms)) != FIDO_OK) {
+ log_debug("%s: tx/rx", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ return (r);
+}
+
+int
+fido_bio_dev_set_template_name(fido_dev_t *dev, const fido_bio_template_t *t,
+ const char *pin)
+{
+ if (pin == NULL || t->name == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (bio_set_template_name_wait(dev, t, pin, -1));
+}
+
+static void
+bio_reset_enroll(fido_bio_enroll_t *e)
+{
+ e->remaining_samples = 0;
+ e->last_status = 0;
+
+ if (e->token)
+ fido_blob_free(&e->token);
+}
+
+static int
+bio_parse_enroll_status(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_bio_enroll_t *e = arg;
+ uint64_t x;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 5:
+ if (decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
+ log_debug("%s: decode_uint64", __func__);
+ return (-1);
+ }
+ e->last_status = (uint8_t)x;
+ break;
+ case 6:
+ if (decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
+ log_debug("%s: decode_uint64", __func__);
+ return (-1);
+ }
+ e->remaining_samples = (uint8_t)x;
+ break;
+ default:
+ return (0); /* ignore */
+ }
+
+ return (0);
+}
+
+static int
+bio_parse_template_id(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_blob_t *id = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 4) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ return (fido_blob_decode(val, id));
+}
+
+static int
+bio_rx_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
+ fido_bio_enroll_t *e, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ bio_reset_template(t);
+
+ e->remaining_samples = 0;
+ e->last_status = 0;
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, e,
+ bio_parse_enroll_status)) != FIDO_OK) {
+ log_debug("%s: bio_parse_enroll_status", __func__);
+ return (r);
+ }
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, &t->id,
+ bio_parse_template_id)) != FIDO_OK) {
+ log_debug("%s: bio_parse_template_id", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+bio_enroll_begin_wait(fido_dev_t *dev, fido_bio_template_t *t,
+ fido_bio_enroll_t *e, uint32_t timo_ms, int ms)
+{
+ cbor_item_t *argv[3];
+ const uint8_t cmd = CMD_ENROLL_BEGIN;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&argv, 0, sizeof(argv));
+
+ if ((argv[2] = cbor_build_uint32(timo_ms)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token)) != FIDO_OK ||
+ (r = bio_rx_enroll_begin(dev, t, e, ms)) != FIDO_OK) {
+ log_debug("%s: tx/rx", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ return (r);
+}
+
+int
+fido_bio_dev_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
+ fido_bio_enroll_t *e, uint32_t timo_ms, const char *pin)
+{
+ es256_pk_t *pk = NULL;
+ fido_blob_t *ecdh = NULL;
+ fido_blob_t *token = NULL;
+ int r;
+
+ if (pin == NULL || e->token != NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((token = fido_blob_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
+ log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+
+ if ((r = fido_dev_get_pin_token(dev, pin, ecdh, pk, token)) != FIDO_OK) {
+ log_debug("%s: fido_dev_get_pin_token", __func__);
+ goto fail;
+ }
+
+ e->token = token;
+ token = NULL;
+fail:
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+ fido_blob_free(&token);
+
+ if (r != FIDO_OK)
+ return (r);
+
+ return (bio_enroll_begin_wait(dev, t, e, timo_ms, -1));
+}
+
+static int
+bio_rx_enroll_continue(fido_dev_t *dev, fido_bio_enroll_t *e, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ e->remaining_samples = 0;
+ e->last_status = 0;
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, e,
+ bio_parse_enroll_status)) != FIDO_OK) {
+ log_debug("%s: bio_parse_enroll_status", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+bio_enroll_continue_wait(fido_dev_t *dev, const fido_bio_template_t *t,
+ fido_bio_enroll_t *e, uint32_t timo_ms, int ms)
+{
+ cbor_item_t *argv[3];
+ const uint8_t cmd = CMD_ENROLL_NEXT;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&argv, 0, sizeof(argv));
+
+ if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
+ (argv[2] = cbor_build_uint32(timo_ms)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token)) != FIDO_OK ||
+ (r = bio_rx_enroll_continue(dev, e, ms)) != FIDO_OK) {
+ log_debug("%s: tx/rx", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ return (r);
+}
+
+int
+fido_bio_dev_enroll_continue(fido_dev_t *dev, const fido_bio_template_t *t,
+ fido_bio_enroll_t *e, uint32_t timo_ms)
+{
+ if (e->token == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (bio_enroll_continue_wait(dev, t, e, timo_ms, -1));
+}
+
+static int
+bio_enroll_cancel_wait(fido_dev_t *dev, int ms)
+{
+ const uint8_t cmd = CMD_ENROLL_CANCEL;
+ int r;
+
+ if ((r = bio_tx(dev, cmd, NULL, 0, NULL, NULL)) != FIDO_OK ||
+ (r = rx_cbor_status(dev, ms)) != FIDO_OK) {
+ log_debug("%s: tx/rx", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_bio_dev_enroll_cancel(fido_dev_t *dev)
+{
+ return (bio_enroll_cancel_wait(dev, -1));
+}
+
+static int
+bio_enroll_remove_wait(fido_dev_t *dev, const fido_bio_template_t *t,
+ const char *pin, int ms)
+{
+ cbor_item_t *argv[1];
+ const uint8_t cmd = CMD_ENROLL_REMOVE;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&argv, 0, sizeof(argv));
+
+ if ((argv[0] = fido_blob_encode(&t->id)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if ((r = bio_tx(dev, cmd, argv, 1, pin, NULL)) != FIDO_OK ||
+ (r = rx_cbor_status(dev, ms)) != FIDO_OK) {
+ log_debug("%s: tx/rx", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ return (r);
+}
+
+int
+fido_bio_dev_enroll_remove(fido_dev_t *dev, const fido_bio_template_t *t,
+ const char *pin)
+{
+ return (bio_enroll_remove_wait(dev, t, pin, -1));
+}
+
+static void
+bio_reset_info(fido_bio_info_t *i)
+{
+ i->type = 0;
+ i->max_samples = 0;
+}
+
+static int
+bio_parse_info(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_bio_info_t *i = arg;
+ uint64_t x;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 2:
+ if (decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
+ log_debug("%s: decode_uint64", __func__);
+ return (-1);
+ }
+ i->type = (uint8_t)x;
+ break;
+ case 3:
+ if (decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
+ log_debug("%s: decode_uint64", __func__);
+ return (-1);
+ }
+ i->max_samples = (uint8_t)x;
+ break;
+ default:
+ return (0); /* ignore */
+ }
+
+ return (0);
+}
+
+static int
+bio_rx_info(fido_dev_t *dev, fido_bio_info_t *i, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ bio_reset_info(i);
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, i,
+ bio_parse_info)) != FIDO_OK) {
+ log_debug("%s: bio_parse_info" , __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+bio_get_info_wait(fido_dev_t *dev, fido_bio_info_t *i, int ms)
+{
+ int r;
+
+ if ((r = bio_tx(dev, CMD_GET_INFO, NULL, 0, NULL, NULL)) != FIDO_OK ||
+ (r = bio_rx_info(dev, i, ms)) != FIDO_OK) {
+ log_debug("%s: tx/rx", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_bio_dev_get_info(fido_dev_t *dev, fido_bio_info_t *i)
+{
+ return (bio_get_info_wait(dev, i, -1));
+}
+
+const char *
+fido_bio_template_name(const fido_bio_template_t *t)
+{
+ return (t->name);
+}
+
+const unsigned char *
+fido_bio_template_id_ptr(const fido_bio_template_t *t)
+{
+ return (t->id.ptr);
+}
+
+size_t
+fido_bio_template_id_len(const fido_bio_template_t *t)
+{
+ return (t->id.len);
+}
+
+size_t
+fido_bio_template_array_count(const fido_bio_template_array_t *ta)
+{
+ return (ta->n_rx);
+}
+
+fido_bio_template_array_t *
+fido_bio_template_array_new(void)
+{
+ return (calloc(1, sizeof(fido_bio_template_array_t)));
+}
+
+fido_bio_template_t *
+fido_bio_template_new(void)
+{
+ return (calloc(1, sizeof(fido_bio_template_t)));
+}
+
+void
+fido_bio_template_array_free(fido_bio_template_array_t **tap)
+{
+ fido_bio_template_array_t *ta;
+
+ if (tap == NULL || (ta = *tap) == NULL)
+ return;
+
+ bio_reset_template_array(ta);
+ free(ta);
+ *tap = NULL;
+}
+
+void
+fido_bio_template_free(fido_bio_template_t **tp)
+{
+ fido_bio_template_t *t;
+
+ if (tp == NULL || (t = *tp) == NULL)
+ return;
+
+ bio_reset_template(t);
+ free(t);
+ *tp = NULL;
+}
+
+int
+fido_bio_template_set_name(fido_bio_template_t *t, const char *name)
+{
+ free(t->name);
+ t->name = NULL;
+
+ if (name && (t->name = strdup(name)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+int
+fido_bio_template_set_id(fido_bio_template_t *t, const unsigned char *ptr,
+ size_t len)
+{
+ free(t->id.ptr);
+ t->id.ptr = NULL;
+ t->id.len = 0;
+
+ if (ptr && fido_blob_set(&t->id, ptr, len) < 0)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+const fido_bio_template_t *
+fido_bio_template(const fido_bio_template_array_t *ta, size_t idx)
+{
+ if (idx >= ta->n_alloc)
+ return (NULL);
+
+ return (&ta->ptr[idx]);
+}
+
+fido_bio_enroll_t *
+fido_bio_enroll_new(void)
+{
+ return (calloc(1, sizeof(fido_bio_enroll_t)));
+}
+
+fido_bio_info_t *
+fido_bio_info_new(void)
+{
+ return (calloc(1, sizeof(fido_bio_info_t)));
+}
+
+uint8_t
+fido_bio_info_type(const fido_bio_info_t *i)
+{
+ return (i->type);
+}
+
+uint8_t
+fido_bio_info_max_samples(const fido_bio_info_t *i)
+{
+ return (i->max_samples);
+}
+
+void
+fido_bio_enroll_free(fido_bio_enroll_t **ep)
+{
+ fido_bio_enroll_t *e;
+
+ if (ep == NULL || (e = *ep) == NULL)
+ return;
+
+ bio_reset_enroll(e);
+
+ free(e);
+ *ep = NULL;
+}
+
+void
+fido_bio_info_free(fido_bio_info_t **ip)
+{
+ fido_bio_info_t *i;
+
+ if (ip == NULL || (i = *ip) == NULL)
+ return;
+
+ free(i);
+ *ip = NULL;
+}
+
+uint8_t
+fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *e)
+{
+ return (e->remaining_samples);
+}
+
+uint8_t
+fido_bio_enroll_last_status(const fido_bio_enroll_t *e)
+{
+ return (e->last_status);
+}
diff --git a/lib/libfido2/src/blob.c b/lib/libfido2/src/blob.c
new file mode 100644
index 00000000000..5415d87c74d
--- /dev/null
+++ b/lib/libfido2/src/blob.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <string.h>
+#include "fido.h"
+
+fido_blob_t *
+fido_blob_new(void)
+{
+ return (calloc(1, sizeof(fido_blob_t)));
+}
+
+int
+fido_blob_set(fido_blob_t *b, const unsigned char *ptr, size_t len)
+{
+ if (b->ptr != NULL) {
+ explicit_bzero(b->ptr, b->len);
+ free(b->ptr);
+ b->ptr = NULL;
+ }
+
+ b->len = 0;
+
+ if (ptr == NULL || len == 0) {
+ log_debug("%s: ptr=%p, len=%zu", __func__, (const void *)ptr,
+ len);
+ return (-1);
+ }
+
+ if ((b->ptr = malloc(len)) == NULL) {
+ log_debug("%s: malloc", __func__);
+ return (-1);
+ }
+
+ memcpy(b->ptr, ptr, len);
+ b->len = len;
+
+ return (0);
+}
+
+void
+fido_blob_free(fido_blob_t **bp)
+{
+ fido_blob_t *b;
+
+ if (bp == NULL || (b = *bp) == NULL)
+ return;
+
+ if (b->ptr) {
+ explicit_bzero(b->ptr, b->len);
+ free(b->ptr);
+ }
+
+ explicit_bzero(b, sizeof(*b));
+ free(b);
+
+ *bp = NULL;
+}
+
+void
+free_blob_array(fido_blob_array_t *array)
+{
+ if (array->ptr == NULL)
+ return;
+
+ for (size_t i = 0; i < array->len; i++) {
+ fido_blob_t *b = &array->ptr[i];
+ if (b->ptr != NULL) {
+ explicit_bzero(b->ptr, b->len);
+ free(b->ptr);
+ b->ptr = NULL;
+ }
+ }
+
+ free(array->ptr);
+ array->ptr = NULL;
+ array->len = 0;
+}
+
+cbor_item_t *
+fido_blob_encode(const fido_blob_t *b)
+{
+ if (b == NULL || b->ptr == NULL)
+ return (NULL);
+
+ return (cbor_build_bytestring(b->ptr, b->len));
+}
+
+int
+fido_blob_decode(const cbor_item_t *item, fido_blob_t *b)
+{
+ return (cbor_bytestring_copy(item, &b->ptr, &b->len));
+}
+
+int
+fido_blob_is_empty(const fido_blob_t *b)
+{
+ return (b->ptr == NULL || b->len == 0);
+}
diff --git a/lib/libfido2/src/blob.h b/lib/libfido2/src/blob.h
new file mode 100644
index 00000000000..6358c0594ae
--- /dev/null
+++ b/lib/libfido2/src/blob.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _BLOB_H
+#define _BLOB_H
+
+typedef struct fido_blob {
+ unsigned char *ptr;
+ size_t len;
+} fido_blob_t;
+
+typedef struct fido_blob_array {
+ fido_blob_t *ptr;
+ size_t len;
+} fido_blob_array_t;
+
+cbor_item_t *fido_blob_encode(const fido_blob_t *);
+fido_blob_t *fido_blob_new(void);
+int fido_blob_decode(const cbor_item_t *, fido_blob_t *);
+int fido_blob_is_empty(const fido_blob_t *);
+int fido_blob_set(fido_blob_t *, const unsigned char *, size_t);
+void fido_blob_free(fido_blob_t **);
+void free_blob_array(fido_blob_array_t *);
+
+#endif /* !_BLOB_H */
diff --git a/lib/libfido2/src/buf.c b/lib/libfido2/src/buf.c
new file mode 100644
index 00000000000..67ff95a2d36
--- /dev/null
+++ b/lib/libfido2/src/buf.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <string.h>
+#include "fido.h"
+
+int
+buf_read(const unsigned char **buf, size_t *len, void *dst, size_t count)
+{
+ if (count > *len)
+ return (-1);
+
+ memcpy(dst, *buf, count);
+ *buf += count;
+ *len -= count;
+
+ return (0);
+}
+
+int
+buf_write(unsigned char **buf, size_t *len, const void *src, size_t count)
+{
+ if (count > *len)
+ return (-1);
+
+ memcpy(*buf, src, count);
+ *buf += count;
+ *len -= count;
+
+ return (0);
+}
diff --git a/lib/libfido2/src/cbor.c b/lib/libfido2/src/cbor.c
new file mode 100644
index 00000000000..40e76223ead
--- /dev/null
+++ b/lib/libfido2/src/cbor.c
@@ -0,0 +1,1514 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#include <string.h>
+#include "fido.h"
+
+static int
+check_key_type(cbor_item_t *item)
+{
+ if (item->type == CBOR_TYPE_UINT || item->type == CBOR_TYPE_NEGINT ||
+ item->type == CBOR_TYPE_STRING)
+ return (0);
+
+ log_debug("%s: invalid type: %d", __func__, item->type);
+
+ return (-1);
+}
+
+/*
+ * Validate CTAP2 canonical CBOR encoding rules for maps.
+ */
+static int
+ctap_check_cbor(cbor_item_t *prev, cbor_item_t *curr)
+{
+ size_t curr_len;
+ size_t prev_len;
+
+ if (check_key_type(prev) < 0 || check_key_type(curr) < 0)
+ return (-1);
+
+ if (prev->type != curr->type) {
+ if (prev->type < curr->type)
+ return (0);
+ log_debug("%s: unsorted types", __func__);
+ return (-1);
+ }
+
+ if (curr->type == CBOR_TYPE_UINT || curr->type == CBOR_TYPE_NEGINT) {
+ if (cbor_int_get_width(curr) >= cbor_int_get_width(prev) &&
+ cbor_get_int(curr) > cbor_get_int(prev))
+ return (0);
+ } else {
+ curr_len = cbor_string_length(curr);
+ prev_len = cbor_string_length(prev);
+
+ if (curr_len > prev_len || (curr_len == prev_len &&
+ memcmp(cbor_string_handle(prev), cbor_string_handle(curr),
+ curr_len) < 0))
+ return (0);
+ }
+
+ log_debug("%s: invalid cbor", __func__);
+
+ return (-1);
+}
+
+int
+cbor_map_iter(const cbor_item_t *item, void *arg, int(*f)(const cbor_item_t *,
+ const cbor_item_t *, void *))
+{
+ struct cbor_pair *v;
+ size_t n;
+
+ if ((v = cbor_map_handle(item)) == NULL) {
+ log_debug("%s: cbor_map_handle", __func__);
+ return (-1);
+ }
+
+ n = cbor_map_size(item);
+
+ for (size_t i = 0; i < n; i++) {
+ if (v[i].key == NULL || v[i].value == NULL) {
+ log_debug("%s: key=%p, value=%p for i=%zu", __func__,
+ (void *)v[i].key, (void *)v[i].value, i);
+ return (-1);
+ }
+ if (i && ctap_check_cbor(v[i - 1].key, v[i].key) < 0) {
+ log_debug("%s: ctap_check_cbor", __func__);
+ return (-1);
+ }
+ if (f(v[i].key, v[i].value, arg) < 0) {
+ log_debug("%s: iterator < 0 on i=%zu", __func__, i);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+int
+cbor_array_iter(const cbor_item_t *item, void *arg, int(*f)(const cbor_item_t *,
+ void *))
+{
+ cbor_item_t **v;
+ size_t n;
+
+ if ((v = cbor_array_handle(item)) == NULL) {
+ log_debug("%s: cbor_array_handle", __func__);
+ return (-1);
+ }
+
+ n = cbor_array_size(item);
+
+ for (size_t i = 0; i < n; i++)
+ if (v[i] == NULL || f(v[i], arg) < 0) {
+ log_debug("%s: iterator < 0 on i=%zu,%p", __func__, i,
+ (void *)v[i]);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+parse_cbor_reply(const unsigned char *blob, size_t blob_len, void *arg,
+ int(*parser)(const cbor_item_t *, const cbor_item_t *, void *))
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int r;
+
+ if (blob_len < 1) {
+ log_debug("%s: blob_len=%zu", __func__, blob_len);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ if (blob[0] != FIDO_OK) {
+ log_debug("%s: blob[0]=0x%02x", __func__, blob[0]);
+ r = blob[0];
+ goto fail;
+ }
+
+ if ((item = cbor_load(blob + 1, blob_len - 1, &cbor)) == NULL) {
+ log_debug("%s: cbor_load", __func__);
+ r = FIDO_ERR_RX_NOT_CBOR;
+ goto fail;
+ }
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ r = FIDO_ERR_RX_INVALID_CBOR;
+ goto fail;
+ }
+
+ if (cbor_map_iter(item, arg, parser) < 0) {
+ log_debug("%s: cbor_map_iter", __func__);
+ r = FIDO_ERR_RX_INVALID_CBOR;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (r);
+}
+
+void
+cbor_vector_free(cbor_item_t **item, size_t len)
+{
+ for (size_t i = 0; i < len; i++)
+ if (item[i] != NULL)
+ cbor_decref(&item[i]);
+}
+
+int
+cbor_bytestring_copy(const cbor_item_t *item, unsigned char **buf, size_t *len)
+{
+ if (*buf != NULL || *len != 0) {
+ log_debug("%s: dup", __func__);
+ return (-1);
+ }
+
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ *len = cbor_bytestring_length(item);
+ if ((*buf = malloc(*len)) == NULL) {
+ *len = 0;
+ return (-1);
+ }
+
+ memcpy(*buf, cbor_bytestring_handle(item), *len);
+
+ return (0);
+}
+
+int
+cbor_string_copy(const cbor_item_t *item, char **str)
+{
+ size_t len;
+
+ if (*str != NULL) {
+ log_debug("%s: dup", __func__);
+ return (-1);
+ }
+
+ if (cbor_isa_string(item) == false ||
+ cbor_string_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if ((len = cbor_string_length(item)) == SIZE_MAX ||
+ (*str = malloc(len + 1)) == NULL)
+ return (-1);
+
+ memcpy(*str, cbor_string_handle(item), len);
+ (*str)[len] = '\0';
+
+ return (0);
+}
+
+int
+cbor_add_bytestring(cbor_item_t *item, const char *key,
+ const unsigned char *value, size_t value_len)
+{
+ struct cbor_pair pair;
+ int ok = -1;
+
+ memset(&pair, 0, sizeof(pair));
+
+ if ((pair.key = cbor_build_string(key)) == NULL ||
+ (pair.value = cbor_build_bytestring(value, value_len)) == NULL) {
+ log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ if (!cbor_map_add(item, pair)) {
+ log_debug("%s: cbor_map_add", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pair.key)
+ cbor_decref(&pair.key);
+ if (pair.value)
+ cbor_decref(&pair.value);
+
+ return (ok);
+}
+
+int
+cbor_add_string(cbor_item_t *item, const char *key, const char *value)
+{
+ struct cbor_pair pair;
+ int ok = -1;
+
+ memset(&pair, 0, sizeof(pair));
+
+ if ((pair.key = cbor_build_string(key)) == NULL ||
+ (pair.value = cbor_build_string(value)) == NULL) {
+ log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ if (!cbor_map_add(item, pair)) {
+ log_debug("%s: cbor_map_add", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pair.key)
+ cbor_decref(&pair.key);
+ if (pair.value)
+ cbor_decref(&pair.value);
+
+ return (ok);
+}
+
+int
+cbor_add_bool(cbor_item_t *item, const char *key, fido_opt_t value)
+{
+ struct cbor_pair pair;
+ int ok = -1;
+
+ memset(&pair, 0, sizeof(pair));
+
+ if ((pair.key = cbor_build_string(key)) == NULL ||
+ (pair.value = cbor_build_bool(value == FIDO_OPT_TRUE)) == NULL) {
+ log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ if (!cbor_map_add(item, pair)) {
+ log_debug("%s: cbor_map_add", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pair.key)
+ cbor_decref(&pair.key);
+ if (pair.value)
+ cbor_decref(&pair.value);
+
+ return (ok);
+}
+
+static int
+cbor_add_arg(cbor_item_t *item, uint8_t n, cbor_item_t *arg)
+{
+ struct cbor_pair pair;
+ int ok = -1;
+
+ memset(&pair, 0, sizeof(pair));
+
+ if (arg == NULL)
+ return (0); /* empty argument */
+
+ if ((pair.key = cbor_build_uint8(n)) == NULL) {
+ log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ pair.value = arg;
+
+ if (!cbor_map_add(item, pair)) {
+ log_debug("%s: cbor_map_add", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pair.key)
+ cbor_decref(&pair.key);
+
+ return (ok);
+}
+
+cbor_item_t *
+cbor_flatten_vector(cbor_item_t *argv[], size_t argc)
+{
+ cbor_item_t *map;
+ uint8_t i;
+
+ if (argc > UINT8_MAX - 1)
+ return (NULL);
+
+ if ((map = cbor_new_definite_map(argc)) == NULL)
+ return (NULL);
+
+ for (i = 0; i < argc; i++)
+ if (cbor_add_arg(map, i + 1, argv[i]) < 0)
+ break;
+
+ if (i != argc) {
+ cbor_decref(&map);
+ map = NULL;
+ }
+
+ return (map);
+}
+
+int
+cbor_build_frame(uint8_t cmd, cbor_item_t *argv[], size_t argc, fido_blob_t *f)
+{
+ cbor_item_t *flat = NULL;
+ unsigned char *cbor = NULL;
+ size_t cbor_len;
+ size_t cbor_alloc_len;
+ int ok = -1;
+
+ if ((flat = cbor_flatten_vector(argv, argc)) == NULL)
+ goto fail;
+
+ cbor_len = cbor_serialize_alloc(flat, &cbor, &cbor_alloc_len);
+ if (cbor_len == 0 || cbor_len == SIZE_MAX) {
+ log_debug("%s: cbor_len=%zu", __func__, cbor_len);
+ goto fail;
+ }
+
+ if ((f->ptr = malloc(cbor_len + 1)) == NULL)
+ goto fail;
+
+ f->len = cbor_len + 1;
+ f->ptr[0] = cmd;
+ memcpy(f->ptr + 1, cbor, f->len - 1);
+
+ ok = 0;
+fail:
+ if (flat != NULL)
+ cbor_decref(&flat);
+
+ free(cbor);
+
+ return (ok);
+}
+
+cbor_item_t *
+encode_rp_entity(const fido_rp_t *rp)
+{
+ cbor_item_t *item = NULL;
+
+ if ((item = cbor_new_definite_map(2)) == NULL)
+ return (NULL);
+
+ if ((rp->id && cbor_add_string(item, "id", rp->id) < 0) ||
+ (rp->name && cbor_add_string(item, "name", rp->name) < 0)) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+encode_user_entity(const fido_user_t *user)
+{
+ cbor_item_t *item = NULL;
+ const fido_blob_t *id = &user->id;
+ const char *display = user->display_name;
+
+ if ((item = cbor_new_definite_map(4)) == NULL)
+ return (NULL);
+
+ if ((id->ptr && cbor_add_bytestring(item, "id", id->ptr, id->len) < 0) ||
+ (user->icon && cbor_add_string(item, "icon", user->icon) < 0) ||
+ (user->name && cbor_add_string(item, "name", user->name) < 0) ||
+ (display && cbor_add_string(item, "displayName", display) < 0)) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+encode_pubkey_param(int cose_alg)
+{
+ cbor_item_t *item = NULL;
+ cbor_item_t *body = NULL;
+ struct cbor_pair alg;
+ int ok = -1;
+
+ memset(&alg, 0, sizeof(alg));
+
+ if ((item = cbor_new_definite_array(1)) == NULL ||
+ (body = cbor_new_definite_map(2)) == NULL ||
+ cose_alg > -1 || cose_alg < INT16_MIN)
+ goto fail;
+
+ alg.key = cbor_build_string("alg");
+
+ if (-cose_alg - 1 > UINT8_MAX)
+ alg.value = cbor_build_negint16((uint16_t)(-cose_alg - 1));
+ else
+ alg.value = cbor_build_negint8((uint8_t)(-cose_alg - 1));
+
+ if (alg.key == NULL || alg.value == NULL) {
+ log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ if (cbor_map_add(body, alg) == false ||
+ cbor_add_string(body, "type", "public-key") < 0 ||
+ cbor_array_push(item, body) == false)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ if (item != NULL) {
+ cbor_decref(&item);
+ item = NULL;
+ }
+ }
+
+ if (body != NULL)
+ cbor_decref(&body);
+ if (alg.key != NULL)
+ cbor_decref(&alg.key);
+ if (alg.value != NULL)
+ cbor_decref(&alg.value);
+
+ return (item);
+}
+
+cbor_item_t *
+encode_pubkey(const fido_blob_t *pubkey)
+{
+ cbor_item_t *cbor_key = NULL;
+
+ if ((cbor_key = cbor_new_definite_map(2)) == NULL ||
+ cbor_add_bytestring(cbor_key, "id", pubkey->ptr, pubkey->len) < 0 ||
+ cbor_add_string(cbor_key, "type", "public-key") < 0) {
+ if (cbor_key)
+ cbor_decref(&cbor_key);
+ return (NULL);
+ }
+
+ return (cbor_key);
+}
+
+cbor_item_t *
+encode_pubkey_list(const fido_blob_array_t *list)
+{
+ cbor_item_t *array = NULL;
+ cbor_item_t *key = NULL;
+
+ if ((array = cbor_new_definite_array(list->len)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < list->len; i++) {
+ if ((key = encode_pubkey(&list->ptr[i])) == NULL ||
+ cbor_array_push(array, key) == false)
+ goto fail;
+ cbor_decref(&key);
+ }
+
+ return (array);
+fail:
+ if (key != NULL)
+ cbor_decref(&key);
+ if (array != NULL)
+ cbor_decref(&array);
+
+ return (NULL);
+}
+
+cbor_item_t *
+encode_extensions(int ext)
+{
+ cbor_item_t *item = NULL;
+
+ if (ext == 0 || ext != FIDO_EXT_HMAC_SECRET)
+ return (NULL);
+
+ if ((item = cbor_new_definite_map(1)) == NULL)
+ return (NULL);
+
+ if (cbor_add_bool(item, "hmac-secret", FIDO_OPT_TRUE) < 0) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+encode_options(fido_opt_t rk, fido_opt_t uv)
+{
+ cbor_item_t *item = NULL;
+
+ if ((item = cbor_new_definite_map(2)) == NULL)
+ return (NULL);
+
+ if ((rk != FIDO_OPT_OMIT && cbor_add_bool(item, "rk", rk) < 0) ||
+ (uv != FIDO_OPT_OMIT && cbor_add_bool(item, "uv", uv) < 0)) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+encode_assert_options(fido_opt_t up, fido_opt_t uv)
+{
+ cbor_item_t *item = NULL;
+
+ if ((item = cbor_new_definite_map(2)) == NULL)
+ return (NULL);
+
+ if ((up != FIDO_OPT_OMIT && cbor_add_bool(item, "up", up) < 0) ||
+ (uv != FIDO_OPT_OMIT && cbor_add_bool(item, "uv", uv) < 0)) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+encode_pin_auth(const fido_blob_t *hmac_key, const fido_blob_t *data)
+{
+ const EVP_MD *md = NULL;
+ unsigned char dgst[SHA256_DIGEST_LENGTH];
+ unsigned int dgst_len;
+
+ if ((md = EVP_sha256()) == NULL || HMAC(md, hmac_key->ptr,
+ (int)hmac_key->len, data->ptr, (int)data->len, dgst,
+ &dgst_len) == NULL || dgst_len != SHA256_DIGEST_LENGTH)
+ return (NULL);
+
+ return (cbor_build_bytestring(dgst, 16));
+}
+
+cbor_item_t *
+encode_pin_opt(void)
+{
+ return (cbor_build_uint8(1));
+}
+
+cbor_item_t *
+encode_pin_enc(const fido_blob_t *key, const fido_blob_t *pin)
+{
+ fido_blob_t pe;
+ cbor_item_t *item = NULL;
+
+ if (aes256_cbc_enc(key, pin, &pe) < 0)
+ return (NULL);
+
+ item = cbor_build_bytestring(pe.ptr, pe.len);
+ free(pe.ptr);
+
+ return (item);
+}
+
+static int
+sha256(const unsigned char *data, size_t data_len, fido_blob_t *digest)
+{
+ if ((digest->ptr = calloc(1, SHA256_DIGEST_LENGTH)) == NULL)
+ return (-1);
+
+ digest->len = SHA256_DIGEST_LENGTH;
+
+ if (SHA256(data, data_len, digest->ptr) != digest->ptr) {
+ free(digest->ptr);
+ digest->ptr = NULL;
+ digest->len = 0;
+ return (-1);
+ }
+
+ return (0);
+}
+
+cbor_item_t *
+encode_change_pin_auth(const fido_blob_t *key, const fido_blob_t *new_pin,
+ const fido_blob_t *pin)
+{
+ unsigned char dgst[SHA256_DIGEST_LENGTH];
+ unsigned int dgst_len;
+ cbor_item_t *item = NULL;
+ const EVP_MD *md = NULL;
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ HMAC_CTX ctx;
+#else
+ HMAC_CTX *ctx = NULL;
+#endif
+ fido_blob_t *npe = NULL; /* new pin, encrypted */
+ fido_blob_t *ph = NULL; /* pin hash */
+ fido_blob_t *phe = NULL; /* pin hash, encrypted */
+ int ok = -1;
+
+ if ((npe = fido_blob_new()) == NULL ||
+ (ph = fido_blob_new()) == NULL ||
+ (phe = fido_blob_new()) == NULL)
+ goto fail;
+
+ if (aes256_cbc_enc(key, new_pin, npe) < 0) {
+ log_debug("%s: aes256_cbc_enc 1", __func__);
+ goto fail;
+ }
+
+ if (sha256(pin->ptr, pin->len, ph) < 0 || ph->len < 16) {
+ log_debug("%s: sha256", __func__);
+ goto fail;
+ }
+
+ ph->len = 16; /* first 16 bytes */
+
+ if (aes256_cbc_enc(key, ph, phe) < 0) {
+ log_debug("%s: aes256_cbc_enc 2", __func__);
+ goto fail;
+ }
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ HMAC_CTX_init(&ctx);
+
+ if ((md = EVP_sha256()) == NULL ||
+ HMAC_Init_ex(&ctx, key->ptr, (int)key->len, md, NULL) == 0 ||
+ HMAC_Update(&ctx, npe->ptr, (int)npe->len) == 0 ||
+ HMAC_Update(&ctx, phe->ptr, (int)phe->len) == 0 ||
+ HMAC_Final(&ctx, dgst, &dgst_len) == 0 || dgst_len != 32) {
+ log_debug("%s: HMAC", __func__);
+ goto fail;
+ }
+#else
+ if ((ctx = HMAC_CTX_new()) == NULL ||
+ (md = EVP_sha256()) == NULL ||
+ HMAC_Init_ex(ctx, key->ptr, (int)key->len, md, NULL) == 0 ||
+ HMAC_Update(ctx, npe->ptr, (int)npe->len) == 0 ||
+ HMAC_Update(ctx, phe->ptr, (int)phe->len) == 0 ||
+ HMAC_Final(ctx, dgst, &dgst_len) == 0 || dgst_len != 32) {
+ log_debug("%s: HMAC", __func__);
+ goto fail;
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+
+ if ((item = cbor_build_bytestring(dgst, 16)) == NULL) {
+ log_debug("%s: cbor_build_bytestring", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ fido_blob_free(&npe);
+ fido_blob_free(&ph);
+ fido_blob_free(&phe);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ if (ctx != NULL)
+ HMAC_CTX_free(ctx);
+#endif
+
+ if (ok < 0) {
+ if (item != NULL) {
+ cbor_decref(&item);
+ item = NULL;
+ }
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+encode_set_pin_auth(const fido_blob_t *key, const fido_blob_t *pin)
+{
+ const EVP_MD *md = NULL;
+ unsigned char dgst[SHA256_DIGEST_LENGTH];
+ unsigned int dgst_len;
+ cbor_item_t *item = NULL;
+ fido_blob_t *pe = NULL;
+
+ if ((pe = fido_blob_new()) == NULL)
+ goto fail;
+
+ if (aes256_cbc_enc(key, pin, pe) < 0) {
+ log_debug("%s: aes256_cbc_enc", __func__);
+ goto fail;
+ }
+
+ if ((md = EVP_sha256()) == NULL || key->len != 32 || HMAC(md, key->ptr,
+ (int)key->len, pe->ptr, (int)pe->len, dgst, &dgst_len) == NULL ||
+ dgst_len != SHA256_DIGEST_LENGTH) {
+ log_debug("%s: HMAC", __func__);
+ goto fail;
+ }
+
+ item = cbor_build_bytestring(dgst, 16);
+fail:
+ fido_blob_free(&pe);
+
+ return (item);
+}
+
+cbor_item_t *
+encode_pin_hash_enc(const fido_blob_t *shared, const fido_blob_t *pin)
+{
+ cbor_item_t *item = NULL;
+ fido_blob_t *ph = NULL;
+ fido_blob_t *phe = NULL;
+
+ if ((ph = fido_blob_new()) == NULL || (phe = fido_blob_new()) == NULL)
+ goto fail;
+
+ if (sha256(pin->ptr, pin->len, ph) < 0 || ph->len < 16) {
+ log_debug("%s: SHA256", __func__);
+ goto fail;
+ }
+
+ ph->len = 16; /* first 16 bytes */
+
+ if (aes256_cbc_enc(shared, ph, phe) < 0) {
+ log_debug("%s: aes256_cbc_enc", __func__);
+ goto fail;
+ }
+
+ item = cbor_build_bytestring(phe->ptr, phe->len);
+fail:
+ fido_blob_free(&ph);
+ fido_blob_free(&phe);
+
+ return (item);
+}
+
+cbor_item_t *
+encode_hmac_secret_param(const fido_blob_t *ecdh, const es256_pk_t *pk,
+ const fido_blob_t *hmac_salt)
+{
+ cbor_item_t *item = NULL;
+ cbor_item_t *param = NULL;
+ cbor_item_t *argv[3];
+ struct cbor_pair pair;
+
+ memset(argv, 0, sizeof(argv));
+ memset(&pair, 0, sizeof(pair));
+
+ if (ecdh == NULL || pk == NULL || hmac_salt->ptr == NULL) {
+ log_debug("%s: ecdh=%p, pk=%p, hmac_salt->ptr=%p", __func__,
+ (const void *)ecdh, (const void *)pk,
+ (const void *)hmac_salt->ptr);
+ goto fail;
+ }
+
+ if (hmac_salt->len != 32 && hmac_salt->len != 64) {
+ log_debug("%s: hmac_salt->len=%zu", __func__, hmac_salt->len);
+ goto fail;
+ }
+
+ /* XXX not pin, but salt */
+ if ((argv[0] = es256_pk_encode(pk, 1)) == NULL ||
+ (argv[1] = encode_pin_enc(ecdh, hmac_salt)) == NULL ||
+ (argv[2] = encode_set_pin_auth(ecdh, hmac_salt)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if ((param = cbor_flatten_vector(argv, 3)) == NULL) {
+ log_debug("%s: cbor_flatten_vector", __func__);
+ goto fail;
+ }
+
+ if ((item = cbor_new_definite_map(1)) == NULL) {
+ log_debug("%s: cbor_new_definite_map", __func__);
+ goto fail;
+ }
+
+ if ((pair.key = cbor_build_string("hmac-secret")) == NULL) {
+ log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ pair.value = param;
+
+ if (!cbor_map_add(item, pair)) {
+ log_debug("%s: cbor_map_add", __func__);
+ cbor_decref(&item);
+ item = NULL;
+ goto fail;
+ }
+
+fail:
+ for (size_t i = 0; i < 3; i++)
+ if (argv[i] != NULL)
+ cbor_decref(&argv[i]);
+
+ if (param != NULL)
+ cbor_decref(&param);
+ if (pair.key != NULL)
+ cbor_decref(&pair.key);
+
+ return (item);
+}
+
+int
+decode_fmt(const cbor_item_t *item, char **fmt)
+{
+ char *type = NULL;
+
+ if (cbor_string_copy(item, &type) < 0) {
+ log_debug("%s: cbor_string_copy", __func__);
+ return (-1);
+ }
+
+ if (strcmp(type, "packed") && strcmp(type, "fido-u2f")) {
+ log_debug("%s: type=%s", __func__, type);
+ free(type);
+ return (-1);
+ }
+
+ *fmt = type;
+
+ return (0);
+}
+
+struct cose_key {
+ int kty;
+ int alg;
+ int crv;
+};
+
+static int
+find_cose_alg(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ struct cose_key *cose_key = arg;
+
+ if (cbor_isa_uint(key) == true &&
+ cbor_int_get_width(key) == CBOR_INT_8) {
+ switch (cbor_get_uint8(key)) {
+ case 1:
+ if (cbor_isa_uint(val) == false ||
+ cbor_get_int(val) > INT_MAX || cose_key->kty != 0) {
+ log_debug("%s: kty", __func__);
+ return (-1);
+ }
+
+ cose_key->kty = (int)cbor_get_int(val);
+
+ break;
+ case 3:
+ if (cbor_isa_negint(val) == false ||
+ cbor_get_int(val) > INT_MAX || cose_key->alg != 0) {
+ log_debug("%s: alg", __func__);
+ return (-1);
+ }
+
+ cose_key->alg = -(int)cbor_get_int(val) - 1;
+
+ break;
+ }
+ } else if (cbor_isa_negint(key) == true &&
+ cbor_int_get_width(key) == CBOR_INT_8) {
+ if (cbor_get_uint8(key) == 0) {
+ /* get crv if not rsa, otherwise ignore */
+ if (cbor_isa_uint(val) == true &&
+ cbor_get_int(val) <= INT_MAX &&
+ cose_key->crv == 0)
+ cose_key->crv = (int)cbor_get_int(val);
+ }
+ }
+
+ return (0);
+}
+
+static int
+get_cose_alg(const cbor_item_t *item, int *cose_alg)
+{
+ struct cose_key cose_key;
+
+ memset(&cose_key, 0, sizeof(cose_key));
+
+ *cose_alg = 0;
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, &cose_key, find_cose_alg) < 0) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ switch (cose_key.alg) {
+ case COSE_ES256:
+ if (cose_key.kty != COSE_KTY_EC2 ||
+ cose_key.crv != COSE_P256) {
+ log_debug("%s: invalid kty/crv", __func__);
+ return (-1);
+ }
+
+ break;
+ case COSE_EDDSA:
+ if (cose_key.kty != COSE_KTY_OKP ||
+ cose_key.crv != COSE_ED25519) {
+ log_debug("%s: invalid kty/crv", __func__);
+ return (-1);
+ }
+
+ break;
+ case COSE_RS256:
+ if (cose_key.kty != COSE_KTY_RSA) {
+ log_debug("%s: invalid kty/crv", __func__);
+ return (-1);
+ }
+
+ break;
+ default:
+ log_debug("%s: unknown alg %d", __func__, cose_key.alg);
+
+ return (-1);
+ }
+
+ *cose_alg = cose_key.alg;
+
+ return (0);
+}
+
+int
+decode_pubkey(const cbor_item_t *item, int *type, void *key)
+{
+ if (get_cose_alg(item, type) < 0) {
+ log_debug("%s: get_cose_alg", __func__);
+ return (-1);
+ }
+
+ switch (*type) {
+ case COSE_ES256:
+ if (es256_pk_decode(item, key) < 0) {
+ log_debug("%s: es256_pk_decode", __func__);
+ return (-1);
+ }
+ break;
+ case COSE_RS256:
+ if (rs256_pk_decode(item, key) < 0) {
+ log_debug("%s: rs256_pk_decode", __func__);
+ return (-1);
+ }
+ break;
+ case COSE_EDDSA:
+ if (eddsa_pk_decode(item, key) < 0) {
+ log_debug("%s: eddsa_pk_decode", __func__);
+ return (-1);
+ }
+ break;
+ default:
+ log_debug("%s: invalid cose_alg %d", __func__, *type);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_attcred(const unsigned char **buf, size_t *len, int cose_alg,
+ fido_attcred_t *attcred)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ uint16_t id_len;
+ int ok = -1;
+
+ log_debug("%s: buf=%p, len=%zu", __func__, (const void *)*buf, *len);
+
+ if (buf_read(buf, len, &attcred->aaguid, sizeof(attcred->aaguid)) < 0) {
+ log_debug("%s: buf_read aaguid", __func__);
+ return (-1);
+ }
+
+ if (buf_read(buf, len, &id_len, sizeof(id_len)) < 0) {
+ log_debug("%s: buf_read id_len", __func__);
+ return (-1);
+ }
+
+ attcred->id.len = (size_t)be16toh(id_len);
+ if ((attcred->id.ptr = malloc(attcred->id.len)) == NULL)
+ return (-1);
+
+ log_debug("%s: attcred->id.len=%zu", __func__, attcred->id.len);
+
+ if (buf_read(buf, len, attcred->id.ptr, attcred->id.len) < 0) {
+ log_debug("%s: buf_read id", __func__);
+ return (-1);
+ }
+
+ if ((item = cbor_load(*buf, *len, &cbor)) == NULL) {
+ log_debug("%s: cbor_load", __func__);
+ log_xxd(*buf, *len);
+ goto fail;
+ }
+
+ if (decode_pubkey(item, &attcred->type, &attcred->pubkey) < 0) {
+ log_debug("%s: decode_pubkey", __func__);
+ goto fail;
+ }
+
+ if (attcred->type != cose_alg) {
+ log_debug("%s: cose_alg mismatch (%d != %d)", __func__,
+ attcred->type, cose_alg);
+ goto fail;
+ }
+
+ *buf += cbor.read;
+ *len -= cbor.read;
+
+ ok = 0;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (ok);
+}
+
+static int
+decode_extension(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ int *authdata_ext = arg;
+ char *type = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &type) < 0 || strcmp(type, "hmac-secret")) {
+ log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (cbor_isa_float_ctrl(val) == false ||
+ cbor_float_get_width(val) != CBOR_FLOAT_0 ||
+ cbor_is_bool(val) == false || *authdata_ext != 0) {
+ log_debug("%s: cbor type", __func__);
+ goto out;
+ }
+
+ if (cbor_ctrl_value(val) == CBOR_CTRL_TRUE)
+ *authdata_ext |= FIDO_EXT_HMAC_SECRET;
+
+ ok = 0;
+out:
+ free(type);
+
+ return (ok);
+}
+
+static int
+decode_extensions(const unsigned char **buf, size_t *len, int *authdata_ext)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int ok = -1;
+
+ log_debug("%s: buf=%p, len=%zu", __func__, (const void *)*buf, *len);
+
+ *authdata_ext = 0;
+
+ if ((item = cbor_load(*buf, *len, &cbor)) == NULL) {
+ log_debug("%s: cbor_load", __func__);
+ log_xxd(*buf, *len);
+ goto fail;
+ }
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_size(item) != 1 ||
+ cbor_map_iter(item, authdata_ext, decode_extension) < 0) {
+ log_debug("%s: cbor type", __func__);
+ goto fail;
+ }
+
+ *buf += cbor.read;
+ *len -= cbor.read;
+
+ ok = 0;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (ok);
+}
+
+static int
+decode_hmac_secret_aux(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_blob_t *out = arg;
+ char *type = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &type) < 0 || strcmp(type, "hmac-secret")) {
+ log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ ok = cbor_bytestring_copy(val, &out->ptr, &out->len);
+out:
+ free(type);
+
+ return (ok);
+}
+
+static int
+decode_hmac_secret(const unsigned char **buf, size_t *len, fido_blob_t *out)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int ok = -1;
+
+ log_debug("%s: buf=%p, len=%zu", __func__, (const void *)*buf, *len);
+
+ if ((item = cbor_load(*buf, *len, &cbor)) == NULL) {
+ log_debug("%s: cbor_load", __func__);
+ log_xxd(*buf, *len);
+ goto fail;
+ }
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_size(item) != 1 ||
+ cbor_map_iter(item, out, decode_hmac_secret_aux) < 0) {
+ log_debug("%s: cbor type", __func__);
+ goto fail;
+ }
+
+ *buf += cbor.read;
+ *len -= cbor.read;
+
+ ok = 0;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (ok);
+}
+
+int
+decode_cred_authdata(const cbor_item_t *item, int cose_alg,
+ fido_blob_t *authdata_cbor, fido_authdata_t *authdata,
+ fido_attcred_t *attcred, int *authdata_ext)
+{
+ const unsigned char *buf = NULL;
+ size_t len;
+ size_t alloc_len;
+
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (authdata_cbor->ptr != NULL ||
+ (authdata_cbor->len = cbor_serialize_alloc(item,
+ &authdata_cbor->ptr, &alloc_len)) == 0) {
+ log_debug("%s: cbor_serialize_alloc", __func__);
+ return (-1);
+ }
+
+ buf = cbor_bytestring_handle(item);
+ len = cbor_bytestring_length(item);
+
+ log_debug("%s: buf=%p, len=%zu", __func__, (const void *)buf, len);
+
+ if (buf_read(&buf, &len, authdata, sizeof(*authdata)) < 0) {
+ log_debug("%s: buf_read", __func__);
+ return (-1);
+ }
+
+ authdata->sigcount = be32toh(authdata->sigcount);
+
+ if (attcred != NULL) {
+ if ((authdata->flags & CTAP_AUTHDATA_ATT_CRED) == 0 ||
+ decode_attcred(&buf, &len, cose_alg, attcred) < 0)
+ return (-1);
+ }
+
+ if (authdata_ext != NULL) {
+ if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0 &&
+ decode_extensions(&buf, &len, authdata_ext) < 0)
+ return (-1);
+ }
+
+ /* XXX we should probably ensure that len == 0 at this point */
+
+ return (FIDO_OK);
+}
+
+int
+decode_assert_authdata(const cbor_item_t *item, fido_blob_t *authdata_cbor,
+ fido_authdata_t *authdata, int *authdata_ext, fido_blob_t *hmac_secret_enc)
+{
+ const unsigned char *buf = NULL;
+ size_t len;
+ size_t alloc_len;
+
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (authdata_cbor->ptr != NULL ||
+ (authdata_cbor->len = cbor_serialize_alloc(item,
+ &authdata_cbor->ptr, &alloc_len)) == 0) {
+ log_debug("%s: cbor_serialize_alloc", __func__);
+ return (-1);
+ }
+
+ buf = cbor_bytestring_handle(item);
+ len = cbor_bytestring_length(item);
+
+ log_debug("%s: buf=%p, len=%zu", __func__, (const void *)buf, len);
+
+ if (buf_read(&buf, &len, authdata, sizeof(*authdata)) < 0) {
+ log_debug("%s: buf_read", __func__);
+ return (-1);
+ }
+
+ authdata->sigcount = be32toh(authdata->sigcount);
+
+ *authdata_ext = 0;
+ if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0) {
+ /* XXX semantic leap: extensions -> hmac_secret */
+ if (decode_hmac_secret(&buf, &len, hmac_secret_enc) < 0) {
+ log_debug("%s: decode_hmac_secret", __func__);
+ return (-1);
+ }
+ *authdata_ext = FIDO_EXT_HMAC_SECRET;
+ }
+
+ /* XXX we should probably ensure that len == 0 at this point */
+
+ return (FIDO_OK);
+}
+
+static int
+decode_x5c(const cbor_item_t *item, void *arg)
+{
+ fido_blob_t *x5c = arg;
+
+ if (x5c->len)
+ return (0); /* ignore */
+
+ return (cbor_bytestring_copy(item, &x5c->ptr, &x5c->len));
+}
+
+static int
+decode_attstmt_entry(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_attstmt_t *attstmt = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (!strcmp(name, "alg")) {
+ if (cbor_isa_negint(val) == false ||
+ cbor_int_get_width(val) != CBOR_INT_8 ||
+ cbor_get_uint8(val) != -COSE_ES256 - 1) {
+ log_debug("%s: alg", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "sig")) {
+ if (cbor_bytestring_copy(val, &attstmt->sig.ptr,
+ &attstmt->sig.len) < 0) {
+ log_debug("%s: sig", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "x5c")) {
+ if (cbor_isa_array(val) == false ||
+ cbor_array_is_definite(val) == false ||
+ cbor_array_iter(val, &attstmt->x5c, decode_x5c) < 0) {
+ log_debug("%s: x5c", __func__);
+ goto out;
+ }
+ }
+
+ ok = 0;
+out:
+ free(name);
+
+ return (ok);
+}
+
+int
+decode_attstmt(const cbor_item_t *item, fido_attstmt_t *attstmt)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, attstmt, decode_attstmt_entry) < 0) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+decode_uint64(const cbor_item_t *item, uint64_t *n)
+{
+ if (cbor_isa_uint(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ *n = cbor_get_int(item);
+
+ return (0);
+}
+
+static int
+decode_cred_id_entry(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_blob_t *id = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (!strcmp(name, "id"))
+ if (cbor_bytestring_copy(val, &id->ptr, &id->len) < 0) {
+ log_debug("%s: cbor_bytestring_copy", __func__);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(name);
+
+ return (ok);
+}
+
+int
+decode_cred_id(const cbor_item_t *item, fido_blob_t *id)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, id, decode_cred_id_entry) < 0) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_user_entry(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_user_t *user = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (!strcmp(name, "icon")) {
+ if (cbor_string_copy(val, &user->icon) < 0) {
+ log_debug("%s: icon", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "name")) {
+ if (cbor_string_copy(val, &user->name) < 0) {
+ log_debug("%s: name", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "displayName")) {
+ if (cbor_string_copy(val, &user->display_name) < 0) {
+ log_debug("%s: display_name", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "id")) {
+ if (cbor_bytestring_copy(val, &user->id.ptr, &user->id.len) < 0) {
+ log_debug("%s: id", __func__);
+ goto out;
+ }
+ }
+
+ ok = 0;
+out:
+ free(name);
+
+ return (ok);
+}
+
+int
+decode_user(const cbor_item_t *item, fido_user_t *user)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, user, decode_user_entry) < 0) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_rp_entity_entry(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_rp_t *rp = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (!strcmp(name, "id")) {
+ if (cbor_string_copy(val, &rp->id) < 0) {
+ log_debug("%s: id", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "name")) {
+ if (cbor_string_copy(val, &rp->name) < 0) {
+ log_debug("%s: name", __func__);
+ goto out;
+ }
+ }
+
+ ok = 0;
+out:
+ free(name);
+
+ return (ok);
+}
+
+int
+decode_rp_entity(const cbor_item_t *item, fido_rp_t *rp)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, rp, decode_rp_entity_entry) < 0) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
diff --git a/lib/libfido2/src/cred.c b/lib/libfido2/src/cred.c
new file mode 100644
index 00000000000..45423677f01
--- /dev/null
+++ b/lib/libfido2/src/cred.c
@@ -0,0 +1,1031 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <openssl/x509.h>
+
+#include <string.h>
+#include "fido.h"
+#include "fido/es256.h"
+
+static int
+parse_makecred_reply(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_cred_t *cred = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* fmt */
+ return (decode_fmt(val, &cred->fmt));
+ case 2: /* authdata */
+ return (decode_cred_authdata(val, cred->type,
+ &cred->authdata_cbor, &cred->authdata, &cred->attcred,
+ &cred->authdata_ext));
+ case 3: /* attestation statement */
+ return (decode_attstmt(val, &cred->attstmt));
+ default: /* ignore */
+ log_debug("%s: cbor type", __func__);
+ return (0);
+ }
+}
+
+static int
+fido_dev_make_cred_tx(fido_dev_t *dev, fido_cred_t *cred, const char *pin)
+{
+ fido_blob_t f;
+ fido_blob_t *ecdh = NULL;
+ es256_pk_t *pk = NULL;
+ cbor_item_t *argv[9];
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if (cred->cdh.ptr == NULL || cred->type == 0) {
+ log_debug("%s: cdh=%p, type=%d", __func__,
+ (void *)cred->cdh.ptr, cred->type);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((argv[0] = fido_blob_encode(&cred->cdh)) == NULL ||
+ (argv[1] = encode_rp_entity(&cred->rp)) == NULL ||
+ (argv[2] = encode_user_entity(&cred->user)) == NULL ||
+ (argv[3] = encode_pubkey_param(cred->type)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* excluded credentials */
+ if (cred->excl.len)
+ if ((argv[4] = encode_pubkey_list(&cred->excl)) == NULL) {
+ log_debug("%s: encode_pubkey_list", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* extensions */
+ if (cred->ext)
+ if ((argv[5] = encode_extensions(cred->ext)) == NULL) {
+ log_debug("%s: encode_extensions", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* options */
+ if (cred->rk != FIDO_OPT_OMIT || cred->uv != FIDO_OPT_OMIT)
+ if ((argv[6] = encode_options(cred->rk, cred->uv)) == NULL) {
+ log_debug("%s: encode_options", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* pin authentication */
+ if (pin) {
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
+ log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ if ((r = add_cbor_pin_params(dev, &cred->cdh, pk, ecdh, pin,
+ &argv[7], &argv[8])) != FIDO_OK) {
+ log_debug("%s: add_cbor_pin_params", __func__);
+ goto fail;
+ }
+ }
+
+ /* framing and transmission */
+ if (cbor_build_frame(CTAP_CBOR_MAKECRED, argv, 9, &f) < 0 ||
+ tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_make_cred_rx(fido_dev_t *dev, fido_cred_t *cred, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ fido_cred_reset_rx(cred);
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, cred,
+ parse_makecred_reply)) != FIDO_OK) {
+ log_debug("%s: parse_makecred_reply", __func__);
+ return (r);
+ }
+
+ if (cred->fmt == NULL || fido_blob_is_empty(&cred->authdata_cbor) ||
+ fido_blob_is_empty(&cred->attcred.id) ||
+ fido_blob_is_empty(&cred->attstmt.sig)) {
+ fido_cred_reset_rx(cred);
+ return (FIDO_ERR_INVALID_CBOR);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_dev_make_cred_wait(fido_dev_t *dev, fido_cred_t *cred, const char *pin, int ms)
+{
+ int r;
+
+ if ((r = fido_dev_make_cred_tx(dev, cred, pin)) != FIDO_OK ||
+ (r = fido_dev_make_cred_rx(dev, cred, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin)
+{
+ if (fido_dev_is_fido2(dev) == false) {
+ if (pin != NULL || cred->rk == FIDO_OPT_TRUE || cred->ext != 0)
+ return (FIDO_ERR_UNSUPPORTED_OPTION);
+ return (u2f_register(dev, cred, -1));
+ }
+
+ return (fido_dev_make_cred_wait(dev, cred, pin, -1));
+}
+
+static int
+check_extensions(int authdata_ext, int ext)
+{
+ if (authdata_ext != ext) {
+ log_debug("%s: authdata_ext=0x%x != ext=0x%x", __func__,
+ authdata_ext, ext);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+check_rp_id(const char *id, const unsigned char *obtained_hash)
+{
+ unsigned char expected_hash[SHA256_DIGEST_LENGTH];
+
+ explicit_bzero(expected_hash, sizeof(expected_hash));
+
+ if (SHA256((const unsigned char *)id, strlen(id),
+ expected_hash) != expected_hash) {
+ log_debug("%s: sha256", __func__);
+ return (-1);
+ }
+
+ return (timingsafe_bcmp(expected_hash, obtained_hash,
+ SHA256_DIGEST_LENGTH));
+}
+
+static int
+get_signed_hash_packed(fido_blob_t *dgst, const fido_blob_t *clientdata,
+ const fido_blob_t *authdata_cbor)
+{
+ cbor_item_t *item = NULL;
+ unsigned char *authdata_ptr = NULL;
+ size_t authdata_len;
+ struct cbor_load_result cbor;
+ SHA256_CTX ctx;
+ int ok = -1;
+
+ if ((item = cbor_load(authdata_cbor->ptr, authdata_cbor->len,
+ &cbor)) == NULL) {
+ log_debug("%s: cbor_load", __func__);
+ goto fail;
+ }
+
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ goto fail;
+ }
+
+ authdata_ptr = cbor_bytestring_handle(item);
+ authdata_len = cbor_bytestring_length(item);
+
+ if (dgst->len != SHA256_DIGEST_LENGTH || SHA256_Init(&ctx) == 0 ||
+ SHA256_Update(&ctx, authdata_ptr, authdata_len) == 0 ||
+ SHA256_Update(&ctx, clientdata->ptr, clientdata->len) == 0 ||
+ SHA256_Final(dgst->ptr, &ctx) == 0) {
+ log_debug("%s: sha256", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (ok);
+}
+
+static int
+get_signed_hash_u2f(fido_blob_t *dgst, const unsigned char *rp_id,
+ size_t rp_id_len, const fido_blob_t *clientdata, const fido_blob_t *id,
+ const es256_pk_t *pk)
+{
+ const uint8_t zero = 0;
+ const uint8_t four = 4; /* uncompressed point */
+ SHA256_CTX ctx;
+
+ if (dgst->len != SHA256_DIGEST_LENGTH || SHA256_Init(&ctx) == 0 ||
+ SHA256_Update(&ctx, &zero, sizeof(zero)) == 0 ||
+ SHA256_Update(&ctx, rp_id, rp_id_len) == 0 ||
+ SHA256_Update(&ctx, clientdata->ptr, clientdata->len) == 0 ||
+ SHA256_Update(&ctx, id->ptr, id->len) == 0 ||
+ SHA256_Update(&ctx, &four, sizeof(four)) == 0 ||
+ SHA256_Update(&ctx, pk->x, sizeof(pk->x)) == 0 ||
+ SHA256_Update(&ctx, pk->y, sizeof(pk->y)) == 0 ||
+ SHA256_Final(dgst->ptr, &ctx) == 0) {
+ log_debug("%s: sha256", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+verify_sig(const fido_blob_t *dgst, const fido_blob_t *x5c,
+ const fido_blob_t *sig)
+{
+ BIO *rawcert = NULL;
+ X509 *cert = NULL;
+ EVP_PKEY *pkey = NULL;
+ EC_KEY *ec;
+ int ok = -1;
+
+ /* openssl needs ints */
+ if (dgst->len > INT_MAX || x5c->len > INT_MAX || sig->len > INT_MAX) {
+ log_debug("%s: dgst->len=%zu, x5c->len=%zu, sig->len=%zu",
+ __func__, dgst->len, x5c->len, sig->len);
+ return (-1);
+ }
+
+ /* fetch key from x509 */
+ if ((rawcert = BIO_new_mem_buf(x5c->ptr, (int)x5c->len)) == NULL ||
+ (cert = d2i_X509_bio(rawcert, NULL)) == NULL ||
+ (pkey = X509_get_pubkey(cert)) == NULL ||
+ (ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL) {
+ log_debug("%s: x509 key", __func__);
+ goto fail;
+ }
+
+ if (ECDSA_verify(0, dgst->ptr, (int)dgst->len, sig->ptr,
+ (int)sig->len, ec) != 1) {
+ log_debug("%s: ECDSA_verify", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (rawcert != NULL)
+ BIO_free(rawcert);
+ if (cert != NULL)
+ X509_free(cert);
+ if (pkey != NULL)
+ EVP_PKEY_free(pkey);
+
+ return (ok);
+}
+
+int
+fido_cred_verify(const fido_cred_t *cred)
+{
+ unsigned char buf[SHA256_DIGEST_LENGTH];
+ fido_blob_t dgst;
+ int r;
+
+ dgst.ptr = buf;
+ dgst.len = sizeof(buf);
+
+ /* do we have everything we need? */
+ if (cred->cdh.ptr == NULL || cred->authdata_cbor.ptr == NULL ||
+ cred->attstmt.x5c.ptr == NULL || cred->attstmt.sig.ptr == NULL ||
+ cred->fmt == NULL || cred->attcred.id.ptr == NULL ||
+ cred->rp.id == NULL) {
+ log_debug("%s: cdh=%p, authdata=%p, x5c=%p, sig=%p, fmt=%p "
+ "id=%p, rp.id=%s", __func__, (void *)cred->cdh.ptr,
+ (void *)cred->authdata_cbor.ptr,
+ (void *)cred->attstmt.x5c.ptr,
+ (void *)cred->attstmt.sig.ptr, (void *)cred->fmt,
+ (void *)cred->attcred.id.ptr, cred->rp.id);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ if (check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) {
+ log_debug("%s: check_rp_id", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (check_flags(cred->authdata.flags, FIDO_OPT_TRUE, cred->uv) < 0) {
+ log_debug("%s: check_flags", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (check_extensions(cred->authdata_ext, cred->ext) < 0) {
+ log_debug("%s: check_extensions", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (!strcmp(cred->fmt, "packed")) {
+ if (get_signed_hash_packed(&dgst, &cred->cdh,
+ &cred->authdata_cbor) < 0) {
+ log_debug("%s: get_signed_hash_packed", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ } else {
+ if (get_signed_hash_u2f(&dgst, cred->authdata.rp_id_hash,
+ sizeof(cred->authdata.rp_id_hash), &cred->cdh,
+ &cred->attcred.id, &cred->attcred.pubkey.es256) < 0) {
+ log_debug("%s: get_signed_hash_u2f", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ }
+
+ if (verify_sig(&dgst, &cred->attstmt.x5c, &cred->attstmt.sig) < 0) {
+ log_debug("%s: verify_sig", __func__);
+ r = FIDO_ERR_INVALID_SIG;
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ explicit_bzero(buf, sizeof(buf));
+
+ return (r);
+}
+
+int
+fido_cred_verify_self(const fido_cred_t *cred)
+{
+ unsigned char buf[SHA256_DIGEST_LENGTH];
+ fido_blob_t dgst;
+ int ok = -1;
+ int r;
+
+ dgst.ptr = buf;
+ dgst.len = sizeof(buf);
+
+ /* do we have everything we need? */
+ if (cred->cdh.ptr == NULL || cred->authdata_cbor.ptr == NULL ||
+ cred->attstmt.x5c.ptr != NULL || cred->attstmt.sig.ptr == NULL ||
+ cred->fmt == NULL || cred->attcred.id.ptr == NULL ||
+ cred->rp.id == NULL) {
+ log_debug("%s: cdh=%p, authdata=%p, x5c=%p, sig=%p, fmt=%p "
+ "id=%p, rp.id=%s", __func__, (void *)cred->cdh.ptr,
+ (void *)cred->authdata_cbor.ptr,
+ (void *)cred->attstmt.x5c.ptr,
+ (void *)cred->attstmt.sig.ptr, (void *)cred->fmt,
+ (void *)cred->attcred.id.ptr, cred->rp.id);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ if (check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) {
+ log_debug("%s: check_rp_id", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (check_flags(cred->authdata.flags, FIDO_OPT_TRUE, cred->uv) < 0) {
+ log_debug("%s: check_flags", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (check_extensions(cred->authdata_ext, cred->ext) < 0) {
+ log_debug("%s: check_extensions", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (!strcmp(cred->fmt, "packed")) {
+ if (get_signed_hash_packed(&dgst, &cred->cdh,
+ &cred->authdata_cbor) < 0) {
+ log_debug("%s: get_signed_hash_packed", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ } else {
+ if (get_signed_hash_u2f(&dgst, cred->authdata.rp_id_hash,
+ sizeof(cred->authdata.rp_id_hash), &cred->cdh,
+ &cred->attcred.id, &cred->attcred.pubkey.es256) < 0) {
+ log_debug("%s: get_signed_hash_u2f", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ }
+
+ switch (cred->attcred.type) {
+ case COSE_ES256:
+ ok = verify_sig_es256(&dgst, &cred->attcred.pubkey.es256,
+ &cred->attstmt.sig);
+ break;
+ case COSE_RS256:
+ ok = verify_sig_rs256(&dgst, &cred->attcred.pubkey.rs256,
+ &cred->attstmt.sig);
+ break;
+ case COSE_EDDSA:
+ ok = verify_sig_eddsa(&dgst, &cred->attcred.pubkey.eddsa,
+ &cred->attstmt.sig);
+ break;
+ default:
+ log_debug("%s: unsupported cose_alg %d", __func__,
+ cred->attcred.type);
+ r = FIDO_ERR_UNSUPPORTED_OPTION;
+ goto out;
+ }
+
+ if (ok < 0)
+ r = FIDO_ERR_INVALID_SIG;
+ else
+ r = FIDO_OK;
+
+out:
+ explicit_bzero(buf, sizeof(buf));
+
+ return (r);
+}
+
+fido_cred_t *
+fido_cred_new(void)
+{
+ return (calloc(1, sizeof(fido_cred_t)));
+}
+
+static void
+fido_cred_clean_authdata(fido_cred_t *cred)
+{
+ free(cred->authdata_cbor.ptr);
+ free(cred->attcred.id.ptr);
+
+ memset(&cred->authdata_ext, 0, sizeof(cred->authdata_ext));
+ memset(&cred->authdata_cbor, 0, sizeof(cred->authdata_cbor));
+ memset(&cred->authdata, 0, sizeof(cred->authdata));
+ memset(&cred->attcred, 0, sizeof(cred->attcred));
+}
+
+void
+fido_cred_reset_tx(fido_cred_t *cred)
+{
+ free(cred->cdh.ptr);
+ free(cred->rp.id);
+ free(cred->rp.name);
+ free(cred->user.id.ptr);
+ free(cred->user.icon);
+ free(cred->user.name);
+ free(cred->user.display_name);
+ free_blob_array(&cred->excl);
+
+ memset(&cred->cdh, 0, sizeof(cred->cdh));
+ memset(&cred->rp, 0, sizeof(cred->rp));
+ memset(&cred->user, 0, sizeof(cred->user));
+ memset(&cred->excl, 0, sizeof(cred->excl));
+
+ cred->type = 0;
+ cred->ext = 0;
+ cred->rk = FIDO_OPT_OMIT;
+ cred->uv = FIDO_OPT_OMIT;
+}
+
+static void
+fido_cred_clean_x509(fido_cred_t *cred)
+{
+ free(cred->attstmt.x5c.ptr);
+ cred->attstmt.x5c.ptr = NULL;
+ cred->attstmt.x5c.len = 0;
+}
+
+static void
+fido_cred_clean_sig(fido_cred_t *cred)
+{
+ free(cred->attstmt.sig.ptr);
+ cred->attstmt.sig.ptr = NULL;
+ cred->attstmt.sig.len = 0;
+}
+
+void
+fido_cred_reset_rx(fido_cred_t *cred)
+{
+ free(cred->fmt);
+ cred->fmt = NULL;
+
+ fido_cred_clean_authdata(cred);
+ fido_cred_clean_x509(cred);
+ fido_cred_clean_sig(cred);
+}
+
+void
+fido_cred_free(fido_cred_t **cred_p)
+{
+ fido_cred_t *cred;
+
+ if (cred_p == NULL || (cred = *cred_p) == NULL)
+ return;
+
+ fido_cred_reset_tx(cred);
+ fido_cred_reset_rx(cred);
+
+ free(cred);
+
+ *cred_p = NULL;
+}
+
+int
+fido_cred_set_authdata(fido_cred_t *cred, const unsigned char *ptr, size_t len)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int r;
+
+ fido_cred_clean_authdata(cred);
+
+ if (ptr == NULL || len == 0) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((item = cbor_load(ptr, len, &cbor)) == NULL) {
+ log_debug("%s: cbor_load", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if (decode_cred_authdata(item, cred->type, &cred->authdata_cbor,
+ &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) {
+ log_debug("%s: decode_cred_authdata", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ if (r != FIDO_OK)
+ fido_cred_clean_authdata(cred);
+
+ return (r);
+
+}
+
+int
+fido_cred_set_authdata_raw(fido_cred_t *cred, const unsigned char *ptr,
+ size_t len)
+{
+ cbor_item_t *item = NULL;
+ int r;
+
+ fido_cred_clean_authdata(cred);
+
+ if (ptr == NULL || len == 0) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((item = cbor_build_bytestring(ptr, len)) == NULL) {
+ log_debug("%s: cbor_build_bytestring", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (decode_cred_authdata(item, cred->type, &cred->authdata_cbor,
+ &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) {
+ log_debug("%s: decode_cred_authdata", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ if (r != FIDO_OK)
+ fido_cred_clean_authdata(cred);
+
+ return (r);
+
+}
+
+int
+fido_cred_set_x509(fido_cred_t *cred, const unsigned char *ptr, size_t len)
+{
+ unsigned char *x509;
+
+ fido_cred_clean_x509(cred);
+
+ if (ptr == NULL || len == 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ if ((x509 = malloc(len)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ memcpy(x509, ptr, len);
+ cred->attstmt.x5c.ptr = x509;
+ cred->attstmt.x5c.len = len;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_sig(fido_cred_t *cred, const unsigned char *ptr, size_t len)
+{
+ unsigned char *sig;
+
+ fido_cred_clean_sig(cred);
+
+ if (ptr == NULL || len == 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ if ((sig = malloc(len)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ memcpy(sig, ptr, len);
+ cred->attstmt.sig.ptr = sig;
+ cred->attstmt.sig.len = len;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_exclude(fido_cred_t *cred, const unsigned char *id_ptr, size_t id_len)
+{
+ fido_blob_t id_blob;
+ fido_blob_t *list_ptr;
+
+ memset(&id_blob, 0, sizeof(id_blob));
+
+ if (fido_blob_set(&id_blob, id_ptr, id_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if (cred->excl.len == SIZE_MAX) {
+ free(id_blob.ptr);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if ((list_ptr = recallocarray(cred->excl.ptr, cred->excl.len,
+ cred->excl.len + 1, sizeof(fido_blob_t))) == NULL) {
+ free(id_blob.ptr);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ list_ptr[cred->excl.len++] = id_blob;
+ cred->excl.ptr = list_ptr;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_clientdata_hash(fido_cred_t *cred, const unsigned char *hash,
+ size_t hash_len)
+{
+ if (fido_blob_set(&cred->cdh, hash, hash_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_rp(fido_cred_t *cred, const char *id, const char *name)
+{
+ fido_rp_t *rp = &cred->rp;
+
+ if (rp->id != NULL) {
+ free(rp->id);
+ rp->id = NULL;
+ }
+ if (rp->name != NULL) {
+ free(rp->name);
+ rp->name = NULL;
+ }
+
+ if (id != NULL && (rp->id = strdup(id)) == NULL)
+ goto fail;
+ if (name != NULL && (rp->name = strdup(name)) == NULL)
+ goto fail;
+
+ return (FIDO_OK);
+fail:
+ free(rp->id);
+ free(rp->name);
+ rp->id = NULL;
+ rp->name = NULL;
+
+ return (FIDO_ERR_INTERNAL);
+}
+
+int
+fido_cred_set_user(fido_cred_t *cred, const unsigned char *user_id,
+ size_t user_id_len, const char *name, const char *display_name,
+ const char *icon)
+{
+ fido_user_t *up = &cred->user;
+
+ if (up->id.ptr != NULL) {
+ free(up->id.ptr);
+ up->id.ptr = NULL;
+ up->id.len = 0;
+ }
+ if (up->name != NULL) {
+ free(up->name);
+ up->name = NULL;
+ }
+ if (up->display_name != NULL) {
+ free(up->display_name);
+ up->display_name = NULL;
+ }
+ if (up->icon != NULL) {
+ free(up->icon);
+ up->icon = NULL;
+ }
+
+ if (user_id != NULL) {
+ if ((up->id.ptr = malloc(user_id_len)) == NULL)
+ goto fail;
+ memcpy(up->id.ptr, user_id, user_id_len);
+ up->id.len = user_id_len;
+ }
+ if (name != NULL && (up->name = strdup(name)) == NULL)
+ goto fail;
+ if (display_name != NULL &&
+ (up->display_name = strdup(display_name)) == NULL)
+ goto fail;
+ if (icon != NULL && (up->icon = strdup(icon)) == NULL)
+ goto fail;
+
+ return (FIDO_OK);
+fail:
+ free(up->id.ptr);
+ free(up->name);
+ free(up->display_name);
+ free(up->icon);
+
+ up->id.ptr = NULL;
+ up->id.len = 0;
+ up->name = NULL;
+ up->display_name = NULL;
+ up->icon = NULL;
+
+ return (FIDO_ERR_INTERNAL);
+}
+
+int
+fido_cred_set_extensions(fido_cred_t *cred, int ext)
+{
+ if (ext != 0 && ext != FIDO_EXT_HMAC_SECRET)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ cred->ext = ext;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_options(fido_cred_t *cred, bool rk, bool uv)
+{
+ cred->rk = rk ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
+ cred->uv = uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_rk(fido_cred_t *cred, fido_opt_t rk)
+{
+ cred->rk = rk;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_uv(fido_cred_t *cred, fido_opt_t uv)
+{
+ cred->uv = uv;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_fmt(fido_cred_t *cred, const char *fmt)
+{
+ free(cred->fmt);
+ cred->fmt = NULL;
+
+ if (fmt == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if (strcmp(fmt, "packed") && strcmp(fmt, "fido-u2f"))
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((cred->fmt = strdup(fmt)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_type(fido_cred_t *cred, int cose_alg)
+{
+ if ((cose_alg != COSE_ES256 && cose_alg != COSE_RS256 &&
+ cose_alg != COSE_EDDSA) || cred->type != 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ cred->type = cose_alg;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_type(const fido_cred_t *cred)
+{
+ return (cred->type);
+}
+
+uint8_t
+fido_cred_flags(const fido_cred_t *cred)
+{
+ return (cred->authdata.flags);
+}
+
+const unsigned char *
+fido_cred_clientdata_hash_ptr(const fido_cred_t *cred)
+{
+ return (cred->cdh.ptr);
+}
+
+size_t
+fido_cred_clientdata_hash_len(const fido_cred_t *cred)
+{
+ return (cred->cdh.len);
+}
+
+const unsigned char *
+fido_cred_x5c_ptr(const fido_cred_t *cred)
+{
+ return (cred->attstmt.x5c.ptr);
+}
+
+size_t
+fido_cred_x5c_len(const fido_cred_t *cred)
+{
+ return (cred->attstmt.x5c.len);
+}
+
+const unsigned char *
+fido_cred_sig_ptr(const fido_cred_t *cred)
+{
+ return (cred->attstmt.sig.ptr);
+}
+
+size_t
+fido_cred_sig_len(const fido_cred_t *cred)
+{
+ return (cred->attstmt.sig.len);
+}
+
+const unsigned char *
+fido_cred_authdata_ptr(const fido_cred_t *cred)
+{
+ return (cred->authdata_cbor.ptr);
+}
+
+size_t
+fido_cred_authdata_len(const fido_cred_t *cred)
+{
+ return (cred->authdata_cbor.len);
+}
+
+const unsigned char *
+fido_cred_pubkey_ptr(const fido_cred_t *cred)
+{
+ const void *ptr;
+
+ switch (cred->attcred.type) {
+ case COSE_ES256:
+ ptr = &cred->attcred.pubkey.es256;
+ break;
+ case COSE_RS256:
+ ptr = &cred->attcred.pubkey.rs256;
+ break;
+ case COSE_EDDSA:
+ ptr = &cred->attcred.pubkey.eddsa;
+ break;
+ default:
+ ptr = NULL;
+ break;
+ }
+
+ return (ptr);
+}
+
+size_t
+fido_cred_pubkey_len(const fido_cred_t *cred)
+{
+ size_t len;
+
+ switch (cred->attcred.type) {
+ case COSE_ES256:
+ len = sizeof(cred->attcred.pubkey.es256);
+ break;
+ case COSE_RS256:
+ len = sizeof(cred->attcred.pubkey.rs256);
+ break;
+ case COSE_EDDSA:
+ len = sizeof(cred->attcred.pubkey.eddsa);
+ break;
+ default:
+ len = 0;
+ break;
+ }
+
+ return (len);
+}
+
+const unsigned char *
+fido_cred_id_ptr(const fido_cred_t *cred)
+{
+ return (cred->attcred.id.ptr);
+}
+
+size_t
+fido_cred_id_len(const fido_cred_t *cred)
+{
+ return (cred->attcred.id.len);
+}
+
+const char *
+fido_cred_fmt(const fido_cred_t *cred)
+{
+ return (cred->fmt);
+}
+
+const char *
+fido_cred_rp_id(const fido_cred_t *cred)
+{
+ return (cred->rp.id);
+}
+
+const char *
+fido_cred_rp_name(const fido_cred_t *cred)
+{
+ return (cred->rp.name);
+}
+
+const char *
+fido_cred_user_name(const fido_cred_t *cred)
+{
+ return (cred->user.name);
+}
+
+const char *
+fido_cred_display_name(const fido_cred_t *cred)
+{
+ return (cred->user.display_name);
+}
+
+const unsigned char *
+fido_cred_user_id_ptr(const fido_cred_t *cred)
+{
+ return (cred->user.id.ptr);
+}
+
+size_t
+fido_cred_user_id_len(const fido_cred_t *cred)
+{
+ return (cred->user.id.len);
+}
diff --git a/lib/libfido2/src/credman.c b/lib/libfido2/src/credman.c
new file mode 100644
index 00000000000..5a8b6edc406
--- /dev/null
+++ b/lib/libfido2/src/credman.c
@@ -0,0 +1,736 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/sha.h>
+
+#include <string.h>
+
+#include "fido.h"
+#include "fido/credman.h"
+#include "fido/es256.h"
+
+#define CMD_CRED_METADATA 0x01
+#define CMD_RP_BEGIN 0x02
+#define CMD_RP_NEXT 0x03
+#define CMD_RK_BEGIN 0x04
+#define CMD_RK_NEXT 0x05
+#define CMD_DELETE_CRED 0x06
+
+static int
+credman_grow_array(void **ptr, size_t *n_alloc, size_t *n_rx, size_t n,
+ size_t size)
+{
+ void *new_ptr;
+
+#ifdef FIDO_FUZZ
+ if (n > UINT8_MAX) {
+ log_debug("%s: n > UINT8_MAX", __func__);
+ return (-1);
+ }
+#endif
+
+ if (n < *n_alloc)
+ return (0);
+
+ /* sanity check */
+ if (*n_rx > 0 || *n_rx > *n_alloc || n < *n_alloc) {
+ log_debug("%s: n=%zu, n_rx=%zu, n_alloc=%zu", __func__, n,
+ *n_rx, *n_alloc);
+ return (-1);
+ }
+
+ if ((new_ptr = recallocarray(*ptr, *n_alloc, n, size)) == NULL)
+ return (-1);
+
+ *ptr = new_ptr;
+ *n_alloc = n;
+
+ return (0);
+}
+
+static int
+credman_prepare_hmac(uint8_t cmd, const fido_blob_t *body, cbor_item_t **param,
+ fido_blob_t *hmac_data)
+{
+ cbor_item_t *param_cbor[2];
+ size_t n;
+ int ok = -1;
+
+ memset(&param_cbor, 0, sizeof(param_cbor));
+
+ if (body == NULL)
+ return (fido_blob_set(hmac_data, &cmd, sizeof(cmd)));
+
+ switch (cmd) {
+ case CMD_RK_BEGIN:
+ n = 1;
+ param_cbor[n - 1] = fido_blob_encode(body);
+ break;
+ case CMD_DELETE_CRED:
+ n = 2;
+ param_cbor[n - 1] = encode_pubkey(body);
+ break;
+ default:
+ log_debug("%s: unknown cmd=0x%02x", __func__, cmd);
+ return (-1);
+ }
+
+ if (param_cbor[n - 1] == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ return (-1);
+ }
+ if ((*param = cbor_flatten_vector(param_cbor, n)) == NULL) {
+ log_debug("%s: cbor_flatten_vector", __func__);
+ goto fail;
+ }
+ if (cbor_build_frame(cmd, param_cbor, n, hmac_data) < 0) {
+ log_debug("%s: cbor_build_frame", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ cbor_vector_free(param_cbor, nitems(param_cbor));
+
+ return (ok);
+}
+
+static int
+credman_tx(fido_dev_t *dev, uint8_t cmd, const fido_blob_t *param,
+ const char *pin)
+{
+ fido_blob_t f;
+ fido_blob_t *ecdh = NULL;
+ fido_blob_t hmac;
+ es256_pk_t *pk = NULL;
+ cbor_item_t *argv[4];
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&f, 0, sizeof(f));
+ memset(&hmac, 0, sizeof(hmac));
+ memset(&argv, 0, sizeof(argv));
+
+ /* subCommand */
+ if ((argv[0] = cbor_build_uint8(cmd)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ /* pinProtocol, pinAuth */
+ if (pin != NULL) {
+ if (credman_prepare_hmac(cmd, param, &argv[1], &hmac) < 0) {
+ log_debug("%s: credman_prepare_hmac", __func__);
+ goto fail;
+ }
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
+ log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ if ((r = add_cbor_pin_params(dev, &hmac, pk, ecdh, pin,
+ &argv[3], &argv[2])) != FIDO_OK) {
+ log_debug("%s: add_cbor_pin_params", __func__);
+ goto fail;
+ }
+ }
+
+ /* framing and transmission */
+ if (cbor_build_frame(CTAP_CBOR_CRED_MGMT_PRE, argv, 4, &f) < 0 ||
+ tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+ free(hmac.ptr);
+
+ return (r);
+}
+
+static int
+credman_parse_metadata(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_credman_metadata_t *metadata = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1:
+ return (decode_uint64(val, &metadata->rk_existing));
+ case 2:
+ return (decode_uint64(val, &metadata->rk_remaining));
+ default:
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+}
+
+static int
+credman_rx_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[512];
+ int reply_len;
+ int r;
+
+ memset(metadata, 0, sizeof(*metadata));
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, metadata,
+ credman_parse_metadata)) != FIDO_OK) {
+ log_debug("%s: credman_parse_metadata", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+credman_get_metadata_wait(fido_dev_t *dev, fido_credman_metadata_t *metadata,
+ const char *pin, int ms)
+{
+ int r;
+
+ if ((r = credman_tx(dev, CMD_CRED_METADATA, NULL, pin)) != FIDO_OK ||
+ (r = credman_rx_metadata(dev, metadata, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_credman_get_dev_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata,
+ const char *pin)
+{
+ if (fido_dev_is_fido2(dev) == false)
+ return (FIDO_ERR_INVALID_COMMAND);
+ if (pin == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (credman_get_metadata_wait(dev, metadata, pin, -1));
+}
+
+static int
+credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_cred_t *cred = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 6: /* user entity */
+ return (decode_user(val, &cred->user));
+ case 7:
+ return (decode_cred_id(val, &cred->attcred.id));
+ case 8:
+ if (decode_pubkey(val, &cred->attcred.type,
+ &cred->attcred.pubkey) < 0)
+ return (-1);
+ cred->type = cred->attcred.type; /* XXX */
+ return (0);
+ default:
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+}
+
+static void
+credman_reset_rk(fido_credman_rk_t *rk)
+{
+ for (size_t i = 0; i < rk->n_alloc; i++) {
+ fido_cred_reset_tx(&rk->ptr[i]);
+ fido_cred_reset_rx(&rk->ptr[i]);
+ }
+
+ free(rk->ptr);
+ rk->ptr = NULL;
+ memset(rk, 0, sizeof(*rk));
+}
+
+static int
+credman_parse_rk_count(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_credman_rk_t *rk = arg;
+ uint64_t n;
+
+ /* totalCredentials */
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 9) {
+ log_debug("%s: cbor_type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
+ log_debug("%s: decode_uint64", __func__);
+ return (-1);
+ }
+
+ if (credman_grow_array((void **)&rk->ptr, &rk->n_alloc, &rk->n_rx,
+ (size_t)n, sizeof(*rk->ptr)) < 0) {
+ log_debug("%s: credman_grow_array", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+credman_rx_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ credman_reset_rk(rk);
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ /* adjust as needed */
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, rk,
+ credman_parse_rk_count)) != FIDO_OK) {
+ log_debug("%s: credman_parse_rk_count", __func__);
+ return (r);
+ }
+
+ if (rk->n_alloc == 0) {
+ log_debug("%s: n_alloc=0", __func__);
+ return (FIDO_OK);
+ }
+
+ /* parse the first rk */
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, &rk->ptr[0],
+ credman_parse_rk)) != FIDO_OK) {
+ log_debug("%s: credman_parse_rk", __func__);
+ return (r);
+ }
+
+ rk->n_rx++;
+
+ return (FIDO_OK);
+}
+
+static int
+credman_rx_next_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ /* sanity check */
+ if (rk->n_rx >= rk->n_alloc) {
+ log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rk->n_rx,
+ rk->n_alloc);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, &rk->ptr[rk->n_rx],
+ credman_parse_rk)) != FIDO_OK) {
+ log_debug("%s: credman_parse_rk", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+credman_get_rk_wait(fido_dev_t *dev, const char *rp_id, fido_credman_rk_t *rk,
+ const char *pin, int ms)
+{
+ fido_blob_t rp_dgst;
+ uint8_t dgst[SHA256_DIGEST_LENGTH];
+ int r;
+
+ if (SHA256((const unsigned char *)rp_id, strlen(rp_id), dgst) != dgst) {
+ log_debug("%s: sha256", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ rp_dgst.ptr = dgst;
+ rp_dgst.len = sizeof(dgst);
+
+ if ((r = credman_tx(dev, CMD_RK_BEGIN, &rp_dgst, pin)) != FIDO_OK ||
+ (r = credman_rx_rk(dev, rk, ms)) != FIDO_OK)
+ return (r);
+
+ while (rk->n_rx < rk->n_alloc) {
+ if ((r = credman_tx(dev, CMD_RK_NEXT, NULL, NULL)) != FIDO_OK ||
+ (r = credman_rx_next_rk(dev, rk, ms)) != FIDO_OK)
+ return (r);
+ rk->n_rx++;
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_credman_get_dev_rk(fido_dev_t *dev, const char *rp_id,
+ fido_credman_rk_t *rk, const char *pin)
+{
+ if (fido_dev_is_fido2(dev) == false)
+ return (FIDO_ERR_INVALID_COMMAND);
+ if (pin == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (credman_get_rk_wait(dev, rp_id, rk, pin, -1));
+}
+
+static int
+credman_del_rk_wait(fido_dev_t *dev, const unsigned char *cred_id,
+ size_t cred_id_len, const char *pin, int ms)
+{
+ fido_blob_t cred;
+ int r;
+
+ memset(&cred, 0, sizeof(cred));
+
+ if (fido_blob_set(&cred, cred_id, cred_id_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((r = credman_tx(dev, CMD_DELETE_CRED, &cred, pin)) != FIDO_OK ||
+ (r = rx_cbor_status(dev, ms)) != FIDO_OK)
+ goto fail;
+
+ r = FIDO_OK;
+fail:
+ free(cred.ptr);
+
+ return (r);
+}
+
+int
+fido_credman_del_dev_rk(fido_dev_t *dev, const unsigned char *cred_id,
+ size_t cred_id_len, const char *pin)
+{
+ if (fido_dev_is_fido2(dev) == false)
+ return (FIDO_ERR_INVALID_COMMAND);
+ if (pin == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (credman_del_rk_wait(dev, cred_id, cred_id_len, pin, -1));
+}
+
+static int
+credman_parse_rp(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ struct fido_credman_single_rp *rp = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 3:
+ return (decode_rp_entity(val, &rp->rp_entity));
+ case 4:
+ return (fido_blob_decode(val, &rp->rp_id_hash));
+ default:
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+}
+
+static void
+credman_reset_rp(fido_credman_rp_t *rp)
+{
+ for (size_t i = 0; i < rp->n_alloc; i++) {
+ free(rp->ptr[i].rp_entity.id);
+ free(rp->ptr[i].rp_entity.name);
+ rp->ptr[i].rp_entity.id = NULL;
+ rp->ptr[i].rp_entity.name = NULL;
+ free(rp->ptr[i].rp_id_hash.ptr);
+ memset(&rp->ptr[i].rp_id_hash, 0,
+ sizeof(rp->ptr[i].rp_id_hash));
+ }
+
+ free(rp->ptr);
+ rp->ptr = NULL;
+ memset(rp, 0, sizeof(*rp));
+}
+
+static int
+credman_parse_rp_count(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_credman_rp_t *rp = arg;
+ uint64_t n;
+
+ /* totalRPs */
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 5) {
+ log_debug("%s: cbor_type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
+ log_debug("%s: decode_uint64", __func__);
+ return (-1);
+ }
+
+ if (credman_grow_array((void **)&rp->ptr, &rp->n_alloc, &rp->n_rx,
+ (size_t)n, sizeof(*rp->ptr)) < 0) {
+ log_debug("%s: credman_grow_array", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+credman_rx_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ credman_reset_rp(rp);
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ /* adjust as needed */
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, rp,
+ credman_parse_rp_count)) != FIDO_OK) {
+ log_debug("%s: credman_parse_rp_count", __func__);
+ return (r);
+ }
+
+ if (rp->n_alloc == 0) {
+ log_debug("%s: n_alloc=0", __func__);
+ return (FIDO_OK);
+ }
+
+ /* parse the first rp */
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, &rp->ptr[0],
+ credman_parse_rp)) != FIDO_OK) {
+ log_debug("%s: credman_parse_rp", __func__);
+ return (r);
+ }
+
+ rp->n_rx++;
+
+ return (FIDO_OK);
+}
+
+static int
+credman_rx_next_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ /* sanity check */
+ if (rp->n_rx >= rp->n_alloc) {
+ log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rp->n_rx,
+ rp->n_alloc);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, &rp->ptr[rp->n_rx],
+ credman_parse_rp)) != FIDO_OK) {
+ log_debug("%s: credman_parse_rp", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+credman_get_rp_wait(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin,
+ int ms)
+{
+ int r;
+
+ if ((r = credman_tx(dev, CMD_RP_BEGIN, NULL, pin)) != FIDO_OK ||
+ (r = credman_rx_rp(dev, rp, ms)) != FIDO_OK)
+ return (r);
+
+ while (rp->n_rx < rp->n_alloc) {
+ if ((r = credman_tx(dev, CMD_RP_NEXT, NULL, NULL)) != FIDO_OK ||
+ (r = credman_rx_next_rp(dev, rp, ms)) != FIDO_OK)
+ return (r);
+ rp->n_rx++;
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_credman_get_dev_rp(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin)
+{
+ if (fido_dev_is_fido2(dev) == false)
+ return (FIDO_ERR_INVALID_COMMAND);
+ if (pin == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (credman_get_rp_wait(dev, rp, pin, -1));
+}
+
+fido_credman_rk_t *
+fido_credman_rk_new(void)
+{
+ return (calloc(1, sizeof(fido_credman_rk_t)));
+}
+
+void
+fido_credman_rk_free(fido_credman_rk_t **rk_p)
+{
+ fido_credman_rk_t *rk;
+
+ if (rk_p == NULL || (rk = *rk_p) == NULL)
+ return;
+
+ credman_reset_rk(rk);
+ free(rk);
+ *rk_p = NULL;
+}
+
+size_t
+fido_credman_rk_count(const fido_credman_rk_t *rk)
+{
+ return (rk->n_rx);
+}
+
+const fido_cred_t *
+fido_credman_rk(const fido_credman_rk_t *rk, size_t idx)
+{
+ if (idx >= rk->n_alloc)
+ return (NULL);
+
+ return (&rk->ptr[idx]);
+}
+
+fido_credman_metadata_t *
+fido_credman_metadata_new(void)
+{
+ return (calloc(1, sizeof(fido_credman_metadata_t)));
+}
+
+void
+fido_credman_metadata_free(fido_credman_metadata_t **metadata_p)
+{
+ fido_credman_metadata_t *metadata;
+
+ if (metadata_p == NULL || (metadata = *metadata_p) == NULL)
+ return;
+
+ free(metadata);
+ *metadata_p = NULL;
+}
+
+uint64_t
+fido_credman_rk_existing(const fido_credman_metadata_t *metadata)
+{
+ return (metadata->rk_existing);
+}
+
+uint64_t
+fido_credman_rk_remaining(const fido_credman_metadata_t *metadata)
+{
+ return (metadata->rk_remaining);
+}
+
+fido_credman_rp_t *
+fido_credman_rp_new(void)
+{
+ return (calloc(1, sizeof(fido_credman_rp_t)));
+}
+
+void
+fido_credman_rp_free(fido_credman_rp_t **rp_p)
+{
+ fido_credman_rp_t *rp;
+
+ if (rp_p == NULL || (rp = *rp_p) == NULL)
+ return;
+
+ credman_reset_rp(rp);
+ free(rp);
+ *rp_p = NULL;
+}
+
+size_t
+fido_credman_rp_count(const fido_credman_rp_t *rp)
+{
+ return (rp->n_rx);
+}
+
+const char *
+fido_credman_rp_id(const fido_credman_rp_t *rp, size_t idx)
+{
+ if (idx >= rp->n_alloc)
+ return (NULL);
+
+ return (rp->ptr[idx].rp_entity.id);
+}
+
+const char *
+fido_credman_rp_name(const fido_credman_rp_t *rp, size_t idx)
+{
+ if (idx >= rp->n_alloc)
+ return (NULL);
+
+ return (rp->ptr[idx].rp_entity.name);
+}
+
+size_t
+fido_credman_rp_id_hash_len(const fido_credman_rp_t *rp, size_t idx)
+{
+ if (idx >= rp->n_alloc)
+ return (0);
+
+ return (rp->ptr[idx].rp_id_hash.len);
+}
+
+const unsigned char *
+fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *rp, size_t idx)
+{
+ if (idx >= rp->n_alloc)
+ return (NULL);
+
+ return (rp->ptr[idx].rp_id_hash.ptr);
+}
diff --git a/lib/libfido2/src/dev.c b/lib/libfido2/src/dev.c
new file mode 100644
index 00000000000..8d4b6e71569
--- /dev/null
+++ b/lib/libfido2/src/dev.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "fido.h"
+
+#if defined(_WIN32)
+#include <windows.h>
+
+#include <winternl.h>
+#include <winerror.h>
+#include <stdio.h>
+#include <bcrypt.h>
+#include <sal.h>
+
+static int
+obtain_nonce(uint64_t *nonce)
+{
+ NTSTATUS status;
+
+ status = BCryptGenRandom(NULL, (unsigned char *)nonce, sizeof(*nonce),
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+
+ if (!NT_SUCCESS(status))
+ return (-1);
+
+ return (0);
+}
+#elif defined(__OpenBSD__)
+static int
+obtain_nonce(uint64_t *nonce)
+{
+ arc4random_buf(nonce, sizeof(*nonce));
+ return 0;
+}
+#elif defined(HAS_DEV_URANDOM)
+static int
+obtain_nonce(uint64_t *nonce)
+{
+ int fd = -1;
+ int ok = -1;
+ ssize_t r;
+
+ if ((fd = open(FIDO_RANDOM_DEV, O_RDONLY)) < 0)
+ goto fail;
+ if ((r = read(fd, nonce, sizeof(*nonce))) < 0 ||
+ (size_t)r != sizeof(*nonce))
+ goto fail;
+
+ ok = 0;
+fail:
+ if (fd != -1)
+ close(fd);
+
+ return (ok);
+}
+#else
+#error "please provide an implementation of obtain_nonce() for your platform"
+#endif /* _WIN32 */
+
+static int
+fido_dev_open_tx(fido_dev_t *dev, const char *path)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_INIT;
+
+ if (dev->io_handle != NULL) {
+ log_debug("%s: handle=%p", __func__, dev->io_handle);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if (dev->io.open == NULL || dev->io.close == NULL) {
+ log_debug("%s: NULL open/close", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if (obtain_nonce(&dev->nonce) < 0) {
+ log_debug("%s: obtain_nonce", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((dev->io_handle = dev->io.open(path)) == NULL) {
+ log_debug("%s: dev->io.open", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if (tx(dev, cmd, &dev->nonce, sizeof(dev->nonce)) < 0) {
+ log_debug("%s: tx", __func__);
+ dev->io.close(dev->io_handle);
+ dev->io_handle = NULL;
+ return (FIDO_ERR_TX);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_dev_open_rx(fido_dev_t *dev, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_INIT;
+ int n;
+
+ if ((n = rx(dev, cmd, &dev->attr, sizeof(dev->attr), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ goto fail;
+ }
+
+#ifdef FIDO_FUZZ
+ dev->attr.nonce = dev->nonce;
+#endif
+
+ if ((size_t)n != sizeof(dev->attr) || dev->attr.nonce != dev->nonce) {
+ log_debug("%s: invalid nonce", __func__);
+ goto fail;
+ }
+
+ dev->cid = dev->attr.cid;
+
+ return (FIDO_OK);
+fail:
+ dev->io.close(dev->io_handle);
+ dev->io_handle = NULL;
+
+ return (FIDO_ERR_RX);
+}
+
+static int
+fido_dev_open_wait(fido_dev_t *dev, const char *path, int ms)
+{
+ int r;
+
+ if ((r = fido_dev_open_tx(dev, path)) != FIDO_OK ||
+ (r = fido_dev_open_rx(dev, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_open(fido_dev_t *dev, const char *path)
+{
+ return (fido_dev_open_wait(dev, path, -1));
+}
+
+int
+fido_dev_close(fido_dev_t *dev)
+{
+ if (dev->io_handle == NULL || dev->io.close == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ dev->io.close(dev->io_handle);
+ dev->io_handle = NULL;
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_cancel(fido_dev_t *dev)
+{
+ if (tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CANCEL, NULL, 0) < 0)
+ return (FIDO_ERR_TX);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_set_io_functions(fido_dev_t *dev, const fido_dev_io_t *io)
+{
+ if (dev->io_handle != NULL) {
+ log_debug("%s: NULL handle", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if (io == NULL || io->open == NULL || io->close == NULL ||
+ io->read == NULL || io->write == NULL) {
+ log_debug("%s: NULL function", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ dev->io.open = io->open;
+ dev->io.close = io->close;
+ dev->io.read = io->read;
+ dev->io.write = io->write;
+
+ return (FIDO_OK);
+}
+
+void
+fido_init(int flags)
+{
+ if (flags & FIDO_DEBUG || getenv("FIDO_DEBUG") != NULL)
+ log_init();
+}
+
+fido_dev_t *
+fido_dev_new(void)
+{
+ fido_dev_t *dev;
+ fido_dev_io_t io;
+
+ if ((dev = calloc(1, sizeof(*dev))) == NULL)
+ return (NULL);
+
+ dev->cid = CTAP_CID_BROADCAST;
+
+ io.open = hid_open;
+ io.close = hid_close;
+ io.read = hid_read;
+ io.write = hid_write;
+
+ if (fido_dev_set_io_functions(dev, &io) != FIDO_OK) {
+ log_debug("%s: fido_dev_set_io_functions", __func__);
+ fido_dev_free(&dev);
+ return (NULL);
+ }
+
+ return (dev);
+}
+
+void
+fido_dev_free(fido_dev_t **dev_p)
+{
+ fido_dev_t *dev;
+
+ if (dev_p == NULL || (dev = *dev_p) == NULL)
+ return;
+
+ free(dev);
+
+ *dev_p = NULL;
+}
+
+uint8_t
+fido_dev_protocol(const fido_dev_t *dev)
+{
+ return (dev->attr.protocol);
+}
+
+uint8_t
+fido_dev_major(const fido_dev_t *dev)
+{
+ return (dev->attr.major);
+}
+
+uint8_t
+fido_dev_minor(const fido_dev_t *dev)
+{
+ return (dev->attr.minor);
+}
+
+uint8_t
+fido_dev_build(const fido_dev_t *dev)
+{
+ return (dev->attr.build);
+}
+
+uint8_t
+fido_dev_flags(const fido_dev_t *dev)
+{
+ return (dev->attr.flags);
+}
+
+bool
+fido_dev_is_fido2(const fido_dev_t *dev)
+{
+ return (dev->attr.flags & FIDO_CAP_CBOR);
+}
+
+void
+fido_dev_force_u2f(fido_dev_t *dev)
+{
+ dev->attr.flags &= ~FIDO_CAP_CBOR;
+}
+
+void
+fido_dev_force_fido2(fido_dev_t *dev)
+{
+ dev->attr.flags |= FIDO_CAP_CBOR;
+}
diff --git a/lib/libfido2/src/ecdh.c b/lib/libfido2/src/ecdh.c
new file mode 100644
index 00000000000..aa8c5c3be36
--- /dev/null
+++ b/lib/libfido2/src/ecdh.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+
+#include "fido.h"
+#include "fido/es256.h"
+
+static int
+do_ecdh(const es256_sk_t *sk, const es256_pk_t *pk, fido_blob_t **ecdh)
+{
+ EVP_PKEY *pk_evp = NULL;
+ EVP_PKEY *sk_evp = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ fido_blob_t *secret = NULL;
+ int ok = -1;
+
+ *ecdh = NULL;
+
+ /* allocate blobs for secret & ecdh */
+ if ((secret = fido_blob_new()) == NULL ||
+ (*ecdh = fido_blob_new()) == NULL)
+ goto fail;
+
+ /* wrap the keys as openssl objects */
+ if ((pk_evp = es256_pk_to_EVP_PKEY(pk)) == NULL ||
+ (sk_evp = es256_sk_to_EVP_PKEY(sk)) == NULL) {
+ log_debug("%s: es256_to_EVP_PKEY", __func__);
+ goto fail;
+ }
+
+ /* set ecdh parameters */
+ if ((ctx = EVP_PKEY_CTX_new(sk_evp, NULL)) == NULL ||
+ EVP_PKEY_derive_init(ctx) <= 0 ||
+ EVP_PKEY_derive_set_peer(ctx, pk_evp) <= 0) {
+ log_debug("%s: EVP_PKEY_derive_init", __func__);
+ goto fail;
+ }
+
+ /* perform ecdh */
+ if (EVP_PKEY_derive(ctx, NULL, &secret->len) <= 0 ||
+ (secret->ptr = calloc(1, secret->len)) == NULL ||
+ EVP_PKEY_derive(ctx, secret->ptr, &secret->len) <= 0) {
+ log_debug("%s: EVP_PKEY_derive", __func__);
+ goto fail;
+ }
+
+ /* use sha256 as a kdf on the resulting secret */
+ (*ecdh)->len = SHA256_DIGEST_LENGTH;
+ if (((*ecdh)->ptr = calloc(1, (*ecdh)->len)) == NULL ||
+ SHA256(secret->ptr, secret->len, (*ecdh)->ptr) != (*ecdh)->ptr) {
+ log_debug("%s: sha256", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pk_evp != NULL)
+ EVP_PKEY_free(pk_evp);
+ if (sk_evp != NULL)
+ EVP_PKEY_free(sk_evp);
+ if (ctx != NULL)
+ EVP_PKEY_CTX_free(ctx);
+ if (ok < 0)
+ fido_blob_free(ecdh);
+
+ fido_blob_free(&secret);
+
+ return (ok);
+}
+
+int
+fido_do_ecdh(fido_dev_t *dev, es256_pk_t **pk, fido_blob_t **ecdh)
+{
+ es256_sk_t *sk = NULL; /* our private key */
+ es256_pk_t *ak = NULL; /* authenticator's public key */
+ int r;
+
+ *pk = NULL; /* our public key; returned */
+ *ecdh = NULL; /* shared ecdh secret; returned */
+
+ if ((sk = es256_sk_new()) == NULL || (*pk = es256_pk_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (es256_sk_create(sk) < 0 || es256_derive_pk(sk, *pk) < 0) {
+ log_debug("%s: es256_derive_pk", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((ak = es256_pk_new()) == NULL ||
+ fido_dev_authkey(dev, ak) != FIDO_OK) {
+ log_debug("%s: fido_dev_authkey", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (do_ecdh(sk, ak, ecdh) < 0) {
+ log_debug("%s: do_ecdh", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ es256_sk_free(&sk);
+ es256_pk_free(&ak);
+
+ if (r != FIDO_OK) {
+ es256_pk_free(pk);
+ fido_blob_free(ecdh);
+ }
+
+ return (r);
+}
diff --git a/lib/libfido2/src/eddsa.c b/lib/libfido2/src/eddsa.c
new file mode 100644
index 00000000000..c346979414b
--- /dev/null
+++ b/lib/libfido2/src/eddsa.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+
+#include <string.h>
+#include "fido.h"
+#include "fido/eddsa.h"
+
+#if defined(LIBRESSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10101000L
+EVP_PKEY *
+EVP_PKEY_new_raw_public_key(int type, ENGINE *e, const unsigned char *key,
+ size_t keylen)
+{
+ (void)type;
+ (void)e;
+ (void)key;
+ (void)keylen;
+
+ return (NULL);
+}
+
+int
+EVP_PKEY_get_raw_public_key(const EVP_PKEY *pkey, unsigned char *pub,
+ size_t *len)
+{
+ (void)pkey;
+ (void)pub;
+ (void)len;
+
+ return (0);
+}
+
+int
+EVP_DigestVerify(EVP_MD_CTX *ctx, const unsigned char *sigret, size_t siglen,
+ const unsigned char *tbs, size_t tbslen)
+{
+ (void)ctx;
+ (void)sigret;
+ (void)siglen;
+ (void)tbs;
+ (void)tbslen;
+
+ return (0);
+}
+#endif /* LIBRESSL_VERSION_NUMBER || OPENSSL_VERSION_NUMBER < 0x10101000L */
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+EVP_MD_CTX *
+EVP_MD_CTX_new(void)
+{
+ return (NULL);
+}
+
+void
+EVP_MD_CTX_free(EVP_MD_CTX *ctx)
+{
+ (void)ctx;
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+
+static int
+decode_coord(const cbor_item_t *item, void *xy, size_t xy_len)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false ||
+ cbor_bytestring_length(item) != xy_len) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memcpy(xy, cbor_bytestring_handle(item), xy_len);
+
+ return (0);
+}
+
+static int
+decode_pubkey_point(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ eddsa_pk_t *k = arg;
+
+ if (cbor_isa_negint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8)
+ return (0); /* ignore */
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* x coordinate */
+ return (decode_coord(val, &k->x, sizeof(k->x)));
+ }
+
+ return (0); /* ignore */
+}
+
+int
+eddsa_pk_decode(const cbor_item_t *item, eddsa_pk_t *k)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, k, decode_pubkey_point) < 0) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+eddsa_pk_t *
+eddsa_pk_new(void)
+{
+ return (calloc(1, sizeof(eddsa_pk_t)));
+}
+
+void
+eddsa_pk_free(eddsa_pk_t **pkp)
+{
+ eddsa_pk_t *pk;
+
+ if (pkp == NULL || (pk = *pkp) == NULL)
+ return;
+
+ explicit_bzero(pk, sizeof(*pk));
+ free(pk);
+
+ *pkp = NULL;
+}
+
+int
+eddsa_pk_from_ptr(eddsa_pk_t *pk, const void *ptr, size_t len)
+{
+ if (len < sizeof(*pk))
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ memcpy(pk, ptr, sizeof(*pk));
+
+ return (FIDO_OK);
+}
+
+EVP_PKEY *
+eddsa_pk_to_EVP_PKEY(const eddsa_pk_t *k)
+{
+ EVP_PKEY *pkey = NULL;
+
+ if ((pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, k->x,
+ sizeof(k->x))) == NULL)
+ log_debug("%s: EVP_PKEY_new_raw_public_key", __func__);
+
+ return (pkey);
+}
+
+int
+eddsa_pk_from_EVP_PKEY(eddsa_pk_t *pk, const EVP_PKEY *pkey)
+{
+ size_t len = 0;
+
+ if (EVP_PKEY_get_raw_public_key(pkey, NULL, &len) != 1 ||
+ len != sizeof(pk->x))
+ return (FIDO_ERR_INTERNAL);
+ if (EVP_PKEY_get_raw_public_key(pkey, pk->x, &len) != 1 ||
+ len != sizeof(pk->x))
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
diff --git a/lib/libfido2/src/err.c b/lib/libfido2/src/err.c
new file mode 100644
index 00000000000..5d3efd4aba7
--- /dev/null
+++ b/lib/libfido2/src/err.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include "fido/err.h"
+
+const char *
+fido_strerr(int n)
+{
+ switch (n) {
+ case FIDO_ERR_SUCCESS:
+ return "FIDO_ERR_SUCCESS";
+ case FIDO_ERR_INVALID_COMMAND:
+ return "FIDO_ERR_INVALID_COMMAND";
+ case FIDO_ERR_INVALID_PARAMETER:
+ return "FIDO_ERR_INVALID_PARAMETER";
+ case FIDO_ERR_INVALID_LENGTH:
+ return "FIDO_ERR_INVALID_LENGTH";
+ case FIDO_ERR_INVALID_SEQ:
+ return "FIDO_ERR_INVALID_SEQ";
+ case FIDO_ERR_TIMEOUT:
+ return "FIDO_ERR_TIMEOUT";
+ case FIDO_ERR_CHANNEL_BUSY:
+ return "FIDO_ERR_CHANNEL_BUSY";
+ case FIDO_ERR_LOCK_REQUIRED:
+ return "FIDO_ERR_LOCK_REQUIRED";
+ case FIDO_ERR_INVALID_CHANNEL:
+ return "FIDO_ERR_INVALID_CHANNEL";
+ case FIDO_ERR_CBOR_UNEXPECTED_TYPE:
+ return "FIDO_ERR_UNEXPECTED_TYPE";
+ case FIDO_ERR_INVALID_CBOR:
+ return "FIDO_ERR_INVALID_CBOR";
+ case FIDO_ERR_MISSING_PARAMETER:
+ return "FIDO_ERR_MISSING_PARAMETER";
+ case FIDO_ERR_LIMIT_EXCEEDED:
+ return "FIDO_ERR_LIMIT_EXCEEDED";
+ case FIDO_ERR_UNSUPPORTED_EXTENSION:
+ return "FIDO_ERR_UNSUPPORTED_EXTENSION";
+ case FIDO_ERR_CREDENTIAL_EXCLUDED:
+ return "FIDO_ERR_CREDENTIAL_EXCLUDED";
+ case FIDO_ERR_PROCESSING:
+ return "FIDO_ERR_PROCESSING";
+ case FIDO_ERR_INVALID_CREDENTIAL:
+ return "FIDO_ERR_INVALID_CREDENTIAL";
+ case FIDO_ERR_USER_ACTION_PENDING:
+ return "FIDO_ERR_ACTION_PENDING";
+ case FIDO_ERR_OPERATION_PENDING:
+ return "FIDO_ERR_OPERATION_PENDING";
+ case FIDO_ERR_NO_OPERATIONS:
+ return "FIDO_ERR_NO_OPERATIONS";
+ case FIDO_ERR_UNSUPPORTED_ALGORITHM:
+ return "FIDO_ERR_UNSUPPORTED_ALGORITHM";
+ case FIDO_ERR_OPERATION_DENIED:
+ return "FIDO_ERR_OPERATION_DENIED";
+ case FIDO_ERR_KEY_STORE_FULL:
+ return "FIDO_ERR_STORE_FULL";
+ case FIDO_ERR_NOT_BUSY:
+ return "FIDO_ERR_NOT_BUSY";
+ case FIDO_ERR_NO_OPERATION_PENDING:
+ return "FIDO_ERR_OPERATION_PENDING";
+ case FIDO_ERR_UNSUPPORTED_OPTION:
+ return "FIDO_ERR_UNSUPPORTED_OPTION";
+ case FIDO_ERR_INVALID_OPTION:
+ return "FIDO_ERR_INVALID_OPTION";
+ case FIDO_ERR_KEEPALIVE_CANCEL:
+ return "FIDO_ERR_KEEPALIVE_CANCEL";
+ case FIDO_ERR_NO_CREDENTIALS:
+ return "FIDO_ERR_NO_CREDENTIALS";
+ case FIDO_ERR_USER_ACTION_TIMEOUT:
+ return "FIDO_ERR_ACTION_TIMEOUT";
+ case FIDO_ERR_NOT_ALLOWED:
+ return "FIDO_ERR_NOT_ALLOWED";
+ case FIDO_ERR_PIN_INVALID:
+ return "FIDO_ERR_PIN_INVALID";
+ case FIDO_ERR_PIN_BLOCKED:
+ return "FIDO_ERR_PIN_BLOCKED";
+ case FIDO_ERR_PIN_AUTH_INVALID:
+ return "FIDO_ERR_AUTH_INVALID";
+ case FIDO_ERR_PIN_AUTH_BLOCKED:
+ return "FIDO_ERR_AUTH_BLOCKED";
+ case FIDO_ERR_PIN_NOT_SET:
+ return "FIDO_ERR_NOT_SET";
+ case FIDO_ERR_PIN_REQUIRED:
+ return "FIDO_ERR_PIN_REQUIRED";
+ case FIDO_ERR_PIN_POLICY_VIOLATION:
+ return "FIDO_ERR_POLICY_VIOLATION";
+ case FIDO_ERR_PIN_TOKEN_EXPIRED:
+ return "FIDO_ERR_TOKEN_EXPIRED";
+ case FIDO_ERR_REQUEST_TOO_LARGE:
+ return "FIDO_ERR_TOO_LARGE";
+ case FIDO_ERR_ACTION_TIMEOUT:
+ return "FIDO_ERR_ACTION_TIMEOUT";
+ case FIDO_ERR_UP_REQUIRED:
+ return "FIDO_ERR_UP_REQUIRED";
+ case FIDO_ERR_ERR_OTHER:
+ return "FIDO_ERR_OTHER";
+ case FIDO_ERR_SPEC_LAST:
+ return "FIDO_ERR_SPEC_LAST";
+ case FIDO_ERR_TX:
+ return "FIDO_ERR_TX";
+ case FIDO_ERR_RX:
+ return "FIDO_ERR_RX";
+ case FIDO_ERR_RX_NOT_CBOR:
+ return "FIDO_ERR_RX_NOT_CBOR";
+ case FIDO_ERR_RX_INVALID_CBOR:
+ return "FIDO_ERR_RX_INVALID_CBOR";
+ case FIDO_ERR_INVALID_PARAM:
+ return "FIDO_ERR_INVALID_PARAM";
+ case FIDO_ERR_INVALID_SIG:
+ return "FIDO_ERR_INVALID_SIG";
+ case FIDO_ERR_INVALID_ARGUMENT:
+ return "FIDO_ERR_INVALID_ARGUMENT";
+ case FIDO_ERR_USER_PRESENCE_REQUIRED:
+ return "FIDO_ERR_USER_PRESENCE_REQUIRED";
+ case FIDO_ERR_INTERNAL:
+ return "FIDO_ERR_INTERNAL";
+ default:
+ return "FIDO_ERR_UNKNOWN";
+ }
+}
diff --git a/lib/libfido2/src/es256.c b/lib/libfido2/src/es256.c
new file mode 100644
index 00000000000..e8f59344fc0
--- /dev/null
+++ b/lib/libfido2/src/es256.c
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+
+#include <string.h>
+#include "fido.h"
+#include "fido/es256.h"
+
+static int
+decode_coord(const cbor_item_t *item, void *xy, size_t xy_len)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false ||
+ cbor_bytestring_length(item) != xy_len) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memcpy(xy, cbor_bytestring_handle(item), xy_len);
+
+ return (0);
+}
+
+static int
+decode_pubkey_point(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ es256_pk_t *k = arg;
+
+ if (cbor_isa_negint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8)
+ return (0); /* ignore */
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* x coordinate */
+ return (decode_coord(val, &k->x, sizeof(k->x)));
+ case 2: /* y coordinate */
+ return (decode_coord(val, &k->y, sizeof(k->y)));
+ }
+
+ return (0); /* ignore */
+}
+
+int
+es256_pk_decode(const cbor_item_t *item, es256_pk_t *k)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, k, decode_pubkey_point) < 0) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+cbor_item_t *
+es256_pk_encode(const es256_pk_t *pk, int ecdh)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_pair argv[5];
+ int alg;
+ int ok = -1;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((item = cbor_new_definite_map(5)) == NULL)
+ goto fail;
+
+ /* kty */
+ if ((argv[0].key = cbor_build_uint8(1)) == NULL ||
+ (argv[0].value = cbor_build_uint8(2)) == NULL ||
+ !cbor_map_add(item, argv[0]))
+ goto fail;
+
+ /*
+ * "The COSEAlgorithmIdentifier used is -25 (ECDH-ES +
+ * HKDF-256) although this is NOT the algorithm actually
+ * used. Setting this to a different value may result in
+ * compatibility issues."
+ */
+ if (ecdh)
+ alg = COSE_ECDH_ES256;
+ else
+ alg = COSE_ES256;
+
+ /* alg */
+ if ((argv[1].key = cbor_build_uint8(3)) == NULL ||
+ (argv[1].value = cbor_build_negint8(-alg - 1)) == NULL ||
+ !cbor_map_add(item, argv[1]))
+ goto fail;
+
+ /* crv */
+ if ((argv[2].key = cbor_build_negint8(0)) == NULL ||
+ (argv[2].value = cbor_build_uint8(1)) == NULL ||
+ !cbor_map_add(item, argv[2]))
+ goto fail;
+
+ /* x */
+ if ((argv[3].key = cbor_build_negint8(1)) == NULL ||
+ (argv[3].value = cbor_build_bytestring(pk->x,
+ sizeof(pk->x))) == NULL || !cbor_map_add(item, argv[3]))
+ goto fail;
+
+ /* y */
+ if ((argv[4].key = cbor_build_negint8(2)) == NULL ||
+ (argv[4].value = cbor_build_bytestring(pk->y,
+ sizeof(pk->y))) == NULL || !cbor_map_add(item, argv[4]))
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ if (item != NULL) {
+ cbor_decref(&item);
+ item = NULL;
+ }
+ }
+
+ for (size_t i = 0; i < 5; i++) {
+ if (argv[i].key)
+ cbor_decref(&argv[i].key);
+ if (argv[i].value)
+ cbor_decref(&argv[i].value);
+ }
+
+ return (item);
+}
+
+es256_sk_t *
+es256_sk_new(void)
+{
+ return (calloc(1, sizeof(es256_sk_t)));
+}
+
+void
+es256_sk_free(es256_sk_t **skp)
+{
+ es256_sk_t *sk;
+
+ if (skp == NULL || (sk = *skp) == NULL)
+ return;
+
+ explicit_bzero(sk, sizeof(*sk));
+ free(sk);
+
+ *skp = NULL;
+}
+
+es256_pk_t *
+es256_pk_new(void)
+{
+ return (calloc(1, sizeof(es256_pk_t)));
+}
+
+void
+es256_pk_free(es256_pk_t **pkp)
+{
+ es256_pk_t *pk;
+
+ if (pkp == NULL || (pk = *pkp) == NULL)
+ return;
+
+ explicit_bzero(pk, sizeof(*pk));
+ free(pk);
+
+ *pkp = NULL;
+}
+
+int
+es256_pk_from_ptr(es256_pk_t *pk, const void *ptr, size_t len)
+{
+ if (len < sizeof(*pk))
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ memcpy(pk, ptr, sizeof(*pk));
+
+ return (FIDO_OK);
+}
+
+int
+es256_pk_set_x(es256_pk_t *pk, const unsigned char *x)
+{
+ memcpy(pk->x, x, sizeof(pk->x));
+
+ return (0);
+}
+
+int
+es256_pk_set_y(es256_pk_t *pk, const unsigned char *y)
+{
+ memcpy(pk->y, y, sizeof(pk->y));
+
+ return (0);
+}
+
+int
+es256_sk_create(es256_sk_t *key)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_PKEY_CTX *kctx = NULL;
+ EVP_PKEY *p = NULL;
+ EVP_PKEY *k = NULL;
+ const EC_KEY *ec;
+ const BIGNUM *d;
+ const int nid = NID_X9_62_prime256v1;
+ int n;
+ int ok = -1;
+
+ if ((pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)) == NULL ||
+ EVP_PKEY_paramgen_init(pctx) <= 0 ||
+ EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, nid) <= 0 ||
+ EVP_PKEY_paramgen(pctx, &p) <= 0) {
+ log_debug("%s: EVP_PKEY_paramgen", __func__);
+ goto fail;
+ }
+
+ if ((kctx = EVP_PKEY_CTX_new(p, NULL)) == NULL ||
+ EVP_PKEY_keygen_init(kctx) <= 0 || EVP_PKEY_keygen(kctx, &k) <= 0) {
+ log_debug("%s: EVP_PKEY_keygen", __func__);
+ goto fail;
+ }
+
+ if ((ec = EVP_PKEY_get0_EC_KEY(k)) == NULL ||
+ (d = EC_KEY_get0_private_key(ec)) == NULL ||
+ (n = BN_num_bytes(d)) < 0 || (size_t)n > sizeof(key->d) ||
+ (n = BN_bn2bin(d, key->d)) < 0 || (size_t)n > sizeof(key->d)) {
+ log_debug("%s: EC_KEY_get0_private_key", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (p != NULL)
+ EVP_PKEY_free(p);
+ if (k != NULL)
+ EVP_PKEY_free(k);
+ if (pctx != NULL)
+ EVP_PKEY_CTX_free(pctx);
+ if (kctx != NULL)
+ EVP_PKEY_CTX_free(kctx);
+
+ return (ok);
+}
+
+EVP_PKEY *
+es256_pk_to_EVP_PKEY(const es256_pk_t *k)
+{
+ BN_CTX *bnctx = NULL;
+ EC_KEY *ec = NULL;
+ EC_POINT *q = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *x = NULL;
+ BIGNUM *y = NULL;
+ const EC_GROUP *g = NULL;
+ const int nid = NID_X9_62_prime256v1;
+ int ok = -1;
+
+ if ((bnctx = BN_CTX_new()) == NULL ||
+ (x = BN_CTX_get(bnctx)) == NULL ||
+ (y = BN_CTX_get(bnctx)) == NULL)
+ goto fail;
+
+ if (BN_bin2bn(k->x, sizeof(k->x), x) == NULL ||
+ BN_bin2bn(k->y, sizeof(k->y), y) == NULL) {
+ log_debug("%s: BN_bin2bn", __func__);
+ goto fail;
+ }
+
+ if ((ec = EC_KEY_new_by_curve_name(nid)) == NULL ||
+ (g = EC_KEY_get0_group(ec)) == NULL) {
+ log_debug("%s: EC_KEY init", __func__);
+ goto fail;
+ }
+
+ if ((q = EC_POINT_new(g)) == NULL ||
+ EC_POINT_set_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 ||
+ EC_KEY_set_public_key(ec, q) == 0) {
+ log_debug("%s: EC_KEY_set_public_key", __func__);
+ goto fail;
+ }
+
+ if ((pkey = EVP_PKEY_new()) == NULL ||
+ EVP_PKEY_assign_EC_KEY(pkey, ec) == 0) {
+ log_debug("%s: EVP_PKEY_assign_EC_KEY", __func__);
+ goto fail;
+ }
+
+ ec = NULL; /* at this point, ec belongs to evp */
+
+ ok = 0;
+fail:
+ if (bnctx != NULL)
+ BN_CTX_free(bnctx);
+ if (ec != NULL)
+ EC_KEY_free(ec);
+ if (q != NULL)
+ EC_POINT_free(q);
+ if (ok < 0 && pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+ return (pkey);
+}
+
+int
+es256_pk_from_EC_KEY(es256_pk_t *pk, const EC_KEY *ec)
+{
+ BN_CTX *ctx = NULL;
+ BIGNUM *x = NULL;
+ BIGNUM *y = NULL;
+ const EC_POINT *q = NULL;
+ const EC_GROUP *g = NULL;
+ int ok = FIDO_ERR_INTERNAL;
+ int n;
+
+ if ((q = EC_KEY_get0_public_key(ec)) == NULL ||
+ (g = EC_KEY_get0_group(ec)) == NULL)
+ goto fail;
+
+ if ((ctx = BN_CTX_new()) == NULL ||
+ (x = BN_CTX_get(ctx)) == NULL ||
+ (y = BN_CTX_get(ctx)) == NULL)
+ goto fail;
+
+ if (EC_POINT_get_affine_coordinates_GFp(g, q, x, y, ctx) == 0 ||
+ (n = BN_num_bytes(x)) < 0 || (size_t)n > sizeof(pk->x) ||
+ (n = BN_num_bytes(y)) < 0 || (size_t)n > sizeof(pk->y)) {
+ log_debug("%s: EC_POINT_get_affine_coordinates_GFp", __func__);
+ goto fail;
+ }
+
+ if ((n = BN_bn2bin(x, pk->x)) < 0 || (size_t)n > sizeof(pk->x) ||
+ (n = BN_bn2bin(y, pk->y)) < 0 || (size_t)n > sizeof(pk->y)) {
+ log_debug("%s: BN_bn2bin", __func__);
+ goto fail;
+ }
+
+ ok = FIDO_OK;
+fail:
+ if (ctx != NULL)
+ BN_CTX_free(ctx);
+
+ return (ok);
+}
+
+EVP_PKEY *
+es256_sk_to_EVP_PKEY(const es256_sk_t *k)
+{
+ BN_CTX *bnctx = NULL;
+ EC_KEY *ec = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *d = NULL;
+ const int nid = NID_X9_62_prime256v1;
+ int ok = -1;
+
+ if ((bnctx = BN_CTX_new()) == NULL || (d = BN_CTX_get(bnctx)) == NULL ||
+ BN_bin2bn(k->d, sizeof(k->d), d) == NULL) {
+ log_debug("%s: BN_bin2bn", __func__);
+ goto fail;
+ }
+
+ if ((ec = EC_KEY_new_by_curve_name(nid)) == NULL ||
+ EC_KEY_set_private_key(ec, d) == 0) {
+ log_debug("%s: EC_KEY_set_private_key", __func__);
+ goto fail;
+ }
+
+ if ((pkey = EVP_PKEY_new()) == NULL ||
+ EVP_PKEY_assign_EC_KEY(pkey, ec) == 0) {
+ log_debug("%s: EVP_PKEY_assign_EC_KEY", __func__);
+ goto fail;
+ }
+
+ ec = NULL; /* at this point, ec belongs to evp */
+
+ ok = 0;
+fail:
+ if (bnctx != NULL)
+ BN_CTX_free(bnctx);
+ if (ec != NULL)
+ EC_KEY_free(ec);
+ if (ok < 0 && pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+ return (pkey);
+}
+
+int
+es256_derive_pk(const es256_sk_t *sk, es256_pk_t *pk)
+{
+ BIGNUM *d = NULL;
+ EC_KEY *ec = NULL;
+ EC_POINT *q = NULL;
+ const EC_GROUP *g = NULL;
+ const int nid = NID_X9_62_prime256v1;
+ int ok = -1;
+
+ if ((d = BN_bin2bn(sk->d, (int)sizeof(sk->d), NULL)) == NULL ||
+ (ec = EC_KEY_new_by_curve_name(nid)) == NULL ||
+ (g = EC_KEY_get0_group(ec)) == NULL ||
+ (q = EC_POINT_new(g)) == NULL) {
+ log_debug("%s: get", __func__);
+ goto fail;
+ }
+
+ if (EC_POINT_mul(g, q, d, NULL, NULL, NULL) == 0 ||
+ EC_KEY_set_public_key(ec, q) == 0 ||
+ es256_pk_from_EC_KEY(pk, ec) != FIDO_OK) {
+ log_debug("%s: set", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (d != NULL)
+ BN_clear_free(d);
+ if (q != NULL)
+ EC_POINT_free(q);
+ if (ec != NULL)
+ EC_KEY_free(ec);
+
+ return (ok);
+}
diff --git a/lib/libfido2/src/export.llvm b/lib/libfido2/src/export.llvm
new file mode 100644
index 00000000000..ef99a2617df
--- /dev/null
+++ b/lib/libfido2/src/export.llvm
@@ -0,0 +1,178 @@
+_eddsa_pk_free
+_eddsa_pk_from_EVP_PKEY
+_eddsa_pk_from_ptr
+_eddsa_pk_new
+_eddsa_pk_to_EVP_PKEY
+_es256_pk_free
+_es256_pk_from_EC_KEY
+_es256_pk_from_ptr
+_es256_pk_new
+_es256_pk_to_EVP_PKEY
+_fido_assert_allow_cred
+_fido_assert_authdata_len
+_fido_assert_authdata_ptr
+_fido_assert_clientdata_hash_len
+_fido_assert_clientdata_hash_ptr
+_fido_assert_count
+_fido_assert_flags
+_fido_assert_free
+_fido_assert_hmac_secret_len
+_fido_assert_hmac_secret_ptr
+_fido_assert_id_len
+_fido_assert_id_ptr
+_fido_assert_new
+_fido_assert_rp_id
+_fido_assert_set_authdata
+_fido_assert_set_authdata_raw
+_fido_assert_set_clientdata_hash
+_fido_assert_set_count
+_fido_assert_set_extensions
+_fido_assert_set_hmac_salt
+_fido_assert_set_options
+_fido_assert_set_rp
+_fido_assert_set_sig
+_fido_assert_set_up
+_fido_assert_set_uv
+_fido_assert_sigcount
+_fido_assert_sig_len
+_fido_assert_sig_ptr
+_fido_assert_user_display_name
+_fido_assert_user_icon
+_fido_assert_user_id_len
+_fido_assert_user_id_ptr
+_fido_assert_user_name
+_fido_assert_verify
+_fido_bio_dev_enroll_begin
+_fido_bio_dev_enroll_cancel
+_fido_bio_dev_enroll_continue
+_fido_bio_dev_enroll_remove
+_fido_bio_dev_get_info
+_fido_bio_dev_get_template_array
+_fido_bio_dev_set_template_name
+_fido_bio_enroll_free
+_fido_bio_enroll_last_status
+_fido_bio_enroll_new
+_fido_bio_enroll_remaining_samples
+_fido_bio_info_free
+_fido_bio_info_max_samples
+_fido_bio_info_new
+_fido_bio_info_type
+_fido_bio_template
+_fido_bio_template_array_count
+_fido_bio_template_array_free
+_fido_bio_template_array_new
+_fido_bio_template_free
+_fido_bio_template_id_len
+_fido_bio_template_id_ptr
+_fido_bio_template_name
+_fido_bio_template_new
+_fido_bio_template_set_id
+_fido_bio_template_set_name
+_fido_cbor_info_aaguid_len
+_fido_cbor_info_aaguid_ptr
+_fido_cbor_info_extensions_len
+_fido_cbor_info_extensions_ptr
+_fido_cbor_info_free
+_fido_cbor_info_maxmsgsiz
+_fido_cbor_info_new
+_fido_cbor_info_options_len
+_fido_cbor_info_options_name_ptr
+_fido_cbor_info_options_value_ptr
+_fido_cbor_info_protocols_len
+_fido_cbor_info_protocols_ptr
+_fido_cbor_info_versions_len
+_fido_cbor_info_versions_ptr
+_fido_cred_authdata_len
+_fido_cred_authdata_ptr
+_fido_cred_clientdata_hash_len
+_fido_cred_clientdata_hash_ptr
+_fido_cred_display_name
+_fido_cred_exclude
+_fido_cred_flags
+_fido_cred_fmt
+_fido_cred_free
+_fido_cred_id_len
+_fido_cred_id_ptr
+_fido_credman_del_dev_rk
+_fido_credman_get_dev_metadata
+_fido_credman_get_dev_rk
+_fido_credman_get_dev_rp
+_fido_credman_metadata_free
+_fido_credman_metadata_new
+_fido_credman_rk
+_fido_credman_rk_count
+_fido_credman_rk_existing
+_fido_credman_rk_free
+_fido_credman_rk_new
+_fido_credman_rk_remaining
+_fido_credman_rp_count
+_fido_credman_rp_free
+_fido_credman_rp_id
+_fido_credman_rp_id_hash_len
+_fido_credman_rp_id_hash_ptr
+_fido_credman_rp_name
+_fido_credman_rp_new
+_fido_cred_new
+_fido_cred_pubkey_len
+_fido_cred_pubkey_ptr
+_fido_cred_rp_id
+_fido_cred_rp_name
+_fido_cred_set_authdata
+_fido_cred_set_authdata_raw
+_fido_cred_set_clientdata_hash
+_fido_cred_set_extensions
+_fido_cred_set_fmt
+_fido_cred_set_options
+_fido_cred_set_rk
+_fido_cred_set_rp
+_fido_cred_set_sig
+_fido_cred_set_type
+_fido_cred_set_user
+_fido_cred_set_uv
+_fido_cred_set_x509
+_fido_cred_sig_len
+_fido_cred_sig_ptr
+_fido_cred_type
+_fido_cred_user_id_len
+_fido_cred_user_id_ptr
+_fido_cred_user_name
+_fido_cred_verify
+_fido_cred_verify_self
+_fido_cred_x5c_len
+_fido_cred_x5c_ptr
+_fido_dev_build
+_fido_dev_cancel
+_fido_dev_close
+_fido_dev_flags
+_fido_dev_force_fido2
+_fido_dev_force_u2f
+_fido_dev_free
+_fido_dev_get_assert
+_fido_dev_get_cbor_info
+_fido_dev_get_retry_count
+_fido_dev_info_free
+_fido_dev_info_manifest
+_fido_dev_info_manufacturer_string
+_fido_dev_info_new
+_fido_dev_info_path
+_fido_dev_info_product
+_fido_dev_info_product_string
+_fido_dev_info_ptr
+_fido_dev_info_vendor
+_fido_dev_is_fido2
+_fido_dev_major
+_fido_dev_make_cred
+_fido_dev_minor
+_fido_dev_new
+_fido_dev_open
+_fido_dev_protocol
+_fido_dev_reset
+_fido_dev_set_io_functions
+_fido_dev_set_pin
+_fido_init
+_fido_strerr
+_rs256_pk_free
+_rs256_pk_from_ptr
+_rs256_pk_from_RSA
+_rs256_pk_new
+_rs256_pk_to_EVP_PKEY
diff --git a/lib/libfido2/src/extern.h b/lib/libfido2/src/extern.h
new file mode 100644
index 00000000000..2f69094f33c
--- /dev/null
+++ b/lib/libfido2/src/extern.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _EXTERN_H
+#define _EXTERN_H
+
+/* aes256 */
+int aes256_cbc_dec(const fido_blob_t *, const fido_blob_t *, fido_blob_t *);
+int aes256_cbc_enc(const fido_blob_t *, const fido_blob_t *, fido_blob_t *);
+
+/* cbor encoding functions */
+cbor_item_t *cbor_flatten_vector(cbor_item_t **, size_t);
+cbor_item_t *encode_assert_options(fido_opt_t, fido_opt_t);
+cbor_item_t *encode_change_pin_auth(const fido_blob_t *, const fido_blob_t *,
+ const fido_blob_t *);
+cbor_item_t *encode_extensions(int);
+cbor_item_t *encode_hmac_secret_param(const fido_blob_t *, const es256_pk_t *,
+ const fido_blob_t *);
+cbor_item_t *encode_options(fido_opt_t, fido_opt_t);
+cbor_item_t *encode_pin_auth(const fido_blob_t *, const fido_blob_t *);
+cbor_item_t *encode_pin_enc(const fido_blob_t *, const fido_blob_t *);
+cbor_item_t *encode_pin_hash_enc(const fido_blob_t *, const fido_blob_t *);
+cbor_item_t *encode_pin_opt(void);
+cbor_item_t *encode_pubkey(const fido_blob_t *);
+cbor_item_t *encode_pubkey_list(const fido_blob_array_t *);
+cbor_item_t *encode_pubkey_param(int);
+cbor_item_t *encode_rp_entity(const fido_rp_t *);
+cbor_item_t *encode_set_pin_auth(const fido_blob_t *, const fido_blob_t *);
+cbor_item_t *encode_user_entity(const fido_user_t *);
+cbor_item_t *es256_pk_encode(const es256_pk_t *, int);
+
+/* cbor decoding functions */
+int decode_attstmt(const cbor_item_t *, fido_attstmt_t *);
+int decode_cred_authdata(const cbor_item_t *, int, fido_blob_t *,
+ fido_authdata_t *, fido_attcred_t *, int *);
+int decode_assert_authdata(const cbor_item_t *, fido_blob_t *,
+ fido_authdata_t *, int *, fido_blob_t *);
+int decode_cred_id(const cbor_item_t *, fido_blob_t *);
+int decode_fmt(const cbor_item_t *, char **);
+int decode_pubkey(const cbor_item_t *, int *, void *);
+int decode_rp_entity(const cbor_item_t *, fido_rp_t *);
+int decode_uint64(const cbor_item_t *, uint64_t *);
+int decode_user(const cbor_item_t *, fido_user_t *);
+int es256_pk_decode(const cbor_item_t *, es256_pk_t *);
+int rs256_pk_decode(const cbor_item_t *, rs256_pk_t *);
+int eddsa_pk_decode(const cbor_item_t *, eddsa_pk_t *);
+
+/* auxiliary cbor routines */
+int cbor_add_bool(cbor_item_t *, const char *, fido_opt_t);
+int cbor_add_bytestring(cbor_item_t *, const char *, const unsigned char *,
+ size_t);
+int cbor_add_string(cbor_item_t *, const char *, const char *);
+int cbor_array_iter(const cbor_item_t *, void *, int(*)(const cbor_item_t *,
+ void *));
+int cbor_build_frame(uint8_t, cbor_item_t *[], size_t, fido_blob_t *);
+int cbor_bytestring_copy(const cbor_item_t *, unsigned char **, size_t *);
+int cbor_map_iter(const cbor_item_t *, void *, int(*)(const cbor_item_t *,
+ const cbor_item_t *, void *));
+int cbor_string_copy(const cbor_item_t *, char **);
+int parse_cbor_reply(const unsigned char *, size_t, void *,
+ int(*)(const cbor_item_t *, const cbor_item_t *, void *));
+int add_cbor_pin_params(fido_dev_t *, const fido_blob_t *, const es256_pk_t *,
+ const fido_blob_t *,const char *, cbor_item_t **, cbor_item_t **);
+void cbor_vector_free(cbor_item_t **, size_t);
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+/* buf */
+int buf_read(const unsigned char **, size_t *, void *, size_t);
+int buf_write(unsigned char **, size_t *, const void *, size_t);
+
+/* hid i/o */
+void *hid_open(const char *);
+void hid_close(void *);
+int hid_read(void *, unsigned char *, size_t, int);
+int hid_write(void *, const unsigned char *, size_t);
+
+/* generic i/o */
+int rx(fido_dev_t *, uint8_t, void *, size_t, int);
+int tx(fido_dev_t *, uint8_t, const void *, size_t);
+int rx_cbor_status(fido_dev_t *, int);
+
+/* log */
+#ifdef FIDO_NO_DIAGNOSTIC
+#define log_init(...) do { /* nothing */ } while (0)
+#define log_debug(...) do { /* nothing */ } while (0)
+#define log_xxd(...) do { /* nothing */ } while (0)
+#else
+#ifdef __GNUC__
+void log_init(void);
+void log_debug(const char *, ...) __attribute__((__format__ (printf, 1, 2)));
+void log_xxd(const void *, size_t);
+#else
+void log_init(void);
+void log_debug(const char *, ...);
+void log_xxd(const void *, size_t);
+#endif /* __GNUC__ */
+#endif /* FIDO_NO_DIAGNOSTIC */
+
+/* u2f */
+int u2f_register(fido_dev_t *, fido_cred_t *, int);
+int u2f_authenticate(fido_dev_t *, fido_assert_t *, int);
+
+/* unexposed fido ops */
+int fido_dev_authkey(fido_dev_t *, es256_pk_t *);
+int fido_dev_get_pin_token(fido_dev_t *, const char *, const fido_blob_t *,
+ const es256_pk_t *, fido_blob_t *);
+int fido_do_ecdh(fido_dev_t *, es256_pk_t **, fido_blob_t **);
+
+/* misc */
+void fido_assert_reset_rx(fido_assert_t *);
+void fido_assert_reset_tx(fido_assert_t *);
+void fido_cred_reset_rx(fido_cred_t *);
+void fido_cred_reset_tx(fido_cred_t *);
+int check_rp_id(const char *, const unsigned char *);
+int check_flags(uint8_t, fido_opt_t, fido_opt_t);
+
+/* crypto */
+int verify_sig_es256(const fido_blob_t *, const es256_pk_t *,
+ const fido_blob_t *);
+int verify_sig_rs256(const fido_blob_t *, const rs256_pk_t *,
+ const fido_blob_t *);
+int verify_sig_eddsa(const fido_blob_t *, const eddsa_pk_t *,
+ const fido_blob_t *);
+
+#endif /* !_EXTERN_H */
diff --git a/lib/libfido2/src/fido.h b/lib/libfido2/src/fido.h
new file mode 100644
index 00000000000..c1f7d831080
--- /dev/null
+++ b/lib/libfido2/src/fido.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _FIDO_H
+#define _FIDO_H
+
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef void *fido_dev_io_open_t(const char *);
+typedef void fido_dev_io_close_t(void *);
+typedef int fido_dev_io_read_t(void *, unsigned char *, size_t, int);
+typedef int fido_dev_io_write_t(void *, const unsigned char *, size_t);
+
+typedef struct fido_dev_io {
+ fido_dev_io_open_t *open;
+ fido_dev_io_close_t *close;
+ fido_dev_io_read_t *read;
+ fido_dev_io_write_t *write;
+} fido_dev_io_t;
+
+typedef enum {
+ FIDO_OPT_OMIT = 0, /* use authenticator's default */
+ FIDO_OPT_FALSE, /* explicitly set option to false */
+ FIDO_OPT_TRUE, /* explicitly set option to true */
+} fido_opt_t;
+
+#ifdef _FIDO_INTERNAL
+#include <cbor.h>
+#include <limits.h>
+
+#include "blob.h"
+#if !defined(__OpenBSD__)
+#include "../openbsd-compat/openbsd-compat.h"
+#endif
+#include "iso7816.h"
+#include "types.h"
+#include "extern.h"
+#endif
+
+#include "fido/err.h"
+#include "fido/param.h"
+
+#ifndef _FIDO_INTERNAL
+typedef struct fido_assert fido_assert_t;
+typedef struct fido_cbor_info fido_cbor_info_t;
+typedef struct fido_cred fido_cred_t;
+typedef struct fido_dev fido_dev_t;
+typedef struct fido_dev_info fido_dev_info_t;
+typedef struct es256_pk es256_pk_t;
+typedef struct es256_sk es256_sk_t;
+typedef struct rs256_pk rs256_pk_t;
+typedef struct eddsa_pk eddsa_pk_t;
+#endif
+
+fido_assert_t *fido_assert_new(void);
+fido_cred_t *fido_cred_new(void);
+fido_dev_t *fido_dev_new(void);
+fido_dev_info_t *fido_dev_info_new(size_t);
+fido_cbor_info_t *fido_cbor_info_new(void);
+
+void fido_assert_free(fido_assert_t **);
+void fido_cbor_info_free(fido_cbor_info_t **);
+void fido_cred_free(fido_cred_t **);
+void fido_dev_force_fido2(fido_dev_t *);
+void fido_dev_force_u2f(fido_dev_t *);
+void fido_dev_free(fido_dev_t **);
+void fido_dev_info_free(fido_dev_info_t **, size_t);
+
+/* fido_init() flags. */
+#define FIDO_DEBUG 0x01
+
+void fido_init(int);
+
+const unsigned char *fido_assert_authdata_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_clientdata_hash_ptr(const fido_assert_t *);
+const unsigned char *fido_assert_hmac_secret_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_id_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_sig_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_user_id_ptr(const fido_assert_t *, size_t);
+
+char **fido_cbor_info_extensions_ptr(const fido_cbor_info_t *);
+char **fido_cbor_info_options_name_ptr(const fido_cbor_info_t *);
+char **fido_cbor_info_versions_ptr(const fido_cbor_info_t *);
+const bool *fido_cbor_info_options_value_ptr(const fido_cbor_info_t *);
+const char *fido_assert_rp_id(const fido_assert_t *);
+const char *fido_assert_user_display_name(const fido_assert_t *, size_t);
+const char *fido_assert_user_icon(const fido_assert_t *, size_t);
+const char *fido_assert_user_name(const fido_assert_t *, size_t);
+const char *fido_cred_display_name(const fido_cred_t *);
+const char *fido_cred_fmt(const fido_cred_t *);
+const char *fido_cred_rp_id(const fido_cred_t *);
+const char *fido_cred_rp_name(const fido_cred_t *);
+const char *fido_cred_user_name(const fido_cred_t *);
+const char *fido_dev_info_manufacturer_string(const fido_dev_info_t *);
+const char *fido_dev_info_path(const fido_dev_info_t *);
+const char *fido_dev_info_product_string(const fido_dev_info_t *);
+const fido_dev_info_t *fido_dev_info_ptr(const fido_dev_info_t *, size_t);
+const uint8_t *fido_cbor_info_protocols_ptr(const fido_cbor_info_t *);
+const unsigned char *fido_cbor_info_aaguid_ptr(const fido_cbor_info_t *);
+const unsigned char *fido_cred_authdata_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_clientdata_hash_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_id_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_user_id_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_pubkey_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_sig_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_x5c_ptr(const fido_cred_t *);
+
+int fido_assert_allow_cred(fido_assert_t *, const unsigned char *, size_t);
+int fido_assert_set_authdata(fido_assert_t *, size_t, const unsigned char *,
+ size_t);
+int fido_assert_set_authdata_raw(fido_assert_t *, size_t, const unsigned char *,
+ size_t);
+int fido_assert_set_clientdata_hash(fido_assert_t *, const unsigned char *,
+ size_t);
+int fido_assert_set_count(fido_assert_t *, size_t);
+int fido_assert_set_extensions(fido_assert_t *, int);
+int fido_assert_set_hmac_salt(fido_assert_t *, const unsigned char *, size_t);
+int fido_assert_set_options(fido_assert_t *, bool, bool) __attribute__((__deprecated__));
+int fido_assert_set_rp(fido_assert_t *, const char *);
+int fido_assert_set_up(fido_assert_t *, fido_opt_t);
+int fido_assert_set_uv(fido_assert_t *, fido_opt_t);
+int fido_assert_set_sig(fido_assert_t *, size_t, const unsigned char *, size_t);
+int fido_assert_verify(const fido_assert_t *, size_t, int, const void *);
+int fido_cred_exclude(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_authdata(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_authdata_raw(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_clientdata_hash(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_extensions(fido_cred_t *, int);
+int fido_cred_set_fmt(fido_cred_t *, const char *);
+int fido_cred_set_options(fido_cred_t *, bool, bool) __attribute__((__deprecated__));
+int fido_cred_set_rk(fido_cred_t *, fido_opt_t);
+int fido_cred_set_rp(fido_cred_t *, const char *, const char *);
+int fido_cred_set_sig(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_type(fido_cred_t *, int);
+int fido_cred_set_uv(fido_cred_t *, fido_opt_t);
+int fido_cred_type(const fido_cred_t *);
+int fido_cred_set_user(fido_cred_t *, const unsigned char *, size_t,
+ const char *, const char *, const char *);
+int fido_cred_set_x509(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_verify(const fido_cred_t *);
+int fido_cred_verify_self(const fido_cred_t *);
+int fido_dev_cancel(fido_dev_t *);
+int fido_dev_close(fido_dev_t *);
+int fido_dev_get_assert(fido_dev_t *, fido_assert_t *, const char *);
+int fido_dev_get_cbor_info(fido_dev_t *, fido_cbor_info_t *);
+int fido_dev_get_retry_count(fido_dev_t *, int *);
+int fido_dev_info_manifest(fido_dev_info_t *, size_t, size_t *);
+int fido_dev_make_cred(fido_dev_t *, fido_cred_t *, const char *);
+int fido_dev_open(fido_dev_t *, const char *);
+int fido_dev_reset(fido_dev_t *);
+int fido_dev_set_io_functions(fido_dev_t *, const fido_dev_io_t *);
+int fido_dev_set_pin(fido_dev_t *, const char *, const char *);
+
+size_t fido_assert_authdata_len(const fido_assert_t *, size_t);
+size_t fido_assert_clientdata_hash_len(const fido_assert_t *);
+size_t fido_assert_count(const fido_assert_t *);
+size_t fido_assert_hmac_secret_len(const fido_assert_t *, size_t);
+size_t fido_assert_id_len(const fido_assert_t *, size_t);
+size_t fido_assert_sig_len(const fido_assert_t *, size_t);
+size_t fido_assert_user_id_len(const fido_assert_t *, size_t);
+size_t fido_cbor_info_aaguid_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_extensions_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_options_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_protocols_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_versions_len(const fido_cbor_info_t *);
+size_t fido_cred_authdata_len(const fido_cred_t *);
+size_t fido_cred_clientdata_hash_len(const fido_cred_t *);
+size_t fido_cred_id_len(const fido_cred_t *);
+size_t fido_cred_user_id_len(const fido_cred_t *);
+size_t fido_cred_pubkey_len(const fido_cred_t *);
+size_t fido_cred_sig_len(const fido_cred_t *);
+size_t fido_cred_x5c_len(const fido_cred_t *);
+
+uint8_t fido_assert_flags(const fido_assert_t *, size_t);
+uint32_t fido_assert_sigcount(const fido_assert_t *, size_t);
+uint8_t fido_cred_flags(const fido_cred_t *);
+uint8_t fido_dev_protocol(const fido_dev_t *);
+uint8_t fido_dev_major(const fido_dev_t *);
+uint8_t fido_dev_minor(const fido_dev_t *);
+uint8_t fido_dev_build(const fido_dev_t *);
+uint8_t fido_dev_flags(const fido_dev_t *);
+int16_t fido_dev_info_vendor(const fido_dev_info_t *);
+int16_t fido_dev_info_product(const fido_dev_info_t *);
+uint64_t fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *);
+
+bool fido_dev_is_fido2(const fido_dev_t *);
+
+#endif /* !_FIDO_H */
diff --git a/lib/libfido2/src/fido/bio.h b/lib/libfido2/src/fido/bio.h
new file mode 100644
index 00000000000..31dffe49df1
--- /dev/null
+++ b/lib/libfido2/src/fido/bio.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _FIDO_BIO_H
+#define _FIDO_BIO_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "fido/err.h"
+#include "fido/param.h"
+
+#ifdef _FIDO_INTERNAL
+struct fido_bio_template {
+ fido_blob_t id;
+ char *name;
+};
+
+struct fido_bio_template_array {
+ struct fido_bio_template *ptr;
+ size_t n_alloc; /* number of allocated entries */
+ size_t n_rx; /* number of populated entries */
+};
+
+struct fido_bio_enroll {
+ uint8_t remaining_samples;
+ uint8_t last_status;
+ fido_blob_t *token;
+};
+
+struct fido_bio_info {
+ uint8_t type;
+ uint8_t max_samples;
+};
+#endif
+
+typedef struct fido_bio_template fido_bio_template_t;
+typedef struct fido_bio_template_array fido_bio_template_array_t;
+typedef struct fido_bio_enroll fido_bio_enroll_t;
+typedef struct fido_bio_info fido_bio_info_t;
+
+#define FIDO_BIO_ENROLL_FP_GOOD 0x00
+#define FIDO_BIO_ENROLL_FP_TOO_HIGH 0x01
+#define FIDO_BIO_ENROLL_FP_TOO_LOW 0x02
+#define FIDO_BIO_ENROLL_FP_TOO_LEFT 0x03
+#define FIDO_BIO_ENROLL_FP_TOO_RIGHT 0x04
+#define FIDO_BIO_ENROLL_FP_TOO_FAST 0x05
+#define FIDO_BIO_ENROLL_FP_TOO_SLOW 0x06
+#define FIDO_BIO_ENROLL_FP_POOR_QUALITY 0x07
+#define FIDO_BIO_ENROLL_FP_TOO_SKEWED 0x08
+#define FIDO_BIO_ENROLL_FP_TOO_SHORT 0x09
+#define FIDO_BIO_ENROLL_FP_MERGE_FAILURE 0x0a
+#define FIDO_BIO_ENROLL_FP_EXISTS 0x0b
+#define FIDO_BIO_ENROLL_FP_DATABASE_FULL 0x0c
+#define FIDO_BIO_ENROLL_NO_USER_ACTIVITY 0x0d
+#define FIDO_BIO_ENROLL_NO_USER_PRESENCE_TRANSITION 0x0e
+
+const char *fido_bio_template_name(const fido_bio_template_t *);
+const fido_bio_template_t *fido_bio_template(const fido_bio_template_array_t *,
+ size_t);
+const unsigned char *fido_bio_template_id_ptr(const fido_bio_template_t *);
+fido_bio_enroll_t *fido_bio_enroll_new(void);
+fido_bio_info_t *fido_bio_info_new(void);
+fido_bio_template_array_t *fido_bio_template_array_new(void);
+fido_bio_template_t *fido_bio_template_new(void);
+int fido_bio_dev_enroll_begin(fido_dev_t *, fido_bio_template_t *,
+ fido_bio_enroll_t *, uint32_t, const char *);
+int fido_bio_dev_enroll_cancel(fido_dev_t *);
+int fido_bio_dev_enroll_continue(fido_dev_t *, const fido_bio_template_t *,
+ fido_bio_enroll_t *, uint32_t);
+int fido_bio_dev_enroll_remove(fido_dev_t *, const fido_bio_template_t *,
+ const char *);
+int fido_bio_dev_get_info(fido_dev_t *, fido_bio_info_t *);
+int fido_bio_dev_get_template_array(fido_dev_t *, fido_bio_template_array_t *,
+ const char *);
+int fido_bio_dev_set_template_name(fido_dev_t *, const fido_bio_template_t *,
+ const char *);
+int fido_bio_template_set_id(fido_bio_template_t *, const unsigned char *,
+ size_t);
+int fido_bio_template_set_name(fido_bio_template_t *, const char *);
+size_t fido_bio_template_array_count(const fido_bio_template_array_t *);
+size_t fido_bio_template_id_len(const fido_bio_template_t *);
+uint8_t fido_bio_enroll_last_status(const fido_bio_enroll_t *);
+uint8_t fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *);
+uint8_t fido_bio_info_max_samples(const fido_bio_info_t *);
+uint8_t fido_bio_info_type(const fido_bio_info_t *);
+void fido_bio_enroll_free(fido_bio_enroll_t **);
+void fido_bio_info_free(fido_bio_info_t **);
+void fido_bio_template_array_free(fido_bio_template_array_t **);
+void fido_bio_template_free(fido_bio_template_t **);
+
+#endif /* !_FIDO_BIO_H */
diff --git a/lib/libfido2/src/fido/credman.h b/lib/libfido2/src/fido/credman.h
new file mode 100644
index 00000000000..1c7cafef723
--- /dev/null
+++ b/lib/libfido2/src/fido/credman.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _FIDO_CREDMAN_H
+#define _FIDO_CREDMAN_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "fido/err.h"
+#include "fido/param.h"
+
+#ifdef _FIDO_INTERNAL
+struct fido_credman_metadata {
+ uint64_t rk_existing;
+ uint64_t rk_remaining;
+};
+
+struct fido_credman_single_rp {
+ fido_rp_t rp_entity;
+ fido_blob_t rp_id_hash;
+};
+
+struct fido_credman_rp {
+ struct fido_credman_single_rp *ptr;
+ size_t n_alloc; /* number of allocated entries */
+ size_t n_rx; /* number of populated entries */
+};
+
+struct fido_credman_rk {
+ fido_cred_t *ptr;
+ size_t n_alloc; /* number of allocated entries */
+ size_t n_rx; /* number of populated entries */
+};
+#endif
+
+typedef struct fido_credman_metadata fido_credman_metadata_t;
+typedef struct fido_credman_rk fido_credman_rk_t;
+typedef struct fido_credman_rp fido_credman_rp_t;
+
+const char *fido_credman_rp_id(const fido_credman_rp_t *, size_t);
+const char *fido_credman_rp_name(const fido_credman_rp_t *, size_t);
+
+const fido_cred_t *fido_credman_rk(const fido_credman_rk_t *, size_t);
+const unsigned char *fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *,
+ size_t);
+
+fido_credman_metadata_t *fido_credman_metadata_new(void);
+fido_credman_rk_t *fido_credman_rk_new(void);
+fido_credman_rp_t *fido_credman_rp_new(void);
+
+int fido_credman_del_dev_rk(fido_dev_t *, const unsigned char *, size_t,
+ const char *);
+int fido_credman_get_dev_metadata(fido_dev_t *, fido_credman_metadata_t *,
+ const char *);
+int fido_credman_get_dev_rk(fido_dev_t *, const char *, fido_credman_rk_t *,
+ const char *);
+int fido_credman_get_dev_rp(fido_dev_t *, fido_credman_rp_t *, const char *);
+
+size_t fido_credman_rk_count(const fido_credman_rk_t *);
+size_t fido_credman_rp_count(const fido_credman_rp_t *);
+size_t fido_credman_rp_id_hash_len(const fido_credman_rp_t *, size_t);
+
+uint64_t fido_credman_rk_existing(const fido_credman_metadata_t *);
+uint64_t fido_credman_rk_remaining(const fido_credman_metadata_t *);
+
+void fido_credman_metadata_free(fido_credman_metadata_t **);
+void fido_credman_rk_free(fido_credman_rk_t **);
+void fido_credman_rp_free(fido_credman_rp_t **);
+
+#endif /* !_FIDO_CREDMAN_H */
diff --git a/lib/libfido2/src/fido/eddsa.h b/lib/libfido2/src/fido/eddsa.h
new file mode 100644
index 00000000000..9de272d584b
--- /dev/null
+++ b/lib/libfido2/src/fido/eddsa.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _FIDO_EDDSA_H
+#define _FIDO_EDDSA_H
+
+#include <openssl/ec.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+eddsa_pk_t *eddsa_pk_new(void);
+void eddsa_pk_free(eddsa_pk_t **);
+EVP_PKEY *eddsa_pk_to_EVP_PKEY(const eddsa_pk_t *);
+
+int eddsa_pk_from_EVP_PKEY(eddsa_pk_t *, const EVP_PKEY *);
+int eddsa_pk_from_ptr(eddsa_pk_t *, const void *, size_t);
+
+#ifdef _FIDO_INTERNAL
+
+#if defined(LIBRESSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10101000L
+#define EVP_PKEY_ED25519 EVP_PKEY_NONE
+int EVP_PKEY_get_raw_public_key(const EVP_PKEY *, unsigned char *, size_t *);
+EVP_PKEY *EVP_PKEY_new_raw_public_key(int, ENGINE *, const unsigned char *,
+ size_t);
+int EVP_DigestVerify(EVP_MD_CTX *, const unsigned char *, size_t,
+ const unsigned char *, size_t);
+#endif /* LIBRESSL_VERSION_NUMBER || OPENSSL_VERSION_NUMBER < 0x10101000L */
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+EVP_MD_CTX *EVP_MD_CTX_new(void);
+void EVP_MD_CTX_free(EVP_MD_CTX *);
+#endif
+
+#endif /* _FIDO_INTERNAL */
+
+#endif /* !_FIDO_EDDSA_H */
diff --git a/lib/libfido2/src/fido/err.h b/lib/libfido2/src/fido/err.h
new file mode 100644
index 00000000000..11f52bcff92
--- /dev/null
+++ b/lib/libfido2/src/fido/err.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _FIDO_ERR_H
+#define _FIDO_ERR_H
+
+#define FIDO_ERR_SUCCESS 0x00
+#define FIDO_ERR_INVALID_COMMAND 0x01
+#define FIDO_ERR_INVALID_PARAMETER 0x02
+#define FIDO_ERR_INVALID_LENGTH 0x03
+#define FIDO_ERR_INVALID_SEQ 0x04
+#define FIDO_ERR_TIMEOUT 0x05
+#define FIDO_ERR_CHANNEL_BUSY 0x06
+#define FIDO_ERR_LOCK_REQUIRED 0x0a
+#define FIDO_ERR_INVALID_CHANNEL 0x0b
+#define FIDO_ERR_CBOR_UNEXPECTED_TYPE 0x11
+#define FIDO_ERR_INVALID_CBOR 0x12
+#define FIDO_ERR_MISSING_PARAMETER 0x14
+#define FIDO_ERR_LIMIT_EXCEEDED 0x15
+#define FIDO_ERR_UNSUPPORTED_EXTENSION 0x16
+#define FIDO_ERR_CREDENTIAL_EXCLUDED 0x19
+#define FIDO_ERR_PROCESSING 0x21
+#define FIDO_ERR_INVALID_CREDENTIAL 0x22
+#define FIDO_ERR_USER_ACTION_PENDING 0x23
+#define FIDO_ERR_OPERATION_PENDING 0x24
+#define FIDO_ERR_NO_OPERATIONS 0x25
+#define FIDO_ERR_UNSUPPORTED_ALGORITHM 0x26
+#define FIDO_ERR_OPERATION_DENIED 0x27
+#define FIDO_ERR_KEY_STORE_FULL 0x28
+#define FIDO_ERR_NOT_BUSY 0x29
+#define FIDO_ERR_NO_OPERATION_PENDING 0x2a
+#define FIDO_ERR_UNSUPPORTED_OPTION 0x2b
+#define FIDO_ERR_INVALID_OPTION 0x2c
+#define FIDO_ERR_KEEPALIVE_CANCEL 0x2d
+#define FIDO_ERR_NO_CREDENTIALS 0x2e
+#define FIDO_ERR_USER_ACTION_TIMEOUT 0x2f
+#define FIDO_ERR_NOT_ALLOWED 0x30
+#define FIDO_ERR_PIN_INVALID 0x31
+#define FIDO_ERR_PIN_BLOCKED 0x32
+#define FIDO_ERR_PIN_AUTH_INVALID 0x33
+#define FIDO_ERR_PIN_AUTH_BLOCKED 0x34
+#define FIDO_ERR_PIN_NOT_SET 0x35
+#define FIDO_ERR_PIN_REQUIRED 0x36
+#define FIDO_ERR_PIN_POLICY_VIOLATION 0x37
+#define FIDO_ERR_PIN_TOKEN_EXPIRED 0x38
+#define FIDO_ERR_REQUEST_TOO_LARGE 0x39
+#define FIDO_ERR_ACTION_TIMEOUT 0x3a
+#define FIDO_ERR_UP_REQUIRED 0x3b
+#define FIDO_ERR_ERR_OTHER 0x7f
+#define FIDO_ERR_SPEC_LAST 0xdf
+
+/* defined internally */
+#define FIDO_OK FIDO_ERR_SUCCESS
+#define FIDO_ERR_TX -1
+#define FIDO_ERR_RX -2
+#define FIDO_ERR_RX_NOT_CBOR -3
+#define FIDO_ERR_RX_INVALID_CBOR -4
+#define FIDO_ERR_INVALID_PARAM -5
+#define FIDO_ERR_INVALID_SIG -6
+#define FIDO_ERR_INVALID_ARGUMENT -7
+#define FIDO_ERR_USER_PRESENCE_REQUIRED -8
+#define FIDO_ERR_INTERNAL -9
+
+const char *fido_strerr(int);
+
+#endif /* _FIDO_ERR_H */
diff --git a/lib/libfido2/src/fido/es256.h b/lib/libfido2/src/fido/es256.h
new file mode 100644
index 00000000000..d3d13dde3fe
--- /dev/null
+++ b/lib/libfido2/src/fido/es256.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _FIDO_ES256_H
+#define _FIDO_ES256_H
+
+#include <openssl/ec.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+es256_pk_t *es256_pk_new(void);
+void es256_pk_free(es256_pk_t **);
+EVP_PKEY *es256_pk_to_EVP_PKEY(const es256_pk_t *);
+
+int es256_pk_from_EC_KEY(es256_pk_t *, const EC_KEY *);
+int es256_pk_from_ptr(es256_pk_t *, const void *, size_t);
+
+#ifdef _FIDO_INTERNAL
+es256_sk_t *es256_sk_new(void);
+void es256_sk_free(es256_sk_t **);
+EVP_PKEY *es256_sk_to_EVP_PKEY(const es256_sk_t *);
+
+int es256_derive_pk(const es256_sk_t *, es256_pk_t *);
+int es256_sk_create(es256_sk_t *);
+
+int es256_pk_set_x(es256_pk_t *, const unsigned char *);
+int es256_pk_set_y(es256_pk_t *, const unsigned char *);
+#endif
+
+#endif /* !_FIDO_ES256_H */
diff --git a/lib/libfido2/src/fido/param.h b/lib/libfido2/src/fido/param.h
new file mode 100644
index 00000000000..9e12ac62eff
--- /dev/null
+++ b/lib/libfido2/src/fido/param.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _FIDO_PARAM_H
+#define _FIDO_PARAM_H
+
+/* Authentication data flags. */
+#define CTAP_AUTHDATA_USER_PRESENT 0x01
+#define CTAP_AUTHDATA_USER_VERIFIED 0x04
+#define CTAP_AUTHDATA_ATT_CRED 0x40
+#define CTAP_AUTHDATA_EXT_DATA 0x80
+
+/* CTAPHID command opcodes. */
+#define CTAP_CMD_PING 0x01
+#define CTAP_CMD_MSG 0x03
+#define CTAP_CMD_LOCK 0x04
+#define CTAP_CMD_INIT 0x06
+#define CTAP_CMD_WINK 0x08
+#define CTAP_CMD_CBOR 0x10
+#define CTAP_CMD_CANCEL 0x11
+#define CTAP_KEEPALIVE 0x3b
+#define CTAP_FRAME_INIT 0x80
+
+/* CTAPHID CBOR command opcodes. */
+#define CTAP_CBOR_MAKECRED 0x01
+#define CTAP_CBOR_ASSERT 0x02
+#define CTAP_CBOR_GETINFO 0x04
+#define CTAP_CBOR_CLIENT_PIN 0x06
+#define CTAP_CBOR_RESET 0x07
+#define CTAP_CBOR_NEXT_ASSERT 0x08
+#define CTAP_CBOR_BIO_ENROLL_PRE 0x40
+#define CTAP_CBOR_CRED_MGMT_PRE 0x41
+
+/* U2F command opcodes. */
+#define U2F_CMD_REGISTER 0x01
+#define U2F_CMD_AUTH 0x02
+
+/* U2F command flags. */
+#define U2F_AUTH_SIGN 0x03
+#define U2F_AUTH_CHECK 0x07
+
+/* ISO7816-4 status words. */
+#define SW_CONDITIONS_NOT_SATISFIED 0x6985
+#define SW_WRONG_DATA 0x6a80
+#define SW_NO_ERROR 0x9000
+
+/* HID Broadcast channel ID. */
+#define CTAP_CID_BROADCAST 0xffffffff
+
+/* Expected size of a HID report in bytes. */
+#define CTAP_RPT_SIZE 64
+
+/* Randomness device on UNIX-like platforms. */
+#ifndef FIDO_RANDOM_DEV
+#define FIDO_RANDOM_DEV "/dev/urandom"
+#endif
+
+/* CTAP capability bits. */
+#define FIDO_CAP_WINK 0x01 /* if set, device supports CTAP_CMD_WINK */
+#define FIDO_CAP_CBOR 0x04 /* if set, device supports CTAP_CMD_CBOR */
+#define FIDO_CAP_NMSG 0x08 /* if set, device doesn't support CTAP_CMD_MSG */
+
+/* Supported COSE algorithms. */
+#define COSE_ES256 -7
+#define COSE_EDDSA -8
+#define COSE_ECDH_ES256 -25
+#define COSE_RS256 -257
+
+/* Supported COSE types. */
+#define COSE_KTY_OKP 1
+#define COSE_KTY_EC2 2
+#define COSE_KTY_RSA 3
+
+/* Supported curves. */
+#define COSE_P256 1
+#define COSE_ED25519 6
+
+/* Supported extensions. */
+#define FIDO_EXT_HMAC_SECRET 0x01
+
+#endif /* !_FIDO_PARAM_H */
diff --git a/lib/libfido2/src/fido/rs256.h b/lib/libfido2/src/fido/rs256.h
new file mode 100644
index 00000000000..d2fa162eb1d
--- /dev/null
+++ b/lib/libfido2/src/fido/rs256.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _FIDO_RS256_H
+#define _FIDO_RS256_H
+
+#include <openssl/rsa.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+rs256_pk_t *rs256_pk_new(void);
+void rs256_pk_free(rs256_pk_t **);
+EVP_PKEY *rs256_pk_to_EVP_PKEY(const rs256_pk_t *);
+
+int rs256_pk_from_RSA(rs256_pk_t *, const RSA *);
+int rs256_pk_from_ptr(rs256_pk_t *, const void *, size_t);
+
+#endif /* !_FIDO_RS256_H */
diff --git a/lib/libfido2/src/hid.c b/lib/libfido2/src/hid.c
new file mode 100644
index 00000000000..783d22c9a37
--- /dev/null
+++ b/lib/libfido2/src/hid.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <string.h>
+#include "fido.h"
+
+fido_dev_info_t *
+fido_dev_info_new(size_t n)
+{
+ return (calloc(n, sizeof(fido_dev_info_t)));
+}
+
+void
+fido_dev_info_free(fido_dev_info_t **devlist_p, size_t n)
+{
+ fido_dev_info_t *devlist;
+
+ if (devlist_p == NULL || (devlist = *devlist_p) == NULL)
+ return;
+
+ for (size_t i = 0; i < n; i++) {
+ const fido_dev_info_t *di = &devlist[i];
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ }
+
+ free(devlist);
+
+ *devlist_p = NULL;
+}
+
+const fido_dev_info_t *
+fido_dev_info_ptr(const fido_dev_info_t *devlist, size_t i)
+{
+ return (&devlist[i]);
+}
+
+const char *
+fido_dev_info_path(const fido_dev_info_t *di)
+{
+ return (di->path);
+}
+
+int16_t
+fido_dev_info_vendor(const fido_dev_info_t *di)
+{
+ return (di->vendor_id);
+}
+
+int16_t
+fido_dev_info_product(const fido_dev_info_t *di)
+{
+ return (di->product_id);
+}
+
+const char *
+fido_dev_info_manufacturer_string(const fido_dev_info_t *di)
+{
+ return (di->manufacturer);
+}
+
+const char *
+fido_dev_info_product_string(const fido_dev_info_t *di)
+{
+ return (di->product);
+}
diff --git a/lib/libfido2/src/hid_openbsd.c b/lib/libfido2/src/hid_openbsd.c
new file mode 100644
index 00000000000..92b7c05b6f7
--- /dev/null
+++ b/lib/libfido2/src/hid_openbsd.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2019 Google LLC. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <sys/types.h>
+
+#include <sys/ioctl.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <usbhid.h>
+#include <poll.h>
+
+#include "fido.h"
+
+#define MAX_UHID 64
+#define MAX_REPORT_LEN (sizeof(((struct usb_ctl_report *)(NULL))->ucr_data))
+
+struct hid_openbsd {
+ int fd;
+ size_t report_in_len;
+ size_t report_out_len;
+};
+
+int
+fido_dev_info_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ size_t i;
+ char path[64];
+ int is_fido, fd;
+ struct usb_device_info udi;
+ report_desc_t rdesc = NULL;
+ hid_data_t hdata = NULL;
+ hid_item_t hitem;
+ fido_dev_info_t *di;
+
+ if (ilen == 0)
+ return (FIDO_OK); /* nothing to do */
+
+ if (devlist == NULL || olen == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {
+ snprintf(path, sizeof(path), "/dev/uhid%zu", i);
+ if ((fd = open(path, O_RDWR)) == -1) {
+ if (errno != ENOENT && errno != ENXIO) {
+ log_debug("%s: open %s: %s", __func__, path,
+ strerror(errno));
+ }
+ continue;
+ }
+ memset(&udi, 0, sizeof(udi));
+ if (ioctl(fd, USB_GET_DEVICEINFO, &udi) != 0) {
+ log_debug("%s: get device info %s: %s", __func__,
+ path, strerror(errno));
+ close(fd);
+ continue;
+ }
+ if ((rdesc = hid_get_report_desc(fd)) == NULL) {
+ log_debug("%s: failed to get report descriptor: %s",
+ __func__, path);
+ close(fd);
+ continue;
+ }
+ if ((hdata = hid_start_parse(rdesc,
+ 1<<hid_collection, -1)) == NULL) {
+ log_debug("%s: failed to parse report descriptor: %s",
+ __func__, path);
+ hid_dispose_report_desc(rdesc);
+ close(fd);
+ continue;
+ }
+ is_fido = 0;
+ for (is_fido = 0; !is_fido;) {
+ memset(&hitem, 0, sizeof(hitem));
+ if (hid_get_item(hdata, &hitem) <= 0)
+ break;
+ if ((hitem._usage_page & 0xFFFF0000) == 0xf1d00000)
+ is_fido = 1;
+ }
+ hid_end_parse(hdata);
+ hid_dispose_report_desc(rdesc);
+ close(fd);
+
+ if (!is_fido)
+ continue;
+
+ log_debug("%s: %s: bus = 0x%02x, addr = 0x%02x",
+ __func__, path, udi.udi_bus, udi.udi_addr);
+ log_debug("%s: %s: vendor = \"%s\", product = \"%s\"",
+ __func__, path, udi.udi_vendor, udi.udi_product);
+ log_debug("%s: %s: productNo = 0x%04x, vendorNo = 0x%04x, "
+ "releaseNo = 0x%04x", __func__, path, udi.udi_productNo,
+ udi.udi_vendorNo, udi.udi_releaseNo);
+
+ di = &devlist[*olen];
+ memset(di, 0, sizeof(*di));
+ if ((di->path = strdup(path)) == NULL ||
+ (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
+ (di->product = strdup(udi.udi_product)) == NULL) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ return FIDO_ERR_INTERNAL;
+ }
+ di->vendor_id = udi.udi_vendorNo;
+ di->product_id = udi.udi_productNo;
+ (*olen)++;
+ }
+
+ return FIDO_OK;
+}
+
+/*
+ * Workaround for OpenBSD <=6.6-current (as of 201910) bug that loses
+ * sync of DATA0/DATA1 sequence bit across uhid open/close.
+ * Send pings until we get a response - early pings with incorrect
+ * sequence bits will be ignored as duplicate packets by the device.
+ */
+static int
+terrible_ping_kludge(struct hid_openbsd *ctx)
+{
+ u_char data[256];
+ int i, n;
+ struct pollfd pfd;
+
+ if (sizeof(data) < ctx->report_out_len + 1)
+ return -1;
+ for (i = 0; i < 4; i++) {
+ memset(data, 0, sizeof(data));
+ /* broadcast channel ID */
+ data[1] = 0xff;
+ data[2] = 0xff;
+ data[3] = 0xff;
+ data[4] = 0xff;
+ /* Ping command */
+ data[5] = 0x81;
+ /* One byte ping only, Vasili */
+ data[6] = 0;
+ data[7] = 1;
+ log_debug("%s: send ping %d", __func__, i);
+ if (hid_write(ctx, data, ctx->report_out_len + 1) == -1)
+ return -1;
+ log_debug("%s: wait reply", __func__);
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = ctx->fd;
+ pfd.events = POLLIN;
+ if ((n = poll(&pfd, 1, 100)) == -1) {
+ log_debug("%s: poll: %s", __func__, strerror(errno));
+ return -1;
+ } else if (n == 0) {
+ log_debug("%s: timed out", __func__);
+ continue;
+ }
+ if (hid_read(ctx, data, ctx->report_out_len, 250) == -1)
+ return -1;
+ /*
+ * Ping isn't always supported on the broadcast channel,
+ * so we might get an error, but we don't care - we're
+ * synched now.
+ */
+ log_debug("%s: got reply", __func__);
+ log_xxd(data, ctx->report_out_len);
+ return 0;
+ }
+ log_debug("%s: no response", __func__);
+ return -1;
+}
+
+void *
+hid_open(const char *path)
+{
+ struct hid_openbsd *ret = NULL;
+ report_desc_t rdesc = NULL;
+ int len, usb_report_id = 0;
+
+ if ((ret = calloc(1, sizeof(*ret))) == NULL ||
+ (ret->fd = open(path, O_RDWR)) < 0) {
+ free(ret);
+ return (NULL);
+ }
+ if (ioctl(ret->fd, USB_GET_REPORT_ID, &usb_report_id) != 0) {
+ log_debug("%s: failed to get report ID: %s", __func__,
+ strerror(errno));
+ goto fail;
+ }
+ if ((rdesc = hid_get_report_desc(ret->fd)) == NULL) {
+ log_debug("%s: failed to get report descriptor", __func__);
+ goto fail;
+ }
+ if ((len = hid_report_size(rdesc, hid_input, usb_report_id)) <= 0 ||
+ (size_t)len > MAX_REPORT_LEN) {
+ log_debug("%s: bad input report size %d", __func__, len);
+ goto fail;
+ }
+ ret->report_in_len = (size_t)len;
+ if ((len = hid_report_size(rdesc, hid_output, usb_report_id)) <= 0 ||
+ (size_t)len > MAX_REPORT_LEN) {
+ log_debug("%s: bad output report size %d", __func__, len);
+ fail:
+ hid_dispose_report_desc(rdesc);
+ close(ret->fd);
+ free(ret);
+ return NULL;
+ }
+ ret->report_out_len = (size_t)len;
+ hid_dispose_report_desc(rdesc);
+ log_debug("%s: USB report ID %d, inlen = %zu outlen = %zu", __func__,
+ usb_report_id, ret->report_in_len, ret->report_out_len);
+
+ /*
+ * OpenBSD (as of 201910) has a bug that causes it to lose
+ * track of the DATA0/DATA1 sequence toggle across uhid device
+ * open and close. This is a terrible hack to work around it.
+ */
+ if (terrible_ping_kludge(ret) != 0) {
+ hid_close(ret);
+ return NULL;
+ }
+
+ return (ret);
+}
+
+void
+hid_close(void *handle)
+{
+ struct hid_openbsd *ctx = (struct hid_openbsd *)handle;
+
+ close(ctx->fd);
+ free(ctx);
+}
+
+int
+hid_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct hid_openbsd *ctx = (struct hid_openbsd *)handle;
+ ssize_t r;
+
+ (void)ms; /* XXX */
+
+ if (len != ctx->report_in_len) {
+ log_debug("%s: invalid len: got %zu, want %zu", __func__,
+ len, ctx->report_in_len);
+ return (-1);
+ }
+ if ((r = read(ctx->fd, buf, len)) == -1 || (size_t)r != len) {
+ log_debug("%s: read: %s", __func__, strerror(errno));
+ return (-1);
+ }
+ return ((int)len);
+}
+
+int
+hid_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct hid_openbsd *ctx = (struct hid_openbsd *)handle;
+ ssize_t r;
+
+ if (len != ctx->report_out_len + 1) {
+ log_debug("%s: invalid len: got %zu, want %zu", __func__,
+ len, ctx->report_out_len);
+ return (-1);
+ }
+ if ((r = write(ctx->fd, buf + 1, len - 1)) == -1 ||
+ (size_t)r != len - 1) {
+ log_debug("%s: write: %s", __func__, strerror(errno));
+ return (-1);
+ }
+ return ((int)len);
+}
diff --git a/lib/libfido2/src/info.c b/lib/libfido2/src/info.c
new file mode 100644
index 00000000000..35f8cdba682
--- /dev/null
+++ b/lib/libfido2/src/info.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <string.h>
+#include "fido.h"
+
+static int
+decode_version(const cbor_item_t *item, void *arg)
+{
+ fido_str_array_t *v = arg;
+ const size_t i = v->len;
+
+ /* keep ptr[x] and len consistent */
+ if (cbor_string_copy(item, &v->ptr[i]) < 0) {
+ log_debug("%s: cbor_string_copy", __func__);
+ return (-1);
+ }
+
+ v->len++;
+
+ return (0);
+}
+
+static int
+decode_versions(const cbor_item_t *item, fido_str_array_t *v)
+{
+ v->ptr = NULL;
+ v->len = 0;
+
+ if (cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ v->ptr = calloc(cbor_array_size(item), sizeof(char *));
+ if (v->ptr == NULL)
+ return (-1);
+
+ if (cbor_array_iter(item, v, decode_version) < 0) {
+ log_debug("%s: decode_version", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_extension(const cbor_item_t *item, void *arg)
+{
+ fido_str_array_t *e = arg;
+ const size_t i = e->len;
+
+ /* keep ptr[x] and len consistent */
+ if (cbor_string_copy(item, &e->ptr[i]) < 0) {
+ log_debug("%s: cbor_string_copy", __func__);
+ return (-1);
+ }
+
+ e->len++;
+
+ return (0);
+}
+
+static int
+decode_extensions(const cbor_item_t *item, fido_str_array_t *e)
+{
+ e->ptr = NULL;
+ e->len = 0;
+
+ if (cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ e->ptr = calloc(cbor_array_size(item), sizeof(char *));
+ if (e->ptr == NULL)
+ return (-1);
+
+ if (cbor_array_iter(item, e, decode_extension) < 0) {
+ log_debug("%s: decode_extension", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_aaguid(const cbor_item_t *item, unsigned char *aaguid, size_t aaguid_len)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false ||
+ cbor_bytestring_length(item) != aaguid_len) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memcpy(aaguid, cbor_bytestring_handle(item), aaguid_len);
+
+ return (0);
+}
+
+static int
+decode_option(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_opt_array_t *o = arg;
+ const size_t i = o->len;
+
+ if (cbor_isa_float_ctrl(val) == false ||
+ cbor_float_get_width(val) != CBOR_FLOAT_0 ||
+ cbor_is_bool(val) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (cbor_string_copy(key, &o->name[i]) < 0) {
+ log_debug("%s: cbor_string_copy", __func__);
+ return (0); /* ignore */
+ }
+
+ /* keep name/value and len consistent */
+ o->value[i] = cbor_ctrl_value(val) == CBOR_CTRL_TRUE;
+ o->len++;
+
+ return (0);
+}
+
+static int
+decode_options(const cbor_item_t *item, fido_opt_array_t *o)
+{
+ o->name = NULL;
+ o->value = NULL;
+ o->len = 0;
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ o->name = calloc(cbor_map_size(item), sizeof(char *));
+ o->value = calloc(cbor_map_size(item), sizeof(bool));
+ if (o->name == NULL || o->value == NULL)
+ return (-1);
+
+ return (cbor_map_iter(item, o, decode_option));
+}
+
+static int
+decode_protocol(const cbor_item_t *item, void *arg)
+{
+ fido_byte_array_t *p = arg;
+ const size_t i = p->len;
+
+ if (cbor_isa_uint(item) == false ||
+ cbor_int_get_width(item) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ /* keep ptr[x] and len consistent */
+ p->ptr[i] = cbor_get_uint8(item);
+ p->len++;
+
+ return (0);
+}
+
+static int
+decode_protocols(const cbor_item_t *item, fido_byte_array_t *p)
+{
+ p->ptr = NULL;
+ p->len = 0;
+
+ if (cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ p->ptr = calloc(cbor_array_size(item), sizeof(uint8_t));
+ if (p->ptr == NULL)
+ return (-1);
+
+ if (cbor_array_iter(item, p, decode_protocol) < 0) {
+ log_debug("%s: decode_protocol", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+parse_reply_element(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_cbor_info_t *ci = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* versions */
+ return (decode_versions(val, &ci->versions));
+ case 2: /* extensions */
+ return (decode_extensions(val, &ci->extensions));
+ case 3: /* aaguid */
+ return (decode_aaguid(val, ci->aaguid, sizeof(ci->aaguid)));
+ case 4: /* options */
+ return (decode_options(val, &ci->options));
+ case 5: /* maxMsgSize */
+ return (decode_uint64(val, &ci->maxmsgsiz));
+ case 6: /* pinProtocols */
+ return (decode_protocols(val, &ci->protocols));
+ default: /* ignore */
+ log_debug("%s: cbor type", __func__);
+ return (0);
+ }
+}
+
+static int
+fido_dev_get_cbor_info_tx(fido_dev_t *dev)
+{
+ const unsigned char cbor[] = { CTAP_CBOR_GETINFO };
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+
+ log_debug("%s: dev=%p", __func__, (void *)dev);
+
+ if (tx(dev, cmd, cbor, sizeof(cbor)) < 0) {
+ log_debug("%s: tx", __func__);
+ return (FIDO_ERR_TX);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_dev_get_cbor_info_rx(fido_dev_t *dev, fido_cbor_info_t *ci, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[512];
+ int reply_len;
+
+ log_debug("%s: dev=%p, ci=%p, ms=%d", __func__, (void *)dev,
+ (void *)ci, ms);
+
+ memset(ci, 0, sizeof(*ci));
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ return (parse_cbor_reply(reply, (size_t)reply_len, ci,
+ parse_reply_element));
+}
+
+static int
+fido_dev_get_cbor_info_wait(fido_dev_t *dev, fido_cbor_info_t *ci, int ms)
+{
+ int r;
+
+ if ((r = fido_dev_get_cbor_info_tx(dev)) != FIDO_OK ||
+ (r = fido_dev_get_cbor_info_rx(dev, ci, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci)
+{
+ return (fido_dev_get_cbor_info_wait(dev, ci, -1));
+}
+
+/*
+ * get/set functions for fido_cbor_info_t; always at the end of the file
+ */
+
+fido_cbor_info_t *
+fido_cbor_info_new(void)
+{
+ return (calloc(1, sizeof(fido_cbor_info_t)));
+}
+
+static void
+free_str_array(fido_str_array_t *sa)
+{
+ for (size_t i = 0; i < sa->len; i++)
+ free(sa->ptr[i]);
+
+ free(sa->ptr);
+ sa->ptr = NULL;
+ sa->len = 0;
+}
+
+static void
+free_opt_array(fido_opt_array_t *oa)
+{
+ for (size_t i = 0; i < oa->len; i++)
+ free(oa->name[i]);
+
+ free(oa->name);
+ free(oa->value);
+ oa->name = NULL;
+ oa->value = NULL;
+}
+
+static void
+free_byte_array(fido_byte_array_t *ba)
+{
+ free(ba->ptr);
+
+ ba->ptr = NULL;
+ ba->len = 0;
+}
+
+void
+fido_cbor_info_free(fido_cbor_info_t **ci_p)
+{
+ fido_cbor_info_t *ci;
+
+ if (ci_p == NULL || (ci = *ci_p) == NULL)
+ return;
+
+ free_str_array(&ci->versions);
+ free_str_array(&ci->extensions);
+ free_opt_array(&ci->options);
+ free_byte_array(&ci->protocols);
+ free(ci);
+
+ *ci_p = NULL;
+}
+
+char **
+fido_cbor_info_versions_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->versions.ptr);
+}
+
+size_t
+fido_cbor_info_versions_len(const fido_cbor_info_t *ci)
+{
+ return (ci->versions.len);
+}
+
+char **
+fido_cbor_info_extensions_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->extensions.ptr);
+}
+
+size_t
+fido_cbor_info_extensions_len(const fido_cbor_info_t *ci)
+{
+ return (ci->extensions.len);
+}
+
+const unsigned char *
+fido_cbor_info_aaguid_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->aaguid);
+}
+
+size_t
+fido_cbor_info_aaguid_len(const fido_cbor_info_t *ci)
+{
+ return (sizeof(ci->aaguid));
+}
+
+char **
+fido_cbor_info_options_name_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->options.name);
+}
+
+const bool *
+fido_cbor_info_options_value_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->options.value);
+}
+
+size_t
+fido_cbor_info_options_len(const fido_cbor_info_t *ci)
+{
+ return (ci->options.len);
+}
+
+uint64_t
+fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *ci)
+{
+ return (ci->maxmsgsiz);
+}
+
+const uint8_t *
+fido_cbor_info_protocols_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->protocols.ptr);
+}
+
+size_t
+fido_cbor_info_protocols_len(const fido_cbor_info_t *ci)
+{
+ return (ci->protocols.len);
+}
diff --git a/lib/libfido2/src/io.c b/lib/libfido2/src/io.c
new file mode 100644
index 00000000000..ce4f497f8b8
--- /dev/null
+++ b/lib/libfido2/src/io.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "fido.h"
+#include "packed.h"
+
+PACKED_TYPE(frame_t,
+struct frame {
+ uint32_t cid; /* channel id */
+ union {
+ uint8_t type;
+ struct {
+ uint8_t cmd;
+ uint8_t bcnth;
+ uint8_t bcntl;
+ uint8_t data[CTAP_RPT_SIZE - 7];
+ } init;
+ struct {
+ uint8_t seq;
+ uint8_t data[CTAP_RPT_SIZE - 5];
+ } cont;
+ } body;
+})
+
+#ifndef MIN
+#define MIN(x, y) ((x) > (y) ? (y) : (x))
+#endif
+
+static size_t
+tx_preamble(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count)
+{
+ struct frame *fp;
+ unsigned char pkt[sizeof(*fp) + 1];
+ int n;
+
+ if (d->io.write == NULL || (cmd & 0x80) == 0)
+ return (0);
+
+ memset(&pkt, 0, sizeof(pkt));
+ fp = (struct frame *)(pkt + 1);
+ fp->cid = d->cid;
+ fp->body.init.cmd = 0x80 | cmd;
+ fp->body.init.bcnth = (count >> 8) & 0xff;
+ fp->body.init.bcntl = count & 0xff;
+ count = MIN(count, sizeof(fp->body.init.data));
+ if (count)
+ memcpy(&fp->body.init.data, buf, count);
+
+ n = d->io.write(d->io_handle, pkt, sizeof(pkt));
+ if (n < 0 || (size_t)n != sizeof(pkt))
+ return (0);
+
+ return (count);
+}
+
+static size_t
+tx_frame(fido_dev_t *d, int seq, const void *buf, size_t count)
+{
+ struct frame *fp;
+ unsigned char pkt[sizeof(*fp) + 1];
+ int n;
+
+ if (d->io.write == NULL || seq < 0 || seq > UINT8_MAX)
+ return (0);
+
+ memset(&pkt, 0, sizeof(pkt));
+ fp = (struct frame *)(pkt + 1);
+ fp->cid = d->cid;
+ fp->body.cont.seq = (uint8_t)seq;
+ count = MIN(count, sizeof(fp->body.cont.data));
+ memcpy(&fp->body.cont.data, buf, count);
+
+ n = d->io.write(d->io_handle, pkt, sizeof(pkt));
+ if (n < 0 || (size_t)n != sizeof(pkt))
+ return (0);
+
+ return (count);
+}
+
+int
+tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count)
+{
+ int seq = 0;
+ size_t sent;
+
+ log_debug("%s: d=%p, cmd=0x%02x, buf=%p, count=%zu", __func__,
+ (void *)d, cmd, buf, count);
+ log_xxd(buf, count);
+
+ if (d->io_handle == NULL || count > UINT16_MAX) {
+ log_debug("%s: invalid argument (%p, %zu)", __func__,
+ d->io_handle, count);
+ return (-1);
+ }
+
+ if ((sent = tx_preamble(d, cmd, buf, count)) == 0) {
+ log_debug("%s: tx_preamble", __func__);
+ return (-1);
+ }
+
+ while (sent < count) {
+ if (seq & 0x80) {
+ log_debug("%s: seq & 0x80", __func__);
+ return (-1);
+ }
+ const uint8_t *p = (const uint8_t *)buf + sent;
+ size_t n = tx_frame(d, seq++, p, count - sent);
+ if (n == 0) {
+ log_debug("%s: tx_frame", __func__);
+ return (-1);
+ }
+ sent += n;
+ }
+
+ return (0);
+}
+
+static int
+rx_frame(fido_dev_t *d, struct frame *fp, int ms)
+{
+ int n;
+
+ if (d->io.read == NULL)
+ return (-1);
+
+ n = d->io.read(d->io_handle, (unsigned char *)fp, sizeof(*fp), ms);
+ if (n < 0 || (size_t)n != sizeof(*fp))
+ return (-1);
+
+ return (0);
+}
+
+static int
+rx_preamble(fido_dev_t *d, struct frame *fp, int ms)
+{
+ do {
+ if (rx_frame(d, fp, ms) < 0)
+ return (-1);
+#ifdef FIDO_FUZZ
+ fp->cid = d->cid;
+#endif
+ } while (fp->cid == d->cid &&
+ fp->body.init.cmd == (CTAP_FRAME_INIT | CTAP_KEEPALIVE));
+
+ return (0);
+}
+
+int
+rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int ms)
+{
+ struct frame f;
+ uint16_t r;
+ uint16_t flen;
+ int seq;
+
+ if (d->io_handle == NULL || (cmd & 0x80) == 0) {
+ log_debug("%s: invalid argument (%p, 0x%02x)", __func__,
+ d->io_handle, cmd);
+ return (-1);
+ }
+
+ if (rx_preamble(d, &f, ms) < 0) {
+ log_debug("%s: rx_preamble", __func__);
+ return (-1);
+ }
+
+ log_debug("%s: initiation frame at %p, len %zu", __func__, (void *)&f,
+ sizeof(f));
+ log_xxd(&f, sizeof(f));
+
+#ifdef FIDO_FUZZ
+ f.cid = d->cid;
+ f.body.init.cmd = cmd;
+#endif
+
+ if (f.cid != d->cid || f.body.init.cmd != cmd) {
+ log_debug("%s: cid (0x%x, 0x%x), cmd (0x%02x, 0x%02x)",
+ __func__, f.cid, d->cid, f.body.init.cmd, cmd);
+ return (-1);
+ }
+
+ flen = (f.body.init.bcnth << 8) | f.body.init.bcntl;
+ if (count < (size_t)flen) {
+ log_debug("%s: count < flen (%zu, %zu)", __func__, count,
+ (size_t)flen);
+ return (-1);
+ }
+ if (flen < sizeof(f.body.init.data)) {
+ memcpy(buf, f.body.init.data, flen);
+ return (flen);
+ }
+
+ memcpy(buf, f.body.init.data, sizeof(f.body.init.data));
+ r = sizeof(f.body.init.data);
+ seq = 0;
+
+ while ((size_t)r < flen) {
+ if (rx_frame(d, &f, ms) < 0) {
+ log_debug("%s: rx_frame", __func__);
+ return (-1);
+ }
+
+ log_debug("%s: continuation frame at %p, len %zu", __func__,
+ (void *)&f, sizeof(f));
+ log_xxd(&f, sizeof(f));
+
+#ifdef FIDO_FUZZ
+ f.cid = d->cid;
+ f.body.cont.seq = seq;
+#endif
+
+ if (f.cid != d->cid || f.body.cont.seq != seq++) {
+ log_debug("%s: cid (0x%x, 0x%x), seq (%d, %d)",
+ __func__, f.cid, d->cid, f.body.cont.seq, seq);
+ return (-1);
+ }
+
+ uint8_t *p = (uint8_t *)buf + r;
+
+ if ((size_t)(flen - r) > sizeof(f.body.cont.data)) {
+ memcpy(p, f.body.cont.data, sizeof(f.body.cont.data));
+ r += sizeof(f.body.cont.data);
+ } else {
+ memcpy(p, f.body.cont.data, flen - r);
+ r += (flen - r); /* break */
+ }
+ }
+
+ log_debug("%s: payload at %p, len %zu", __func__, buf, (size_t)r);
+ log_xxd(buf, r);
+
+ return (r);
+}
+
+int
+rx_cbor_status(fido_dev_t *d, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[2048];
+ int reply_len;
+
+ if ((reply_len = rx(d, cmd, &reply, sizeof(reply), ms)) < 0 ||
+ (size_t)reply_len < 1) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ return (reply[0]);
+}
diff --git a/lib/libfido2/src/iso7816.c b/lib/libfido2/src/iso7816.c
new file mode 100644
index 00000000000..e2ea2813010
--- /dev/null
+++ b/lib/libfido2/src/iso7816.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <string.h>
+#include "fido.h"
+
+iso7816_apdu_t *
+iso7816_new(uint8_t ins, uint8_t p1, uint16_t payload_len)
+{
+ iso7816_apdu_t *apdu;
+ size_t alloc_len;
+
+ alloc_len = sizeof(iso7816_apdu_t) + payload_len;
+
+ if ((apdu = calloc(1, alloc_len)) == NULL)
+ return (NULL);
+
+ apdu->alloc_len = alloc_len;
+ apdu->payload_len = payload_len;
+ apdu->payload_ptr = apdu->payload;
+ apdu->header.ins = ins;
+ apdu->header.p1 = p1;
+ apdu->header.lc2 = (payload_len >> 8) & 0xff;
+ apdu->header.lc3 = payload_len & 0xff;
+
+ return (apdu);
+}
+
+void
+iso7816_free(iso7816_apdu_t **apdu_p)
+{
+ iso7816_apdu_t *apdu;
+
+ if (apdu_p == NULL || (apdu = *apdu_p) == NULL)
+ return;
+
+ explicit_bzero(apdu, apdu->alloc_len);
+ free(apdu);
+
+ *apdu_p = NULL;
+}
+
+int
+iso7816_add(iso7816_apdu_t *apdu, const void *buf, size_t cnt)
+{
+ if (cnt > apdu->payload_len || cnt > UINT16_MAX)
+ return (-1);
+
+ memcpy(apdu->payload_ptr, buf, cnt);
+ apdu->payload_ptr += cnt;
+ apdu->payload_len -= (uint16_t)cnt;
+
+ return (0);
+}
+
+const unsigned char *
+iso7816_ptr(const iso7816_apdu_t *apdu)
+{
+ return ((const unsigned char *)&apdu->header);
+}
+
+size_t
+iso7816_len(const iso7816_apdu_t *apdu)
+{
+ return (apdu->alloc_len - sizeof(apdu->alloc_len) -
+ sizeof(apdu->payload_len) - sizeof(apdu->payload_ptr));
+}
diff --git a/lib/libfido2/src/iso7816.h b/lib/libfido2/src/iso7816.h
new file mode 100644
index 00000000000..426cd97207a
--- /dev/null
+++ b/lib/libfido2/src/iso7816.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _ISO7816_H
+#define _ISO7816_H
+
+#include "packed.h"
+
+PACKED_TYPE(iso7816_header_t,
+struct iso7816_header {
+ uint8_t cla;
+ uint8_t ins;
+ uint8_t p1;
+ uint8_t p2;
+ uint8_t lc1;
+ uint8_t lc2;
+ uint8_t lc3;
+})
+
+PACKED_TYPE(iso7816_apdu_t,
+struct iso7816_apdu {
+ size_t alloc_len;
+ uint16_t payload_len;
+ uint8_t *payload_ptr;
+ iso7816_header_t header;
+ uint8_t payload[];
+})
+
+const unsigned char *iso7816_ptr(const iso7816_apdu_t *);
+int iso7816_add(iso7816_apdu_t *, const void *, size_t);
+iso7816_apdu_t *iso7816_new(uint8_t, uint8_t, uint16_t);
+size_t iso7816_len(const iso7816_apdu_t *);
+void iso7816_free(iso7816_apdu_t **);
+
+#endif /* !_ISO7816_H */
diff --git a/lib/libfido2/src/log.c b/lib/libfido2/src/log.c
new file mode 100644
index 00000000000..413c16f5606
--- /dev/null
+++ b/lib/libfido2/src/log.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "fido.h"
+
+#ifndef FIDO_NO_DIAGNOSTIC
+
+#ifndef TLS
+#define TLS
+#endif
+
+static TLS int logging;
+
+void
+log_init()
+{
+ logging = 1;
+}
+
+void
+log_xxd(const void *buf, size_t count)
+{
+ const uint8_t *ptr = buf;
+ size_t i;
+
+ if (!logging)
+ return;
+
+ fprintf(stderr, " ");
+
+ for (i = 0; i < count; i++) {
+ fprintf(stderr, "%02x ", *ptr++);
+ if ((i + 1) % 16 == 0 && i + 1 < count)
+ fprintf(stderr, "\n ");
+ }
+
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+void
+log_debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!logging)
+ return;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+#endif /* !FIDO_NO_DIAGNOSTIC */
diff --git a/lib/libfido2/src/packed.h b/lib/libfido2/src/packed.h
new file mode 100644
index 00000000000..3857c22dd2b
--- /dev/null
+++ b/lib/libfido2/src/packed.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _PACKED_H
+#define _PACKED_H
+
+#if defined(__GNUC__)
+#define PACKED_TYPE(type, def) \
+ typedef def __attribute__ ((__packed__)) type;
+#elif defined(_MSC_VER)
+#define PACKED_TYPE(type, def) \
+ __pragma(pack(push, 1)) \
+ typedef def type; \
+ __pragma(pack(pop))
+#else
+#error "please provide a way to define packed types on your platform"
+#endif
+
+#endif /* !_PACKED_H */
diff --git a/lib/libfido2/src/pin.c b/lib/libfido2/src/pin.c
new file mode 100644
index 00000000000..5789cd86ea7
--- /dev/null
+++ b/lib/libfido2/src/pin.c
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <string.h>
+#include "fido.h"
+#include "fido/es256.h"
+
+static int
+parse_pintoken(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_blob_t *token = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 2) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ return (fido_blob_decode(val, token));
+}
+
+static int
+fido_dev_get_pin_token_tx(fido_dev_t *dev, const char *pin,
+ const fido_blob_t *ecdh, const es256_pk_t *pk)
+{
+ fido_blob_t f;
+ fido_blob_t *p = NULL;
+ cbor_item_t *argv[6];
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if ((p = fido_blob_new()) == NULL || fido_blob_set(p,
+ (const unsigned char *)pin, strlen(pin)) < 0) {
+ log_debug("%s: fido_blob_set", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((argv[0] = cbor_build_uint8(1)) == NULL ||
+ (argv[1] = cbor_build_uint8(5)) == NULL ||
+ (argv[2] = es256_pk_encode(pk, 0)) == NULL ||
+ (argv[5] = encode_pin_hash_enc(ecdh, p)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, 6, &f) < 0 ||
+ tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ fido_blob_free(&p);
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_get_pin_token_rx(fido_dev_t *dev, const fido_blob_t *ecdh,
+ fido_blob_t *token, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ fido_blob_t *aes_token = NULL;
+ unsigned char reply[2048];
+ int reply_len;
+ int r;
+
+ if ((aes_token = fido_blob_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, aes_token,
+ parse_pintoken)) != FIDO_OK) {
+ log_debug("%s: parse_pintoken", __func__);
+ goto fail;
+ }
+
+ if (aes256_cbc_dec(ecdh, aes_token, token) < 0) {
+ log_debug("%s: aes256_cbc_dec", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ fido_blob_free(&aes_token);
+
+ return (r);
+}
+
+static int
+fido_dev_get_pin_token_wait(fido_dev_t *dev, const char *pin,
+ const fido_blob_t *ecdh, const es256_pk_t *pk, fido_blob_t *token, int ms)
+{
+ int r;
+
+ if ((r = fido_dev_get_pin_token_tx(dev, pin, ecdh, pk)) != FIDO_OK ||
+ (r = fido_dev_get_pin_token_rx(dev, ecdh, token, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_get_pin_token(fido_dev_t *dev, const char *pin,
+ const fido_blob_t *ecdh, const es256_pk_t *pk, fido_blob_t *token)
+{
+ return (fido_dev_get_pin_token_wait(dev, pin, ecdh, pk, token, -1));
+}
+
+static int
+pad64(const char *pin, fido_blob_t **ppin)
+{
+ size_t pin_len;
+ size_t ppin_len;
+
+ pin_len = strlen(pin);
+ if (pin_len < 4 || pin_len > 255) {
+ log_debug("%s: invalid pin length", __func__);
+ return (FIDO_ERR_PIN_POLICY_VIOLATION);
+ }
+
+ if ((*ppin = fido_blob_new()) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ ppin_len = (pin_len + 63) & ~63;
+ if (ppin_len < pin_len || ((*ppin)->ptr = calloc(1, ppin_len)) == NULL) {
+ fido_blob_free(ppin);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ memcpy((*ppin)->ptr, pin, pin_len);
+ (*ppin)->len = ppin_len;
+
+ return (FIDO_OK);
+}
+
+static int
+fido_dev_change_pin_tx(fido_dev_t *dev, const char *pin, const char *oldpin)
+{
+ fido_blob_t f;
+ fido_blob_t *ppin = NULL;
+ fido_blob_t *ecdh = NULL;
+ fido_blob_t *opin = NULL;
+ cbor_item_t *argv[6];
+ es256_pk_t *pk = NULL;
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if ((opin = fido_blob_new()) == NULL || fido_blob_set(opin,
+ (const unsigned char *)oldpin, strlen(oldpin)) < 0) {
+ log_debug("%s: fido_blob_set", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((r = pad64(pin, &ppin)) != FIDO_OK) {
+ log_debug("%s: pad64", __func__);
+ goto fail;
+ }
+
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
+ log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+
+ if ((argv[0] = cbor_build_uint8(1)) == NULL ||
+ (argv[1] = cbor_build_uint8(4)) == NULL ||
+ (argv[2] = es256_pk_encode(pk, 0)) == NULL ||
+ (argv[3] = encode_change_pin_auth(ecdh, ppin, opin)) == NULL ||
+ (argv[4] = encode_pin_enc(ecdh, ppin)) == NULL ||
+ (argv[5] = encode_pin_hash_enc(ecdh, opin)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, 6, &f) < 0 ||
+ tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ es256_pk_free(&pk);
+ fido_blob_free(&ppin);
+ fido_blob_free(&ecdh);
+ fido_blob_free(&opin);
+ free(f.ptr);
+
+ return (r);
+
+}
+
+static int
+fido_dev_set_pin_tx(fido_dev_t *dev, const char *pin)
+{
+ fido_blob_t f;
+ fido_blob_t *ppin = NULL;
+ fido_blob_t *ecdh = NULL;
+ cbor_item_t *argv[5];
+ es256_pk_t *pk = NULL;
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if ((r = pad64(pin, &ppin)) != FIDO_OK) {
+ log_debug("%s: pad64", __func__);
+ goto fail;
+ }
+
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) {
+ log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+
+ if ((argv[0] = cbor_build_uint8(1)) == NULL ||
+ (argv[1] = cbor_build_uint8(3)) == NULL ||
+ (argv[2] = es256_pk_encode(pk, 0)) == NULL ||
+ (argv[3] = encode_set_pin_auth(ecdh, ppin)) == NULL ||
+ (argv[4] = encode_pin_enc(ecdh, ppin)) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, 5, &f) < 0 ||
+ tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ es256_pk_free(&pk);
+ fido_blob_free(&ppin);
+ fido_blob_free(&ecdh);
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_set_pin_wait(fido_dev_t *dev, const char *pin, const char *oldpin,
+ int ms)
+{
+ int r;
+
+ if (oldpin != NULL) {
+ if ((r = fido_dev_change_pin_tx(dev, pin, oldpin)) != FIDO_OK) {
+ log_debug("%s: fido_dev_change_pin_tx", __func__);
+ return (r);
+ }
+ } else {
+ if ((r = fido_dev_set_pin_tx(dev, pin)) != FIDO_OK) {
+ log_debug("%s: fido_dev_set_pin_tx", __func__);
+ return (r);
+ }
+ }
+
+ if ((r = rx_cbor_status(dev, ms)) != FIDO_OK) {
+ log_debug("%s: rx_cbor_status", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_set_pin(fido_dev_t *dev, const char *pin, const char *oldpin)
+{
+ return (fido_dev_set_pin_wait(dev, pin, oldpin, -1));
+}
+
+static int
+parse_retry_count(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ int *retries = arg;
+ uint64_t n;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 3) {
+ log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (decode_uint64(val, &n) < 0 || n > INT_MAX) {
+ log_debug("%s: decode_uint64", __func__);
+ return (-1);
+ }
+
+ *retries = (int)n;
+
+ return (0);
+}
+
+static int
+fido_dev_get_retry_count_tx(fido_dev_t *dev)
+{
+ fido_blob_t f;
+ cbor_item_t *argv[2];
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if ((argv[0] = cbor_build_uint8(1)) == NULL ||
+ (argv[1] = cbor_build_uint8(1)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, 2, &f) < 0 ||
+ tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_get_retry_count_rx(fido_dev_t *dev, int *retries, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+ unsigned char reply[512];
+ int reply_len;
+ int r;
+
+ *retries = 0;
+
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) {
+ log_debug("%s: rx", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if ((r = parse_cbor_reply(reply, (size_t)reply_len, retries,
+ parse_retry_count)) != FIDO_OK) {
+ log_debug("%s: parse_retry_count", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_dev_get_retry_count_wait(fido_dev_t *dev, int *retries, int ms)
+{
+ int r;
+
+ if ((r = fido_dev_get_retry_count_tx(dev)) != FIDO_OK ||
+ (r = fido_dev_get_retry_count_rx(dev, retries, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_get_retry_count(fido_dev_t *dev, int *retries)
+{
+ return (fido_dev_get_retry_count_wait(dev, retries, -1));
+}
+
+int
+add_cbor_pin_params(fido_dev_t *dev, const fido_blob_t *hmac_data,
+ const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin,
+ cbor_item_t **auth, cbor_item_t **opt)
+{
+ fido_blob_t *token = NULL;
+ int r;
+
+ if ((token = fido_blob_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((r = fido_dev_get_pin_token(dev, pin, ecdh, pk, token)) != FIDO_OK) {
+ log_debug("%s: fido_dev_get_pin_token", __func__);
+ goto fail;
+ }
+
+ if ((*auth = encode_pin_auth(token, hmac_data)) == NULL ||
+ (*opt = encode_pin_opt()) == NULL) {
+ log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ fido_blob_free(&token);
+
+ return (r);
+}
diff --git a/lib/libfido2/src/reset.c b/lib/libfido2/src/reset.c
new file mode 100644
index 00000000000..fe5ee92972f
--- /dev/null
+++ b/lib/libfido2/src/reset.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <stdlib.h>
+#include "fido.h"
+
+static int
+fido_dev_reset_tx(fido_dev_t *dev)
+{
+ const unsigned char cbor[] = { CTAP_CBOR_RESET };
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR;
+
+ if (tx(dev, cmd, cbor, sizeof(cbor)) < 0) {
+ log_debug("%s: tx", __func__);
+ return (FIDO_ERR_TX);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_dev_reset_wait(fido_dev_t *dev, int ms)
+{
+ int r;
+
+ if ((r = fido_dev_reset_tx(dev)) != FIDO_OK ||
+ (r = rx_cbor_status(dev, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_reset(fido_dev_t *dev)
+{
+ return (fido_dev_reset_wait(dev, -1));
+}
diff --git a/lib/libfido2/src/rs256.c b/lib/libfido2/src/rs256.c
new file mode 100644
index 00000000000..8cda9c36a27
--- /dev/null
+++ b/lib/libfido2/src/rs256.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+
+#include <string.h>
+#include "fido.h"
+#include "fido/rs256.h"
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static int
+RSA_bits(const RSA *r)
+{
+ return (BN_num_bits(r->n));
+}
+
+static int
+RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
+{
+ r->n = n;
+ r->e = e;
+ r->d = d;
+
+ return (1);
+}
+
+static void
+RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
+{
+ *n = r->n;
+ *e = r->e;
+ *d = r->d;
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+
+static int
+decode_bignum(const cbor_item_t *item, void *ptr, size_t len)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false ||
+ cbor_bytestring_length(item) != len) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memcpy(ptr, cbor_bytestring_handle(item), len);
+
+ return (0);
+}
+
+static int
+decode_rsa_pubkey(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ rs256_pk_t *k = arg;
+
+ if (cbor_isa_negint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8)
+ return (0); /* ignore */
+
+ switch (cbor_get_uint8(key)) {
+ case 0: /* modulus */
+ return (decode_bignum(val, &k->n, sizeof(k->n)));
+ case 1: /* public exponent */
+ return (decode_bignum(val, &k->e, sizeof(k->e)));
+ }
+
+ return (0); /* ignore */
+}
+
+int
+rs256_pk_decode(const cbor_item_t *item, rs256_pk_t *k)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, k, decode_rsa_pubkey) < 0) {
+ log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+rs256_pk_t *
+rs256_pk_new(void)
+{
+ return (calloc(1, sizeof(rs256_pk_t)));
+}
+
+void
+rs256_pk_free(rs256_pk_t **pkp)
+{
+ rs256_pk_t *pk;
+
+ if (pkp == NULL || (pk = *pkp) == NULL)
+ return;
+
+ explicit_bzero(pk, sizeof(*pk));
+ free(pk);
+
+ *pkp = NULL;
+}
+
+int
+rs256_pk_from_ptr(rs256_pk_t *pk, const void *ptr, size_t len)
+{
+ if (len < sizeof(*pk))
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ memcpy(pk, ptr, sizeof(*pk));
+
+ return (FIDO_OK);
+}
+
+EVP_PKEY *
+rs256_pk_to_EVP_PKEY(const rs256_pk_t *k)
+{
+ RSA *rsa = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *n = NULL;
+ BIGNUM *e = NULL;
+ int ok = -1;
+
+ if ((n = BN_new()) == NULL || (e = BN_new()) == NULL)
+ goto fail;
+
+ if (BN_bin2bn(k->n, sizeof(k->n), n) == NULL ||
+ BN_bin2bn(k->e, sizeof(k->e), e) == NULL) {
+ log_debug("%s: BN_bin2bn", __func__);
+ goto fail;
+ }
+
+ if ((rsa = RSA_new()) == NULL || RSA_set0_key(rsa, n, e, NULL) == 0) {
+ log_debug("%s: RSA_set0_key", __func__);
+ goto fail;
+ }
+
+ /* at this point, n and e belong to rsa */
+ n = NULL;
+ e = NULL;
+
+ if ((pkey = EVP_PKEY_new()) == NULL ||
+ EVP_PKEY_assign_RSA(pkey, rsa) == 0) {
+ log_debug("%s: EVP_PKEY_assign_RSA", __func__);
+ goto fail;
+ }
+
+ rsa = NULL; /* at this point, rsa belongs to evp */
+
+ ok = 0;
+fail:
+ if (n != NULL)
+ BN_free(n);
+ if (e != NULL)
+ BN_free(e);
+ if (rsa != NULL)
+ RSA_free(rsa);
+ if (ok < 0 && pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+ return (pkey);
+}
+
+int
+rs256_pk_from_RSA(rs256_pk_t *pk, const RSA *rsa)
+{
+ const BIGNUM *n = NULL;
+ const BIGNUM *e = NULL;
+ const BIGNUM *d = NULL;
+ int k;
+
+ if (RSA_bits(rsa) != 2048) {
+ log_debug("%s: invalid key length", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ RSA_get0_key(rsa, &n, &e, &d);
+
+ if (n == NULL || e == NULL) {
+ log_debug("%s: RSA_get0_key", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((k = BN_num_bytes(n)) < 0 || (size_t)k > sizeof(pk->n) ||
+ (k = BN_num_bytes(e)) < 0 || (size_t)k > sizeof(pk->e)) {
+ log_debug("%s: invalid key", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((k = BN_bn2bin(n, pk->n)) < 0 || (size_t)k > sizeof(pk->n) ||
+ (k = BN_bn2bin(e, pk->e)) < 0 || (size_t)k > sizeof(pk->e)) {
+ log_debug("%s: BN_bn2bin", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ return (FIDO_OK);
+}
diff --git a/lib/libfido2/src/types.h b/lib/libfido2/src/types.h
new file mode 100644
index 00000000000..42ed1b7cc89
--- /dev/null
+++ b/lib/libfido2/src/types.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#ifndef _TYPES_H
+#define _TYPES_H
+
+#include "packed.h"
+
+/* COSE ES256 (ECDSA over P-256 with SHA-256) public key */
+typedef struct es256_pk {
+ unsigned char x[32];
+ unsigned char y[32];
+} es256_pk_t;
+
+/* COSE ES256 (ECDSA over P-256 with SHA-256) (secret) key */
+typedef struct es256_sk {
+ unsigned char d[32];
+} es256_sk_t;
+
+/* COSE RS256 (2048-bit RSA with PKCS1 padding and SHA-256) public key */
+typedef struct rs256_pk {
+ unsigned char n[256];
+ unsigned char e[3];
+} rs256_pk_t;
+
+/* COSE EDDSA (ED25519) */
+typedef struct eddsa_pk {
+ unsigned char x[32];
+} eddsa_pk_t;
+
+PACKED_TYPE(fido_authdata_t,
+struct fido_authdata {
+ unsigned char rp_id_hash[32]; /* sha256 of fido_rp.id */
+ uint8_t flags; /* user present/verified */
+ uint32_t sigcount; /* signature counter */
+ /* actually longer */
+})
+
+PACKED_TYPE(fido_attcred_raw_t,
+struct fido_attcred_raw {
+ unsigned char aaguid[16]; /* credential's aaguid */
+ uint16_t id_len; /* credential id length */
+ uint8_t body[]; /* credential id + pubkey */
+})
+
+typedef struct fido_attcred {
+ unsigned char aaguid[16]; /* credential's aaguid */
+ fido_blob_t id; /* credential id */
+ int type; /* credential's cose algorithm */
+ union { /* credential's public key */
+ es256_pk_t es256;
+ rs256_pk_t rs256;
+ eddsa_pk_t eddsa;
+ } pubkey;
+} fido_attcred_t;
+
+typedef struct fido_attstmt {
+ fido_blob_t x5c; /* attestation certificate */
+ fido_blob_t sig; /* attestation signature */
+} fido_attstmt_t;
+
+typedef struct fido_rp {
+ char *id; /* relying party id */
+ char *name; /* relying party name */
+} fido_rp_t;
+
+typedef struct fido_user {
+ fido_blob_t id; /* required */
+ char *icon; /* optional */
+ char *name; /* optional */
+ char *display_name; /* required */
+} fido_user_t;
+
+typedef struct fido_cred {
+ fido_blob_t cdh; /* client data hash */
+ fido_rp_t rp; /* relying party */
+ fido_user_t user; /* user entity */
+ fido_blob_array_t excl; /* list of credential ids to exclude */
+ fido_opt_t rk; /* resident key */
+ fido_opt_t uv; /* user verification */
+ int ext; /* enabled extensions */
+ int type; /* cose algorithm */
+ char *fmt; /* credential format */
+ int authdata_ext; /* decoded extensions */
+ fido_blob_t authdata_cbor; /* raw cbor payload */
+ fido_authdata_t authdata; /* decoded authdata payload */
+ fido_attcred_t attcred; /* returned credential (key + id) */
+ fido_attstmt_t attstmt; /* attestation statement (x509 + sig) */
+} fido_cred_t;
+
+typedef struct _fido_assert_stmt {
+ fido_blob_t id; /* credential id */
+ fido_user_t user; /* user attributes */
+ fido_blob_t hmac_secret_enc; /* hmac secret, encrypted */
+ fido_blob_t hmac_secret; /* hmac secret */
+ int authdata_ext; /* decoded extensions */
+ fido_blob_t authdata_cbor; /* raw cbor payload */
+ fido_authdata_t authdata; /* decoded authdata payload */
+ fido_blob_t sig; /* signature of cdh + authdata */
+} fido_assert_stmt;
+
+typedef struct fido_assert {
+ char *rp_id; /* relying party id */
+ fido_blob_t cdh; /* client data hash */
+ fido_blob_t hmac_salt; /* optional hmac-secret salt */
+ fido_blob_array_t allow_list; /* list of allowed credentials */
+ fido_opt_t up; /* user presence */
+ fido_opt_t uv; /* user verification */
+ int ext; /* enabled extensions */
+ fido_assert_stmt *stmt; /* array of expected assertions */
+ size_t stmt_cnt; /* number of allocated assertions */
+ size_t stmt_len; /* number of received assertions */
+} fido_assert_t;
+
+typedef struct fido_opt_array {
+ char **name;
+ bool *value;
+ size_t len;
+} fido_opt_array_t;
+
+typedef struct fido_str_array {
+ char **ptr;
+ size_t len;
+} fido_str_array_t;
+
+typedef struct fido_byte_array {
+ uint8_t *ptr;
+ size_t len;
+} fido_byte_array_t;
+
+typedef struct fido_cbor_info {
+ fido_str_array_t versions; /* supported versions: fido2|u2f */
+ fido_str_array_t extensions; /* list of supported extensions */
+ unsigned char aaguid[16]; /* aaguid */
+ fido_opt_array_t options; /* list of supported options */
+ uint64_t maxmsgsiz; /* maximum message size */
+ fido_byte_array_t protocols; /* supported pin protocols */
+} fido_cbor_info_t;
+
+typedef struct fido_dev_info {
+ char *path; /* device path */
+ int16_t vendor_id; /* 2-byte vendor id */
+ int16_t product_id; /* 2-byte product id */
+ char *manufacturer; /* manufacturer string */
+ char *product; /* product string */
+} fido_dev_info_t;
+
+PACKED_TYPE(fido_ctap_info_t,
+/* defined in section 8.1.9.1.3 (CTAPHID_INIT) of the fido2 ctap spec */
+struct fido_ctap_info {
+ uint64_t nonce; /* echoed nonce */
+ uint32_t cid; /* channel id */
+ uint8_t protocol; /* ctaphid protocol id */
+ uint8_t major; /* major version number */
+ uint8_t minor; /* minor version number */
+ uint8_t build; /* build version number */
+ uint8_t flags; /* capabilities flags; see FIDO_CAP_* */
+})
+
+typedef struct fido_dev {
+ uint64_t nonce; /* issued nonce */
+ fido_ctap_info_t attr; /* device attributes */
+ uint32_t cid; /* assigned channel id */
+ void *io_handle; /* abstract i/o handle */
+ fido_dev_io_t io; /* i/o functions & data */
+} fido_dev_t;
+
+#endif /* !_TYPES_H */
diff --git a/lib/libfido2/src/u2f.c b/lib/libfido2/src/u2f.c
new file mode 100644
index 00000000000..161e3b580e2
--- /dev/null
+++ b/lib/libfido2/src/u2f.c
@@ -0,0 +1,751 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+#include <openssl/sha.h>
+#include <openssl/x509.h>
+
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "fido.h"
+#include "fido/es256.h"
+
+#if defined(_MSC_VER)
+static int
+usleep(unsigned int usec)
+{
+ Sleep(usec / 1000);
+
+ return (0);
+}
+#endif
+
+static int
+sig_get(fido_blob_t *sig, const unsigned char **buf, size_t *len)
+{
+ sig->len = *len; /* consume the whole buffer */
+ if ((sig->ptr = calloc(1, sig->len)) == NULL ||
+ buf_read(buf, len, sig->ptr, sig->len) < 0) {
+ log_debug("%s: buf_read", __func__);
+ if (sig->ptr != NULL) {
+ explicit_bzero(sig->ptr, sig->len);
+ free(sig->ptr);
+ sig->ptr = NULL;
+ sig->len = 0;
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+static int
+x5c_get(fido_blob_t *x5c, const unsigned char **buf, size_t *len)
+{
+ X509 *cert = NULL;
+ int ok = -1;
+
+ if (*len > LONG_MAX) {
+ log_debug("%s: invalid len %zu", __func__, *len);
+ goto fail;
+ }
+
+ /* find out the certificate's length */
+ const unsigned char *end = *buf;
+ if ((cert = d2i_X509(NULL, &end, (long)*len)) == NULL || end <= *buf ||
+ (x5c->len = (size_t)(end - *buf)) >= *len) {
+ log_debug("%s: d2i_X509", __func__);
+ goto fail;
+ }
+
+ /* read accordingly */
+ if ((x5c->ptr = calloc(1, x5c->len)) == NULL ||
+ buf_read(buf, len, x5c->ptr, x5c->len) < 0) {
+ log_debug("%s: buf_read", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (cert != NULL)
+ X509_free(cert);
+
+ if (ok < 0) {
+ free(x5c->ptr);
+ x5c->ptr = NULL;
+ x5c->len = 0;
+ }
+
+ return (ok);
+}
+
+static int
+authdata_fake(const char *rp_id, uint8_t flags, uint32_t sigcount,
+ fido_blob_t *fake_cbor_ad)
+{
+ fido_authdata_t ad;
+ cbor_item_t *item = NULL;
+ size_t alloc_len;
+
+ memset(&ad, 0, sizeof(ad));
+
+ if (SHA256((const void *)rp_id, strlen(rp_id),
+ ad.rp_id_hash) != ad.rp_id_hash) {
+ log_debug("%s: sha256", __func__);
+ return (-1);
+ }
+
+ ad.flags = flags; /* XXX translate? */
+ ad.sigcount = sigcount;
+
+ if ((item = cbor_build_bytestring((const unsigned char *)&ad,
+ sizeof(ad))) == NULL) {
+ log_debug("%s: cbor_build_bytestring", __func__);
+ return (-1);
+ }
+
+ if (fake_cbor_ad->ptr != NULL ||
+ (fake_cbor_ad->len = cbor_serialize_alloc(item, &fake_cbor_ad->ptr,
+ &alloc_len)) == 0) {
+ log_debug("%s: cbor_serialize_alloc", __func__);
+ cbor_decref(&item);
+ return (-1);
+ }
+
+ cbor_decref(&item);
+
+ return (0);
+}
+
+static int
+send_dummy_register(fido_dev_t *dev, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_MSG;
+ iso7816_apdu_t *apdu = NULL;
+ unsigned char challenge[SHA256_DIGEST_LENGTH];
+ unsigned char application[SHA256_DIGEST_LENGTH];
+ unsigned char reply[2048];
+ int r;
+
+#ifdef FIDO_FUZZ
+ ms = 0; /* XXX */
+#endif
+
+ /* dummy challenge & application */
+ memset(&challenge, 0xff, sizeof(challenge));
+ memset(&application, 0xff, sizeof(application));
+
+ if ((apdu = iso7816_new(U2F_CMD_REGISTER, 0, 2 *
+ SHA256_DIGEST_LENGTH)) == NULL ||
+ iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 ||
+ iso7816_add(apdu, &application, sizeof(application)) < 0) {
+ log_debug("%s: iso7816", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ do {
+ if (tx(dev, cmd, iso7816_ptr(apdu), iso7816_len(apdu)) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+ if (rx(dev, cmd, &reply, sizeof(reply), ms) < 2) {
+ log_debug("%s: rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ if (usleep((ms == -1 ? 100 : ms) * 1000) < 0) {
+ log_debug("%s: usleep", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED);
+
+ r = FIDO_OK;
+fail:
+ iso7816_free(&apdu);
+
+ return (r);
+}
+
+static int
+key_lookup(fido_dev_t *dev, const char *rp_id, const fido_blob_t *key_id,
+ int *found, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_MSG;
+ iso7816_apdu_t *apdu = NULL;
+ unsigned char challenge[SHA256_DIGEST_LENGTH];
+ unsigned char rp_id_hash[SHA256_DIGEST_LENGTH];
+ unsigned char reply[8];
+ uint8_t key_id_len;
+ int r;
+
+ if (key_id->len > UINT8_MAX || rp_id == NULL) {
+ log_debug("%s: key_id->len=%zu, rp_id=%p", __func__,
+ key_id->len, (const void *)rp_id);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ memset(&challenge, 0xff, sizeof(challenge));
+ memset(&rp_id_hash, 0, sizeof(rp_id_hash));
+
+ if (SHA256((const void *)rp_id, strlen(rp_id),
+ rp_id_hash) != rp_id_hash) {
+ log_debug("%s: sha256", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ key_id_len = (uint8_t)key_id->len;
+
+ if ((apdu = iso7816_new(U2F_CMD_AUTH, U2F_AUTH_CHECK, 2 *
+ SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len)) == NULL ||
+ iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 ||
+ iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 ||
+ iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 ||
+ iso7816_add(apdu, key_id->ptr, key_id_len) < 0) {
+ log_debug("%s: iso7816", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (tx(dev, cmd, iso7816_ptr(apdu), iso7816_len(apdu)) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+ if (rx(dev, cmd, &reply, sizeof(reply), ms) != 2) {
+ log_debug("%s: rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ switch ((reply[0] << 8) | reply[1]) {
+ case SW_CONDITIONS_NOT_SATISFIED:
+ *found = 1; /* key exists */
+ break;
+ case SW_WRONG_DATA:
+ *found = 0; /* key does not exist */
+ break;
+ default:
+ /* unexpected sw */
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ iso7816_free(&apdu);
+
+ return (r);
+}
+
+static int
+parse_auth_reply(fido_blob_t *sig, fido_blob_t *ad, const char *rp_id,
+ const unsigned char *reply, size_t len)
+{
+ uint8_t flags;
+ uint32_t sigcount;
+
+ if (len < 2 || ((reply[len - 2] << 8) | reply[len - 1]) != SW_NO_ERROR) {
+ log_debug("%s: unexpected sw", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ len -= 2;
+
+ if (buf_read(&reply, &len, &flags, sizeof(flags)) < 0 ||
+ buf_read(&reply, &len, &sigcount, sizeof(sigcount)) < 0) {
+ log_debug("%s: buf_read", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if (sig_get(sig, &reply, &len) < 0) {
+ log_debug("%s: sig_get", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if (authdata_fake(rp_id, flags, sigcount, ad) < 0) {
+ log_debug("%s; authdata_fake", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+do_auth(fido_dev_t *dev, const fido_blob_t *cdh, const char *rp_id,
+ const fido_blob_t *key_id, fido_blob_t *sig, fido_blob_t *ad, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_MSG;
+ iso7816_apdu_t *apdu = NULL;
+ unsigned char rp_id_hash[SHA256_DIGEST_LENGTH];
+ unsigned char reply[128];
+ int reply_len;
+ uint8_t key_id_len;
+ int r;
+
+#ifdef FIDO_FUZZ
+ ms = 0; /* XXX */
+#endif
+
+ if (cdh->len != SHA256_DIGEST_LENGTH || key_id->len > UINT8_MAX ||
+ rp_id == NULL) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ memset(&rp_id_hash, 0, sizeof(rp_id_hash));
+
+ if (SHA256((const void *)rp_id, strlen(rp_id),
+ rp_id_hash) != rp_id_hash) {
+ log_debug("%s: sha256", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ key_id_len = (uint8_t)key_id->len;
+
+ if ((apdu = iso7816_new(U2F_CMD_AUTH, U2F_AUTH_SIGN, 2 *
+ SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len)) == NULL ||
+ iso7816_add(apdu, cdh->ptr, cdh->len) < 0 ||
+ iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 ||
+ iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 ||
+ iso7816_add(apdu, key_id->ptr, key_id_len) < 0) {
+ log_debug("%s: iso7816", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ do {
+ if (tx(dev, cmd, iso7816_ptr(apdu), iso7816_len(apdu)) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 2) {
+ log_debug("%s: rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ if (usleep((ms == -1 ? 100 : ms) * 1000) < 0) {
+ log_debug("%s: usleep", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED);
+
+ if ((r = parse_auth_reply(sig, ad, rp_id, reply,
+ (size_t)reply_len)) != FIDO_OK) {
+ log_debug("%s: parse_auth_reply", __func__);
+ goto fail;
+ }
+
+fail:
+ iso7816_free(&apdu);
+
+ return (r);
+}
+
+static int
+cbor_blob_from_ec_point(const uint8_t *ec_point, size_t ec_point_len,
+ fido_blob_t *cbor_blob)
+{
+ es256_pk_t *pk = NULL;
+ cbor_item_t *pk_cbor = NULL;
+ size_t alloc_len;
+ int ok = -1;
+
+ /* only handle uncompressed points */
+ if (ec_point_len != 65 || ec_point[0] != 0x04) {
+ log_debug("%s: unexpected format", __func__);
+ goto fail;
+ }
+
+ if ((pk = es256_pk_new()) == NULL ||
+ es256_pk_set_x(pk, &ec_point[1]) < 0 ||
+ es256_pk_set_y(pk, &ec_point[33]) < 0) {
+ log_debug("%s: es256_pk_set", __func__);
+ goto fail;
+ }
+
+ if ((pk_cbor = es256_pk_encode(pk, 0)) == NULL) {
+ log_debug("%s: es256_pk_encode", __func__);
+ goto fail;
+ }
+
+ if ((cbor_blob->len = cbor_serialize_alloc(pk_cbor, &cbor_blob->ptr,
+ &alloc_len)) != 77) {
+ log_debug("%s: cbor_serialize_alloc", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ es256_pk_free(&pk);
+
+ if (pk_cbor)
+ cbor_decref(&pk_cbor);
+
+ return (ok);
+}
+
+static int
+encode_cred_authdata(const char *rp_id, const uint8_t *kh, uint8_t kh_len,
+ const uint8_t *pubkey, size_t pubkey_len, fido_blob_t *out)
+{
+ fido_authdata_t authdata;
+ fido_attcred_raw_t attcred_raw;
+ fido_blob_t pk_blob;
+ fido_blob_t authdata_blob;
+ cbor_item_t *authdata_cbor = NULL;
+ unsigned char *ptr;
+ size_t len;
+ size_t alloc_len;
+ int ok = -1;
+
+ memset(&pk_blob, 0, sizeof(pk_blob));
+ memset(&authdata, 0, sizeof(authdata));
+ memset(&authdata_blob, 0, sizeof(authdata_blob));
+ memset(out, 0, sizeof(*out));
+
+ if (rp_id == NULL) {
+ log_debug("%s: NULL rp_id", __func__);
+ goto fail;
+ }
+
+ if (cbor_blob_from_ec_point(pubkey, pubkey_len, &pk_blob) < 0) {
+ log_debug("%s: cbor_blob_from_ec_point", __func__);
+ goto fail;
+ }
+
+ if (SHA256((const void *)rp_id, strlen(rp_id),
+ authdata.rp_id_hash) != authdata.rp_id_hash) {
+ log_debug("%s: sha256", __func__);
+ goto fail;
+ }
+
+ authdata.flags = (CTAP_AUTHDATA_ATT_CRED | CTAP_AUTHDATA_USER_PRESENT);
+ authdata.sigcount = 0;
+
+ memset(&attcred_raw.aaguid, 0, sizeof(attcred_raw.aaguid));
+ attcred_raw.id_len = (uint16_t)(kh_len << 8); /* XXX */
+
+ len = authdata_blob.len = sizeof(authdata) + sizeof(attcred_raw) +
+ kh_len + pk_blob.len;
+ ptr = authdata_blob.ptr = calloc(1, authdata_blob.len);
+
+ log_debug("%s: ptr=%p, len=%zu", __func__, (void *)ptr, len);
+
+ if (authdata_blob.ptr == NULL)
+ goto fail;
+
+ if (buf_write(&ptr, &len, &authdata, sizeof(authdata)) < 0 ||
+ buf_write(&ptr, &len, &attcred_raw, sizeof(attcred_raw)) < 0 ||
+ buf_write(&ptr, &len, kh, kh_len) < 0 ||
+ buf_write(&ptr, &len, pk_blob.ptr, pk_blob.len) < 0) {
+ log_debug("%s: buf_write", __func__);
+ goto fail;
+ }
+
+ if ((authdata_cbor = fido_blob_encode(&authdata_blob)) == NULL) {
+ log_debug("%s: fido_blob_encode", __func__);
+ goto fail;
+ }
+
+ if ((out->len = cbor_serialize_alloc(authdata_cbor, &out->ptr,
+ &alloc_len)) == 0) {
+ log_debug("%s: cbor_serialize_alloc", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (authdata_cbor)
+ cbor_decref(&authdata_cbor);
+
+ if (pk_blob.ptr) {
+ explicit_bzero(pk_blob.ptr, pk_blob.len);
+ free(pk_blob.ptr);
+ }
+ if (authdata_blob.ptr) {
+ explicit_bzero(authdata_blob.ptr, authdata_blob.len);
+ free(authdata_blob.ptr);
+ }
+
+ return (ok);
+}
+
+static int
+parse_register_reply(fido_cred_t *cred, const unsigned char *reply, size_t len)
+{
+ fido_blob_t x5c;
+ fido_blob_t sig;
+ fido_blob_t ad;
+ uint8_t dummy;
+ uint8_t pubkey[65];
+ uint8_t kh_len = 0;
+ uint8_t *kh = NULL;
+ int r;
+
+ memset(&x5c, 0, sizeof(x5c));
+ memset(&sig, 0, sizeof(sig));
+ memset(&ad, 0, sizeof(ad));
+ r = FIDO_ERR_RX;
+
+ /* status word */
+ if (len < 2 || ((reply[len - 2] << 8) | reply[len - 1]) != SW_NO_ERROR) {
+ log_debug("%s: unexpected sw", __func__);
+ goto fail;
+ }
+
+ len -= 2;
+
+ /* reserved byte */
+ if (buf_read(&reply, &len, &dummy, sizeof(dummy)) < 0 ||
+ dummy != 0x05) {
+ log_debug("%s: reserved byte", __func__);
+ goto fail;
+ }
+
+ /* pubkey + key handle */
+ if (buf_read(&reply, &len, &pubkey, sizeof(pubkey)) < 0 ||
+ buf_read(&reply, &len, &kh_len, sizeof(kh_len)) < 0 ||
+ (kh = calloc(1, kh_len)) == NULL ||
+ buf_read(&reply, &len, kh, kh_len) < 0) {
+ log_debug("%s: buf_read", __func__);
+ goto fail;
+ }
+
+ /* x5c + sig */
+ if (x5c_get(&x5c, &reply, &len) < 0 ||
+ sig_get(&sig, &reply, &len) < 0) {
+ log_debug("%s: x5c || sig", __func__);
+ goto fail;
+ }
+
+ /* authdata */
+ if (encode_cred_authdata(cred->rp.id, kh, kh_len, pubkey,
+ sizeof(pubkey), &ad) < 0) {
+ log_debug("%s: encode_cred_authdata", __func__);
+ goto fail;
+ }
+
+ if (fido_cred_set_fmt(cred, "fido-u2f") != FIDO_OK ||
+ fido_cred_set_authdata(cred, ad.ptr, ad.len) != FIDO_OK ||
+ fido_cred_set_x509(cred, x5c.ptr, x5c.len) != FIDO_OK ||
+ fido_cred_set_sig(cred, sig.ptr, sig.len) != FIDO_OK) {
+ log_debug("%s: fido_cred_set", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (kh) {
+ explicit_bzero(kh, kh_len);
+ free(kh);
+ }
+ if (x5c.ptr) {
+ explicit_bzero(x5c.ptr, x5c.len);
+ free(x5c.ptr);
+ }
+ if (sig.ptr) {
+ explicit_bzero(sig.ptr, sig.len);
+ free(sig.ptr);
+ }
+ if (ad.ptr) {
+ explicit_bzero(ad.ptr, ad.len);
+ free(ad.ptr);
+ }
+
+ return (r);
+}
+
+int
+u2f_register(fido_dev_t *dev, fido_cred_t *cred, int ms)
+{
+ const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_MSG;
+ iso7816_apdu_t *apdu = NULL;
+ unsigned char rp_id_hash[SHA256_DIGEST_LENGTH];
+ unsigned char reply[2048];
+ int reply_len;
+ int found;
+ int r;
+
+#ifdef FIDO_FUZZ
+ ms = 0; /* XXX */
+#endif
+
+ if (cred->rk == FIDO_OPT_TRUE || cred->uv == FIDO_OPT_TRUE) {
+ log_debug("%s: rk=%d, uv=%d", __func__, cred->rk, cred->uv);
+ return (FIDO_ERR_UNSUPPORTED_OPTION);
+ }
+
+ if (cred->type != COSE_ES256 || cred->cdh.ptr == NULL ||
+ cred->rp.id == NULL || cred->cdh.len != SHA256_DIGEST_LENGTH) {
+ log_debug("%s: type=%d, cdh=(%p,%zu)" , __func__, cred->type,
+ (void *)cred->cdh.ptr, cred->cdh.len);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ for (size_t i = 0; i < cred->excl.len; i++) {
+ if ((r = key_lookup(dev, cred->rp.id, &cred->excl.ptr[i],
+ &found, ms)) != FIDO_OK) {
+ log_debug("%s: key_lookup", __func__);
+ return (r);
+ }
+ if (found) {
+ if ((r = send_dummy_register(dev, ms)) != FIDO_OK) {
+ log_debug("%s: send_dummy_register", __func__);
+ return (r);
+ }
+ return (FIDO_ERR_CREDENTIAL_EXCLUDED);
+ }
+ }
+
+ memset(&rp_id_hash, 0, sizeof(rp_id_hash));
+
+ if (SHA256((const void *)cred->rp.id, strlen(cred->rp.id),
+ rp_id_hash) != rp_id_hash) {
+ log_debug("%s: sha256", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((apdu = iso7816_new(U2F_CMD_REGISTER, 0, 2 *
+ SHA256_DIGEST_LENGTH)) == NULL ||
+ iso7816_add(apdu, cred->cdh.ptr, cred->cdh.len) < 0 ||
+ iso7816_add(apdu, rp_id_hash, sizeof(rp_id_hash)) < 0) {
+ log_debug("%s: iso7816", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ do {
+ if (tx(dev, cmd, iso7816_ptr(apdu), iso7816_len(apdu)) < 0) {
+ log_debug("%s: tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+ if ((reply_len = rx(dev, cmd, &reply, sizeof(reply), ms)) < 2) {
+ log_debug("%s: rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ if (usleep((ms == -1 ? 100 : ms) * 1000) < 0) {
+ log_debug("%s: usleep", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED);
+
+ if ((r = parse_register_reply(cred, reply,
+ (size_t)reply_len)) != FIDO_OK) {
+ log_debug("%s: parse_register_reply", __func__);
+ goto fail;
+ }
+fail:
+ iso7816_free(&apdu);
+
+ return (r);
+}
+
+static int
+u2f_authenticate_single(fido_dev_t *dev, const fido_blob_t *key_id,
+ fido_assert_t *fa, size_t idx, int ms)
+{
+ fido_blob_t sig;
+ fido_blob_t ad;
+ int found;
+ int r;
+
+ memset(&sig, 0, sizeof(sig));
+ memset(&ad, 0, sizeof(ad));
+
+ if ((r = key_lookup(dev, fa->rp_id, key_id, &found, ms)) != FIDO_OK) {
+ log_debug("%s: key_lookup", __func__);
+ goto fail;
+ }
+
+ if (!found) {
+ log_debug("%s: not found", __func__);
+ r = FIDO_ERR_CREDENTIAL_EXCLUDED;
+ goto fail;
+ }
+
+ if (fa->up == FIDO_OPT_FALSE) {
+ log_debug("%s: checking for key existence only", __func__);
+ r = FIDO_ERR_USER_PRESENCE_REQUIRED;
+ goto fail;
+ }
+
+ if ((r = do_auth(dev, &fa->cdh, fa->rp_id, key_id, &sig, &ad,
+ ms)) != FIDO_OK) {
+ log_debug("%s: do_auth", __func__);
+ goto fail;
+ }
+
+ if (fido_blob_set(&fa->stmt[idx].id, key_id->ptr, key_id->len) < 0 ||
+ fido_assert_set_authdata(fa, idx, ad.ptr, ad.len) != FIDO_OK ||
+ fido_assert_set_sig(fa, idx, sig.ptr, sig.len) != FIDO_OK) {
+ log_debug("%s: fido_assert_set", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (sig.ptr) {
+ explicit_bzero(sig.ptr, sig.len);
+ free(sig.ptr);
+ }
+ if (ad.ptr) {
+ explicit_bzero(ad.ptr, ad.len);
+ free(ad.ptr);
+ }
+
+ return (r);
+}
+
+int
+u2f_authenticate(fido_dev_t *dev, fido_assert_t *fa, int ms)
+{
+ int nauth_ok = 0;
+ int r;
+
+ if (fa->uv == FIDO_OPT_TRUE || fa->allow_list.ptr == NULL) {
+ log_debug("%s: uv=%d, allow_list=%p", __func__, fa->uv,
+ (void *)fa->allow_list.ptr);
+ return (FIDO_ERR_UNSUPPORTED_OPTION);
+ }
+
+ if ((r = fido_assert_set_count(fa, fa->allow_list.len)) != FIDO_OK) {
+ log_debug("%s: fido_assert_set_count", __func__);
+ return (r);
+ }
+
+ for (size_t i = 0; i < fa->allow_list.len; i++) {
+ if ((r = u2f_authenticate_single(dev, &fa->allow_list.ptr[i],
+ fa, nauth_ok, ms)) == FIDO_OK) {
+ nauth_ok++;
+ } else if (r != FIDO_ERR_CREDENTIAL_EXCLUDED) {
+ log_debug("%s: u2f_authenticate_single", __func__);
+ return (r);
+ }
+ /* ignore credentials that don't exist */
+ }
+
+ fa->stmt_len = nauth_ok;
+
+ if (nauth_ok == 0)
+ return (FIDO_ERR_NO_CREDENTIALS);
+
+ return (FIDO_OK);
+}